gammasimtools 0.25.0__py3-none-any.whl → 0.27.0__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 (138) hide show
  1. {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/METADATA +6 -1
  2. {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/RECORD +135 -130
  3. {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/WHEEL +1 -1
  4. {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/entry_points.txt +3 -2
  5. {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/licenses/LICENSE +1 -1
  6. simtools/_version.py +2 -2
  7. simtools/application_control.py +35 -7
  8. simtools/applications/convert_geo_coordinates_of_array_elements.py +3 -3
  9. simtools/applications/db_add_file_to_db.py +1 -1
  10. simtools/applications/db_add_simulation_model_from_repository_to_db.py +1 -1
  11. simtools/applications/db_add_value_from_json_to_db.py +1 -1
  12. simtools/applications/db_generate_compound_indexes.py +1 -1
  13. simtools/applications/db_get_array_layouts_from_db.py +3 -7
  14. simtools/applications/db_get_file_from_db.py +1 -1
  15. simtools/applications/db_get_parameter_from_db.py +1 -1
  16. simtools/applications/db_inspect_databases.py +1 -1
  17. simtools/applications/db_upload_model_repository.py +1 -1
  18. simtools/applications/derive_ctao_array_layouts.py +1 -2
  19. simtools/applications/{calculate_incident_angles.py → derive_incident_angle.py} +16 -18
  20. simtools/applications/derive_mirror_rnda.py +112 -180
  21. simtools/applications/derive_psf_parameters.py +0 -1
  22. simtools/applications/derive_pulse_shape_parameters.py +0 -1
  23. simtools/applications/derive_trigger_rates.py +1 -1
  24. simtools/applications/docs_produce_array_element_report.py +2 -8
  25. simtools/applications/docs_produce_calibration_reports.py +1 -3
  26. simtools/applications/docs_produce_model_parameter_reports.py +0 -2
  27. simtools/applications/docs_produce_simulation_configuration_report.py +1 -3
  28. simtools/applications/generate_array_config.py +0 -1
  29. simtools/applications/generate_corsika_histograms.py +79 -229
  30. simtools/applications/generate_regular_arrays.py +76 -69
  31. simtools/applications/generate_simtel_event_data.py +2 -2
  32. simtools/applications/maintain_simulation_model_add_production.py +2 -2
  33. simtools/applications/maintain_simulation_model_write_array_element_positions.py +87 -0
  34. simtools/applications/plot_array_layout.py +5 -111
  35. simtools/applications/plot_simulated_event_distributions.py +57 -0
  36. simtools/applications/plot_tabular_data.py +0 -1
  37. simtools/applications/plot_tabular_data_for_model_parameter.py +1 -6
  38. simtools/applications/production_derive_corsika_limits.py +1 -1
  39. simtools/applications/production_generate_grid.py +0 -1
  40. simtools/applications/run_application.py +1 -1
  41. simtools/applications/simulate_flasher.py +3 -15
  42. simtools/applications/simulate_illuminator.py +2 -11
  43. simtools/applications/simulate_pedestals.py +1 -5
  44. simtools/applications/simulate_prod.py +8 -11
  45. simtools/applications/simulate_prod_htcondor_generator.py +1 -1
  46. simtools/applications/submit_array_layouts.py +2 -4
  47. simtools/applications/submit_data_from_external.py +2 -1
  48. simtools/applications/submit_model_parameter_from_external.py +1 -3
  49. simtools/applications/validate_camera_efficiency.py +28 -28
  50. simtools/applications/validate_camera_fov.py +0 -1
  51. simtools/applications/validate_cumulative_psf.py +1 -5
  52. simtools/applications/validate_optics.py +2 -14
  53. simtools/atmosphere.py +83 -0
  54. simtools/camera/camera_efficiency.py +171 -53
  55. simtools/camera/single_photon_electron_spectrum.py +8 -7
  56. simtools/configuration/commandline_parser.py +82 -11
  57. simtools/configuration/configurator.py +6 -11
  58. simtools/constants.py +5 -0
  59. simtools/corsika/corsika_config.py +100 -202
  60. simtools/corsika/corsika_histograms.py +561 -1708
  61. simtools/corsika/primary_particle.py +1 -1
  62. simtools/data_model/metadata_collector.py +5 -2
  63. simtools/data_model/metadata_model.py +0 -4
  64. simtools/data_model/model_data_writer.py +59 -64
  65. simtools/data_model/schema.py +2 -0
  66. simtools/data_model/validate_data.py +1 -3
  67. simtools/db/db_handler.py +23 -10
  68. simtools/db/mongo_db.py +2 -2
  69. simtools/dependencies.py +81 -38
  70. simtools/io/ascii_handler.py +55 -5
  71. simtools/io/io_handler.py +23 -12
  72. simtools/io/table_handler.py +1 -1
  73. simtools/job_execution/job_manager.py +154 -79
  74. simtools/job_execution/process_pool.py +137 -0
  75. simtools/layout/array_layout.py +4 -13
  76. simtools/layout/array_layout_utils.py +348 -57
  77. simtools/model/array_model.py +23 -63
  78. simtools/model/calibration_model.py +4 -8
  79. simtools/model/legacy_model_parameter.py +134 -0
  80. simtools/model/model_parameter.py +147 -86
  81. simtools/model/model_utils.py +40 -6
  82. simtools/model/site_model.py +4 -8
  83. simtools/model/telescope_model.py +10 -16
  84. simtools/production_configuration/derive_corsika_limits.py +6 -11
  85. simtools/production_configuration/interpolation_handler.py +16 -16
  86. simtools/ray_tracing/incident_angles.py +92 -17
  87. simtools/ray_tracing/mirror_panel_psf.py +338 -222
  88. simtools/ray_tracing/psf_analysis.py +62 -48
  89. simtools/ray_tracing/psf_parameter_optimisation.py +3 -3
  90. simtools/ray_tracing/ray_tracing.py +43 -25
  91. simtools/reporting/docs_auto_report_generator.py +8 -13
  92. simtools/reporting/docs_read_parameters.py +2 -8
  93. simtools/runners/corsika_runner.py +52 -195
  94. simtools/runners/corsika_simtel_runner.py +77 -108
  95. simtools/runners/runner_services.py +214 -213
  96. simtools/runners/simtel_runner.py +27 -160
  97. simtools/runners/simtools_runner.py +11 -73
  98. simtools/schemas/application_workflow.metaschema.yml +8 -0
  99. simtools/settings.py +173 -0
  100. simtools/{io/eventio_handler.py → sim_events/file_info.py} +3 -3
  101. simtools/{simtel/simtel_io_event_histograms.py → sim_events/histograms.py} +25 -15
  102. simtools/{simtel/simtel_io_event_reader.py → sim_events/reader.py} +20 -17
  103. simtools/{simtel/simtel_io_event_writer.py → sim_events/writer.py} +84 -25
  104. simtools/simtel/pulse_shapes.py +7 -2
  105. simtools/simtel/simtel_config_writer.py +79 -91
  106. simtools/simtel/simtel_seeds.py +184 -0
  107. simtools/simtel/simtel_table_reader.py +6 -4
  108. simtools/simtel/simulator_array.py +114 -109
  109. simtools/simtel/simulator_camera_efficiency.py +68 -46
  110. simtools/simtel/simulator_light_emission.py +164 -132
  111. simtools/simtel/simulator_ray_tracing.py +80 -71
  112. simtools/simulator.py +137 -355
  113. simtools/telescope_trigger_rates.py +3 -4
  114. simtools/testing/assertions.py +84 -33
  115. simtools/testing/configuration.py +1 -2
  116. simtools/testing/helpers.py +2 -3
  117. simtools/testing/log_inspector.py +1 -0
  118. simtools/testing/sim_telarray_metadata.py +14 -12
  119. simtools/testing/validate_output.py +121 -42
  120. simtools/utils/general.py +43 -17
  121. simtools/utils/geometry.py +0 -77
  122. simtools/utils/names.py +5 -5
  123. simtools/utils/random.py +36 -0
  124. simtools/visualization/legend_handlers.py +7 -6
  125. simtools/visualization/plot_array_layout.py +91 -16
  126. simtools/visualization/plot_corsika_histograms.py +145 -605
  127. simtools/visualization/plot_incident_angles.py +48 -1
  128. simtools/visualization/plot_mirrors.py +1 -4
  129. simtools/visualization/plot_pixels.py +2 -4
  130. simtools/visualization/plot_psf.py +160 -19
  131. simtools/visualization/plot_simtel_event_histograms.py +4 -4
  132. simtools/visualization/plot_simtel_events.py +6 -11
  133. simtools/visualization/plot_tables.py +8 -19
  134. simtools/visualization/visualize.py +22 -2
  135. simtools/applications/db_development_tools/write_array_elements_positions_to_repository.py +0 -160
  136. simtools/applications/print_version.py +0 -53
  137. simtools/io/hdf5_handler.py +0 -139
  138. {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/top_level.txt +0 -0
simtools/simulator.py CHANGED
@@ -1,81 +1,65 @@
1
1
  """Simulator class for managing simulations of showers and array of telescopes."""
2
2
 
3
- import gzip
4
3
  import logging
5
- import re
6
4
  import shutil
7
- from collections import defaultdict
8
5
  from pathlib import Path
9
6
 
10
7
  import numpy as np
11
8
  from astropy import units as u
12
9
 
10
+ from simtools import settings
13
11
  from simtools.corsika.corsika_config import CorsikaConfig
14
- from simtools.io import eventio_handler, io_handler, table_handler
15
- from simtools.job_execution.job_manager import JobManager
12
+ from simtools.io import io_handler, table_handler
13
+ from simtools.job_execution import job_manager
16
14
  from simtools.model.array_model import ArrayModel
17
- from simtools.runners.corsika_runner import CorsikaRunner
18
- from simtools.runners.corsika_simtel_runner import CorsikaSimtelRunner
19
- from simtools.simtel.simtel_io_event_writer import SimtelIOEventDataWriter
15
+ from simtools.runners import corsika_runner, corsika_simtel_runner, runner_services, simtel_runner
16
+ from simtools.sim_events import file_info, writer
20
17
  from simtools.simtel.simulator_array import SimulatorArray
21
18
  from simtools.testing.sim_telarray_metadata import assert_sim_telarray_metadata
22
- from simtools.utils import general, names
23
- from simtools.version import semver_to_int
19
+ from simtools.utils import general
24
20
 
25
21
 
26
22
  class Simulator:
27
23
  """
28
- Simulator is managing the simulation of showers and of the array of telescopes.
24
+ Simulation of showers and of the array of telescopes.
29
25
 
30
- It interfaces with simulation software packages (e.g., CORSIKA or sim_telarray).
26
+ Interface with the simulation software packages (e.g., CORSIKA or sim_telarray).
31
27
  A single run is simulated per instance, possibly for multiple model versions.
32
28
 
33
- The configuration is set as a dict corresponding to the command line configuration groups
34
- (especially simulation_software, simulation_model, simulation_parameters).
35
-
36
29
  Parameters
37
30
  ----------
38
- args_dict : dict
39
- Configuration dictionary
40
- (includes simulation_software, simulation_model, simulation_parameters groups).
41
31
  label: str
42
32
  Instance label.
43
33
  extra_commands: str or list of str
44
34
  Extra commands to be added to the run script before the run command.
45
- db_config: dict
46
- Database configuration.
47
35
  """
48
36
 
49
- def __init__(
50
- self,
51
- args_dict,
52
- label=None,
53
- extra_commands=None,
54
- db_config=None,
55
- ):
37
+ def __init__(self, label=None, extra_commands=None):
56
38
  """Initialize Simulator class."""
57
39
  self.logger = logging.getLogger(__name__)
58
40
  self.label = label
59
41
 
60
- self.args_dict = args_dict
61
- self.db_config = db_config
62
- self.site = self.args_dict.get("site", None)
63
- self.model_version = self.args_dict.get("model_version", None)
42
+ self.site = settings.config.args.get("site", None)
43
+ self.model_version = settings.config.args.get("model_version", None)
64
44
 
65
- self.simulation_software = self.args_dict.get("simulation_software", "corsika_sim_telarray")
66
- self.logger.debug(f"Init Simulator {self.simulation_software}")
67
- self.run_mode = args_dict.get("run_mode", None)
45
+ self.simulation_software = settings.config.args.get(
46
+ "simulation_software", "corsika_sim_telarray"
47
+ )
48
+ self.run_mode = settings.config.args.get("run_mode", None)
68
49
 
69
50
  self.io_handler = io_handler.IOHandler()
70
51
 
71
- self.run_number = None
72
- self._results = defaultdict(list)
73
- self._test = None
74
52
  self._extra_commands = extra_commands
75
- self.sim_telarray_seeds = None
76
- self._initialize_from_tool_configuration()
53
+ self.run_number = self._initialize_from_tool_configuration()
54
+
77
55
  self.array_models, self.corsika_configurations = self._initialize_array_models()
78
56
  self._simulation_runner = self._initialize_simulation_runner()
57
+ self.runner_service = runner_services.RunnerServices(
58
+ self._get_first_corsika_config(),
59
+ "sub",
60
+ label,
61
+ )
62
+ self.file_list = self.runner_service.load_files(self.run_number)
79
63
 
80
64
  @property
81
65
  def simulation_software(self):
@@ -104,21 +88,13 @@ class Simulator:
104
88
 
105
89
  def _initialize_from_tool_configuration(self):
106
90
  """Initialize simulator from tool configuration."""
107
- self._test = self.args_dict.get("test", False)
108
- self.sim_telarray_seeds = {
109
- "seed": self.args_dict.get("sim_telarray_instrument_seeds"),
110
- "random_instrument_instances": self.args_dict.get(
111
- "sim_telarray_random_instrument_instances"
112
- ),
113
- "seed_file_name": "sim_telarray_instrument_seeds.txt", # name only; no directory
114
- }
115
-
116
- if self.args_dict.get("corsika_file"):
117
- self.run_number = eventio_handler.get_corsika_run_number(self.args_dict["corsika_file"])
91
+ if settings.config.args.get("corsika_file"):
92
+ run_number = file_info.get_corsika_run_number(settings.config.args["corsika_file"])
118
93
  else:
119
- self.run_number = self.args_dict.get("run_number_offset", 0) + self.args_dict.get(
120
- "run_number", 1
121
- )
94
+ run_number = settings.config.args.get(
95
+ "run_number_offset", 0
96
+ ) + settings.config.args.get("run_number", 1)
97
+ return runner_services.validate_corsika_run_number(run_number)
122
98
 
123
99
  def _initialize_array_models(self):
124
100
  """
@@ -126,8 +102,8 @@ class Simulator:
126
102
 
127
103
  Returns
128
104
  -------
129
- list
130
- List of ArrayModel objects.
105
+ list, list
106
+ List of ArrayModel and CorsikaConfig objects.
131
107
  """
132
108
  versions = general.ensure_iterable(self.model_version)
133
109
 
@@ -135,39 +111,19 @@ class Simulator:
135
111
  corsika_configurations = []
136
112
 
137
113
  for version in versions:
138
- array_model.append(
139
- ArrayModel(
140
- label=self.label,
141
- site=self.site,
142
- layout_name=self.args_dict.get("array_layout_name"),
143
- db_config=self.db_config,
144
- model_version=version,
145
- calibration_device_types=self._get_calibration_device_types(self.run_mode),
146
- overwrite_model_parameters=self.args_dict.get("overwrite_model_parameters"),
147
- simtel_path=self.args_dict.get("simtel_path"),
148
- )
114
+ model = ArrayModel(
115
+ label=self.label,
116
+ site=self.site,
117
+ layout_name=settings.config.args.get("array_layout_name"),
118
+ model_version=version,
119
+ calibration_device_types=self._get_calibration_device_types(self.run_mode),
120
+ overwrite_model_parameters=settings.config.args.get("overwrite_model_parameters"),
149
121
  )
150
- corsika_configurations.append(
151
- CorsikaConfig(
152
- array_model=array_model[-1],
153
- label=self.label,
154
- args_dict=self.args_dict,
155
- db_config=self.db_config,
156
- dummy_simulations=self._is_calibration_run(self.run_mode),
157
- )
158
- )
159
- array_model[-1].sim_telarray_seeds = {
160
- "seed": self._get_seed_for_random_instrument_instances(
161
- self.sim_telarray_seeds["seed"],
162
- version,
163
- corsika_configurations[-1].zenith_angle,
164
- corsika_configurations[-1].azimuth_angle,
165
- ),
166
- "random_instrument_instances": self.sim_telarray_seeds[
167
- "random_instrument_instances"
168
- ],
169
- "seed_file_name": self.sim_telarray_seeds["seed_file_name"],
170
- }
122
+ cfg = CorsikaConfig(array_model=model, label=self.label, run_number=self.run_number)
123
+ model.initialize_seeds(cfg.zenith_angle, cfg.azimuth_angle)
124
+
125
+ array_model.append(model)
126
+ corsika_configurations.append(cfg)
171
127
 
172
128
  # 'corsika_sim_telarray' allows for multiple model versions (multipipe option)
173
129
  corsika_configurations = (
@@ -178,42 +134,6 @@ class Simulator:
178
134
 
179
135
  return array_model, corsika_configurations
180
136
 
181
- def _get_seed_for_random_instrument_instances(
182
- self, seed, model_version, zenith_angle, azimuth_angle
183
- ):
184
- """
185
- Generate seed for random instances of the instrument.
186
-
187
- Parameters
188
- ----------
189
- seed : str
190
- Seed string given through configuration.
191
- model_version: str
192
- Model version.
193
- zenith_angle: float
194
- Zenith angle of the observation (in degrees).
195
- azimuth_angle: float
196
- Azimuth angle of the observation (in degrees).
197
-
198
- Returns
199
- -------
200
- int
201
- Seed for random instances of the instrument.
202
- """
203
- if seed:
204
- return int(seed.split(",")[0].strip())
205
-
206
- def key_index(key):
207
- try:
208
- return list(names.site_names()).index(key) + 1
209
- except ValueError:
210
- return 1
211
-
212
- seed = semver_to_int(model_version) * 10000000
213
- seed = seed + key_index(self.site) * 1000000
214
- seed = seed + (int)(zenith_angle) * 1000
215
- return seed + (int)(azimuth_angle)
216
-
217
137
  def _initialize_simulation_runner(self):
218
138
  """
219
139
  Initialize corsika configuration and simulation runners.
@@ -224,30 +144,22 @@ class Simulator:
224
144
  Simulation runner object.
225
145
  """
226
146
  runner_class = {
227
- "corsika": CorsikaRunner,
147
+ "corsika": corsika_runner.CorsikaRunner,
228
148
  "sim_telarray": SimulatorArray,
229
- "corsika_sim_telarray": CorsikaSimtelRunner,
149
+ "corsika_sim_telarray": corsika_simtel_runner.CorsikaSimtelRunner,
230
150
  }.get(self.simulation_software)
231
151
 
232
152
  runner_args = {
233
153
  "label": self.label,
234
154
  "corsika_config": self.corsika_configurations,
235
- "simtel_path": self.args_dict.get("simtel_path"),
236
- "use_multipipe": runner_class is CorsikaSimtelRunner,
237
155
  }
238
156
 
239
157
  if runner_class is not SimulatorArray:
240
- runner_args["keep_seeds"] = self.args_dict.get("corsika_test_seeds", False)
241
- runner_args["curved_atmosphere_min_zenith_angle"] = self.args_dict.get(
158
+ runner_args["curved_atmosphere_min_zenith_angle"] = settings.config.args.get(
242
159
  "curved_atmosphere_min_zenith_angle", 65 * u.deg
243
160
  )
244
- if runner_class is not CorsikaRunner:
245
- runner_args["sim_telarray_seeds"] = self.sim_telarray_seeds
246
- if runner_class is CorsikaSimtelRunner:
247
- runner_args["sequential"] = self.args_dict.get("sequential", False)
248
- runner_args["calibration_config"] = (
249
- self.args_dict if self._is_calibration_run(self.run_mode) else None
250
- )
161
+ if runner_class is corsika_simtel_runner.CorsikaSimtelRunner:
162
+ runner_args["sequential"] = settings.config.args.get("sequential", False)
251
163
 
252
164
  return runner_class(**runner_args)
253
165
 
@@ -258,25 +170,21 @@ class Simulator:
258
170
  Writes submission scripts using the simulation runners and submits the
259
171
  run script to the job manager. Collects generated files.
260
172
  """
261
- run_script = self._simulation_runner.prepare_run_script(
173
+ self._simulation_runner.prepare_run(
262
174
  run_number=self.run_number,
263
- input_file=self._get_corsika_file(),
175
+ corsika_file=self._get_corsika_file(),
176
+ sub_script=self.runner_service.get_file_name("sub_script", self.run_number),
264
177
  extra_commands=self._extra_commands,
265
178
  )
179
+ self.update_file_lists()
266
180
 
267
- job_manager = JobManager(test=self._test)
268
181
  job_manager.submit(
269
- run_script=run_script,
270
- run_out_file=self._simulation_runner.get_file_name(
271
- file_type="sub_log", run_number=self.run_number
272
- ),
273
- log_file=self._simulation_runner.get_file_name(
274
- file_type=("log"), run_number=self.run_number
275
- ),
182
+ command=self.runner_service.get_file_name("sub_script", self.run_number),
183
+ out_file=self.runner_service.get_file_name("sub_out", self.run_number),
184
+ err_file=self.runner_service.get_file_name("sub_err", self.run_number),
185
+ env=simtel_runner.SIM_TELARRAY_ENV,
276
186
  )
277
187
 
278
- self._fill_list_of_generated_files()
279
-
280
188
  def _get_corsika_file(self):
281
189
  """
282
190
  Get the CORSIKA input file if applicable (for sim_telarray simulations).
@@ -287,61 +195,12 @@ class Simulator:
287
195
  Path to the CORSIKA input file.
288
196
  """
289
197
  if self.simulation_software == "sim_telarray":
290
- return self.args_dict.get("corsika_file", None)
198
+ return settings.config.args.get("corsika_file", None)
291
199
  return None
292
200
 
293
- def _fill_list_of_generated_files(self):
294
- """Fill a dictionary with lists of generated files."""
295
- keys = [
296
- "simtel_output",
297
- "sub_out",
298
- "log",
299
- "input",
300
- "histogram",
301
- "corsika_log",
302
- "corsika_output",
303
- "event_data",
304
- ]
305
- results = {key: [] for key in keys}
306
-
307
- def get_file_name(name, **kwargs):
308
- return str(self._simulation_runner.get_file_name(file_type=name, **kwargs))
309
-
310
- results["sub_out"].append(get_file_name("sub_log", mode="out", run_number=self.run_number))
311
-
312
- for i in range(len(self.array_models)):
313
- results["simtel_output"].append(
314
- get_file_name("simtel_output", run_number=self.run_number, model_version_index=i)
315
- )
316
-
317
- if "sim_telarray" in self.simulation_software:
318
- for file_type in ("log", "histogram", "event_data"):
319
- results[file_type].append(
320
- get_file_name(
321
- file_type,
322
- simulation_software="sim_telarray",
323
- run_number=self.run_number,
324
- model_version_index=i,
325
- )
326
- )
327
-
328
- if "corsika" in self.simulation_software:
329
- for file_type in ("corsika_output", "corsika_log"):
330
- results[file_type].append(
331
- get_file_name(
332
- file_type,
333
- simulation_software="corsika",
334
- run_number=self.run_number,
335
- model_version_index=i,
336
- )
337
- )
338
-
339
- for key in keys:
340
- self._results[key].extend(results[key])
341
-
342
- def get_file_list(self, file_type="simtel_output"):
343
- """
344
- Get list of files generated by simulations.
201
+ def get_files(self, file_type):
202
+ """
203
+ Get file(s) generated by simulations.
345
204
 
346
205
  Not all file types are available for all simulation types.
347
206
  Returns an empty list for an unknown file type.
@@ -353,34 +212,24 @@ class Simulator:
353
212
 
354
213
  Returns
355
214
  -------
356
- list
357
- List with the full path of all output files.
215
+ Path or list[Path]:
216
+ File or list with the full path of output files of a certain file type.
358
217
 
359
218
  """
360
- return self._results[file_type]
219
+ return self.file_list[file_type]
361
220
 
362
- def _make_resources_report(self, input_file_list):
221
+ def _make_resources_report(self):
363
222
  """
364
223
  Prepare a simple report on computing wall clock time used in the simulations.
365
224
 
366
- Parameters
367
- ----------
368
- input_file_list: str or list of str
369
- Single file or list of files of shower simulations.
370
-
371
225
  Returns
372
226
  -------
373
227
  str
374
228
  string reporting on computing resources
375
229
 
376
230
  """
377
- if len(self._results["sub_out"]) == 0 and input_file_list is None:
378
- return "Mean wall time/run [sec]: np.nan"
379
-
380
231
  runtime = []
381
-
382
- _resources = {}
383
- _resources = self._simulation_runner.get_resources(run_number=self.run_number)
232
+ _resources = self._simulation_runner.get_resources(self.get_files(file_type="sub_out"))
384
233
  if _resources.get("runtime"):
385
234
  runtime.append(_resources["runtime"])
386
235
 
@@ -392,17 +241,11 @@ class Simulator:
392
241
 
393
242
  return resource_summary
394
243
 
395
- def report(self, input_file_list=None):
244
+ def report(self):
396
245
  """
397
246
  Report on simulations and computing resources used.
398
247
 
399
248
  Includes run time per run only at this point.
400
-
401
- Parameters
402
- ----------
403
- input_file_list: str or list of str
404
- Single file or list of files of shower simulations.
405
-
406
249
  """
407
250
  _corsika_config = self._get_first_corsika_config()
408
251
  self.logger.info(
@@ -412,22 +255,23 @@ class Simulator:
412
255
  f"at {self.site} site, using {self.model_version} model."
413
256
  )
414
257
  self.logger.info(
415
- f"Computing for {self.simulation_software} Simulations: "
416
- f"{self._make_resources_report(input_file_list)}"
258
+ f"Computing for {self.simulation_software} Simulations: {self._make_resources_report()}"
417
259
  )
418
260
 
419
261
  def save_file_lists(self):
420
- """Save files lists for output and log files."""
421
- for file_type in ["simtel_output", "log", "corsika_log", "histogram"]:
422
- file_name = self.io_handler.get_output_directory().joinpath(f"{file_type}_files.txt")
423
- file_list = self.get_file_list(file_type=file_type)
424
- if all(element is not None for element in file_list) and len(file_list) > 0:
425
- self.logger.info(f"Saving list of {file_type} files to {file_name}")
426
- with open(file_name, "w", encoding="utf-8") as f:
427
- for line in self.get_file_list(file_type=file_type):
428
- f.write(f"{line}\n")
429
- else:
262
+ """Save file lists for output and log files."""
263
+ outdir = self.io_handler.get_output_directory()
264
+
265
+ for file_type, files in self.file_list.items():
266
+ if not files or any(f is None for f in files):
430
267
  self.logger.debug(f"No files to save for {file_type} files.")
268
+ continue
269
+
270
+ path = outdir / f"{file_type}_files.txt"
271
+ self.logger.info(f"Saving list of {file_type} files to {path}")
272
+
273
+ with open(path, "w", encoding="utf-8") as f:
274
+ f.write("\n".join(map(str, files)) + "\n")
431
275
 
432
276
  def save_reduced_event_lists(self):
433
277
  """
@@ -442,10 +286,10 @@ class Simulator:
442
286
  )
443
287
  return
444
288
 
445
- input_files = self.get_file_list(file_type="simtel_output")
446
- output_files = self.get_file_list(file_type="event_data")
289
+ input_files = self.get_files(file_type="sim_telarray_output")
290
+ output_files = self.get_files(file_type="sim_telarray_event_data")
447
291
  for input_file, output_file in zip(input_files, output_files):
448
- generator = SimtelIOEventDataWriter([input_file])
292
+ generator = writer.EventDataWriter([input_file])
449
293
  table_handler.write_tables(
450
294
  tables=generator.process_files(),
451
295
  output_file=Path(output_file),
@@ -465,15 +309,14 @@ class Simulator:
465
309
 
466
310
  """
467
311
  self.logger.info(
468
- f"Packing the output files for registering on the grid ({directory_for_grid_upload})"
312
+ f"Packing output files for registering on the grid ({directory_for_grid_upload})"
469
313
  )
470
- output_files = self.get_file_list(file_type="simtel_output")
471
- log_files = self.get_file_list(file_type="log")
472
- corsika_log_files = self.get_file_list(file_type="corsika_log")
473
- histogram_files = self.get_file_list(file_type="histogram")
314
+ output_files = self.get_files(file_type="sim_telarray_output")
315
+ log_files = self.get_files(file_type="sim_telarray_log")
316
+ histogram_files = self.get_files(file_type="sim_telarray_histogram")
474
317
  reduced_event_files = (
475
- self.get_file_list(file_type="event_data")
476
- if self.args_dict.get("save_reduced_event_lists")
318
+ self.get_files(file_type="sim_telarray_event_data")
319
+ if settings.config.args.get("save_reduced_event_lists")
477
320
  else []
478
321
  )
479
322
 
@@ -484,36 +327,32 @@ class Simulator:
484
327
  )
485
328
  directory_for_grid_upload.mkdir(parents=True, exist_ok=True)
486
329
 
487
- # If there are more than one model version,
488
- # duplicate the corsika log file to have one for each model version with the "right name".
489
- if len(self.array_models) > 1 and corsika_log_files:
490
- self._copy_corsika_log_file_for_all_versions(corsika_log_files)
491
-
492
330
  # Group files by model version
493
331
  for model in self.array_models:
494
332
  model_version = model.model_version
495
- model_files = general.ensure_iterable(model.pack_model_files())
333
+ model_logs = [f for f in log_files if model_version in str(f)]
496
334
 
497
- # Filter files for this model version
498
- model_logs = [f for f in log_files if model_version in f]
499
- model_hists = [f for f in histogram_files if model_version in f]
500
- model_corsika_logs = [f for f in corsika_log_files if model_version in f]
335
+ if not model_logs:
336
+ continue
501
337
 
502
- if model_logs:
503
- tar_file_name = Path(model_logs[0]).name.replace("log.gz", "log_hist.tar.gz")
504
- tar_file_path = directory_for_grid_upload.joinpath(tar_file_name)
505
- # Add all relevant model, log, histogram, and CORSIKA log files to the tarball
506
- files_to_tar = model_logs + model_hists + model_corsika_logs + model_files
507
- general.pack_tar_file(tar_file_path, files_to_tar)
338
+ tar_name = Path(model_logs[0]).name.replace("simtel.log.gz", "log_hist.tar.gz")
339
+ tar_path = directory_for_grid_upload / tar_name
340
+
341
+ files_to_tar = (
342
+ model_logs
343
+ + [f for f in histogram_files if model_version in str(f)]
344
+ + [str(self.get_files(file_type="corsika_log"))]
345
+ + list(general.ensure_iterable(model.pack_model_files()))
346
+ )
347
+ general.pack_tar_file(tar_path, files_to_tar)
508
348
 
509
349
  for file_to_move in output_files + reduced_event_files:
510
- source_file = Path(file_to_move)
511
- destination_file = directory_for_grid_upload / source_file.name
350
+ destination_file = directory_for_grid_upload / Path(file_to_move).name
512
351
  if destination_file.exists():
513
352
  self.logger.warning(f"Overwriting existing file: {destination_file}")
514
- shutil.move(source_file, destination_file)
353
+ shutil.move(file_to_move, destination_file)
515
354
 
516
- self.logger.info(f"Output files for the grid placed in {directory_for_grid_upload!s}")
355
+ self.logger.info(f"Grid output files grid placed in {directory_for_grid_upload!s}")
517
356
 
518
357
  def validate_metadata(self):
519
358
  """Validate metadata in the sim_telarray output files."""
@@ -522,8 +361,9 @@ class Simulator:
522
361
  return
523
362
 
524
363
  for model in self.array_models:
525
- files = self.get_file_list(file_type="simtel_output")
526
- output_file = next((f for f in files if model.model_version in f), None)
364
+ files = general.ensure_iterable(self.get_files(file_type="sim_telarray_output"))
365
+
366
+ output_file = next((f for f in files if model.model_version in str(f)), None)
527
367
  if output_file:
528
368
  self.logger.info(f"Validating metadata for {output_file}")
529
369
  assert_sim_telarray_metadata(output_file, model)
@@ -533,76 +373,6 @@ class Simulator:
533
373
  f"No sim_telarray file found for model version {model.model_version}: {files}"
534
374
  )
535
375
 
536
- def _copy_corsika_log_file_for_all_versions(self, corsika_log_files):
537
- """
538
- Create copies of the CORSIKA log file for each model version.
539
-
540
- Adds a header comment to each copy explaining its relationship to the original.
541
-
542
- Parameters
543
- ----------
544
- corsika_log_files: list
545
- List containing the original CORSIKA log file path.
546
- """
547
- original_log = Path(corsika_log_files[0])
548
- # Find which model version the original log belongs to
549
- original_version = next(
550
- model.model_version
551
- for model in self.array_models
552
- if re.search(
553
- rf"(?<![0-9A-Za-z]){re.escape(model.model_version)}(?![0-9A-Za-z])",
554
- original_log.name,
555
- )
556
- )
557
-
558
- for model in self.array_models:
559
- if model.model_version == original_version:
560
- continue
561
-
562
- new_log = original_log.parent / original_log.name.replace(
563
- original_version, model.model_version
564
- )
565
-
566
- with gzip.open(new_log, "wt", encoding="utf-8") as new_file:
567
- # Write the header to the new file
568
- header = (
569
- f"###############################################################\n"
570
- f"Copy of CORSIKA log file from model version {original_version}.\n"
571
- f"Applicable also for {model.model_version} (same CORSIKA configuration,\n"
572
- f"different sim_telarray model versions in the same run).\n"
573
- f"###############################################################\n\n"
574
- )
575
- new_file.write(header)
576
-
577
- # Copy the content of the original log file, ignoring invalid characters
578
- with gzip.open(original_log, "rt", encoding="utf-8", errors="ignore") as orig_file:
579
- for line in orig_file:
580
- new_file.write(line)
581
-
582
- corsika_log_files.append(str(new_log))
583
-
584
- @staticmethod
585
- def _is_calibration_run(run_mode):
586
- """
587
- Check if this simulation is a calibration run.
588
-
589
- Parameters
590
- ----------
591
- run_mode: str
592
- Run mode of the simulation.
593
-
594
- Returns
595
- -------
596
- bool
597
- True if it is a calibration run, False otherwise.
598
- """
599
- return run_mode in [
600
- "pedestals",
601
- "pedestals_dark",
602
- "pedestals_nsb_only",
603
- "direct_injection",
604
- ]
605
-
606
376
  @staticmethod
607
377
  def _get_calibration_device_types(run_mode):
608
378
  """
@@ -666,7 +436,7 @@ class Simulator:
666
436
  )
667
437
  if self.simulation_software == "corsika":
668
438
  self._verify_simulated_events_corsika(expected_mc_events)
669
- if self.args_dict.get("save_reduced_event_lists"):
439
+ if settings.config.args.get("save_reduced_event_lists"):
670
440
  self._verify_simulated_events_in_reduced_event_lists(expected_mc_events)
671
441
 
672
442
  def _verify_simulated_events_corsika(self, expected_mc_events, tolerance=1.0e-3):
@@ -690,24 +460,25 @@ class Simulator:
690
460
  return abs(a - b) / max(a, b) <= tol
691
461
 
692
462
  event_errors = []
693
- for file in self.get_file_list(file_type="corsika_output"):
694
- shower_events, _ = eventio_handler.get_simulated_events(file)
695
-
696
- if shower_events != expected_mc_events:
697
- if consistent(shower_events, expected_mc_events, tol=tolerance):
698
- self.logger.warning(
699
- f"Small mismatch in number of events in: {file}: "
700
- f"shower events: {shower_events} (expected: {expected_mc_events})"
701
- )
702
- continue
463
+
464
+ file = self.get_files(file_type="corsika_output")
465
+ shower_events, _ = file_info.get_simulated_events(file)
466
+
467
+ if shower_events != expected_mc_events:
468
+ if consistent(shower_events, expected_mc_events, tol=tolerance):
469
+ self.logger.warning(
470
+ f"Small mismatch in number of events in: {file}: "
471
+ f"shower events: {shower_events} (expected: {expected_mc_events})"
472
+ )
473
+ else:
703
474
  event_errors.append(
704
475
  f"Number of simulated MC events ({shower_events}) does not match "
705
476
  f"the expected number ({expected_mc_events}) in CORSIKA {file}."
706
477
  )
707
- else:
708
- self.logger.info(
709
- f"Consistent number of events in: {file}: shower events: {shower_events}"
710
- )
478
+ else:
479
+ self.logger.info(
480
+ f"Consistent number of events in: {file}: shower events: {shower_events}"
481
+ )
711
482
 
712
483
  if event_errors:
713
484
  self.logger.error("Inconsistent event counts found in CORSIKA output:")
@@ -735,8 +506,8 @@ class Simulator:
735
506
  If the number of simulated events does not match the expected number.
736
507
  """
737
508
  event_errors = []
738
- for file in self.get_file_list(file_type="simtel_output"):
739
- shower_events, mc_events = eventio_handler.get_simulated_events(file)
509
+ for file in general.ensure_iterable(self.get_files(file_type="sim_telarray_output")):
510
+ shower_events, mc_events = file_info.get_simulated_events(file)
740
511
 
741
512
  if (shower_events, mc_events) != (expected_shower_events, expected_mc_events):
742
513
  event_errors.append(
@@ -774,7 +545,7 @@ class Simulator:
774
545
  If the number of simulated events does not match the expected number.
775
546
  """
776
547
  event_errors = []
777
- for file in self.get_file_list(file_type="event_data"):
548
+ for file in self.get_files(file_type="sim_telarray_event_data"):
778
549
  tables = table_handler.read_tables(file, ["SHOWERS"])
779
550
  try:
780
551
  mc_events = len(tables["SHOWERS"])
@@ -800,3 +571,14 @@ class Simulator:
800
571
  f" - {error}" for error in event_errors
801
572
  )
802
573
  raise ValueError(error_message)
574
+
575
+ def update_file_lists(self):
576
+ """
577
+ Update file lists with all data, log, histogram and submission files.
578
+
579
+ Some of these files are generated by the simulation runners.
580
+ """
581
+ if self.file_list is None:
582
+ self.file_list = self._simulation_runner.file_list
583
+ else:
584
+ self.file_list.update(self._simulation_runner.file_list)