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.version
from simtools import dependencies
from simtools.constants import SIM_TELARRAY_INCLUDE_FILENAME_MAX_LENGTH
from simtools.context import RunContext
from simtools.data_model import parameter_catalog as catalog
from simtools.data_model import schema
from simtools.data_model.table_asset import get_simtel_serialization
from simtools.model.mirrors import uses_segmented_dual_mirror_geometry
from simtools.model_repository.asset_names import get_simtel_table_file_name
from simtools.simtel import (
    segmentation,
    sim_telarray_metadata,
    simtel_file_writer,
    table_serializers,
)
from simtools.utils import names, value_conversion

logger = logging.getLogger(__name__)

_CAMERA_COMPONENT_PARAMETERS = (
    "camera_rotate",
    "camera_pixel_types",
    "camera_pixel_layout",
    "camera_trigger_groups",
    "camera_trigger_members",
)
_FLASHER_LEGACY_PARAMETERS = ("flasher_pulse_width", "flasher_pulse_exp_decay")
_LIGHTGUIDE_KEYS = (
    ("lightguide_angle_parameter", "lightguide_angle_file"),
    ("lightguide_wavelength_parameter", "lightguide_wavelength_file"),
)
_SKIPPED_PARAMETERS = (
    *_CAMERA_COMPONENT_PARAMETERS,
    "camera_config_file",
    *_FLASHER_LEGACY_PARAMETERS,
)


[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, telescope_design_model=None, label=None, model_reader=None, context=None, ): """Initialize SimtelConfigWriter.""" self._site = site self._model_version = model_version self._label = label self._layout_name = layout_name self._telescope_model_name = telescope_model_name self._telescope_design_model = telescope_design_model self._model_reader = model_reader self._context = context or RunContext.from_environment() self._serialized_table_cache = {} self._camera_file_cache = {} @property def label(self): """Return the label used for generated configuration files.""" return self._label
[docs] def write_telescope_config_file( self, config_file_path, parameters, telescope_name=None, telescope_design_model=None ): """ 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) telescope_design_model: str Telescope design model. """ logger.debug(f"Writing telescope config file {config_file_path}") telescope_name = telescope_name or self._telescope_model_name _telescope_design_model = telescope_design_model or self._telescope_design_model simtel_par = self._get_parameters_for_sim_telarray( parameters, config_file_path, telescope_name=telescope_name ) with open(config_file_path, "w", encoding="utf-8") as file: self._write_header(file, "TELESCOPE CONFIGURATION FILE") file.write( "#ifdef TELESCOPE\n" f" echo Configuration for {telescope_name} - TELESCOPE $(TELESCOPE)\n" "#endif\n\n" ) for simtel_name, simtel_value in simtel_par.items(): file.write(f"{simtel_name} = {self._get_value_string_for_simtel(simtel_value)}\n") metadata = self._get_sim_telarray_metadata( "telescope", parameters, telescope_name, telescope_design_model=_telescope_design_model, additional_meta_parameters=( ["camera_config_file"] if "camera_config_file" in simtel_par else [] ), ) file.write("".join(f"{meta}\n" for meta in metadata))
def _get_parameters_for_sim_telarray(self, parameters, config_file_path, telescope_name=None): """Convert model parameters to sim_telarray names and values.""" simtel_par = {} camera_file = self._write_camera_file( parameters, config_file_path, telescope_name=telescope_name ) if camera_file is not None: simtel_par["camera_config_file"] = camera_file elif "camera_config_file" in parameters: simtel_par["camera_config_file"] = parameters["camera_config_file"]["value"] for par, value in parameters.items(): if par in _SKIPPED_PARAMETERS: continue if par == "mirror_list" and uses_segmented_dual_mirror_geometry(parameters): simtel_par["mirror_list"] = None continue simtel_name, simtel_value = self._convert_model_parameters_to_simtel_format( self._get_sim_telarray_config_parameter_name(par), value["value"], config_file_path, None, parameter_name=par, parameter_data=value, ) if simtel_name: simtel_par[simtel_name] = simtel_value if "stars" not in parameters: # sim_telarray requires 'stars' to be set simtel_par["stars"] = None return dict( sorted(self._get_flasher_parameters_for_sim_telarray(parameters, simtel_par).items()) ) def _write_camera_file(self, parameters, config_file_path, telescope_name=None): """Write a sim_telarray camera file from independent camera parameters.""" missing = sorted(set(_CAMERA_COMPONENT_PARAMETERS) - set(parameters)) if len(missing) == len(_CAMERA_COMPONENT_PARAMETERS): return None if missing: raise ValueError(f"Camera component parameters are missing: {missing}") destination = Path(config_file_path).parent telescope_name = telescope_name or Path(config_file_path).stem cache_key = repr( tuple((name, parameters.get(name)) for name in _CAMERA_COMPONENT_PARAMETERS) ) shared_camera = self._telescope_design_model is not None and all( parameters[name].get("instrument") == self._telescope_design_model for name in _CAMERA_COMPONENT_PARAMETERS ) camera_name = self._telescope_design_model if shared_camera else telescope_name output = destination / f"camera-{camera_name}.dat" if output.is_file() and (shared_camera or self._camera_file_cache.get(output) == cache_key): return output.name pixel_types = self._write_lightguide_tables(parameters, destination, telescope_name) pixels = self._validated_camera_pixels(parameters) configuration = { "rotate": self._parameter_value(parameters, "camera_rotate"), "pixel_types": pixel_types, "pixels": pixels, "triggers": self._camera_table_records(parameters, "camera_trigger_groups"), "trigger_members": self._camera_table_records(parameters, "camera_trigger_members"), } result = simtel_file_writer.write_camera_file(configuration, output) self._camera_file_cache[output] = cache_key return result def _write_lightguide_tables(self, parameters, destination, telescope_name): """Return pixel types with light-guide table references written to sim_telarray files.""" pixel_types = deepcopy(self._parameter_value(parameters, "camera_pixel_types")) for pixel_type in pixel_types: for parameter_key, output_key in _LIGHTGUIDE_KEYS: parameter_name = pixel_type.pop(parameter_key, None) if parameter_name is not None: pixel_type[output_key] = self._write_parameter_table( parameter_name, parameters[parameter_name], destination, f"{parameter_name}-{telescope_name}.dat", require_rows=True, ) return pixel_types def _validated_camera_pixels(self, parameters): """Return camera pixel rows after checking them against the expected pixel count.""" pixels = self._camera_table_records(parameters, "camera_pixel_layout") expected_pixels = self._parameter_value(parameters, "camera_pixels") if expected_pixels is not None and int(expected_pixels) != len(pixels): raise ValueError( f"camera_pixels={expected_pixels} does not match camera layout rows={len(pixels)}" ) return pixels @staticmethod def _parameter_value(parameters, parameter_name): """Return a selected parameter value or ``None`` for an absent optional value.""" return (parameters.get(parameter_name) or {}).get("value") def _camera_table_records(self, parameters, parameter_name): """Return the rows of an exported camera component table as scalar dicts.""" parameter_data = parameters.get(parameter_name) if parameter_data is None: raise ValueError(f"Camera component parameter is missing: {parameter_name}") table = self._read_parameter_table(parameter_data) columns = [[getattr(v, "value", v) for v in table[column]] for column in table.colnames] return [dict(zip(table.colnames, row)) for row in zip(*columns)] def _read_parameter_table(self, parameter_data): """Return the astropy table of a model parameter via the model reader.""" if self._model_reader is None: raise RuntimeError("sim_telarray table serialization requires a model reader") return self._model_reader.get_parameter_table(parameter_data) def _write_parameter_table( self, parameter_name, parameter_data, destination, default_name, require_rows=False ): """Write an ECSV model parameter as sim_telarray table unless it is already up to date.""" contract = get_simtel_serialization( schema.get_model_parameter_schema( parameter_name, parameter_data.get("model_parameter_schema_version") ) ) shared_name = get_simtel_table_file_name(parameter_data) output_name = shared_name or default_name output_path = Path(destination) / output_name serialization_key = repr(((parameter_name, parameter_data), contract)) if not ( output_path.is_file() and ( shared_name is not None or self._serialized_table_cache.get(output_path) == serialization_key ) ): table = self._read_parameter_table(parameter_data) if require_rows and len(table) == 0: raise ValueError(f"Selected lightguide parameter is empty: {parameter_name}") table_serializers.write_simtel_table( table, destination, contract=contract, output_name=output_name ) self._serialized_table_cache[output_path] = serialization_key return output_name @staticmethod def _get_sim_telarray_config_parameter_name(parameter_name): """Return the sim_telarray name when it is an ordinary config parameter.""" simtel_name = catalog.get_simulation_software_name_from_parameter_name( parameter_name, software_name="sim_telarray" ) mode = catalog.get_simulation_software_meta_parameter_mode( parameter_name, software_name="sim_telarray" ) return None if simtel_name and mode == "set" else simtel_name def _get_flasher_parameters_for_sim_telarray(self, parameters, simtel_par): """Combine flasher pulse time parameters into the single sim_telarray parameter set.""" if "flasher_pulse_shape" not in parameters: return simtel_par mapping = { "gauss": "laser_pulse_sigtime", "tophat": "laser_pulse_twidth", "gauss-exponential": "laser_pulse_sigtime", } shape_value = self._parameter_value(parameters, "flasher_pulse_shape") if isinstance(shape_value, str): shape = shape_value.lower() width = self._parameter_value(parameters, "flasher_pulse_width") width = 0.0 if width is None else width exp_decay = self._parameter_value(parameters, "flasher_pulse_exp_decay") exp_decay = 0.0 if exp_decay is None else exp_decay else: shape, width, exp_decay = shape_value shape = shape.lower() simtel_par.pop("flasher_pulse_width", None) simtel_par.pop("flasher_pulse_exp_decay", None) simtel_par["laser_pulse_exptime"] = exp_decay if ("exponential" in shape) else 0.0 simtel_par.update(dict.fromkeys(mapping.values(), 0.0)) if shape in mapping: simtel_par[mapping[shape]] = width elif shape != "exponential": logger.warning(f"Flasher pulse shape '{shape}' without width definition") return simtel_par def _get_value_string_for_simtel(self, value): """Return a value string for simtel (none, 0/1 for booleans, space-separated lists).""" 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): value = value_conversion.convert_list_to_string(value, shorten_list=True) return value def _config_type_meta_parameters(self, config_type, telescope_model_name, design_model): """Return the release/config header lines and metaparam prefix for a config type.""" meta_parameters = [ f"config_release = {self._model_version} with simtools v{simtools.version.__version__}", f"config_version = {self._model_version}", ] design_model = design_model or "design_model_not_set" if config_type == "telescope": meta_parameters.extend( [ f"camera_config_name = {design_model}", f"camera_config_variant = {telescope_model_name}", f"camera_config_version = {self._model_version}", f"optics_config_name = {design_model}", f"optics_config_variant = {telescope_model_name}", f"optics_config_version = {self._model_version}", ] ) return meta_parameters, "metaparam telescope" if 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}", "metaparam global add random_seed", ] ) return meta_parameters, "metaparam global" raise ValueError(f"Unknown metadata type {config_type}") @staticmethod def _model_parameter_meta_lines(model_parameters, prefix): """Return add/set metaparam lines for model parameters known to sim_telarray.""" lines = [] for key, value in (model_parameters or {}).items(): if key == "camera_config_file": continue simtel_name = catalog.get_simulation_software_name_from_parameter_name( key, software_name="sim_telarray" ) mode = catalog.get_simulation_software_meta_parameter_mode( key, software_name="sim_telarray" ) if simtel_name and mode == "add": lines.append(f"{prefix} add {simtel_name}") elif simtel_name and mode == "set": lines.append(f"{prefix} set {simtel_name}={value['value']}") return lines def _get_sim_telarray_metadata( self, config_type, model_parameters, telescope_model_name, additional_metadata=None, telescope_design_model=None, additional_meta_parameters=None, ): """Return sim_telarray metadata lines for a 'telescope' or 'site' configuration file.""" meta_parameters, prefix = self._config_type_meta_parameters( config_type, telescope_model_name, telescope_design_model ) meta_parameters.extend(self._model_parameter_meta_lines(model_parameters, prefix)) for parameter in additional_meta_parameters or []: meta_parameters.append(f"{prefix} add {parameter}") for key, value in (additional_metadata or {}).items(): if value is not None: meta_parameters.append(f"{prefix} set {key}={value}") sim_telarray_metadata.validate_metadata(meta_parameters) return meta_parameters
[docs] def write_array_config_file( self, config_file_path, telescope_model, site_model, additional_metadata=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. additional_metadata: dict Dictionary with additional metadata to include. Raises ------ FileExistsError If the config file already exists. """ config_file_directory = Path(config_file_path).parent config_file_path = Path(config_file_path) if config_file_path.exists(): raise FileExistsError(f"Array config file {config_file_path} already exists.") with open(config_file_path, "w", encoding="utf-8") as file: self._write_header(file, "ARRAY CONFIGURATION FILE") file.write("#ifndef TELESCOPE\n# define TELESCOPE 0\n#endif\n\n") # TELESCOPE 0 - global parameters file.write( "#if TELESCOPE == 0\n" f"{self.TAB}echo *****************************\n" f"{self.TAB}echo Site: {self._site}\n" f"{self.TAB}echo LayoutName: {self._layout_name}\n" f"{self.TAB}echo ModelVersion: {self._model_version}\n" f"{self.TAB}echo *****************************\n\n" ) self._write_simtools_parameters(file) global_parameters = { name: parameter for name, parameter in { **site_model.get_simulation_software_parameters("sim_telarray"), **site_model.parameters, }.items() if not catalog.is_global_sim_telarray_parameter(name) } self._write_site_parameters( file, global_parameters, config_file_path, telescope_model, additional_metadata, ) 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" invalid_telescope_config = f"{invalid_telescope_name}.cfg" self._validate_include_file_name_length(invalid_telescope_config) file.write(f"# include <{invalid_telescope_config}>\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 self._validate_include_file_name_length(tel_config_file) 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
def _validate_include_file_name_length(self, file_name): """Validate include-file names against the sim_telarray parser word-length limit.""" if len(file_name) > SIM_TELARRAY_INCLUDE_FILENAME_MAX_LENGTH: raise ValueError( "sim_telarray include filename exceeds parser limit " f"({len(file_name)}>{SIM_TELARRAY_INCLUDE_FILENAME_MAX_LENGTH}): {file_name}" )
[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" "# Column 2: Y pos. [cm] (West/Right from camera)\n" "# Column 3: flat-to-flat diameter [cm]\n" "# Column 4: focal length [cm], typically zero = adapting in sim_telarray.\n" "# Column 5: shape type: 0=circular, 1=hex. with flat side parallel to y, " "2=square, 3=other hex. (default: 0)\n" "# Column 6: Z pos (height above dish backplane) [cm], typ. omitted (or zero)" " to adapt to dish shape settings.\n" "#\n" 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 with site, model version and optional names.""" sep = f"{comment_char}{50 * '='}" optional = { "TelescopeModelName": self._telescope_model_name, "LayoutName": self._layout_name, "Label": self._label, } lines = [ sep, f"{comment_char} {title}", f"{comment_char} Site: {self._site}", f"{comment_char} ModelVersion: {self._model_version}", *(f"{comment_char} {key}: {val}" for key, val in optional.items() if val is not None), sep, comment_char, ] file.write("".join(f"{line}\n" for line in lines)) def _write_simtools_parameters(self, file): """Write simtools-specific parameters as metadata.""" meta_items = { "simtools_version": simtools.version.__version__, "simtools_model_production_version": self._model_version, **dependencies.get_dependency_metadata(context=self._context), } try: build_opts = dependencies.get_build_options(context=self._context) for key, value in build_opts.items(): if key in dependencies.SIMTEL_METADATA_BUILD_OPTION_KEYS: meta_items[f"simtools_{key}"] = value except FileNotFoundError, TypeError: pass # don't expect build_opts.yml to be present on all systems # CORSIKA executable without _flat/_curved suffix (do not know here if curved or flat) try: meta_items["simtools_corsika_exec"] = ( self._context.software.corsika_exe.name.removesuffix("_flat") ) except AttributeError as exc: raise AttributeError("CORSIKA executable path is not set in settings.") from exc file.write(f"{self.TAB}% Simtools parameters\n") meta_lines = [ f"metaparam global set {key} = {value}" for key, value in meta_items.items() if not isinstance(value, list) ] sim_telarray_metadata.validate_metadata(meta_lines) file.write("".join(f"{self.TAB}{line}\n" for line in meta_lines)) def _write_site_parameters( self, file, site_parameters, model_path, telescope_model, additional_metadata=None ): """Write site parameters and their sim_telarray metadata.""" file.write(self.TAB + "% Site parameters\n") for par, value in site_parameters.items(): simtel_name, simtel_value = self._convert_model_parameters_to_simtel_format( self._get_sim_telarray_config_parameter_name(par), value["value"], model_path, telescope_model, parameter_name=par, parameter_data=value, ) if simtel_name is not None: file.write(f"{self.TAB}{simtel_name} = {simtel_value}\n") for meta in self._get_sim_telarray_metadata( "site", site_parameters, None, additional_metadata ): 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, parameter_name=None, parameter_data=None, ): """Convert a model parameter value to sim_telarray format (may write a table file).""" if simtel_name is None: return None, None if simtel_name in {"primary_segmentation", "secondary_segmentation"} and isinstance( value, list ): instrument = parameter_data.get("instrument") shared_output = instrument is not None and instrument == self._telescope_design_model suffix = instrument if shared_output else Path(model_path).stem output = Path(model_path).parent / f"{parameter_name}-{suffix}.dat" if shared_output and output.is_file(): return simtel_name, output.name return simtel_name, segmentation.write_mirror_segmentation( value, output, parameter_name=parameter_name, schema_version=parameter_data["model_parameter_schema_version"], ) if isinstance(value, str) and value.lower().endswith(".ecsv"): return simtel_name, self._write_table_parameter_file( simtel_name, value, model_path, telescope_model, parameter_name, parameter_data ) conversions = { "array_triggers": lambda: self._write_array_triggers_file( value, model_path, telescope_model ), "fadc_pulse_shape": lambda: self._write_table_parameter_file( "fadc_pulse_shape", value, model_path, telescope_model, parameter_name, parameter_data, ), } try: value = conversions[simtel_name]() except KeyError: pass except AttributeError: # covers cases where telescope_model is None return None, None return simtel_name, value def _write_table_parameter_file( self, parameter_name, value, model_path, _telescope_model, source_parameter=None, parameter_data=None, ): """Write an ECSV table parameter as sim_telarray table; return its basename or value.""" if isinstance(value, str) and value.lower().endswith(".ecsv"): if self._model_reader is None: raise RuntimeError("sim_telarray table serialization requires a model reader") name = source_parameter or parameter_name return self._write_parameter_table( name, parameter_data, Path(model_path).parent, f"{name}-{Path(model_path).stem}.dat", ) if not isinstance(value, dict): return value raise TypeError("sim_telarray table writing requires a validated Astropy ECSV table") def _write_array_triggers_file(self, array_triggers, model_path, telescope_model): """Write the array trigger definition file (simtel format) and return its basename.""" 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) lines = [] non_hardstereo_groups = {} all_non_hardstereo_tels = [] multiplicity = None for tel_type, tel_list in trigger_per_telescope_type.items(): suffix = "_single_telescope" if len(tel_list) == 1 else "_array" trigger = next((t for t in array_triggers if t["name"] == tel_type + suffix), None) width = trigger["width"]["value"] * u.Unit(trigger["width"]["unit"]).to("ns") sep = trigger["min_separation"] minsep = None if all(sep[key] is not None for key in ["value", "unit"]): minsep = sep["value"] * u.Unit(sep["unit"]).to("m") multiplicity = trigger["multiplicity"]["value"] # Store for later use if trigger.get("hard_stereo", {}).get("value"): lines.append(self._trigger_line(multiplicity, tel_list, width, minsep, True)) else: non_hardstereo_groups.setdefault((width, minsep), []).extend(tel_list) all_non_hardstereo_tels.extend(tel_list) # Individual non-hardstereo groups if they have different parameters if len(non_hardstereo_groups) > 1: lines += [ self._trigger_line(multiplicity, tel_list, width, minsep) for (width, minsep), tel_list in non_hardstereo_groups.items() ] # Combined line with all non-hardstereo telescopes using shortest values if all_non_hardstereo_tels: group_keys = list(non_hardstereo_groups) minseps = [minsep for _, minsep in group_keys if minsep is not None] lines.append( self._trigger_line( multiplicity, sorted(all_non_hardstereo_tels), min(width for width, _ in group_keys), min(minseps) if minseps else None, ) ) array_triggers_file = "array_triggers.dat" model_path = Path(model_path) model_directory = model_path if model_path.is_dir() else model_path.parent with open(model_directory / array_triggers_file, "w", encoding="utf-8") as file: file.write("# Array trigger definition\n" + "".join(f"{line}\n" for line in lines)) return array_triggers_file @staticmethod def _trigger_line(multiplicity, tel_list, width, minsep, hardstereo=False): """Return one sim_telarray 'Trigger' line.""" line = f"Trigger {multiplicity} of " + ", ".join(map(str, tel_list)) + f" width {width}" if hardstereo: line += " hardstereo" if minsep is not None: line += f" minsep {minsep}" return line
[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. """ 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": 9999, "dsum_threshold": 9999, "discriminator_threshold": 9999, "fadc_amplitude": 1.0, "discriminator_amplitude": 1.0, } for key, val in dummy_defaults.items(): if key in parameters: parameters[key]["value"] = val parameters["camera_config_file"] = {"value": dummy_defaults["camera_config_file"]} camera_components = { n: parameters.pop(n) for n in _CAMERA_COMPONENT_PARAMETERS if n in parameters } try: self.write_telescope_config_file(config_file_path, parameters, telescope_name) finally: parameters.update(camera_components) t = telescope_name dummy_files = { f"{t}_single_12m_mirror.dat": "0 0 1200 0.0 0\n", f"{t}_reflectivity.dat": "".join(f"{w} 0.8\n" for w in range(200, 801, 50)), f"{t}_single_pixel_camera.dat": ( f'PixType 1 0 0 300 1 300 0.00 "{t}_funnels.dat"\n' "Pixel 0 1 0. 0. 0 0 0 0x00 1\n" "Trigger 1 of 0\n" ), f"{t}_funnel.dat": "".join(f"{a} 0.78 1.5\n" for a in range(36)), f"{t}_pulse.dat": "0 0 0\n1 1 1\n2 2 2\n3 3 3\n4 0 0\n", } config_directory = Path(config_file_path).parent for file_name, text in dummy_files.items(): (config_directory / file_name).write_text(text, encoding="utf-8")