Source code for simtel.simtel_config_writer

#!/usr/bin/python3
"""Configuration file writer for sim_telarray."""

import logging
from copy import deepcopy
from pathlib import Path

import astropy.units as u
import numpy as np

import simtools.utils.general as gen
import simtools.version
from simtools.utils import names

__all__ = ["SimtelConfigWriter"]


def sim_telarray_random_seeds(seed, number):
    """
    Generate random seeds to be used in sim_telarray.

    Parameters
    ----------
    seed: int
        Seed for the random number generator.
    number: int
        Number of random seeds to generate.

    Returns
    -------
    list
        List of random seeds.
    """
    rng = np.random.default_rng(seed)
    max_int32 = np.iinfo(np.int32).max  # sim_telarray requires 32 bit integers
    return list(rng.integers(low=1, high=max_int32, size=number, dtype=np.int32))


[docs] class SimtelConfigWriter: """ SimtelConfigWriter writes sim_telarray configuration files. It is designed to be used by model classes (TelescopeModel and ArrayModel) only. Parameters ---------- site: str South or North. model_version: str Model version. telescope_model_name: str Telescope model name. layout_name: str Layout name. label: str Instance label. Important for output file naming. """ TAB = " " * 3 def __init__( self, site, model_version, layout_name=None, telescope_model_name=None, label=None ): """Initialize SimtelConfigWriter.""" self._logger = logging.getLogger(__name__) self._logger.debug("Init SimtelConfigWriter") self._site = site self._model_version = model_version self._label = label self._layout_name = layout_name self._telescope_model_name = telescope_model_name
[docs] def write_telescope_config_file( self, config_file_path, parameters, telescope_name=None, write_dummy_config=False ): """ Write the sim_telarray config file for a single telescope. Parameters ---------- config_file_path: str or Path Path of the file to write on. parameters: dict Model parameters telescope_name: str Name of the telescope (use self._telescope_model_name if None) write_dummy_config: bool Flag to write a dummy telescope configuration file. """ self._logger.debug(f"Writing telescope config file {config_file_path}") with open(config_file_path, "w", encoding="utf-8") as file: self._write_header(file, "TELESCOPE CONFIGURATION FILE") telescope_name = telescope_name or self._telescope_model_name file.write("#ifdef TELESCOPE\n") file.write(f" echo Configuration for {telescope_name} - TELESCOPE $(TELESCOPE)\n") file.write("#endif\n\n") if write_dummy_config: file.write("#define DUMMY_CONFIG 1\n") for par, value in parameters.items(): simtel_name, value = self._convert_model_parameters_to_simtel_format( names.get_simulation_software_name_from_parameter_name( par, software_name="sim_telarray" ), value["value"], config_file_path, None, ) if simtel_name: file.write(f"{simtel_name} = {self._get_value_string_for_simtel(value)}\n") if "stars" not in parameters: # sim_telarray requires 'stars' to be set file.write("stars = none\n") for meta in self._get_sim_telarray_metadata("telescope", parameters, telescope_name): file.write(f"{meta}\n")
def _get_value_string_for_simtel(self, value): """ Return a value string for simtel. Parameters ---------- value: any Value to convert to string. Returns ------- str Value string for simtel. """ value = "none" if value is None else value # simtel requires 'none' if isinstance(value, bool): value = 1 if value else 0 elif isinstance(value, (list, np.ndarray)): # noqa: UP038 value = gen.convert_list_to_string(value, shorten_list=True) return value def _get_sim_telarray_metadata( self, config_type, model_parameters, telescope_model_name, sim_telarray_seeds=None ): """ Return sim_telarray metadata. Parameters ---------- type: str Type of the configuration file (telescope, site) model_parameters: dict Model parameters dictionary. telescope_model_name: str Name of the telescope model sim_telarray_seeds: dict Dictionary with configuration for sim_telarray random instrument setup. Returns ------- list List with sim_telarray metadata. """ meta_parameters = [ f"config_release = {self._model_version} with simtools v{simtools.version.__version__}", f"config_version = {self._model_version}", ] if config_type == "telescope": meta_parameters.extend( [ f"camera_config_name = {telescope_model_name}", "camera_config_variant = ", f"camera_config_version = {self._model_version}", f"optics_config_name = {telescope_model_name}", "optics_config_variant = ", f"optics_config_version = {self._model_version}", ] ) prefix = "metaparam telescope" elif config_type == "site": meta_parameters.extend( [ f"site_config_name = {self._site}", "site_config_variant = ", f"site_config_version = {self._model_version}", f"array_config_name = {self._layout_name}", "array_config_variant = ", f"array_config_version = {self._model_version}", ] ) prefix = "metaparam global" meta_parameters.append("metaparam global add random_seed") else: raise ValueError(f"Unknown metadata type {config_type}") self._add_model_parameters_to_metadata(model_parameters, meta_parameters, prefix) if sim_telarray_seeds and sim_telarray_seeds.get("random_instrument_instances"): meta_parameters.append(f"{prefix} set instrument_seed={sim_telarray_seeds['seed']}") meta_parameters.append( f"{prefix} set instrument_instances=" f"{sim_telarray_seeds['random_instrument_instances']}" ) return meta_parameters def _add_model_parameters_to_metadata(self, model_parameters, meta_parameters, prefix): """Add model parameters to metadata.""" if not model_parameters: return for key, value in model_parameters.items(): simtel_name = names.get_simulation_software_name_from_parameter_name( key, software_name="sim_telarray", set_meta_parameter=False ) if simtel_name and value.get("meta_parameter"): meta_parameters.append(f"{prefix} add {simtel_name}") simtel_name = names.get_simulation_software_name_from_parameter_name( key, software_name="sim_telarray", set_meta_parameter=True ) if simtel_name and value.get("meta_parameter"): meta_parameters.append(f"{prefix} set {simtel_name}={value['value']}")
[docs] def write_array_config_file( self, config_file_path, telescope_model, site_model, sim_telarray_seeds=None ): """ Write the sim_telarray config file for an array of telescopes. Parameters ---------- config_file_path: str or Path Path of the file to write on. telescope_model: dict of TelescopeModel Dictionary of TelescopeModel's instances as used by the ArrayModel instance. site_model: Site model Site model. sim_telarray_seeds: dict Dictionary with configuration for sim_telarray random instrument setup. """ config_file_directory = Path(config_file_path).parent with open(config_file_path, "w", encoding="utf-8") as file: self._write_header(file, "ARRAY CONFIGURATION FILE") file.write("#ifndef TELESCOPE\n") file.write("# define TELESCOPE 0\n") file.write("#endif\n\n") # TELESCOPE 0 - global parameters file.write("#if TELESCOPE == 0\n") file.write(self.TAB + "echo *****************************\n") file.write(self.TAB + f"echo Site: {self._site}\n") file.write(self.TAB + f"echo LayoutName: {self._layout_name}\n") file.write(self.TAB + f"echo ModelVersion: {self._model_version}\n") file.write(self.TAB + "echo *****************************\n\n") self._write_site_parameters( file, site_model.parameters, config_file_directory, telescope_model, sim_telarray_seeds, ) file.write(self.TAB + f"maximum_telescopes = {len(telescope_model)}\n\n") # Default telescope in sim_telarray - 0th tel in telescope list _, first_telescope = next(iter(telescope_model.items())) invalid_telescope_name = "InvalidTelescope" file.write(f"# include <{invalid_telescope_name}.cfg>\n\n") self.write_dummy_telescope_configuration_file( deepcopy(first_telescope.parameters), config_file_directory / f"{invalid_telescope_name}.cfg", invalid_telescope_name, ) for count, (tel_name, tel_model) in enumerate(telescope_model.items()): tel_config_file = tel_model.config_file_path.name file.write(f"% {tel_name}\n") file.write(f"#elif TELESCOPE == {count + 1}\n\n") file.write(f"# include <{tel_config_file}>\n\n") file.write("#endif \n\n") # configuration files need to end with \n\n if sim_telarray_seeds and sim_telarray_seeds.get("random_instrument_instances"): self._write_random_seeds_file(sim_telarray_seeds, config_file_directory)
def _write_random_seeds_file(self, sim_telarray_seeds, config_file_directory): """ Write list of random number used to generate random instances of instrument. Parameters ---------- random_instrument_instances: int Number of random instances of the instrument. """ self._logger.info( "Writing random seed file " f"{config_file_directory}/{sim_telarray_seeds['seed_file_name']}" f" (global seed {sim_telarray_seeds['seed']})" ) if sim_telarray_seeds["random_instrument_instances"] > 1024: raise ValueError("Number of random instances of instrument must be less than 1024") random_integers = sim_telarray_random_seeds( sim_telarray_seeds["seed"], sim_telarray_seeds["random_instrument_instances"] ) with open( config_file_directory / sim_telarray_seeds["seed_file_name"], "w", encoding="utf-8" ) as file: file.write( "# Random seeds for instrument configuration generated with seed " f"{sim_telarray_seeds['seed']}" f" (model version {self._model_version}, site {self._site})\n" ) for number in random_integers: file.write(f"{number}\n")
[docs] def write_single_mirror_list_file( self, mirror_number, mirrors, single_mirror_list_file, set_focal_length_to_zero=False ): """ Write the sim_telarray mirror list file for a single mirror. Parameters ---------- mirror_number: int Mirror number. mirrors: Mirrors Instance of Mirrors. single_mirror_list_file: str or Path Path of the file to write on. set_focal_length_to_zero: bool Flag to set the focal length to zero. """ ( __, __, mirror_panel_diameter, focal_length, shape_type, ) = mirrors.get_single_mirror_parameters(mirror_number) with open(single_mirror_list_file, "w", encoding="utf-8") as file: self._write_header(file, "MIRROR LIST FILE", "#") file.write("# Column 1: X pos. [cm] (North/Down)\n") file.write("# Column 2: Y pos. [cm] (West/Right from camera)\n") file.write("# Column 3: flat-to-flat diameter [cm]\n") file.write( "# Column 4: focal length [cm], typically zero = adapting in sim_telarray.\n" ) file.write( "# Column 5: shape type: 0=circular, 1=hex. with flat side parallel to y, " "2=square, 3=other hex. (default: 0)\n" ) file.write( "# Column 6: Z pos (height above dish backplane) [cm], typ. omitted (or zero)" " to adapt to dish shape settings.\n" ) file.write("#\n") file.write( f"0. 0. {mirror_panel_diameter.to('cm').value} " f"{focal_length.to('cm').value if not set_focal_length_to_zero else 0} " f"{shape_type} 0.\n" )
def _write_header(self, file, title, comment_char="%"): """ Write a generic header. Parameters ---------- file: file File to write. title: str Title of the header. comment_char: str Character to be used for comments, which differs among types of config files. """ header = f"{comment_char}{50 * '='}\n" header += f"{comment_char} {title}\n" header += f"{comment_char} Site: {self._site}\n" header += f"{comment_char} ModelVersion: {self._model_version}\n" header += ( f"{comment_char} TelescopeModelName: {self._telescope_model_name}\n" if self._telescope_model_name is not None else "" ) header += ( f"{comment_char} LayoutName: {self._layout_name}\n" if self._layout_name is not None else "" ) header += f"{comment_char} Label: {self._label}\n" if self._label is not None else "" header += f"{comment_char}{50 * '='}\n" header += f"{comment_char}\n" file.write(header) def _write_site_parameters( self, file, site_parameters, model_path, telescope_model, sim_telarray_seeds=None ): """ Write site parameters. Parameters ---------- file: file File to write on. site_parameters: site parameters Site parameters. model_path: Path Path to the model for writing of additional files. telescope_model: dict of TelescopeModel Telescope models. sim_telarray_seeds: dict Dictionary with configuration for sim_telarray random instrument setup. """ file.write(self.TAB + "% Site parameters\n") for par, value in site_parameters.items(): simtel_name, value = self._convert_model_parameters_to_simtel_format( names.get_simulation_software_name_from_parameter_name( par, software_name="sim_telarray" ), value["value"], model_path, telescope_model, ) if simtel_name is not None: file.write(f"{self.TAB}{simtel_name} = {value}\n") for meta in self._get_sim_telarray_metadata( "site", site_parameters, self._telescope_model_name, sim_telarray_seeds ): file.write(f"{self.TAB}{meta}\n") file.write("\n") def _convert_model_parameters_to_simtel_format( self, simtel_name, value, model_path, telescope_model ): """ Convert model parameter value to simtel format. This might involve format or unit conversion and writing to a parameter file. Parameters ---------- simtel_name: str Parameter name. value: any Value to convert. model_path: Path Path to the model for writing of additional files. telescope_model: dict of TelescopeModel Telescope models. Returns ------- str, any Converted parameter name and value. """ conversion_dict = { "array_triggers": self._write_array_triggers_file, } try: value = conversion_dict[simtel_name](value, model_path, telescope_model) except KeyError: pass except AttributeError: # covers cases where telescope_model is None return None, None return simtel_name, value def _write_array_triggers_file(self, array_triggers, model_path, telescope_model): """ Write array trigger definition file in simtel format. Parameters ---------- array_triggers: dict Array trigger definitions. model_path: Path Path to the model for writing of additional files. telescope_model: dict of TelescopeModel Telescope models. """ trigger_per_telescope_type = {} for count, tel_name in enumerate(telescope_model.keys()): telescope_type = names.get_array_element_type_from_name(tel_name) trigger_per_telescope_type.setdefault(telescope_type, []).append(count + 1) trigger_lines = {} for tel_type, tel_list in trigger_per_telescope_type.items(): trigger_dict = self._get_array_triggers_for_telescope_type(array_triggers, tel_type) trigger_lines[tel_type] = f"Trigger {trigger_dict['multiplicity']['value']} of " trigger_lines[tel_type] += ", ".join(map(str, tel_list)) width = trigger_dict["width"]["value"] * u.Unit(trigger_dict["width"]["unit"]).to("ns") trigger_lines[tel_type] += f" width {width}" if trigger_dict.get("hard_stereo"): trigger_lines[tel_type] += " hard_stereo" if all(trigger_dict["min_separation"][key] is not None for key in ["value", "unit"]): min_sep = trigger_dict["min_separation"]["value"] * u.Unit( trigger_dict["min_separation"]["unit"] ).to("m") trigger_lines[tel_type] += f" minsep {min_sep}" array_triggers_file = "array_triggers.dat" with open(model_path / array_triggers_file, "w", encoding="utf-8") as file: file.write("# Array trigger definition\n") file.writelines(f"{line}\n" for line in trigger_lines.values()) return array_triggers_file def _get_array_triggers_for_telescope_type(self, array_triggers, telescope_type): """ Get array trigger for a specific telescope type. Parameters ---------- array_triggers: dict Array trigger definitions. telescope_type: str Telescope type. Returns ------- dict Array trigger for the telescope type. """ for trigger_dict in array_triggers: if trigger_dict["name"] == telescope_type + "_array": return trigger_dict return None
[docs] def write_dummy_telescope_configuration_file( self, parameters, config_file_path, telescope_name ): """ Write 'dummy' telescope configuration file used as zeroth telescope in sim_telarray. Replaces key telescope configuration values with dummy values. Parameters ---------- parameters: dict Telescope parameters used as template. config_file_path: str or Path Path of the dummy configuration file to write on. telescope_name: str Name of the telescope. """ self._logger.debug(f"Writing {telescope_name} telescope config file {config_file_path}") dummy_defaults = { "camera_config_file": f"{telescope_name}_single_pixel_camera.dat", "discriminator_pulse_shape": f"{telescope_name}_pulse.dat", "fadc_pulse_shape": f"{telescope_name}_pulse.dat", "mirror_list": f"{telescope_name}_single_12m_mirror.dat", "mirror_reflectivity": f"{telescope_name}_reflectivity.dat", "camera_pixels": 1, "trigger_pixels": 1, "num_gains": 1, "fadc_bins": 10, "disc_bins": 10, "fadc_sum_bins": 10, "fadc_sum_offset": 0, "asum_threshold": 0, "dsum_threshold": 0, "discriminator_threshold": 1, "fadc_amplitude": 1.0, "discriminator_amplitude": 1.0, } for key, val in dummy_defaults.items(): if key in parameters: parameters[key]["value"] = val self.write_telescope_config_file( config_file_path, parameters, telescope_name, write_dummy_config=True ) config_file_directory = Path(config_file_path).parent self._write_dummy_mirror_list_files(config_file_directory, telescope_name) self._write_dummy_camera_files(config_file_directory, telescope_name)
def _write_dummy_mirror_list_files(self, config_directory, telescope_name): """Write dummy mirror list with single mirror and reflectivity file.""" with open( config_directory / f"{telescope_name}_single_12m_mirror.dat", "w", encoding="utf-8" ) as file: file.write("0 0 1200 0.0 0\n") with open( config_directory / f"{telescope_name}_reflectivity.dat", "w", encoding="utf-8" ) as file: file.writelines(f"{w} 0.8\n" for w in range(200, 801, 50)) def _write_dummy_camera_files(self, config_directory, telescope_name): """Write dummy camera, pulse shape, and funnels file with a single pixel.""" with open( config_directory / f"{telescope_name}_single_pixel_camera.dat", "w", encoding="utf-8" ) as file: file.write(f'PixType 1 0 0 300 1 300 0.00 "{telescope_name}_funnels.dat"\n') file.write("Pixel 0 1 0. 0. 0 0 0 0x00 1\n") file.write("Trigger 1 of 0\n") with open(config_directory / f"{telescope_name}_funnel.dat", "w", encoding="utf-8") as file: file.writelines(f"{a} 0.78 1.5\n" for a in range(36)) with open(config_directory / f"{telescope_name}_pulse.dat", "w", encoding="utf-8") as file: file.write("0 0 0\n") file.write("1 1 1\n") file.write("2 2 2\n") file.write("3 3 3\n") file.write("4 0 0\n")