Source code for model.telescope_model

"""MC model of a telescope."""

import logging
from copy import deepcopy

import numpy as np

import simtools.utils.general as gen
from simtools.model.camera import Camera
from simtools.model.mirrors import Mirrors
from simtools.model.model_parameter import InvalidModelParameterError, ModelParameter
from simtools.model_repository.asset_names import get_simtel_table_file_name
from simtools.simulation.configuration import get_model_writer


[docs] class TelescopeModel(ModelParameter): """ TelescopeModel represents the MC model of an individual telescope. TelescopeModel contains parameter names and values for a specific telescope model. Parameters ---------- site: str Site name (e.g., South or North). telescope_name: str Telescope name (ex. LSTN-01, LSTN-design, ...). model_version: str Model version. label: str, optional Instance label. overwrite_model_parameter_dict: dict, optional Dictionary to overwrite model parameters from the model repository with provided values. ignore_software_version: bool, optional If True, ignore software version checks for deprecated parameters. model_directory: pathlib.Path or str, optional Directory for generated model assets and sim_telarray configuration files. """ def __init__( self, site, telescope_name, model_version, label=None, overwrite_model_parameter_dict=None, ignore_software_version=False, model_reader=None, model_directory=None, context=None, ): """Initialize TelescopeModel.""" super().__init__( site=site, array_element_name=telescope_name, model_version=model_version, label=label, overwrite_model_parameter_dict=overwrite_model_parameter_dict, ignore_software_version=ignore_software_version, model_reader=model_reader, model_directory=model_directory, context=context, ) self._logger = logging.getLogger(__name__) self._mirrors = None self._camera = None @property def mirrors(self): """Load the mirror information if the class instance hasn't done it yet.""" if self._mirrors is None: self._load_mirrors() return self._mirrors @property def camera(self): """Load the camera information if the class instance hasn't done it yet.""" if self._camera is None: self._load_camera() return self._camera
[docs] def export_single_mirror_list_file(self, mirror_number: int, set_focal_length_to_zero: bool): """ Export a mirror list file with a single mirror in it. Parameters ---------- mirror_number: int Number index of the mirror. set_focal_length_to_zero: bool Set the focal length to zero if True. """ return get_model_writer(self, context=self.context).export_single_mirror_list_file( mirror_number=mirror_number, set_focal_length_to_zero=set_focal_length_to_zero )
[docs] def get_single_mirror_list_file( self, mirror_number: int, set_focal_length_to_zero: bool = False ): """ Get the path to the single mirror list file. Parameters ---------- mirror_number: int Mirror number. set_focal_length_to_zero: bool Flag to set the focal length to zero. Returns ------- Path Path of the single mirror list file. """ return get_model_writer(self, context=self.context).get_single_mirror_list_file( mirror_number, set_focal_length_to_zero )
def _load_mirrors(self): """Load the attribute mirrors by creating a Mirrors object with the mirror list file.""" mirror_list_file_name = self.get_parameter_value("mirror_list") self._logger.debug(f"Reading mirror list from {mirror_list_file_name}") try: mirror_list_file = gen.find_file(mirror_list_file_name, self.config_file_directory) except FileNotFoundError: mirror_list_file = gen.find_file(mirror_list_file_name, self.io_handler.model_path) self._logger.warning( "Mirror_list_file was not found in the config directory - " "Using the one found in the model_path" ) except TypeError as exc: raise TypeError("Undefined mirror list") from exc self._mirrors = Mirrors(mirror_list_file, parameters=self.parameters)
[docs] def get_telescope_effective_focal_length( self, unit: str = "m", return_focal_length_if_zero: bool = False ) -> float: """ Return effective focal length. The function ensures backwards compatibility with older sim-telarray versions. Parameters ---------- unit: str Unit of the effective focal length. Default is 'm'. return_focal_length_if_zero: bool If True, return the focal length if the effective focal length is 0. Returns ------- float: Effective focal length. """ try: eff_focal_length = self.get_parameter_value_with_unit("effective_focal_length")[0] except TypeError: eff_focal_length = self.get_parameter_value_with_unit("effective_focal_length") try: eff_focal_length = eff_focal_length.to(unit).value except AttributeError: eff_focal_length = 0.0 if return_focal_length_if_zero and ( eff_focal_length is None or np.isclose(eff_focal_length, 0.0) ): self._logger.warning("Using focal_length because effective_focal_length is 0") return self.get_parameter_value_with_unit("focal_length").to(unit).value return eff_focal_length
def _load_camera(self): """Load the camera from its validated component parameters.""" configuration = self._resolve_camera_components() focal_length = self.get_telescope_effective_focal_length("cm", True) self._camera = Camera.from_configuration(self.name, configuration, focal_length) def _resolve_camera_components(self): """Resolve the independent camera component parameters.""" pixel_types = deepcopy(self.get_parameter_value("camera_pixel_types")) for pixel_type in pixel_types: angle_parameter = pixel_type.get("lightguide_angle_parameter") if angle_parameter: parameter_data = self.parameters.get(angle_parameter, {}) pixel_type["lightguide_angle_file"] = ( get_simtel_table_file_name(parameter_data) or f"{angle_parameter}-{self.name}.dat" ) wavelength_parameter = pixel_type.get("lightguide_wavelength_parameter") if wavelength_parameter: parameter_data = self.parameters.get(wavelength_parameter, {}) pixel_type["lightguide_wavelength_file"] = ( get_simtel_table_file_name(parameter_data) or f"{wavelength_parameter}-{self.name}.dat" ) return { "rotate": self.get_parameter_value("camera_rotate"), "pixel_types": pixel_types, "pixels": self.model_reader.get_parameter_table_records( self._get_parameter_dict("camera_pixel_layout") ), "triggers": self.model_reader.get_parameter_table_records( self._get_parameter_dict("camera_trigger_groups") ), "trigger_members": self.model_reader.get_parameter_table_records( self._get_parameter_dict("camera_trigger_members") ), }
[docs] def get_on_axis_eff_optical_area(self) -> float: """Return the on-axis effective optical area (derived previously for this telescope).""" ray_tracing_data = self.get_parameter_table("optics_properties") if not np.isclose(ray_tracing_data["off_axis_angle"][0], 0): msg = ( f"No value for the on-axis effective optical area exists." f" The minimum off-axis angle is {ray_tracing_data['off_axis_angle'][0]}" ) raise ValueError(msg) return ray_tracing_data["eff_area"][0]
[docs] def position(self, coordinate_system: str = "ground") -> list: """ Get coordinates in the given system. Parameters ---------- coordinate_system: str Coordinates system. Default is 'ground'. Returns ------- list : List of telescope position in the requested coordinate system. Raises ------ KeyError If the coordinate system is not found. """ try: return self.get_parameter_value_with_unit(f"array_element_position_{coordinate_system}") except InvalidModelParameterError as exc: raise InvalidModelParameterError( f"Coordinate system {coordinate_system} not found." ) from exc
[docs] def get_calibration_device_name(self, device_type): """ Get the calibration device name for this telescope. Parameters ---------- device_type: str Type of the calibration device (e.g., 'flasher', 'illuminator') Returns ------- str or None Calibration device name or None if not defined. """ try: devices = self.get_parameter_value("calibration_devices") or {} except InvalidModelParameterError: return None return devices.get(device_type)