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
simtools/simulator.py CHANGED
@@ -1,78 +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
12
  from simtools.io import io_handler, table_handler
15
- from simtools.job_execution.job_manager import JobManager
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.sim_events import file_info
20
- from simtools.sim_events.writer import EventDataWriter
15
+ from simtools.runners import corsika_runner, corsika_simtel_runner, runner_services, simtel_runner
16
+ from simtools.sim_events import file_info, writer
21
17
  from simtools.simtel.simulator_array import SimulatorArray
22
18
  from simtools.testing.sim_telarray_metadata import assert_sim_telarray_metadata
23
- from simtools.utils import general, names
24
- from simtools.version import semver_to_int
19
+ from simtools.utils import general
25
20
 
26
21
 
27
22
  class Simulator:
28
23
  """
29
- Simulator is managing the simulation of showers and of the array of telescopes.
24
+ Simulation of showers and of the array of telescopes.
30
25
 
31
- 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).
32
27
  A single run is simulated per instance, possibly for multiple model versions.
33
28
 
34
- The configuration is set as a dict corresponding to the command line configuration groups
35
- (especially simulation_software, simulation_model, simulation_parameters).
36
-
37
29
  Parameters
38
30
  ----------
39
- args_dict : dict
40
- Configuration dictionary
41
- (includes simulation_software, simulation_model, simulation_parameters groups).
42
31
  label: str
43
32
  Instance label.
44
33
  extra_commands: str or list of str
45
34
  Extra commands to be added to the run script before the run command.
46
35
  """
47
36
 
48
- def __init__(
49
- self,
50
- args_dict,
51
- label=None,
52
- extra_commands=None,
53
- ):
37
+ def __init__(self, label=None, extra_commands=None):
54
38
  """Initialize Simulator class."""
55
39
  self.logger = logging.getLogger(__name__)
56
40
  self.label = label
57
41
 
58
- self.args_dict = args_dict
59
- self.site = self.args_dict.get("site", None)
60
- 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)
61
44
 
62
- self.simulation_software = self.args_dict.get("simulation_software", "corsika_sim_telarray")
63
- self.logger.debug(f"Init Simulator {self.simulation_software}")
64
- 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)
65
49
 
66
50
  self.io_handler = io_handler.IOHandler()
67
51
 
68
- self.run_number = None
69
- self._results = defaultdict(list)
70
- self._test = None
71
52
  self._extra_commands = extra_commands
72
- self.sim_telarray_seeds = None
73
- self._initialize_from_tool_configuration()
53
+ self.run_number = self._initialize_from_tool_configuration()
54
+
74
55
  self.array_models, self.corsika_configurations = self._initialize_array_models()
75
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)
76
63
 
77
64
  @property
78
65
  def simulation_software(self):
@@ -101,21 +88,13 @@ class Simulator:
101
88
 
102
89
  def _initialize_from_tool_configuration(self):
103
90
  """Initialize simulator from tool configuration."""
104
- self._test = self.args_dict.get("test", False)
105
- self.sim_telarray_seeds = {
106
- "seed": self.args_dict.get("sim_telarray_instrument_seeds"),
107
- "random_instrument_instances": self.args_dict.get(
108
- "sim_telarray_random_instrument_instances"
109
- ),
110
- "seed_file_name": "sim_telarray_instrument_seeds.txt", # name only; no directory
111
- }
112
-
113
- if self.args_dict.get("corsika_file"):
114
- self.run_number = file_info.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"])
115
93
  else:
116
- self.run_number = self.args_dict.get("run_number_offset", 0) + self.args_dict.get(
117
- "run_number", 1
118
- )
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)
119
98
 
120
99
  def _initialize_array_models(self):
121
100
  """
@@ -123,8 +102,8 @@ class Simulator:
123
102
 
124
103
  Returns
125
104
  -------
126
- list
127
- List of ArrayModel objects.
105
+ list, list
106
+ List of ArrayModel and CorsikaConfig objects.
128
107
  """
129
108
  versions = general.ensure_iterable(self.model_version)
130
109
 
@@ -132,36 +111,19 @@ class Simulator:
132
111
  corsika_configurations = []
133
112
 
134
113
  for version in versions:
135
- array_model.append(
136
- ArrayModel(
137
- label=self.label,
138
- site=self.site,
139
- layout_name=self.args_dict.get("array_layout_name"),
140
- model_version=version,
141
- calibration_device_types=self._get_calibration_device_types(self.run_mode),
142
- overwrite_model_parameters=self.args_dict.get("overwrite_model_parameters"),
143
- )
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"),
144
121
  )
145
- corsika_configurations.append(
146
- CorsikaConfig(
147
- array_model=array_model[-1],
148
- label=self.label,
149
- args_dict=self.args_dict,
150
- dummy_simulations=self._is_calibration_run(self.run_mode),
151
- )
152
- )
153
- array_model[-1].sim_telarray_seeds = {
154
- "seed": self._get_seed_for_random_instrument_instances(
155
- self.sim_telarray_seeds["seed"],
156
- version,
157
- corsika_configurations[-1].zenith_angle,
158
- corsika_configurations[-1].azimuth_angle,
159
- ),
160
- "random_instrument_instances": self.sim_telarray_seeds[
161
- "random_instrument_instances"
162
- ],
163
- "seed_file_name": self.sim_telarray_seeds["seed_file_name"],
164
- }
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)
165
127
 
166
128
  # 'corsika_sim_telarray' allows for multiple model versions (multipipe option)
167
129
  corsika_configurations = (
@@ -172,42 +134,6 @@ class Simulator:
172
134
 
173
135
  return array_model, corsika_configurations
174
136
 
175
- def _get_seed_for_random_instrument_instances(
176
- self, seed, model_version, zenith_angle, azimuth_angle
177
- ):
178
- """
179
- Generate seed for random instances of the instrument.
180
-
181
- Parameters
182
- ----------
183
- seed : str
184
- Seed string given through configuration.
185
- model_version: str
186
- Model version.
187
- zenith_angle: float
188
- Zenith angle of the observation (in degrees).
189
- azimuth_angle: float
190
- Azimuth angle of the observation (in degrees).
191
-
192
- Returns
193
- -------
194
- int
195
- Seed for random instances of the instrument.
196
- """
197
- if seed:
198
- return int(seed.split(",")[0].strip())
199
-
200
- def key_index(key):
201
- try:
202
- return list(names.site_names()).index(key) + 1
203
- except ValueError:
204
- return 1
205
-
206
- seed = semver_to_int(model_version) * 10000000
207
- seed = seed + key_index(self.site) * 1000000
208
- seed = seed + (int)(zenith_angle) * 1000
209
- return seed + (int)(azimuth_angle)
210
-
211
137
  def _initialize_simulation_runner(self):
212
138
  """
213
139
  Initialize corsika configuration and simulation runners.
@@ -218,29 +144,22 @@ class Simulator:
218
144
  Simulation runner object.
219
145
  """
220
146
  runner_class = {
221
- "corsika": CorsikaRunner,
147
+ "corsika": corsika_runner.CorsikaRunner,
222
148
  "sim_telarray": SimulatorArray,
223
- "corsika_sim_telarray": CorsikaSimtelRunner,
149
+ "corsika_sim_telarray": corsika_simtel_runner.CorsikaSimtelRunner,
224
150
  }.get(self.simulation_software)
225
151
 
226
152
  runner_args = {
227
153
  "label": self.label,
228
154
  "corsika_config": self.corsika_configurations,
229
- "use_multipipe": runner_class is CorsikaSimtelRunner,
230
155
  }
231
156
 
232
157
  if runner_class is not SimulatorArray:
233
- runner_args["keep_seeds"] = self.args_dict.get("corsika_test_seeds", False)
234
- runner_args["curved_atmosphere_min_zenith_angle"] = self.args_dict.get(
158
+ runner_args["curved_atmosphere_min_zenith_angle"] = settings.config.args.get(
235
159
  "curved_atmosphere_min_zenith_angle", 65 * u.deg
236
160
  )
237
- if runner_class is not CorsikaRunner:
238
- runner_args["sim_telarray_seeds"] = self.sim_telarray_seeds
239
- if runner_class is CorsikaSimtelRunner:
240
- runner_args["sequential"] = self.args_dict.get("sequential", False)
241
- runner_args["calibration_config"] = (
242
- self.args_dict if self._is_calibration_run(self.run_mode) else None
243
- )
161
+ if runner_class is corsika_simtel_runner.CorsikaSimtelRunner:
162
+ runner_args["sequential"] = settings.config.args.get("sequential", False)
244
163
 
245
164
  return runner_class(**runner_args)
246
165
 
@@ -251,25 +170,21 @@ class Simulator:
251
170
  Writes submission scripts using the simulation runners and submits the
252
171
  run script to the job manager. Collects generated files.
253
172
  """
254
- run_script = self._simulation_runner.prepare_run_script(
173
+ self._simulation_runner.prepare_run(
255
174
  run_number=self.run_number,
256
- 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),
257
177
  extra_commands=self._extra_commands,
258
178
  )
179
+ self.update_file_lists()
259
180
 
260
- job_manager = JobManager(test=self._test)
261
181
  job_manager.submit(
262
- run_script=run_script,
263
- run_out_file=self._simulation_runner.get_file_name(
264
- file_type="sub_log", run_number=self.run_number
265
- ),
266
- log_file=self._simulation_runner.get_file_name(
267
- file_type=("log"), run_number=self.run_number
268
- ),
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,
269
186
  )
270
187
 
271
- self._fill_list_of_generated_files()
272
-
273
188
  def _get_corsika_file(self):
274
189
  """
275
190
  Get the CORSIKA input file if applicable (for sim_telarray simulations).
@@ -280,61 +195,12 @@ class Simulator:
280
195
  Path to the CORSIKA input file.
281
196
  """
282
197
  if self.simulation_software == "sim_telarray":
283
- return self.args_dict.get("corsika_file", None)
198
+ return settings.config.args.get("corsika_file", None)
284
199
  return None
285
200
 
286
- def _fill_list_of_generated_files(self):
287
- """Fill a dictionary with lists of generated files."""
288
- keys = [
289
- "simtel_output",
290
- "sub_out",
291
- "log",
292
- "input",
293
- "histogram",
294
- "corsika_log",
295
- "corsika_output",
296
- "event_data",
297
- ]
298
- results = {key: [] for key in keys}
299
-
300
- def get_file_name(name, **kwargs):
301
- return str(self._simulation_runner.get_file_name(file_type=name, **kwargs))
302
-
303
- results["sub_out"].append(get_file_name("sub_log", mode="out", run_number=self.run_number))
304
-
305
- for i in range(len(self.array_models)):
306
- results["simtel_output"].append(
307
- get_file_name("simtel_output", run_number=self.run_number, model_version_index=i)
308
- )
309
-
310
- if "sim_telarray" in self.simulation_software:
311
- for file_type in ("log", "histogram", "event_data"):
312
- results[file_type].append(
313
- get_file_name(
314
- file_type,
315
- simulation_software="sim_telarray",
316
- run_number=self.run_number,
317
- model_version_index=i,
318
- )
319
- )
320
-
321
- if "corsika" in self.simulation_software:
322
- for file_type in ("corsika_output", "corsika_log"):
323
- results[file_type].append(
324
- get_file_name(
325
- file_type,
326
- simulation_software="corsika",
327
- run_number=self.run_number,
328
- model_version_index=i,
329
- )
330
- )
331
-
332
- for key in keys:
333
- self._results[key].extend(results[key])
334
-
335
- def get_file_list(self, file_type="simtel_output"):
336
- """
337
- Get list of files generated by simulations.
201
+ def get_files(self, file_type):
202
+ """
203
+ Get file(s) generated by simulations.
338
204
 
339
205
  Not all file types are available for all simulation types.
340
206
  Returns an empty list for an unknown file type.
@@ -346,34 +212,24 @@ class Simulator:
346
212
 
347
213
  Returns
348
214
  -------
349
- list
350
- 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.
351
217
 
352
218
  """
353
- return self._results[file_type]
219
+ return self.file_list[file_type]
354
220
 
355
- def _make_resources_report(self, input_file_list):
221
+ def _make_resources_report(self):
356
222
  """
357
223
  Prepare a simple report on computing wall clock time used in the simulations.
358
224
 
359
- Parameters
360
- ----------
361
- input_file_list: str or list of str
362
- Single file or list of files of shower simulations.
363
-
364
225
  Returns
365
226
  -------
366
227
  str
367
228
  string reporting on computing resources
368
229
 
369
230
  """
370
- if len(self._results["sub_out"]) == 0 and input_file_list is None:
371
- return "Mean wall time/run [sec]: np.nan"
372
-
373
231
  runtime = []
374
-
375
- _resources = {}
376
- _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"))
377
233
  if _resources.get("runtime"):
378
234
  runtime.append(_resources["runtime"])
379
235
 
@@ -385,17 +241,11 @@ class Simulator:
385
241
 
386
242
  return resource_summary
387
243
 
388
- def report(self, input_file_list=None):
244
+ def report(self):
389
245
  """
390
246
  Report on simulations and computing resources used.
391
247
 
392
248
  Includes run time per run only at this point.
393
-
394
- Parameters
395
- ----------
396
- input_file_list: str or list of str
397
- Single file or list of files of shower simulations.
398
-
399
249
  """
400
250
  _corsika_config = self._get_first_corsika_config()
401
251
  self.logger.info(
@@ -405,22 +255,23 @@ class Simulator:
405
255
  f"at {self.site} site, using {self.model_version} model."
406
256
  )
407
257
  self.logger.info(
408
- f"Computing for {self.simulation_software} Simulations: "
409
- f"{self._make_resources_report(input_file_list)}"
258
+ f"Computing for {self.simulation_software} Simulations: {self._make_resources_report()}"
410
259
  )
411
260
 
412
261
  def save_file_lists(self):
413
- """Save files lists for output and log files."""
414
- for file_type in ["simtel_output", "log", "corsika_log", "histogram"]:
415
- file_name = self.io_handler.get_output_directory().joinpath(f"{file_type}_files.txt")
416
- file_list = self.get_file_list(file_type=file_type)
417
- if all(element is not None for element in file_list) and len(file_list) > 0:
418
- self.logger.info(f"Saving list of {file_type} files to {file_name}")
419
- with open(file_name, "w", encoding="utf-8") as f:
420
- for line in self.get_file_list(file_type=file_type):
421
- f.write(f"{line}\n")
422
- 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):
423
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")
424
275
 
425
276
  def save_reduced_event_lists(self):
426
277
  """
@@ -435,10 +286,10 @@ class Simulator:
435
286
  )
436
287
  return
437
288
 
438
- input_files = self.get_file_list(file_type="simtel_output")
439
- 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")
440
291
  for input_file, output_file in zip(input_files, output_files):
441
- generator = EventDataWriter([input_file])
292
+ generator = writer.EventDataWriter([input_file])
442
293
  table_handler.write_tables(
443
294
  tables=generator.process_files(),
444
295
  output_file=Path(output_file),
@@ -458,15 +309,14 @@ class Simulator:
458
309
 
459
310
  """
460
311
  self.logger.info(
461
- 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})"
462
313
  )
463
- output_files = self.get_file_list(file_type="simtel_output")
464
- log_files = self.get_file_list(file_type="log")
465
- corsika_log_files = self.get_file_list(file_type="corsika_log")
466
- 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")
467
317
  reduced_event_files = (
468
- self.get_file_list(file_type="event_data")
469
- 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")
470
320
  else []
471
321
  )
472
322
 
@@ -477,36 +327,32 @@ class Simulator:
477
327
  )
478
328
  directory_for_grid_upload.mkdir(parents=True, exist_ok=True)
479
329
 
480
- # If there are more than one model version,
481
- # duplicate the corsika log file to have one for each model version with the "right name".
482
- if len(self.array_models) > 1 and corsika_log_files:
483
- self._copy_corsika_log_file_for_all_versions(corsika_log_files)
484
-
485
330
  # Group files by model version
486
331
  for model in self.array_models:
487
332
  model_version = model.model_version
488
- model_files = general.ensure_iterable(model.pack_model_files())
333
+ model_logs = [f for f in log_files if model_version in str(f)]
489
334
 
490
- # Filter files for this model version
491
- model_logs = [f for f in log_files if model_version in f]
492
- model_hists = [f for f in histogram_files if model_version in f]
493
- model_corsika_logs = [f for f in corsika_log_files if model_version in f]
335
+ if not model_logs:
336
+ continue
494
337
 
495
- if model_logs:
496
- tar_file_name = Path(model_logs[0]).name.replace("log.gz", "log_hist.tar.gz")
497
- tar_file_path = directory_for_grid_upload.joinpath(tar_file_name)
498
- # Add all relevant model, log, histogram, and CORSIKA log files to the tarball
499
- files_to_tar = model_logs + model_hists + model_corsika_logs + model_files
500
- 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)
501
348
 
502
349
  for file_to_move in output_files + reduced_event_files:
503
- source_file = Path(file_to_move)
504
- destination_file = directory_for_grid_upload / source_file.name
350
+ destination_file = directory_for_grid_upload / Path(file_to_move).name
505
351
  if destination_file.exists():
506
352
  self.logger.warning(f"Overwriting existing file: {destination_file}")
507
- shutil.move(source_file, destination_file)
353
+ shutil.move(file_to_move, destination_file)
508
354
 
509
- 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}")
510
356
 
511
357
  def validate_metadata(self):
512
358
  """Validate metadata in the sim_telarray output files."""
@@ -515,8 +361,9 @@ class Simulator:
515
361
  return
516
362
 
517
363
  for model in self.array_models:
518
- files = self.get_file_list(file_type="simtel_output")
519
- 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)
520
367
  if output_file:
521
368
  self.logger.info(f"Validating metadata for {output_file}")
522
369
  assert_sim_telarray_metadata(output_file, model)
@@ -526,76 +373,6 @@ class Simulator:
526
373
  f"No sim_telarray file found for model version {model.model_version}: {files}"
527
374
  )
528
375
 
529
- def _copy_corsika_log_file_for_all_versions(self, corsika_log_files):
530
- """
531
- Create copies of the CORSIKA log file for each model version.
532
-
533
- Adds a header comment to each copy explaining its relationship to the original.
534
-
535
- Parameters
536
- ----------
537
- corsika_log_files: list
538
- List containing the original CORSIKA log file path.
539
- """
540
- original_log = Path(corsika_log_files[0])
541
- # Find which model version the original log belongs to
542
- original_version = next(
543
- model.model_version
544
- for model in self.array_models
545
- if re.search(
546
- rf"(?<![0-9A-Za-z]){re.escape(model.model_version)}(?![0-9A-Za-z])",
547
- original_log.name,
548
- )
549
- )
550
-
551
- for model in self.array_models:
552
- if model.model_version == original_version:
553
- continue
554
-
555
- new_log = original_log.parent / original_log.name.replace(
556
- original_version, model.model_version
557
- )
558
-
559
- with gzip.open(new_log, "wt", encoding="utf-8") as new_file:
560
- # Write the header to the new file
561
- header = (
562
- f"###############################################################\n"
563
- f"Copy of CORSIKA log file from model version {original_version}.\n"
564
- f"Applicable also for {model.model_version} (same CORSIKA configuration,\n"
565
- f"different sim_telarray model versions in the same run).\n"
566
- f"###############################################################\n\n"
567
- )
568
- new_file.write(header)
569
-
570
- # Copy the content of the original log file, ignoring invalid characters
571
- with gzip.open(original_log, "rt", encoding="utf-8", errors="ignore") as orig_file:
572
- for line in orig_file:
573
- new_file.write(line)
574
-
575
- corsika_log_files.append(str(new_log))
576
-
577
- @staticmethod
578
- def _is_calibration_run(run_mode):
579
- """
580
- Check if this simulation is a calibration run.
581
-
582
- Parameters
583
- ----------
584
- run_mode: str
585
- Run mode of the simulation.
586
-
587
- Returns
588
- -------
589
- bool
590
- True if it is a calibration run, False otherwise.
591
- """
592
- return run_mode in [
593
- "pedestals",
594
- "pedestals_dark",
595
- "pedestals_nsb_only",
596
- "direct_injection",
597
- ]
598
-
599
376
  @staticmethod
600
377
  def _get_calibration_device_types(run_mode):
601
378
  """
@@ -659,7 +436,7 @@ class Simulator:
659
436
  )
660
437
  if self.simulation_software == "corsika":
661
438
  self._verify_simulated_events_corsika(expected_mc_events)
662
- if self.args_dict.get("save_reduced_event_lists"):
439
+ if settings.config.args.get("save_reduced_event_lists"):
663
440
  self._verify_simulated_events_in_reduced_event_lists(expected_mc_events)
664
441
 
665
442
  def _verify_simulated_events_corsika(self, expected_mc_events, tolerance=1.0e-3):
@@ -683,24 +460,25 @@ class Simulator:
683
460
  return abs(a - b) / max(a, b) <= tol
684
461
 
685
462
  event_errors = []
686
- for file in self.get_file_list(file_type="corsika_output"):
687
- shower_events, _ = file_info.get_simulated_events(file)
688
-
689
- if shower_events != expected_mc_events:
690
- if consistent(shower_events, expected_mc_events, tol=tolerance):
691
- self.logger.warning(
692
- f"Small mismatch in number of events in: {file}: "
693
- f"shower events: {shower_events} (expected: {expected_mc_events})"
694
- )
695
- 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:
696
474
  event_errors.append(
697
475
  f"Number of simulated MC events ({shower_events}) does not match "
698
476
  f"the expected number ({expected_mc_events}) in CORSIKA {file}."
699
477
  )
700
- else:
701
- self.logger.info(
702
- f"Consistent number of events in: {file}: shower events: {shower_events}"
703
- )
478
+ else:
479
+ self.logger.info(
480
+ f"Consistent number of events in: {file}: shower events: {shower_events}"
481
+ )
704
482
 
705
483
  if event_errors:
706
484
  self.logger.error("Inconsistent event counts found in CORSIKA output:")
@@ -728,7 +506,7 @@ class Simulator:
728
506
  If the number of simulated events does not match the expected number.
729
507
  """
730
508
  event_errors = []
731
- for file in self.get_file_list(file_type="simtel_output"):
509
+ for file in general.ensure_iterable(self.get_files(file_type="sim_telarray_output")):
732
510
  shower_events, mc_events = file_info.get_simulated_events(file)
733
511
 
734
512
  if (shower_events, mc_events) != (expected_shower_events, expected_mc_events):
@@ -767,7 +545,7 @@ class Simulator:
767
545
  If the number of simulated events does not match the expected number.
768
546
  """
769
547
  event_errors = []
770
- for file in self.get_file_list(file_type="event_data"):
548
+ for file in self.get_files(file_type="sim_telarray_event_data"):
771
549
  tables = table_handler.read_tables(file, ["SHOWERS"])
772
550
  try:
773
551
  mc_events = len(tables["SHOWERS"])
@@ -793,3 +571,14 @@ class Simulator:
793
571
  f" - {error}" for error in event_errors
794
572
  )
795
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)