gammasimtools 0.26.0__py3-none-any.whl → 0.27.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. {gammasimtools-0.26.0.dist-info → gammasimtools-0.27.1.dist-info}/METADATA +5 -1
  2. {gammasimtools-0.26.0.dist-info → gammasimtools-0.27.1.dist-info}/RECORD +70 -66
  3. {gammasimtools-0.26.0.dist-info → gammasimtools-0.27.1.dist-info}/WHEEL +1 -1
  4. {gammasimtools-0.26.0.dist-info → gammasimtools-0.27.1.dist-info}/entry_points.txt +1 -1
  5. simtools/_version.py +2 -2
  6. simtools/applications/convert_geo_coordinates_of_array_elements.py +2 -1
  7. simtools/applications/db_get_array_layouts_from_db.py +1 -1
  8. simtools/applications/{calculate_incident_angles.py → derive_incident_angle.py} +16 -16
  9. simtools/applications/derive_mirror_rnda.py +111 -177
  10. simtools/applications/generate_corsika_histograms.py +38 -1
  11. simtools/applications/generate_regular_arrays.py +73 -36
  12. simtools/applications/simulate_flasher.py +3 -13
  13. simtools/applications/simulate_illuminator.py +2 -10
  14. simtools/applications/simulate_pedestals.py +1 -1
  15. simtools/applications/simulate_prod.py +8 -7
  16. simtools/applications/submit_data_from_external.py +2 -1
  17. simtools/applications/validate_camera_efficiency.py +28 -27
  18. simtools/applications/validate_cumulative_psf.py +1 -3
  19. simtools/applications/validate_optics.py +2 -1
  20. simtools/atmosphere.py +83 -0
  21. simtools/camera/camera_efficiency.py +171 -48
  22. simtools/camera/single_photon_electron_spectrum.py +6 -6
  23. simtools/configuration/commandline_parser.py +47 -9
  24. simtools/constants.py +5 -0
  25. simtools/corsika/corsika_config.py +88 -185
  26. simtools/corsika/corsika_histograms.py +246 -69
  27. simtools/data_model/model_data_writer.py +46 -49
  28. simtools/data_model/schema.py +2 -0
  29. simtools/db/db_handler.py +4 -2
  30. simtools/db/mongo_db.py +2 -2
  31. simtools/io/ascii_handler.py +52 -4
  32. simtools/io/io_handler.py +23 -12
  33. simtools/job_execution/job_manager.py +154 -79
  34. simtools/job_execution/process_pool.py +137 -0
  35. simtools/layout/array_layout.py +0 -1
  36. simtools/layout/array_layout_utils.py +143 -21
  37. simtools/model/array_model.py +22 -50
  38. simtools/model/calibration_model.py +4 -4
  39. simtools/model/model_parameter.py +123 -73
  40. simtools/model/model_utils.py +40 -1
  41. simtools/model/site_model.py +4 -4
  42. simtools/model/telescope_model.py +4 -5
  43. simtools/ray_tracing/incident_angles.py +87 -6
  44. simtools/ray_tracing/mirror_panel_psf.py +337 -217
  45. simtools/ray_tracing/psf_analysis.py +57 -42
  46. simtools/ray_tracing/psf_parameter_optimisation.py +3 -2
  47. simtools/ray_tracing/ray_tracing.py +37 -10
  48. simtools/runners/corsika_runner.py +52 -191
  49. simtools/runners/corsika_simtel_runner.py +74 -100
  50. simtools/runners/runner_services.py +214 -213
  51. simtools/runners/simtel_runner.py +27 -155
  52. simtools/runners/simtools_runner.py +9 -69
  53. simtools/schemas/application_workflow.metaschema.yml +8 -0
  54. simtools/settings.py +19 -0
  55. simtools/simtel/simtel_config_writer.py +0 -55
  56. simtools/simtel/simtel_seeds.py +184 -0
  57. simtools/simtel/simulator_array.py +115 -103
  58. simtools/simtel/simulator_camera_efficiency.py +66 -42
  59. simtools/simtel/simulator_light_emission.py +110 -123
  60. simtools/simtel/simulator_ray_tracing.py +78 -63
  61. simtools/simulator.py +135 -346
  62. simtools/testing/sim_telarray_metadata.py +13 -11
  63. simtools/testing/validate_output.py +87 -19
  64. simtools/utils/general.py +6 -17
  65. simtools/utils/random.py +36 -0
  66. simtools/visualization/plot_corsika_histograms.py +2 -0
  67. simtools/visualization/plot_incident_angles.py +48 -1
  68. simtools/visualization/plot_psf.py +160 -18
  69. {gammasimtools-0.26.0.dist-info → gammasimtools-0.27.1.dist-info}/licenses/LICENSE +0 -0
  70. {gammasimtools-0.26.0.dist-info → gammasimtools-0.27.1.dist-info}/top_level.txt +0 -0
@@ -1,18 +1,18 @@
1
1
  """Base class for running sim_telarray simulations."""
2
2
 
3
3
  import logging
4
- import subprocess
5
4
 
6
- import simtools.utils.general as gen
5
+ from simtools.job_execution import job_manager
7
6
  from simtools.runners.runner_services import RunnerServices
8
7
 
8
+ SIM_TELARRAY_ENV = {
9
+ "SIM_TELARRAY_CONFIG_PATH": "",
10
+ }
9
11
 
10
- class SimtelExecutionError(Exception):
11
- """Exception for sim_telarray execution error."""
12
12
 
13
-
14
- class InvalidOutputFileError(Exception):
15
- """Exception for invalid output file."""
13
+ def sim_telarray_env_as_string():
14
+ """Return the sim_telarray environment variables as a string."""
15
+ return " ".join(f'{key}="{value}" ' for key, value in SIM_TELARRAY_ENV.items())
16
16
 
17
17
 
18
18
  class SimtelRunner:
@@ -26,34 +26,24 @@ class SimtelRunner:
26
26
  ----------
27
27
  label: str
28
28
  Instance label. Important for output file naming.
29
- corsika_config: CorsikaConfig
30
- CORSIKA configuration.
31
- use_multipipe: bool
32
- Use multipipe to run CORSIKA and sim_telarray.
33
- calibration_run_mode: str
34
- Calibration run mode.
29
+ config: CorsikaConfig or dict
30
+ Configuration parameters.
31
+ is_calibration_run: bool
32
+ Flag to indicate if this is a calibration run.
35
33
  """
36
34
 
37
- def __init__(
38
- self,
39
- label=None,
40
- corsika_config=None,
41
- use_multipipe=False,
42
- calibration_run_mode=None,
43
- ):
35
+ def __init__(self, label=None, config=None, is_calibration_run=False):
44
36
  """Initialize SimtelRunner."""
45
37
  self._logger = logging.getLogger(__name__)
46
38
 
47
39
  self.label = label
48
40
  self._base_directory = None
49
- self.calibration_run_mode = calibration_run_mode
41
+ self.is_calibration_run = is_calibration_run
50
42
 
51
43
  self.runs_per_set = 1
52
44
 
53
- self.runner_service = RunnerServices(corsika_config, label)
54
- self._directory = self.runner_service.load_data_directories(
55
- "corsika_sim_telarray" if use_multipipe else "sim_telarray"
56
- )
45
+ self.runner_service = RunnerServices(config, run_type="sim_telarray", label=label)
46
+ self.file_list = None
57
47
 
58
48
  def run(self, test=False, input_file=None, run_number=None):
59
49
  """
@@ -70,142 +60,24 @@ class SimtelRunner:
70
60
  """
71
61
  self._logger.debug("Running sim_telarray")
72
62
 
73
- command, stdout_file, stderr_file = self._make_run_command(
63
+ command, stdout_file, stderr_file = self.make_run_command(
74
64
  run_number=run_number, input_file=input_file
75
65
  )
76
-
77
- if test:
78
- self._logger.info(f"Running (test) with command: {command}")
79
- self._run_simtel_and_check_output(command, stdout_file, stderr_file)
80
- else:
81
- self._logger.debug(f"Running ({self.runs_per_set}x) with command: {command}")
82
- for _ in range(self.runs_per_set):
83
- self._run_simtel_and_check_output(command, stdout_file, stderr_file)
84
-
85
- self._check_run_result(run_number=run_number)
86
-
87
- def _check_run_result(self, run_number=None): # pylint: disable=all
88
- """Check if simtel output file exists."""
89
- pass
90
-
91
- def _raise_simtel_error(self):
92
- """
93
- Raise sim_telarray execution error.
94
-
95
- Final 30 lines from the log file are collected and printed.
96
-
97
- Raises
98
- ------
99
- SimtelExecutionError
100
- """
101
- if hasattr(self, "_log_file"):
102
- msg = gen.get_log_excerpt(self._log_file)
103
- else:
104
- msg = "Simtel log file does not exist."
105
- raise SimtelExecutionError(msg)
106
-
107
- def _run_simtel_and_check_output(self, command, stdout_file, stderr_file):
108
- """
109
- Run the sim_telarray command and check the exit code.
110
-
111
- Raises
112
- ------
113
- SimtelExecutionError
114
- if run was not successful.
115
- """
116
- stdout_file = stdout_file if stdout_file else "/dev/null"
117
- stderr_file = stderr_file if stderr_file else "/dev/null"
118
- with (
119
- open(f"{stdout_file}", "w", encoding="utf-8") as stdout,
120
- open(f"{stderr_file}", "w", encoding="utf-8") as stderr,
121
- ):
122
- result = subprocess.run(
66
+ runs = 1 if test else self.runs_per_set
67
+ label = "test" if test else f"{self.runs_per_set}x"
68
+ self._logger.info(f"Running ({label}) with command: {command}")
69
+ for _ in range(runs):
70
+ job_manager.submit(
123
71
  command,
124
- shell=True,
125
- text=True,
126
- stdout=stdout,
127
- stderr=stderr,
72
+ out_file=stdout_file,
73
+ err_file=stderr_file,
74
+ env=SIM_TELARRAY_ENV,
128
75
  )
129
76
 
130
- if result.returncode != 0:
131
- self._logger.error(result.stderr)
132
- self._raise_simtel_error()
133
- return result.returncode
134
-
135
- def _make_run_command(self, run_number=None, input_file=None):
136
- self._logger.debug(
137
- "make_run_command is being called from the base class - "
138
- "it should be implemented in the sub class"
139
- )
140
- input_file = input_file if input_file else "nofile"
141
- run_number = run_number if run_number else 1
142
- return f"{input_file}-{run_number}", None, None
143
-
144
- @staticmethod
145
- def get_config_option(par, value=None, weak_option=False):
146
- """
147
- Build sim_telarray command.
148
-
149
- Parameters
150
- ----------
151
- par: str
152
- Parameter name.
153
- value: str
154
- Parameter value.
155
- weak_option: bool
156
- If True, use -W option instead of -C.
157
-
158
- Returns
159
- -------
160
- str
161
- Command for sim_telarray.
162
- """
163
- option_syntax = "-W" if weak_option else "-C"
164
- c = f" {option_syntax} {par}"
165
- c += f"={value}" if value is not None else ""
166
- return c
77
+ def make_run_command(self, run_number=None, input_file=None):
78
+ """Make the sim_telarray run command (to implemented in subclasses)."""
79
+ raise NotImplementedError("Must be implemented in concrete subclass")
167
80
 
168
81
  def get_resources(self, run_number=None):
169
82
  """Return computing resources used."""
170
83
  return self.runner_service.get_resources(run_number)
171
-
172
- def get_file_name(
173
- self,
174
- simulation_software="sim_telarray",
175
- file_type=None,
176
- run_number=None,
177
- mode="",
178
- model_version_index=0,
179
- ):
180
- """
181
- Get the full path of a file for a given run number.
182
-
183
- Parameters
184
- ----------
185
- simulation_software: str
186
- Simulation software.
187
- file_type: str
188
- File type.
189
- run_number: int
190
- Run number.
191
- model_version_index: int
192
- Index of the model version.
193
- This is used to select the correct simulator_array instance in case
194
- multiple array models are simulated.
195
-
196
- Returns
197
- -------
198
- str
199
- File name with full path.
200
- """
201
- if simulation_software.lower() != "sim_telarray":
202
- raise ValueError(
203
- f"simulation_software ({simulation_software}) is not supported in SimulatorArray"
204
- )
205
- return self.runner_service.get_file_name(
206
- file_type=file_type,
207
- run_number=run_number,
208
- mode=mode,
209
- _model_version_index=model_version_index,
210
- calibration_run_mode=self.calibration_run_mode,
211
- )
@@ -1,12 +1,12 @@
1
1
  """Tools for running applications in the simtools framework."""
2
2
 
3
3
  import shutil
4
- import subprocess
5
4
  from pathlib import Path
6
5
 
7
6
  import simtools.utils.general as gen
8
7
  from simtools import dependencies
9
8
  from simtools.io import ascii_handler
9
+ from simtools.job_execution import job_manager
10
10
 
11
11
 
12
12
  def run_applications(args_dict, logger):
@@ -39,75 +39,15 @@ def run_applications(args_dict, logger):
39
39
  logger.info(f"Skipping application: {app}")
40
40
  continue
41
41
  logger.info(f"Running application: {app}")
42
- stdout, stderr = run_application(run_time, app, config.get("configuration"), logger)
42
+ result = job_manager.submit(
43
+ app,
44
+ out_file=None,
45
+ err_file=None,
46
+ configuration=config.get("configuration"),
47
+ runtime_environment=run_time,
48
+ )
43
49
  file.write("=" * 80 + "\n")
44
- file.write(f"Application: {app}\nSTDOUT:\n{stdout}\nSTDERR:\n{stderr}\n")
45
-
46
-
47
- def run_application(runtime_environment, application, configuration, logger):
48
- """
49
- Run a simtools application and return stdout and stderr.
50
-
51
- Allow to specify a runtime environment (e.g., Docker) and a working directory.
52
-
53
- Parameters
54
- ----------
55
- runtime_environment : list
56
- Command to run the application in the specified runtime environment.
57
- application : str
58
- Name of the application to run.
59
- configuration : dict
60
- Configuration for the application.
61
- logger : logging.Logger
62
- Logger for logging application output.
63
-
64
- Returns
65
- -------
66
- tuple
67
- stdout and stderr from the application run.
68
-
69
- """
70
- command = [application, *_convert_dict_to_args(configuration)]
71
- if runtime_environment:
72
- command = runtime_environment + command
73
- try:
74
- result = subprocess.run(
75
- command,
76
- check=True,
77
- capture_output=True,
78
- text=True,
79
- )
80
- except subprocess.CalledProcessError as exc:
81
- logger.error(f"Error running application {application}: {exc.stderr}")
82
- raise exc
83
-
84
- return result.stdout, result.stderr
85
-
86
-
87
- def _convert_dict_to_args(parameters):
88
- """
89
- Convert a dictionary of parameters to a list of command line arguments.
90
-
91
- Parameters
92
- ----------
93
- parameters : dict
94
- Dictionary containing parameters to convert.
95
-
96
- Returns
97
- -------
98
- list
99
- List of command line arguments.
100
- """
101
- args = []
102
- for key, value in parameters.items():
103
- if isinstance(value, bool):
104
- if value:
105
- args.append(f"--{key}")
106
- elif isinstance(value, list):
107
- args.extend([f"--{key}", *(str(item) for item in value)])
108
- else:
109
- args.extend([f"--{key}", str(value)])
110
- return args
50
+ file.write(f"Application: {app}\nSTDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}\n")
111
51
 
112
52
 
113
53
  def _read_application_configuration(configuration_file, steps, logger):
@@ -142,6 +142,10 @@ definitions:
142
142
  description: |
143
143
  "Allowed tolerance for floating point comparison."
144
144
  type: number
145
+ scaling:
146
+ description: |
147
+ "Scaling factor to apply to the model parameter value before comparison."
148
+ type: number
145
149
  test_simtel_cfg_files:
146
150
  description: |
147
151
  "Reference file used for comparison of sim_telarray configuration files."
@@ -152,6 +156,10 @@ definitions:
152
156
  type: string
153
157
  description: Path to the configuration file for the given version.
154
158
  minProperties: 1
159
+ test_output_file:
160
+ description: |
161
+ "Output file generated by the integration test for comparison with the reference file."
162
+ type: string
155
163
  tolerance:
156
164
  description: "Allowed tolerance for floating point comparison."
157
165
  type: number
simtools/settings.py CHANGED
@@ -1,6 +1,7 @@
1
1
  """Centralized settings object with command line and environment variables."""
2
2
 
3
3
  import os
4
+ import socket
4
5
  from pathlib import Path
5
6
  from types import MappingProxyType
6
7
 
@@ -15,7 +16,10 @@ class _Config:
15
16
  self._sim_telarray_path = None
16
17
  self._sim_telarray_exe = None
17
18
  self._corsika_path = None
19
+ self._corsika_interaction_table_path = None
18
20
  self._corsika_exe = None
21
+ self.user = os.getenv("USER", "unknown")
22
+ self.hostname = socket.gethostname()
19
23
 
20
24
  def load(self, args=None, db_config=None):
21
25
  """
@@ -51,6 +55,12 @@ class _Config:
51
55
  else os.getenv("SIMTOOLS_CORSIKA_PATH")
52
56
  )
53
57
 
58
+ self._corsika_interaction_table_path = (
59
+ args.get("corsika_interaction_table_path")
60
+ if args is not None and "corsika_interaction_table_path" in args
61
+ else os.getenv("SIMTOOLS_CORSIKA_INTERACTION_TABLE_PATH")
62
+ )
63
+
54
64
  self._corsika_exe = self._get_corsika_exec() if self._corsika_path is not None else None
55
65
 
56
66
  def _get_corsika_exec(self):
@@ -118,6 +128,15 @@ class _Config:
118
128
  """Path to the CORSIKA installation directory."""
119
129
  return Path(self._corsika_path) if self._corsika_path is not None else None
120
130
 
131
+ @property
132
+ def corsika_interaction_table_path(self):
133
+ """Path to the CORSIKA interaction table directory."""
134
+ return (
135
+ Path(self._corsika_interaction_table_path)
136
+ if self._corsika_interaction_table_path is not None
137
+ else self.corsika_path
138
+ )
139
+
121
140
  @property
122
141
  def corsika_exe(self):
123
142
  """Path to the CORSIKA executable."""
@@ -17,27 +17,6 @@ from simtools.utils import names
17
17
  logger = logging.getLogger(__name__)
18
18
 
19
19
 
20
- def sim_telarray_random_seeds(seed, number):
21
- """
22
- Generate random seeds to be used in sim_telarray.
23
-
24
- Parameters
25
- ----------
26
- seed: int
27
- Seed for the random number generator.
28
- number: int
29
- Number of random seeds to generate.
30
-
31
- Returns
32
- -------
33
- list
34
- List of random seeds.
35
- """
36
- rng = np.random.default_rng(seed)
37
- max_int32 = np.iinfo(np.int32).max # sim_telarray requires 32 bit integers
38
- return list(rng.integers(low=1, high=max_int32, size=number, dtype=np.int32))
39
-
40
-
41
20
  class SimtelConfigWriter:
42
21
  """
43
22
  SimtelConfigWriter writes sim_telarray configuration files.
@@ -71,7 +50,6 @@ class SimtelConfigWriter:
71
50
  ):
72
51
  """Initialize SimtelConfigWriter."""
73
52
  self._logger = logging.getLogger(__name__)
74
- self._logger.debug("Init SimtelConfigWriter")
75
53
 
76
54
  self._site = site
77
55
  self._model_version = model_version
@@ -492,39 +470,6 @@ class SimtelConfigWriter:
492
470
  file.write(f"# include <{tel_config_file}>\n\n")
493
471
  file.write("#endif \n\n") # configuration files need to end with \n\n
494
472
 
495
- if additional_metadata and additional_metadata.get("random_instrument_instances"):
496
- self._write_random_seeds_file(additional_metadata, config_file_directory)
497
-
498
- def _write_random_seeds_file(self, sim_telarray_seeds, config_file_directory):
499
- """
500
- Write list of random number used to generate random instances of instrument.
501
-
502
- Parameters
503
- ----------
504
- random_instrument_instances: int
505
- Number of random instances of the instrument.
506
- """
507
- self._logger.info(
508
- "Writing random seed file "
509
- f"{config_file_directory}/{sim_telarray_seeds['seed_file_name']}"
510
- f" (global seed {sim_telarray_seeds['seed']})"
511
- )
512
- if sim_telarray_seeds["random_instrument_instances"] > 1024:
513
- raise ValueError("Number of random instances of instrument must be less than 1024")
514
- random_integers = sim_telarray_random_seeds(
515
- sim_telarray_seeds["seed"], sim_telarray_seeds["random_instrument_instances"]
516
- )
517
- with open(
518
- config_file_directory / sim_telarray_seeds["seed_file_name"], "w", encoding="utf-8"
519
- ) as file:
520
- file.write(
521
- "# Random seeds for instrument configuration generated with seed "
522
- f"{sim_telarray_seeds['seed']}"
523
- f" (model version {self._model_version}, site {self._site})\n"
524
- )
525
- for number in random_integers:
526
- file.write(f"{number}\n")
527
-
528
473
  def write_single_mirror_list_file(
529
474
  self, mirror_number, mirrors, single_mirror_list_file, set_focal_length_to_zero=False
530
475
  ):
@@ -0,0 +1,184 @@
1
+ """Seeds for sim_telarray simulations."""
2
+
3
+ import logging
4
+ from pathlib import Path
5
+
6
+ from simtools import settings
7
+ from simtools.constants import SIMTEL_MAX_SEED
8
+ from simtools.io import ascii_handler
9
+ from simtools.utils import names, random
10
+ from simtools.version import semver_to_int
11
+
12
+
13
+ class SimtelSeeds:
14
+ """Manage seeds for sim_telarray simulations."""
15
+
16
+ def __init__(
17
+ self, output_path=None, site=None, model_version=None, zenith_angle=None, azimuth_angle=None
18
+ ):
19
+ """
20
+ Initialize seeds for sim_telarray simulations.
21
+
22
+ Two seeds are set for sim_telarray simulations:
23
+
24
+ - Instrument seed: used to randomize the instrument setup
25
+ - Simulation seed: used for the shower simulation
26
+
27
+ Parameters
28
+ ----------
29
+ output_path : str or Path or None
30
+ Output path for the seed file.
31
+ site : str or None
32
+ Site name.
33
+ model_version : str or None
34
+ Model version.
35
+ zenith_angle : float or None
36
+ Zenith angle.
37
+ azimuth_angle : float or None
38
+ Azimuth angle.
39
+ """
40
+ self._logger = logging.getLogger(__name__)
41
+
42
+ self.instrument_seed = settings.config.args.get("sim_telarray_instrument_seed", None)
43
+ self.instruments = settings.config.args.get(
44
+ "sim_telarray_random_instrument_instances", None
45
+ )
46
+ self.simulation_seed = settings.config.args.get("sim_telarray_seed", None)
47
+ self.seed_file = settings.config.args.get("sim_telarray_seed_file", None)
48
+ if output_path is not None:
49
+ self.seed_file = Path(output_path) / self.seed_file
50
+
51
+ self.seed_string = self.initialize_seeds(site, model_version, zenith_angle, azimuth_angle)
52
+
53
+ def initialize_seeds(self, site, model_version, zenith_angle, azimuth_angle):
54
+ """Initialize seeds based on provided parameters."""
55
+ if isinstance(self.simulation_seed, list):
56
+ return self._set_fixed_seeds()
57
+
58
+ if not self.simulation_seed:
59
+ self.simulation_seed = random.seeds(max_seed=SIMTEL_MAX_SEED)
60
+
61
+ if not self.instruments or self.instruments <= 1:
62
+ return self._generate_seed_pair()
63
+
64
+ return self._generate_seeds_with_file(site, model_version, zenith_angle, azimuth_angle)
65
+
66
+ def _set_fixed_seeds(self):
67
+ """
68
+ Set fixed seeds to be using for testing purposes only.
69
+
70
+ Fixes both instrument and simulation seeds.
71
+ """
72
+ try:
73
+ seed_string = f"{self.simulation_seed[0]},{self.simulation_seed[1]}"
74
+ except IndexError as exc:
75
+ raise IndexError(
76
+ "Two seeds must be provided for testing purposes: "
77
+ "first for instrument, second for shower simulation."
78
+ ) from exc
79
+ self._logger.warning(f"Using fixed test seeds: {seed_string}")
80
+ return seed_string
81
+
82
+ def _generate_seed_pair(self):
83
+ """Generate seed string."""
84
+ if not self.instrument_seed:
85
+ self.instrument_seed = random.seeds(max_seed=SIMTEL_MAX_SEED)
86
+
87
+ self._logger.info(
88
+ f"Generated sim_telarray seeds - Instrument: {self.instrument_seed}, "
89
+ f"Shower simulation: {self.simulation_seed}"
90
+ )
91
+ return f"{self.instrument_seed},{self.simulation_seed}"
92
+
93
+ def _generate_seeds_with_file(self, site, model_version, zenith_angle, azimuth_angle):
94
+ """Generate a seed file for the instrument seeds and return the seed string."""
95
+ self.instrument_seed = self._get_instrument_seed(
96
+ site, model_version, zenith_angle, azimuth_angle
97
+ )
98
+
99
+ self._logger.info(
100
+ f"Writing random instrument seed file {self.seed_file}"
101
+ f" (instrument seed {self.instrument_seed})"
102
+ )
103
+ if self.instruments > 1024:
104
+ raise ValueError("Number of random instances of instrument must be less than 1024")
105
+ random_integers = random.seeds(
106
+ n_seeds=self.instruments,
107
+ max_seed=SIMTEL_MAX_SEED,
108
+ fixed_seed=self.instrument_seed,
109
+ )
110
+ with open(self.seed_file, "w", encoding="utf-8") as file:
111
+ file.write(
112
+ "# Random seeds for instrument configuration generated with seed "
113
+ f"{self.instrument_seed} (model version {model_version}, site {site})\n"
114
+ f"# Zenith angle: {zenith_angle}, Azimuth angle: {azimuth_angle}\n"
115
+ )
116
+ for number in random_integers:
117
+ file.write(f"{number}\n")
118
+
119
+ return f"file-by-run:{self.seed_file},{self.simulation_seed}"
120
+
121
+ def _get_instrument_seed(self, site, model_version, zenith_angle, azimuth_angle):
122
+ """
123
+ Get configuration dependent instrument seed.
124
+
125
+ Three different scenarios are possible:
126
+
127
+ - instrument seed provided through configuration: use it
128
+ - site, model_version, zenith_angle, azimuth_angle provided:
129
+ generate a seed based on these parameters
130
+ - none of the above: generate a random seed
131
+
132
+ Parameters
133
+ ----------
134
+ site : str or None
135
+ Site name.
136
+ model_version : str or None
137
+ Model version.
138
+ zenith_angle : float or None
139
+ Zenith angle.
140
+ azimuth_angle : float or None
141
+ Azimuth angle.
142
+
143
+ Returns
144
+ -------
145
+ int
146
+ Instrument seed.
147
+ """
148
+ # Use the instrument seed from the configuration if provided
149
+ if self.instrument_seed:
150
+ return self.instrument_seed
151
+
152
+ # Generate a seed based on site, model_version, zenith_angle, and azimuth_angle
153
+ if model_version and zenith_angle is not None and azimuth_angle is not None:
154
+ try:
155
+ key_index = next(
156
+ i + 1
157
+ for i, (_, values) in enumerate(names.site_names().items())
158
+ if site.lower() in values
159
+ )
160
+ except StopIteration as exc:
161
+ raise ValueError(f"Unknown site: {site!r}") from exc
162
+
163
+ seed = semver_to_int(model_version) * 10000000
164
+ seed = seed + key_index * 1000000
165
+ seed = seed + int(zenith_angle) * 1000
166
+ return seed + int(azimuth_angle)
167
+
168
+ # Generate a random instrument seed
169
+ return random.seeds(max_seed=SIMTEL_MAX_SEED)
170
+
171
+ def save_seeds(self, path):
172
+ """
173
+ Save the seeds to a file.
174
+
175
+ Parameters
176
+ ----------
177
+ path : str or Path
178
+ Path to the seed file.
179
+ """
180
+ seed_dict = {
181
+ "instrument_seed": self.instrument_seed,
182
+ "simulation_seed": self.simulation_seed,
183
+ }
184
+ ascii_handler.write_data_to_file(path, seed_dict)