Source code for model.site_model

#!/usr/bin/python3
"""Definition of site model."""

import logging

import numpy as np
from astropy import units as u

from simtools.model.model_parameter import ModelParameter
from simtools.simulation.configuration import get_model_writer
from simtools.utils import names


[docs] class SiteModel(ModelParameter): """ Representation of an observatory site model. The site model includes (among others): - Reference point coordinates - Array elements - Geomagnetic field parameters - Atmospheric parameters - NSB parameters Parameters ---------- site: str Site name (e.g., South or North). model_version: str or list Model version or list of model versions (in which case only the first one is used). 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. parameter_names: iterable of str, optional If supplied, load only these site parameters. load_simulation_software_parameters: bool If False, do not load CORSIKA and sim_telarray configuration parameters. """ def __init__( self, site, model_version, label=None, overwrite_model_parameter_dict=None, ignore_software_version=False, model_reader=None, context=None, model_directory=None, parameter_names=None, load_simulation_software_parameters=True, ): """Initialize SiteModel.""" self._logger = logging.getLogger(__name__) self._logger.debug("Init SiteModel for site %s", site) super().__init__( site=site, model_version=model_version, label=label, collection="sites", overwrite_model_parameter_dict=overwrite_model_parameter_dict, ignore_software_version=ignore_software_version, model_reader=model_reader, context=context, model_directory=model_directory, parameter_names=parameter_names, load_simulation_software_parameters=load_simulation_software_parameters, )
[docs] def get_reference_point(self): """ Get reference point coordinates. Ground coordinates are calculated relative to this point. Returns ------- dict Reference point coordinates. """ return { "center_altitude": self.get_parameter_value_with_unit("reference_point_altitude"), "center_northing": self.get_parameter_value_with_unit("reference_point_utm_north"), "center_easting": self.get_parameter_value_with_unit("reference_point_utm_east"), "epsg_code": self.get_parameter_value("epsg_code"), }
[docs] def get_corsika_site_parameters(self, config_file_style=False, model_directory=None): """ Get site-related CORSIKA parameters. Parameters are returned with units wherever possible ('config_file_style=False') or in CORSIKA-expected coordinates. Parameters ---------- config_file_style: bool Return using CORSIKA config file syntax model_directory: Path, optional Model directory to use for file paths Returns ------- dict Site-related CORSIKA parameters. """ if config_file_style: # pylint: disable-next=import-outside-toplevel from simtools.corsika.corsika_config import get_site_config_parameters return get_site_config_parameters(self, model_directory) return { "corsika_observation_level": self.get_parameter_value_with_unit( "corsika_observation_level" ), "geomag_horizontal": self.get_parameter_value_with_unit("geomag_horizontal"), "geomag_vertical": self.get_parameter_value_with_unit("geomag_vertical"), "geomag_rotation": self.get_parameter_value_with_unit("geomag_rotation"), }
[docs] def get_array_elements_for_layout(self, layout_name): """ Return list of array elements for a given array layout. If ``layout_name`` is not found in the model repository but is a valid, concrete telescope name (e.g., ``MSTN-05``) belonging to this site, a single-telescope layout is returned automatically. Parameters ---------- layout_name: str Name of the array layout or a single telescope name (e.g., ``MSTN-05``). Returns ------- list List of array elements. """ layouts = self.get_parameter_value("array_layouts") for layout in layouts: if layout["name"].lower() == layout_name.lower(): return layout["elements"] validated_name = self._validate_as_single_telescope(layout_name) if validated_name is not None: self._logger.debug( f"Array layout '{layout_name}' not found in the model repository; " "treating as single-telescope layout." ) return [validated_name] raise ValueError(f"Array layout '{layout_name}' not found in '{self.site}' site model.")
def _validate_as_single_telescope(self, layout_name): """ Validate that layout_name is a concrete telescope belonging to this site. Parameters ---------- layout_name: str Candidate telescope name. Returns ------- str or None Validated and normalized telescope name, or None if not a valid single telescope. """ try: validated_name = names.validate_array_element_name(layout_name) if names.get_collection_name_from_array_element_name(validated_name) != "telescopes": return None if names.is_design_type(validated_name): return None if names.get_site_from_array_element_name(validated_name) != self.site: return None if validated_name not in self.get_array_elements_of_type( names.get_array_element_type_from_name(validated_name) ): return None except ValueError: return None return validated_name
[docs] def get_array_elements_of_type(self, array_element_type): """ Get all array elements of a given type. Parameters ---------- array_element_type : str Type of the array element (e.g. LSTN, MSTS) Returns ------- list List with array elements. """ try: collection = names.get_collection_name_from_array_element_name(array_element_type) except ValueError: return [] return self.model_reader.get_array_elements_of_type( array_element_type=array_element_type, model_version=self.model_version, collection=collection, )
[docs] def get_list_of_array_layouts(self): """ Get list of available array layouts. Returns ------- list List of available array layouts """ return [layout["name"] for layout in self.get_parameter_value("array_layouts")]
[docs] def export_atmospheric_transmission_file(self, model_directory): """ Export the atmospheric profile source and sim_telarray table files. Parameters ---------- model_directory: Path Model directory to export the file to. """ return get_model_writer(self, context=self.context).export_atmospheric_transmission_file( model_directory=model_directory )
[docs] def get_nsb_integrated_flux(self, wavelength_min=300 * u.nm, wavelength_max=650 * u.nm): """ Get the integrated flux for the NSB (Night Sky Background) model. Returns ------- float Integrated flux value. """ table = self.get_parameter_table("nsb_spectrum") table.sort("wavelength") wavelength_column = table["wavelength"] rate_column = table["differential_photon_rate"] wl = getattr(wavelength_column, "quantity", wavelength_column).to(u.nm) rate = getattr(rate_column, "quantity", rate_column).to(1 / (u.nm * u.cm**2 * u.ns * u.sr)) mask = (wl >= wavelength_min) & (wl <= wavelength_max) integral_cm2 = np.trapezoid(rate[mask], wl[mask]) self._logger.debug( f"NSB integral between {wavelength_min} and {wavelength_max}: {integral_cm2}" ) return float(getattr(integral_cm2, "value", integral_cm2))