gammasimtools 0.15.0__py3-none-any.whl → 0.17.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 (248) hide show
  1. {gammasimtools-0.15.0.dist-info → gammasimtools-0.17.0.dist-info}/METADATA +5 -33
  2. {gammasimtools-0.15.0.dist-info → gammasimtools-0.17.0.dist-info}/RECORD +243 -229
  3. {gammasimtools-0.15.0.dist-info → gammasimtools-0.17.0.dist-info}/WHEEL +1 -1
  4. {gammasimtools-0.15.0.dist-info → gammasimtools-0.17.0.dist-info}/entry_points.txt +8 -3
  5. simtools/_version.py +2 -2
  6. simtools/applications/calculate_trigger_rate.py +10 -10
  7. simtools/applications/convert_all_model_parameters_from_simtel.py +16 -16
  8. simtools/applications/convert_model_parameter_from_simtel.py +1 -1
  9. simtools/applications/derive_ctao_array_layouts.py +5 -5
  10. simtools/applications/derive_psf_parameters.py +12 -9
  11. simtools/applications/docs_produce_array_element_report.py +3 -3
  12. simtools/applications/docs_produce_calibration_reports.py +49 -0
  13. simtools/applications/docs_produce_simulation_configuration_report.py +50 -0
  14. simtools/applications/{generate_simtel_array_histograms.py → generate_sim_telarray_histograms.py} +2 -2
  15. simtools/applications/generate_simtel_event_data.py +36 -46
  16. simtools/applications/merge_tables.py +104 -0
  17. simtools/applications/plot_array_layout.py +145 -258
  18. simtools/applications/production_derive_corsika_limits.py +35 -167
  19. simtools/applications/production_derive_statistics.py +159 -0
  20. simtools/applications/production_generate_grid.py +197 -0
  21. simtools/applications/simulate_light_emission.py +6 -13
  22. simtools/applications/simulate_prod.py +45 -21
  23. simtools/applications/simulate_prod_htcondor_generator.py +0 -1
  24. simtools/applications/submit_array_layouts.py +93 -0
  25. simtools/applications/validate_cumulative_psf.py +6 -4
  26. simtools/applications/validate_file_using_schema.py +7 -3
  27. simtools/applications/validate_optics.py +5 -4
  28. simtools/applications/verify_simulation_model_production_tables.py +52 -0
  29. simtools/camera/camera_efficiency.py +17 -42
  30. simtools/configuration/commandline_parser.py +32 -37
  31. simtools/configuration/configurator.py +10 -4
  32. simtools/corsika/corsika_config.py +120 -17
  33. simtools/corsika/primary_particle.py +46 -13
  34. simtools/data_model/format_checkers.py +9 -0
  35. simtools/data_model/metadata_collector.py +7 -3
  36. simtools/data_model/model_data_writer.py +3 -0
  37. simtools/data_model/schema.py +27 -16
  38. simtools/data_model/validate_data.py +27 -7
  39. simtools/db/db_handler.py +21 -15
  40. simtools/db/db_model_upload.py +2 -2
  41. simtools/io_operations/io_handler.py +2 -2
  42. simtools/io_operations/io_table_handler.py +345 -0
  43. simtools/job_execution/htcondor_script_generator.py +2 -2
  44. simtools/job_execution/job_manager.py +7 -121
  45. simtools/layout/array_layout.py +1 -0
  46. simtools/layout/array_layout_utils.py +385 -0
  47. simtools/model/array_model.py +68 -29
  48. simtools/model/model_parameter.py +76 -51
  49. simtools/model/model_repository.py +134 -0
  50. simtools/model/model_utils.py +43 -1
  51. simtools/model/site_model.py +3 -2
  52. simtools/model/telescope_model.py +4 -4
  53. simtools/production_configuration/{calculate_statistical_errors_grid_point.py → calculate_statistical_uncertainties_grid_point.py} +101 -116
  54. simtools/production_configuration/derive_corsika_limits.py +239 -111
  55. simtools/production_configuration/derive_corsika_limits_grid.py +189 -0
  56. simtools/production_configuration/derive_production_statistics.py +155 -0
  57. simtools/production_configuration/derive_production_statistics_handler.py +152 -0
  58. simtools/production_configuration/generate_production_grid.py +364 -0
  59. simtools/production_configuration/interpolation_handler.py +303 -96
  60. simtools/ray_tracing/mirror_panel_psf.py +16 -20
  61. simtools/ray_tracing/psf_analysis.py +2 -2
  62. simtools/ray_tracing/ray_tracing.py +12 -7
  63. simtools/reporting/docs_read_parameters.py +426 -81
  64. simtools/runners/corsika_runner.py +11 -1
  65. simtools/runners/corsika_simtel_runner.py +84 -90
  66. simtools/runners/runner_services.py +22 -8
  67. simtools/runners/simtel_runner.py +27 -10
  68. simtools/schemas/model_parameter.metaschema.yml +4 -0
  69. simtools/schemas/model_parameter_and_data_schema.metaschema.yml +1 -0
  70. simtools/schemas/model_parameters/adjust_gain.schema.yml +2 -2
  71. simtools/schemas/model_parameters/array_element_position_ground.schema.yml +2 -2
  72. simtools/schemas/model_parameters/array_element_position_utm.schema.yml +2 -2
  73. simtools/schemas/model_parameters/array_window.schema.yml +2 -2
  74. simtools/schemas/model_parameters/asum_offset.schema.yml +2 -2
  75. simtools/schemas/model_parameters/asum_shaping.schema.yml +2 -2
  76. simtools/schemas/model_parameters/asum_threshold.schema.yml +2 -2
  77. simtools/schemas/model_parameters/axes_offsets.schema.yml +2 -2
  78. simtools/schemas/model_parameters/camera_body_diameter.schema.yml +2 -2
  79. simtools/schemas/model_parameters/camera_body_shape.schema.yml +2 -2
  80. simtools/schemas/model_parameters/camera_config_file.schema.yml +2 -2
  81. simtools/schemas/model_parameters/camera_config_rotate.schema.yml +2 -2
  82. simtools/schemas/model_parameters/camera_degraded_efficiency.schema.yml +2 -2
  83. simtools/schemas/model_parameters/camera_degraded_map.schema.yml +2 -2
  84. simtools/schemas/model_parameters/camera_depth.schema.yml +2 -2
  85. simtools/schemas/model_parameters/camera_filter.schema.yml +2 -2
  86. simtools/schemas/model_parameters/camera_pixels.schema.yml +2 -2
  87. simtools/schemas/model_parameters/camera_transmission.schema.yml +2 -2
  88. simtools/schemas/model_parameters/channels_per_chip.schema.yml +2 -2
  89. simtools/schemas/model_parameters/correct_nsb_spectrum_to_telescope_altitude.schema.yml +2 -2
  90. simtools/schemas/model_parameters/corsika_starting_grammage.schema.yml +90 -1
  91. simtools/schemas/model_parameters/default_trigger.schema.yml +2 -2
  92. simtools/schemas/model_parameters/design_model.schema.yml +2 -2
  93. simtools/schemas/model_parameters/disc_ac_coupled.schema.yml +2 -2
  94. simtools/schemas/model_parameters/disc_bins.schema.yml +2 -2
  95. simtools/schemas/model_parameters/disc_start.schema.yml +2 -2
  96. simtools/schemas/model_parameters/discriminator_amplitude.schema.yml +2 -2
  97. simtools/schemas/model_parameters/discriminator_fall_time.schema.yml +2 -2
  98. simtools/schemas/model_parameters/discriminator_gate_length.schema.yml +2 -2
  99. simtools/schemas/model_parameters/discriminator_hysteresis.schema.yml +2 -2
  100. simtools/schemas/model_parameters/discriminator_output_amplitude.schema.yml +2 -2
  101. simtools/schemas/model_parameters/discriminator_output_var_percent.schema.yml +2 -2
  102. simtools/schemas/model_parameters/discriminator_pulse_shape.schema.yml +2 -2
  103. simtools/schemas/model_parameters/discriminator_rise_time.schema.yml +2 -2
  104. simtools/schemas/model_parameters/discriminator_scale_threshold.schema.yml +2 -2
  105. simtools/schemas/model_parameters/discriminator_sigsum_over_threshold.schema.yml +2 -2
  106. simtools/schemas/model_parameters/discriminator_threshold.schema.yml +2 -2
  107. simtools/schemas/model_parameters/discriminator_time_over_threshold.schema.yml +2 -2
  108. simtools/schemas/model_parameters/discriminator_var_gate_length.schema.yml +2 -2
  109. simtools/schemas/model_parameters/discriminator_var_sigsum_over_threshold.schema.yml +2 -2
  110. simtools/schemas/model_parameters/discriminator_var_threshold.schema.yml +2 -2
  111. simtools/schemas/model_parameters/discriminator_var_time_over_threshold.schema.yml +2 -2
  112. simtools/schemas/model_parameters/dish_shape_length.schema.yml +2 -2
  113. simtools/schemas/model_parameters/dsum_clipping.schema.yml +2 -2
  114. simtools/schemas/model_parameters/dsum_ignore_below.schema.yml +2 -2
  115. simtools/schemas/model_parameters/dsum_offset.schema.yml +2 -2
  116. simtools/schemas/model_parameters/dsum_pedsub.schema.yml +2 -2
  117. simtools/schemas/model_parameters/dsum_pre_clipping.schema.yml +2 -2
  118. simtools/schemas/model_parameters/dsum_prescale.schema.yml +2 -2
  119. simtools/schemas/model_parameters/dsum_presum_max.schema.yml +2 -2
  120. simtools/schemas/model_parameters/dsum_presum_shift.schema.yml +2 -2
  121. simtools/schemas/model_parameters/dsum_shaping.schema.yml +2 -2
  122. simtools/schemas/model_parameters/dsum_shaping_renormalize.schema.yml +2 -2
  123. simtools/schemas/model_parameters/dsum_threshold.schema.yml +44 -3
  124. simtools/schemas/model_parameters/dsum_zero_clip.schema.yml +2 -2
  125. simtools/schemas/model_parameters/effective_focal_length.schema.yml +2 -2
  126. simtools/schemas/model_parameters/fadc_ac_coupled.schema.yml +2 -2
  127. simtools/schemas/model_parameters/fadc_amplitude.schema.yml +2 -2
  128. simtools/schemas/model_parameters/fadc_bins.schema.yml +2 -2
  129. simtools/schemas/model_parameters/fadc_compensate_pedestal.schema.yml +2 -2
  130. simtools/schemas/model_parameters/fadc_err_compensate_pedestal.schema.yml +2 -2
  131. simtools/schemas/model_parameters/fadc_err_pedestal.schema.yml +2 -2
  132. simtools/schemas/model_parameters/fadc_lg_amplitude.schema.yml +2 -2
  133. simtools/schemas/model_parameters/fadc_lg_compensate_pedestal.schema.yml +2 -2
  134. simtools/schemas/model_parameters/fadc_lg_err_compensate_pedestal.schema.yml +2 -2
  135. simtools/schemas/model_parameters/fadc_lg_err_pedestal.schema.yml +2 -2
  136. simtools/schemas/model_parameters/fadc_lg_max_signal.schema.yml +2 -2
  137. simtools/schemas/model_parameters/fadc_lg_noise.schema.yml +2 -2
  138. simtools/schemas/model_parameters/fadc_lg_pedestal.schema.yml +2 -2
  139. simtools/schemas/model_parameters/fadc_lg_sensitivity.schema.yml +2 -2
  140. simtools/schemas/model_parameters/fadc_lg_sysvar_pedestal.schema.yml +2 -2
  141. simtools/schemas/model_parameters/fadc_lg_var_pedestal.schema.yml +2 -2
  142. simtools/schemas/model_parameters/fadc_lg_var_sensitivity.schema.yml +2 -2
  143. simtools/schemas/model_parameters/fadc_max_signal.schema.yml +2 -2
  144. simtools/schemas/model_parameters/fadc_mhz.schema.yml +2 -2
  145. simtools/schemas/model_parameters/fadc_noise.schema.yml +2 -2
  146. simtools/schemas/model_parameters/fadc_pedestal.schema.yml +2 -2
  147. simtools/schemas/model_parameters/fadc_pulse_shape.schema.yml +2 -2
  148. simtools/schemas/model_parameters/fadc_sensitivity.schema.yml +2 -2
  149. simtools/schemas/model_parameters/fadc_sum_bins.schema.yml +2 -2
  150. simtools/schemas/model_parameters/fadc_sum_offset.schema.yml +2 -2
  151. simtools/schemas/model_parameters/fadc_sysvar_pedestal.schema.yml +2 -2
  152. simtools/schemas/model_parameters/fadc_var_pedestal.schema.yml +2 -2
  153. simtools/schemas/model_parameters/fadc_var_sensitivity.schema.yml +2 -2
  154. simtools/schemas/model_parameters/fake_mirror_list.schema.yml +1 -1
  155. simtools/schemas/model_parameters/flatfielding.schema.yml +2 -2
  156. simtools/schemas/model_parameters/focal_length.schema.yml +2 -2
  157. simtools/schemas/model_parameters/focus_offset.schema.yml +2 -2
  158. simtools/schemas/model_parameters/gain_variation.schema.yml +2 -2
  159. simtools/schemas/model_parameters/hg_lg_variation.schema.yml +2 -2
  160. simtools/schemas/model_parameters/iobuf_maximum.schema.yml +2 -2
  161. simtools/schemas/model_parameters/iobuf_output_maximum.schema.yml +2 -2
  162. simtools/schemas/model_parameters/laser_events.schema.yml +1 -1
  163. simtools/schemas/model_parameters/lightguide_efficiency_vs_incidence_angle.schema.yml +2 -2
  164. simtools/schemas/model_parameters/lightguide_efficiency_vs_wavelength.schema.yml +2 -2
  165. simtools/schemas/model_parameters/min_photoelectrons.schema.yml +2 -2
  166. simtools/schemas/model_parameters/min_photons.schema.yml +2 -2
  167. simtools/schemas/model_parameters/mirror_align_random_distance.schema.yml +2 -2
  168. simtools/schemas/model_parameters/mirror_align_random_horizontal.schema.yml +2 -2
  169. simtools/schemas/model_parameters/mirror_align_random_vertical.schema.yml +2 -2
  170. simtools/schemas/model_parameters/mirror_class.schema.yml +2 -2
  171. simtools/schemas/model_parameters/mirror_degraded_reflection.schema.yml +2 -2
  172. simtools/schemas/model_parameters/mirror_focal_length.schema.yml +2 -2
  173. simtools/schemas/model_parameters/mirror_list.schema.yml +2 -2
  174. simtools/schemas/model_parameters/mirror_offset.schema.yml +2 -2
  175. simtools/schemas/model_parameters/mirror_reflection_random_angle.schema.yml +2 -2
  176. simtools/schemas/model_parameters/mirror_reflectivity.schema.yml +2 -2
  177. simtools/schemas/model_parameters/multiplicity_offset.schema.yml +2 -2
  178. simtools/schemas/model_parameters/muon_mono_threshold.schema.yml +2 -2
  179. simtools/schemas/model_parameters/nsb_autoscale_airmass.schema.yml +2 -2
  180. simtools/schemas/model_parameters/nsb_offaxis.schema.yml +2 -2
  181. simtools/schemas/model_parameters/nsb_pixel_rate.schema.yml +2 -2
  182. simtools/schemas/model_parameters/num_gains.schema.yml +2 -2
  183. simtools/schemas/model_parameters/only_triggered_telescopes.schema.yml +2 -2
  184. simtools/schemas/model_parameters/optics_properties.schema.yml +2 -2
  185. simtools/schemas/model_parameters/pedestal_events.schema.yml +7 -3
  186. simtools/schemas/model_parameters/photon_delay.schema.yml +2 -2
  187. simtools/schemas/model_parameters/pixeltrg_time_step.schema.yml +2 -2
  188. simtools/schemas/model_parameters/pm_average_gain.schema.yml +2 -2
  189. simtools/schemas/model_parameters/pm_collection_efficiency.schema.yml +2 -2
  190. simtools/schemas/model_parameters/pm_gain_index.schema.yml +2 -2
  191. simtools/schemas/model_parameters/pm_photoelectron_spectrum.schema.yml +2 -2
  192. simtools/schemas/model_parameters/pm_transit_time.schema.yml +2 -2
  193. simtools/schemas/model_parameters/pm_voltage_variation.schema.yml +2 -2
  194. simtools/schemas/model_parameters/primary_mirror_degraded_map.schema.yml +2 -2
  195. simtools/schemas/model_parameters/qe_variation.schema.yml +2 -2
  196. simtools/schemas/model_parameters/quantum_efficiency.schema.yml +2 -2
  197. simtools/schemas/model_parameters/random_focal_length.schema.yml +2 -2
  198. simtools/schemas/model_parameters/random_generator.schema.yml +2 -2
  199. simtools/schemas/model_parameters/random_mono_probability.schema.yml +2 -2
  200. simtools/schemas/model_parameters/sampled_output.schema.yml +2 -2
  201. simtools/schemas/model_parameters/save_pe_with_amplitude.schema.yml +2 -2
  202. simtools/schemas/model_parameters/store_photoelectrons.schema.yml +2 -2
  203. simtools/schemas/model_parameters/tailcut_scale.schema.yml +2 -2
  204. simtools/schemas/model_parameters/telescope_axis_height.schema.yml +2 -2
  205. simtools/schemas/model_parameters/telescope_random_angle.schema.yml +2 -2
  206. simtools/schemas/model_parameters/telescope_random_error.schema.yml +2 -2
  207. simtools/schemas/model_parameters/telescope_sphere_radius.schema.yml +2 -2
  208. simtools/schemas/model_parameters/telescope_transmission.schema.yml +2 -2
  209. simtools/schemas/model_parameters/teltrig_min_sigsum.schema.yml +2 -2
  210. simtools/schemas/model_parameters/teltrig_min_time.schema.yml +2 -2
  211. simtools/schemas/model_parameters/transit_time_calib_error.schema.yml +2 -2
  212. simtools/schemas/model_parameters/transit_time_compensate_error.schema.yml +2 -2
  213. simtools/schemas/model_parameters/transit_time_compensate_step.schema.yml +2 -2
  214. simtools/schemas/model_parameters/transit_time_error.schema.yml +2 -2
  215. simtools/schemas/model_parameters/transit_time_jitter.schema.yml +2 -2
  216. simtools/schemas/model_parameters/trigger_current_limit.schema.yml +2 -2
  217. simtools/schemas/model_parameters/trigger_delay_compensation.schema.yml +2 -2
  218. simtools/schemas/model_parameters/trigger_pixels.schema.yml +2 -2
  219. simtools/schemas/production_configuration_metrics.schema.yml +2 -2
  220. simtools/simtel/simtel_config_reader.py +21 -17
  221. simtools/simtel/simtel_config_writer.py +258 -66
  222. simtools/simtel/simtel_io_event_reader.py +301 -194
  223. simtools/simtel/simtel_io_event_writer.py +207 -227
  224. simtools/simtel/simtel_io_file_info.py +62 -0
  225. simtools/simtel/simtel_io_histogram.py +10 -14
  226. simtools/simtel/simtel_io_histograms.py +2 -2
  227. simtools/simtel/simtel_io_metadata.py +106 -0
  228. simtools/simtel/simulator_array.py +28 -14
  229. simtools/simtel/simulator_camera_efficiency.py +12 -6
  230. simtools/simtel/simulator_light_emission.py +85 -45
  231. simtools/simtel/simulator_ray_tracing.py +16 -6
  232. simtools/simulator.py +286 -89
  233. simtools/testing/configuration.py +5 -0
  234. simtools/testing/helpers.py +18 -0
  235. simtools/testing/sim_telarray_metadata.py +212 -0
  236. simtools/testing/validate_output.py +16 -6
  237. simtools/utils/general.py +18 -27
  238. simtools/utils/names.py +32 -10
  239. simtools/visualization/plot_array_layout.py +242 -0
  240. simtools/visualization/plot_pixels.py +681 -0
  241. simtools/visualization/visualize.py +5 -221
  242. simtools/applications/production_generate_simulation_config.py +0 -162
  243. simtools/applications/production_scale_events.py +0 -185
  244. simtools/layout/ctao_array_layouts.py +0 -172
  245. simtools/production_configuration/event_scaler.py +0 -120
  246. simtools/production_configuration/generate_simulation_config.py +0 -158
  247. {gammasimtools-0.15.0.dist-info → gammasimtools-0.17.0.dist-info}/licenses/LICENSE +0 -0
  248. {gammasimtools-0.15.0.dist-info → gammasimtools-0.17.0.dist-info}/top_level.txt +0 -0
simtools/simulator.py CHANGED
@@ -1,5 +1,6 @@
1
1
  """Simulator class for managing simulations of showers and array of telescopes."""
2
2
 
3
+ import gzip
3
4
  import logging
4
5
  import re
5
6
  import shutil
@@ -11,12 +12,14 @@ import numpy as np
11
12
 
12
13
  import simtools.utils.general as gen
13
14
  from simtools.corsika.corsika_config import CorsikaConfig
14
- from simtools.io_operations import io_handler
15
+ from simtools.io_operations import io_handler, io_table_handler
15
16
  from simtools.job_execution.job_manager import JobManager
16
17
  from simtools.model.array_model import ArrayModel
17
18
  from simtools.runners.corsika_runner import CorsikaRunner
18
19
  from simtools.runners.corsika_simtel_runner import CorsikaSimtelRunner
20
+ from simtools.simtel.simtel_io_event_writer import SimtelIOEventDataWriter
19
21
  from simtools.simtel.simulator_array import SimulatorArray
22
+ from simtools.testing.sim_telarray_metadata import assert_sim_telarray_metadata
20
23
 
21
24
  __all__ = [
22
25
  "InvalidRunsToSimulateError",
@@ -70,11 +73,16 @@ class Simulator:
70
73
  self.runs = self._initialize_run_list()
71
74
  self._results = defaultdict(list)
72
75
  self._test = self.args_dict.get("test", False)
73
- self.submit_engine = self.args_dict.get("submit_engine", "local")
74
- self._submit_options = self.args_dict.get("submit_options", None)
75
76
  self._extra_commands = extra_commands
76
77
 
77
- self.array_model = self._initialize_array_model(mongo_db_config)
78
+ self.sim_telarray_seeds = {
79
+ "seed": self.args_dict.get("sim_telarray_instrument_seeds"),
80
+ "random_instrument_instances": self.args_dict.get(
81
+ "sim_telarray_random_instrument_instances"
82
+ ),
83
+ "seed_file_name": "sim_telarray_instrument_seeds.txt", # name only; no directory
84
+ }
85
+ self.array_models = self._initialize_array_models(mongo_db_config)
78
86
  self._simulation_runner = self._initialize_simulation_runner(mongo_db_config)
79
87
 
80
88
  @property
@@ -89,8 +97,8 @@ class Simulator:
89
97
 
90
98
  Parameters
91
99
  ----------
92
- simulation_software: choices: [simtel, corsika, corsika_simtel]
93
- implemented are sim_telarray and CORSIKA or corsika_simtel
100
+ simulation_software: choices: [sim_telarray, corsika, corsika_sim_telarray]
101
+ implemented are sim_telarray and CORSIKA or corsika_sim_telarray
94
102
  (running CORSIKA and piping it directly to sim_telarray)
95
103
 
96
104
  Raises
@@ -98,14 +106,14 @@ class Simulator:
98
106
  gen.InvalidConfigDataError
99
107
 
100
108
  """
101
- if simulation_software not in ["simtel", "corsika", "corsika_simtel"]:
109
+ if simulation_software not in ["sim_telarray", "corsika", "corsika_sim_telarray"]:
102
110
  self._logger.error(f"Invalid simulation software: {simulation_software}")
103
111
  raise gen.InvalidConfigDataError
104
112
  self._simulation_software = simulation_software.lower()
105
113
 
106
- def _initialize_array_model(self, mongo_db_config):
114
+ def _initialize_array_models(self, mongo_db_config):
107
115
  """
108
- Initialize array simulation model.
116
+ Initialize array simulation models.
109
117
 
110
118
  Parameters
111
119
  ----------
@@ -114,20 +122,67 @@ class Simulator:
114
122
 
115
123
  Returns
116
124
  -------
117
- ArrayModel
118
- ArrayModel object.
119
- """
120
- return ArrayModel(
121
- label=self.label,
122
- site=self.args_dict.get("site"),
123
- layout_name=self.args_dict.get("array_layout_name"),
124
- mongo_db_config=mongo_db_config,
125
- model_version=self.args_dict.get("model_version", None),
126
- )
125
+ list
126
+ List of ArrayModel objects.
127
+ """
128
+ model_version = self.args_dict.get("model_version", [])
129
+ versions = [model_version] if not isinstance(model_version, list) else model_version
130
+
131
+ return [
132
+ ArrayModel(
133
+ label=self.label,
134
+ site=self.args_dict.get("site"),
135
+ layout_name=self.args_dict.get("array_layout_name"),
136
+ mongo_db_config=mongo_db_config,
137
+ model_version=version,
138
+ sim_telarray_seeds={
139
+ "seed": self._get_seed_for_random_instrument_instances(
140
+ self.sim_telarray_seeds["seed"], version
141
+ ),
142
+ "random_instrument_instances": self.sim_telarray_seeds[
143
+ "random_instrument_instances"
144
+ ],
145
+ "seed_file_name": self.sim_telarray_seeds["seed_file_name"],
146
+ },
147
+ simtel_path=self.args_dict.get("simtel_path", None),
148
+ )
149
+ for version in versions
150
+ ]
151
+
152
+ def _get_seed_for_random_instrument_instances(self, seed, model_version):
153
+ """
154
+ Generate seed for random instances of the instrument.
155
+
156
+ Parameters
157
+ ----------
158
+ seed : str
159
+ Seed string given through configuration.
160
+ model_version: str
161
+ Model version.
162
+
163
+ Returns
164
+ -------
165
+ int
166
+ Seed for random instances of the instrument.
167
+ """
168
+ if seed:
169
+ return int(seed.split(",")[0].strip())
170
+
171
+ def semver_to_int(version: str):
172
+ major, minor, patch = map(int, version.split("."))
173
+ return major * 10000 + minor * 100 + patch
174
+
175
+ seed = semver_to_int(model_version) * 10000000
176
+ seed = seed + 1000000 if self.args_dict.get("site") != "North" else seed + 2000000
177
+ seed = seed + (int)(self.args_dict["zenith_angle"].value) * 1000
178
+ return seed + (int)(self.args_dict["azimuth_angle"].value)
127
179
 
128
180
  def _initialize_run_list(self):
129
181
  """
130
- Initialize run list using the configuration values 'run_number_start' and 'number_of_runs'.
182
+ Initialize run list using the configuration values.
183
+
184
+ Uses 'run_number', 'run_number_offset' and 'number_of_runs' arguments
185
+ to create a list of run numbers.
131
186
 
132
187
  Returns
133
188
  -------
@@ -137,19 +192,27 @@ class Simulator:
137
192
  Raises
138
193
  ------
139
194
  KeyError
140
- If 'run_number_start' or 'number_of_runs' are not found in the configuration.
195
+ If 'run_number', 'run_number_offset' and 'number_of_runs' are
196
+ not found in the configuration.
141
197
  """
142
198
  try:
199
+ offset_run_number = self.args_dict["run_number_offset"] + self.args_dict["run_number"]
200
+ if self.args_dict["number_of_runs"] <= 1:
201
+ return self._validate_run_list_and_range(
202
+ run_list=offset_run_number,
203
+ run_range=None,
204
+ )
143
205
  return self._validate_run_list_and_range(
144
206
  run_list=None,
145
207
  run_range=[
146
- self.args_dict["run_number_start"],
147
- self.args_dict["run_number_start"] + self.args_dict["number_of_runs"],
208
+ offset_run_number,
209
+ offset_run_number + self.args_dict["number_of_runs"],
148
210
  ],
149
211
  )
150
212
  except KeyError as exc:
151
213
  self._logger.error(
152
- "Error in initializing run list (missing 'run_number_start' or 'number_of_runs')"
214
+ "Error in initializing run list "
215
+ "(missing 'run_number', 'run_number_offset' or 'number_of_runs')."
153
216
  )
154
217
  raise exc
155
218
 
@@ -214,22 +277,33 @@ class Simulator:
214
277
  CorsikaRunner or SimulatorArray or CorsikaSimtelRunner
215
278
  Simulation runner object.
216
279
  """
217
- corsika_config = CorsikaConfig(
218
- array_model=self.array_model,
219
- label=self.label,
220
- args_dict=self.args_dict,
221
- db_config=db_config,
222
- )
280
+ corsika_configurations = []
281
+ for array_model in self.array_models:
282
+ corsika_configurations.append(
283
+ CorsikaConfig(
284
+ array_model=array_model,
285
+ label=self.label,
286
+ args_dict=self.args_dict,
287
+ db_config=db_config,
288
+ )
289
+ )
223
290
 
224
291
  runner_class = {
225
292
  "corsika": CorsikaRunner,
226
- "simtel": SimulatorArray,
227
- "corsika_simtel": CorsikaSimtelRunner,
293
+ "sim_telarray": SimulatorArray,
294
+ "corsika_sim_telarray": CorsikaSimtelRunner,
228
295
  }.get(self.simulation_software)
229
296
 
297
+ # In case of CorsikaSimtelRunner we should pass all corsika_configurations
298
+ # to the runner, since we define multiple configurations to run in a single job
299
+ # using multipipe. In other cases we pass only the first one (there's only one anyway).
230
300
  runner_args = {
231
301
  "label": self.label,
232
- "corsika_config": corsika_config,
302
+ "corsika_config": (
303
+ corsika_configurations
304
+ if runner_class is CorsikaSimtelRunner
305
+ else corsika_configurations[0]
306
+ ),
233
307
  "simtel_path": self.args_dict.get("simtel_path"),
234
308
  "use_multipipe": runner_class is CorsikaSimtelRunner,
235
309
  }
@@ -237,7 +311,9 @@ class Simulator:
237
311
  if runner_class is not SimulatorArray:
238
312
  runner_args["keep_seeds"] = self.args_dict.get("corsika_test_seeds", False)
239
313
  if runner_class is not CorsikaRunner:
240
- runner_args["sim_telarray_seeds"] = self.args_dict.get("sim_telarray_seeds")
314
+ runner_args["sim_telarray_seeds"] = self.sim_telarray_seeds
315
+ if runner_class is CorsikaSimtelRunner:
316
+ runner_args["sequential"] = self.args_dict.get("sequential", False)
241
317
 
242
318
  return runner_class(**runner_args)
243
319
 
@@ -267,8 +343,6 @@ class Simulator:
267
343
  input_file_list: str or list of str
268
344
  Single file or list of files of shower simulations.
269
345
  """
270
- self._logger.info(f"Submission command: {self.submit_engine}")
271
-
272
346
  runs_and_files_to_submit = self._get_runs_and_files_to_submit(
273
347
  input_file_list=input_file_list
274
348
  )
@@ -282,11 +356,7 @@ class Simulator:
282
356
  run_number=run_number, input_file=input_file, extra_commands=self._extra_commands
283
357
  )
284
358
 
285
- job_manager = JobManager(
286
- submit_engine=self.submit_engine,
287
- submit_options=self._submit_options,
288
- test=self._test,
289
- )
359
+ job_manager = JobManager(test=self._test)
290
360
  job_manager.submit(
291
361
  run_script=run_script,
292
362
  run_out_file=self._simulation_runner.get_file_name(
@@ -325,10 +395,10 @@ class Simulator:
325
395
  _runs_and_files = {}
326
396
  self._logger.debug(f"Getting runs and files to submit ({input_file_list})")
327
397
 
328
- if self.simulation_software == "simtel":
398
+ if self.simulation_software == "sim_telarray":
329
399
  input_file_list = self._enforce_list_type(input_file_list)
330
400
  _runs_and_files = {self._guess_run_from_file(file): file for file in input_file_list}
331
- elif self.simulation_software in ["corsika", "corsika_simtel"]:
401
+ elif self.simulation_software in ["corsika", "corsika_sim_telarray"]:
332
402
  _runs_and_files = dict.fromkeys(self._get_runs_to_simulate())
333
403
  if len(_runs_and_files) == 0:
334
404
  raise ValueError("No runs to submit.")
@@ -391,46 +461,66 @@ class Simulator:
391
461
  run number
392
462
 
393
463
  """
394
- keys = ["output", "sub_out", "log", "input", "hist", "corsika_log"]
395
- defaults = dict.fromkeys(keys)
396
- results = {key: defaults[key] for key in keys}
397
- results["output"] = str(
398
- self._simulation_runner.get_file_name(file_type="output", run_number=run_number)
399
- )
400
- results["sub_out"] = str(
401
- self._simulation_runner.get_file_name(
402
- file_type="sub_log", mode="out", run_number=run_number
464
+ keys = ["simtel_output", "sub_out", "log", "input", "hist", "corsika_log", "event_data"]
465
+ results = {key: [] for key in keys}
466
+
467
+ def get_file_name(name, **kwargs):
468
+ return str(self._simulation_runner.get_file_name(file_type=name, **kwargs))
469
+
470
+ if "sim_telarray" in self.simulation_software:
471
+ results["input"].append(str(file))
472
+
473
+ results["sub_out"].append(get_file_name("sub_log", mode="out", run_number=run_number))
474
+
475
+ for i in range(len(self.array_models)):
476
+ results["simtel_output"].append(
477
+ get_file_name("simtel_output", run_number=run_number, model_version_index=i)
403
478
  )
404
- )
405
479
 
406
- if "simtel" in self.simulation_software:
407
- results["log"] = str(
408
- self._simulation_runner.get_file_name(
409
- file_type="log", simulation_software="simtel", run_number=run_number
480
+ if "sim_telarray" in self.simulation_software:
481
+ results["log"].append(
482
+ get_file_name(
483
+ "log",
484
+ simulation_software="sim_telarray",
485
+ run_number=run_number,
486
+ model_version_index=i,
487
+ )
410
488
  )
411
- )
412
- results["input"] = str(file)
413
- results["hist"] = str(
414
- self._simulation_runner.get_file_name(
415
- file_type="histogram", simulation_software="simtel", run_number=run_number
489
+ results["hist"].append(
490
+ get_file_name(
491
+ "histogram",
492
+ simulation_software="sim_telarray",
493
+ run_number=run_number,
494
+ model_version_index=i,
495
+ )
496
+ )
497
+ results["event_data"].append(
498
+ get_file_name(
499
+ "event_data",
500
+ simulation_software="sim_telarray",
501
+ run_number=run_number,
502
+ model_version_index=i,
503
+ )
416
504
  )
417
- )
418
505
 
419
- if "corsika" in self.simulation_software:
420
- results["corsika_log"] = str(
421
- self._simulation_runner.get_file_name(
422
- file_type="corsika_log", simulation_software="corsika", run_number=run_number
506
+ if "corsika" in self.simulation_software:
507
+ results["corsika_log"].append(
508
+ get_file_name(
509
+ "corsika_log",
510
+ simulation_software="corsika",
511
+ run_number=run_number,
512
+ model_version_index=i,
513
+ )
423
514
  )
424
- )
425
515
 
426
516
  for key in keys:
427
- self._results[key].append(results[key])
517
+ self._results[key].extend(results[key])
428
518
 
429
- def get_file_list(self, file_type="output"):
519
+ def get_file_list(self, file_type="simtel_output"):
430
520
  """
431
521
  Get list of files generated by simulations.
432
522
 
433
- Options are "input", "output", "hist", "log", "corsika_log".
523
+ Options are "input", "simtel_output", "hist", "log", "corsika_log".
434
524
  Not all file types are available for all simulation types.
435
525
  Returns an empty list for an unknown file type.
436
526
 
@@ -448,11 +538,11 @@ class Simulator:
448
538
  self._logger.info(f"Getting list of {file_type} files")
449
539
  return self._results[file_type]
450
540
 
451
- def print_list_of_files(self, file_type="output"):
541
+ def print_list_of_files(self, file_type="simtel_output"):
452
542
  """
453
543
  Print list of output files generated by simulations.
454
544
 
455
- Options are "input", "output", "hist", "log".
545
+ Options are "input", "simtel_output", "hist", "log".
456
546
 
457
547
  Parameters
458
548
  ----------
@@ -546,7 +636,7 @@ class Simulator:
546
636
 
547
637
  def save_file_lists(self):
548
638
  """Save files lists for output and log files."""
549
- for file_type in ["output", "log", "corsika_log", "hist"]:
639
+ for file_type in ["simtel_output", "log", "corsika_log", "hist"]:
550
640
  file_name = self.io_handler.get_output_directory(label=self.label).joinpath(
551
641
  f"{file_type}_files.txt"
552
642
  )
@@ -559,10 +649,35 @@ class Simulator:
559
649
  else:
560
650
  self._logger.debug(f"No files to save for {file_type} files.")
561
651
 
652
+ def save_reduced_event_lists(self):
653
+ """
654
+ Save reduced event lists with event data on simulated and triggered events.
655
+
656
+ The files are saved with the same name as the sim_telarray output file
657
+ but with a 'hdf5' extension.
658
+ """
659
+ if "sim_telarray" not in self.simulation_software:
660
+ self._logger.warning(
661
+ "Reduced event lists can only be saved for sim_telarray simulations."
662
+ )
663
+ return
664
+
665
+ input_files = self.get_file_list(file_type="simtel_output")
666
+ output_files = self.get_file_list(file_type="event_data")
667
+ for input_file, output_file in zip(input_files, output_files):
668
+ generator = SimtelIOEventDataWriter([input_file])
669
+ io_table_handler.write_tables(
670
+ tables=generator.process_files(),
671
+ output_file=Path(output_file),
672
+ overwrite_existing=True,
673
+ )
674
+
562
675
  def pack_for_register(self, directory_for_grid_upload=None):
563
676
  """
564
677
  Pack simulation output files for registering on the grid.
565
678
 
679
+ Creates separate tarballs for each model version's log files.
680
+
566
681
  Parameters
567
682
  ----------
568
683
  directory_for_grid_upload: str
@@ -572,11 +687,16 @@ class Simulator:
572
687
  self._logger.info(
573
688
  f"Packing the output files for registering on the grid ({directory_for_grid_upload})"
574
689
  )
575
- output_files = self.get_file_list(file_type="output")
690
+ output_files = self.get_file_list(file_type="simtel_output")
576
691
  log_files = self.get_file_list(file_type="log")
577
692
  corsika_log_files = self.get_file_list(file_type="corsika_log")
578
693
  histogram_files = self.get_file_list(file_type="hist")
579
- tar_file_name = Path(log_files[0]).name.replace("log.gz", "log_hist.tar.gz")
694
+ reduced_event_files = (
695
+ self.get_file_list(file_type="event_data")
696
+ if self.args_dict.get("save_reduced_event_lists")
697
+ else []
698
+ )
699
+
580
700
  directory_for_grid_upload = (
581
701
  Path(directory_for_grid_upload)
582
702
  if directory_for_grid_upload
@@ -586,24 +706,101 @@ class Simulator:
586
706
  )
587
707
  directory_for_grid_upload.mkdir(parents=True, exist_ok=True)
588
708
 
589
- tar_file_name = directory_for_grid_upload.joinpath(tar_file_name)
709
+ # If there are more than one model version,
710
+ # duplicate the corsika log file to have one for each model version with the "right name".
711
+ if len(self.array_models) > 1 and corsika_log_files:
712
+ self._copy_corsika_log_file_for_all_versions(corsika_log_files)
590
713
 
591
- with tarfile.open(tar_file_name, "w:gz") as tar:
592
- files_to_tar = (
593
- (log_files[:1] if log_files else [])
594
- + (histogram_files[:1] if histogram_files else [])
595
- + (corsika_log_files[:1] if corsika_log_files else [])
596
- )
597
- for file_to_tar in files_to_tar:
598
- tar.add(file_to_tar, arcname=Path(file_to_tar).name)
714
+ # Group files by model version
715
+ for model in self.array_models:
716
+ model_version = model.model_version
717
+
718
+ # Filter files for this model version
719
+ model_logs = [f for f in log_files if model_version in f]
720
+ model_hists = [f for f in histogram_files if model_version in f]
721
+ model_corsika_logs = [f for f in corsika_log_files if model_version in f]
722
+
723
+ if model_logs:
724
+ tar_file_name = Path(model_logs[0]).name.replace("log.gz", "log_hist.tar.gz")
725
+ tar_file_path = directory_for_grid_upload.joinpath(tar_file_name)
599
726
 
600
- for file_to_move in [*output_files]:
727
+ with tarfile.open(tar_file_path, "w:gz") as tar:
728
+ # Add all relevant log, histogram, and CORSIKA log files to the tarball
729
+ files_to_tar = model_logs + model_hists + model_corsika_logs
730
+ for file_to_tar in files_to_tar:
731
+ tar.add(file_to_tar, arcname=Path(file_to_tar).name)
732
+
733
+ for file_to_move in output_files + reduced_event_files:
601
734
  source_file = Path(file_to_move)
602
735
  destination_file = directory_for_grid_upload / source_file.name
603
736
  if destination_file.exists():
604
737
  self._logger.warning(f"Overwriting existing file: {destination_file}")
605
- # Note that this will overwrite previous files which exist in the directory
606
- # It should be fine for normal production since each run is on a separate node
607
- # so no files are expected there.
608
738
  shutil.move(source_file, destination_file)
739
+
609
740
  self._logger.info(f"Output files for the grid placed in {directory_for_grid_upload!s}")
741
+
742
+ def validate_metadata(self):
743
+ """Validate metadata in the sim_telarray output files."""
744
+ if "sim_telarray" not in self.simulation_software:
745
+ self._logger.info("No sim_telarray files to validate.")
746
+ return
747
+
748
+ for model in self.array_models:
749
+ files = self.get_file_list(file_type="simtel_output")
750
+ output_file = next((f for f in files if model.model_version in f), None)
751
+ if output_file:
752
+ self._logger.info(f"Validating metadata for {output_file}")
753
+ assert_sim_telarray_metadata(output_file, model)
754
+ self._logger.info(f"Metadata for sim_telarray file {output_file} is valid.")
755
+ else:
756
+ self._logger.warning(
757
+ f"No sim_telarray file found for model version {model.model_version}: {files}"
758
+ )
759
+
760
+ def _copy_corsika_log_file_for_all_versions(self, corsika_log_files):
761
+ """
762
+ Create copies of the CORSIKA log file for each model version.
763
+
764
+ Adds a header comment to each copy explaining its relationship to the original.
765
+
766
+ Parameters
767
+ ----------
768
+ corsika_log_files: list
769
+ List containing the original CORSIKA log file path.
770
+ """
771
+ original_log = Path(corsika_log_files[0])
772
+ # Find which model version the original log belongs to
773
+ original_version = next(
774
+ model.model_version
775
+ for model in self.array_models
776
+ if re.search(
777
+ rf"(?<![0-9A-Za-z]){re.escape(model.model_version)}(?![0-9A-Za-z])",
778
+ original_log.name,
779
+ )
780
+ )
781
+
782
+ for model in self.array_models:
783
+ if model.model_version == original_version:
784
+ continue
785
+
786
+ new_log = original_log.parent / original_log.name.replace(
787
+ original_version, model.model_version
788
+ )
789
+
790
+ with gzip.open(new_log, "wt", encoding="utf-8") as new_file:
791
+ # Write the header to the new file
792
+ header = (
793
+ f"###############################################################\n"
794
+ f"Copy of CORSIKA log file from model version {original_version}.\n"
795
+ f"Applicable also for {model.model_version} (same CORSIKA configuration,\n"
796
+ f"different sim_telarray model versions in the same run).\n"
797
+ f"###############################################################\n\n"
798
+ )
799
+ new_file.write(header)
800
+
801
+ # Copy the content of the original log file, ignoring invalid characters
802
+ with gzip.open(original_log, "rt", encoding="utf-8", errors="ignore") as orig_file:
803
+ for line in orig_file:
804
+ new_file.write(line)
805
+
806
+ corsika_log_files.append(str(new_log))
@@ -102,6 +102,11 @@ def configure(config, tmp_test_directory, request):
102
102
  """
103
103
  tmp_output_path = create_tmp_output_path(tmp_test_directory, config)
104
104
  model_version_requested = request.config.getoption("--model_version")
105
+ model_version_requested = (
106
+ model_version_requested.split(",") if model_version_requested else None
107
+ )
108
+ if isinstance(model_version_requested, list) and len(model_version_requested) == 1:
109
+ model_version_requested = model_version_requested[0]
105
110
 
106
111
  if "CONFIGURATION" in config:
107
112
  _skip_test_for_model_version(config, model_version_requested)
@@ -33,3 +33,21 @@ def _new_testeff_version():
33
33
  return False
34
34
  except FileNotFoundError as exc:
35
35
  raise FileNotFoundError("The testeff executable could not be found.") from exc
36
+
37
+
38
+ def skip_multiple_version_test(config, model_version):
39
+ """Skip a test which is not meant for multiple versions if multiple versions are given."""
40
+ message = "Skipping test not meant for multiple model versions."
41
+
42
+ if not isinstance(model_version, list):
43
+ return None
44
+
45
+ config_model_version = config.get("CONFIGURATION", {}).get("MODEL_VERSION", [])
46
+
47
+ if not isinstance(config_model_version, list):
48
+ config_model_version = [config_model_version]
49
+
50
+ if 1 < len(model_version) != len(config_model_version):
51
+ return message
52
+
53
+ return None