gammasimtools 0.15.0__py3-none-any.whl → 0.16.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 (223) hide show
  1. {gammasimtools-0.15.0.dist-info → gammasimtools-0.16.0.dist-info}/METADATA +2 -32
  2. {gammasimtools-0.15.0.dist-info → gammasimtools-0.16.0.dist-info}/RECORD +222 -214
  3. {gammasimtools-0.15.0.dist-info → gammasimtools-0.16.0.dist-info}/WHEEL +1 -1
  4. {gammasimtools-0.15.0.dist-info → gammasimtools-0.16.0.dist-info}/entry_points.txt +5 -2
  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_psf_parameters.py +12 -9
  10. simtools/applications/docs_produce_array_element_report.py +3 -3
  11. simtools/applications/docs_produce_calibration_reports.py +49 -0
  12. simtools/applications/docs_produce_simulation_configuration_report.py +50 -0
  13. simtools/applications/{generate_simtel_array_histograms.py → generate_sim_telarray_histograms.py} +2 -2
  14. simtools/applications/production_derive_corsika_limits.py +63 -10
  15. simtools/applications/production_derive_statistics.py +125 -0
  16. simtools/applications/production_generate_grid.py +197 -0
  17. simtools/applications/production_generate_simulation_config.py +0 -10
  18. simtools/applications/simulate_light_emission.py +5 -13
  19. simtools/applications/simulate_prod.py +16 -4
  20. simtools/applications/validate_cumulative_psf.py +6 -4
  21. simtools/applications/validate_file_using_schema.py +7 -3
  22. simtools/applications/validate_optics.py +5 -4
  23. simtools/camera/camera_efficiency.py +14 -39
  24. simtools/configuration/commandline_parser.py +4 -3
  25. simtools/configuration/configurator.py +10 -0
  26. simtools/corsika/corsika_config.py +103 -5
  27. simtools/data_model/format_checkers.py +9 -0
  28. simtools/data_model/model_data_writer.py +3 -0
  29. simtools/data_model/schema.py +27 -16
  30. simtools/data_model/validate_data.py +27 -7
  31. simtools/db/db_handler.py +10 -4
  32. simtools/layout/array_layout.py +1 -0
  33. simtools/model/array_model.py +63 -29
  34. simtools/model/model_parameter.py +76 -51
  35. simtools/model/model_utils.py +43 -1
  36. simtools/model/site_model.py +3 -2
  37. simtools/model/telescope_model.py +4 -4
  38. simtools/production_configuration/calculate_statistical_errors_grid_point.py +0 -4
  39. simtools/production_configuration/{event_scaler.py → derive_production_statistics.py} +24 -20
  40. simtools/production_configuration/derive_production_statistics_handler.py +119 -0
  41. simtools/production_configuration/generate_production_grid.py +364 -0
  42. simtools/production_configuration/generate_simulation_config.py +9 -9
  43. simtools/production_configuration/interpolation_handler.py +16 -11
  44. simtools/ray_tracing/mirror_panel_psf.py +16 -20
  45. simtools/ray_tracing/psf_analysis.py +2 -2
  46. simtools/ray_tracing/ray_tracing.py +5 -1
  47. simtools/reporting/docs_read_parameters.py +361 -58
  48. simtools/runners/corsika_runner.py +11 -1
  49. simtools/runners/corsika_simtel_runner.py +80 -89
  50. simtools/runners/runner_services.py +17 -4
  51. simtools/runners/simtel_runner.py +27 -10
  52. simtools/schemas/model_parameter.metaschema.yml +4 -0
  53. simtools/schemas/model_parameter_and_data_schema.metaschema.yml +1 -0
  54. simtools/schemas/model_parameters/adjust_gain.schema.yml +2 -2
  55. simtools/schemas/model_parameters/array_element_position_ground.schema.yml +2 -2
  56. simtools/schemas/model_parameters/array_element_position_utm.schema.yml +2 -2
  57. simtools/schemas/model_parameters/array_window.schema.yml +2 -2
  58. simtools/schemas/model_parameters/asum_offset.schema.yml +2 -2
  59. simtools/schemas/model_parameters/asum_shaping.schema.yml +2 -2
  60. simtools/schemas/model_parameters/asum_threshold.schema.yml +2 -2
  61. simtools/schemas/model_parameters/axes_offsets.schema.yml +2 -2
  62. simtools/schemas/model_parameters/camera_body_diameter.schema.yml +2 -2
  63. simtools/schemas/model_parameters/camera_body_shape.schema.yml +2 -2
  64. simtools/schemas/model_parameters/camera_config_file.schema.yml +2 -2
  65. simtools/schemas/model_parameters/camera_config_rotate.schema.yml +2 -2
  66. simtools/schemas/model_parameters/camera_degraded_efficiency.schema.yml +2 -2
  67. simtools/schemas/model_parameters/camera_degraded_map.schema.yml +2 -2
  68. simtools/schemas/model_parameters/camera_depth.schema.yml +2 -2
  69. simtools/schemas/model_parameters/camera_filter.schema.yml +2 -2
  70. simtools/schemas/model_parameters/camera_pixels.schema.yml +2 -2
  71. simtools/schemas/model_parameters/camera_transmission.schema.yml +2 -2
  72. simtools/schemas/model_parameters/channels_per_chip.schema.yml +2 -2
  73. simtools/schemas/model_parameters/correct_nsb_spectrum_to_telescope_altitude.schema.yml +2 -2
  74. simtools/schemas/model_parameters/corsika_starting_grammage.schema.yml +90 -1
  75. simtools/schemas/model_parameters/default_trigger.schema.yml +2 -2
  76. simtools/schemas/model_parameters/design_model.schema.yml +2 -2
  77. simtools/schemas/model_parameters/disc_ac_coupled.schema.yml +2 -2
  78. simtools/schemas/model_parameters/disc_bins.schema.yml +2 -2
  79. simtools/schemas/model_parameters/disc_start.schema.yml +2 -2
  80. simtools/schemas/model_parameters/discriminator_amplitude.schema.yml +2 -2
  81. simtools/schemas/model_parameters/discriminator_fall_time.schema.yml +2 -2
  82. simtools/schemas/model_parameters/discriminator_gate_length.schema.yml +2 -2
  83. simtools/schemas/model_parameters/discriminator_hysteresis.schema.yml +2 -2
  84. simtools/schemas/model_parameters/discriminator_output_amplitude.schema.yml +2 -2
  85. simtools/schemas/model_parameters/discriminator_output_var_percent.schema.yml +2 -2
  86. simtools/schemas/model_parameters/discriminator_pulse_shape.schema.yml +2 -2
  87. simtools/schemas/model_parameters/discriminator_rise_time.schema.yml +2 -2
  88. simtools/schemas/model_parameters/discriminator_scale_threshold.schema.yml +2 -2
  89. simtools/schemas/model_parameters/discriminator_sigsum_over_threshold.schema.yml +2 -2
  90. simtools/schemas/model_parameters/discriminator_threshold.schema.yml +2 -2
  91. simtools/schemas/model_parameters/discriminator_time_over_threshold.schema.yml +2 -2
  92. simtools/schemas/model_parameters/discriminator_var_gate_length.schema.yml +2 -2
  93. simtools/schemas/model_parameters/discriminator_var_sigsum_over_threshold.schema.yml +2 -2
  94. simtools/schemas/model_parameters/discriminator_var_threshold.schema.yml +2 -2
  95. simtools/schemas/model_parameters/discriminator_var_time_over_threshold.schema.yml +2 -2
  96. simtools/schemas/model_parameters/dish_shape_length.schema.yml +2 -2
  97. simtools/schemas/model_parameters/dsum_clipping.schema.yml +2 -2
  98. simtools/schemas/model_parameters/dsum_ignore_below.schema.yml +2 -2
  99. simtools/schemas/model_parameters/dsum_offset.schema.yml +2 -2
  100. simtools/schemas/model_parameters/dsum_pedsub.schema.yml +2 -2
  101. simtools/schemas/model_parameters/dsum_pre_clipping.schema.yml +2 -2
  102. simtools/schemas/model_parameters/dsum_prescale.schema.yml +2 -2
  103. simtools/schemas/model_parameters/dsum_presum_max.schema.yml +2 -2
  104. simtools/schemas/model_parameters/dsum_presum_shift.schema.yml +2 -2
  105. simtools/schemas/model_parameters/dsum_shaping.schema.yml +2 -2
  106. simtools/schemas/model_parameters/dsum_shaping_renormalize.schema.yml +2 -2
  107. simtools/schemas/model_parameters/dsum_threshold.schema.yml +3 -3
  108. simtools/schemas/model_parameters/dsum_zero_clip.schema.yml +2 -2
  109. simtools/schemas/model_parameters/effective_focal_length.schema.yml +2 -2
  110. simtools/schemas/model_parameters/fadc_ac_coupled.schema.yml +2 -2
  111. simtools/schemas/model_parameters/fadc_amplitude.schema.yml +2 -2
  112. simtools/schemas/model_parameters/fadc_bins.schema.yml +2 -2
  113. simtools/schemas/model_parameters/fadc_compensate_pedestal.schema.yml +2 -2
  114. simtools/schemas/model_parameters/fadc_err_compensate_pedestal.schema.yml +2 -2
  115. simtools/schemas/model_parameters/fadc_err_pedestal.schema.yml +2 -2
  116. simtools/schemas/model_parameters/fadc_lg_amplitude.schema.yml +2 -2
  117. simtools/schemas/model_parameters/fadc_lg_compensate_pedestal.schema.yml +2 -2
  118. simtools/schemas/model_parameters/fadc_lg_err_compensate_pedestal.schema.yml +2 -2
  119. simtools/schemas/model_parameters/fadc_lg_err_pedestal.schema.yml +2 -2
  120. simtools/schemas/model_parameters/fadc_lg_max_signal.schema.yml +2 -2
  121. simtools/schemas/model_parameters/fadc_lg_noise.schema.yml +2 -2
  122. simtools/schemas/model_parameters/fadc_lg_pedestal.schema.yml +2 -2
  123. simtools/schemas/model_parameters/fadc_lg_sensitivity.schema.yml +2 -2
  124. simtools/schemas/model_parameters/fadc_lg_sysvar_pedestal.schema.yml +2 -2
  125. simtools/schemas/model_parameters/fadc_lg_var_pedestal.schema.yml +2 -2
  126. simtools/schemas/model_parameters/fadc_lg_var_sensitivity.schema.yml +2 -2
  127. simtools/schemas/model_parameters/fadc_max_signal.schema.yml +2 -2
  128. simtools/schemas/model_parameters/fadc_mhz.schema.yml +2 -2
  129. simtools/schemas/model_parameters/fadc_noise.schema.yml +2 -2
  130. simtools/schemas/model_parameters/fadc_pedestal.schema.yml +2 -2
  131. simtools/schemas/model_parameters/fadc_pulse_shape.schema.yml +2 -2
  132. simtools/schemas/model_parameters/fadc_sensitivity.schema.yml +2 -2
  133. simtools/schemas/model_parameters/fadc_sum_bins.schema.yml +2 -2
  134. simtools/schemas/model_parameters/fadc_sum_offset.schema.yml +2 -2
  135. simtools/schemas/model_parameters/fadc_sysvar_pedestal.schema.yml +2 -2
  136. simtools/schemas/model_parameters/fadc_var_pedestal.schema.yml +2 -2
  137. simtools/schemas/model_parameters/fadc_var_sensitivity.schema.yml +2 -2
  138. simtools/schemas/model_parameters/fake_mirror_list.schema.yml +1 -1
  139. simtools/schemas/model_parameters/flatfielding.schema.yml +2 -2
  140. simtools/schemas/model_parameters/focal_length.schema.yml +2 -2
  141. simtools/schemas/model_parameters/focus_offset.schema.yml +2 -2
  142. simtools/schemas/model_parameters/gain_variation.schema.yml +2 -2
  143. simtools/schemas/model_parameters/hg_lg_variation.schema.yml +2 -2
  144. simtools/schemas/model_parameters/iobuf_maximum.schema.yml +2 -2
  145. simtools/schemas/model_parameters/iobuf_output_maximum.schema.yml +2 -2
  146. simtools/schemas/model_parameters/laser_events.schema.yml +1 -1
  147. simtools/schemas/model_parameters/lightguide_efficiency_vs_incidence_angle.schema.yml +2 -2
  148. simtools/schemas/model_parameters/lightguide_efficiency_vs_wavelength.schema.yml +2 -2
  149. simtools/schemas/model_parameters/min_photoelectrons.schema.yml +2 -2
  150. simtools/schemas/model_parameters/min_photons.schema.yml +2 -2
  151. simtools/schemas/model_parameters/mirror_align_random_distance.schema.yml +2 -2
  152. simtools/schemas/model_parameters/mirror_align_random_horizontal.schema.yml +2 -2
  153. simtools/schemas/model_parameters/mirror_align_random_vertical.schema.yml +2 -2
  154. simtools/schemas/model_parameters/mirror_class.schema.yml +2 -2
  155. simtools/schemas/model_parameters/mirror_degraded_reflection.schema.yml +2 -2
  156. simtools/schemas/model_parameters/mirror_focal_length.schema.yml +2 -2
  157. simtools/schemas/model_parameters/mirror_list.schema.yml +2 -2
  158. simtools/schemas/model_parameters/mirror_offset.schema.yml +2 -2
  159. simtools/schemas/model_parameters/mirror_reflection_random_angle.schema.yml +2 -2
  160. simtools/schemas/model_parameters/mirror_reflectivity.schema.yml +2 -2
  161. simtools/schemas/model_parameters/multiplicity_offset.schema.yml +2 -2
  162. simtools/schemas/model_parameters/muon_mono_threshold.schema.yml +2 -2
  163. simtools/schemas/model_parameters/nsb_autoscale_airmass.schema.yml +2 -2
  164. simtools/schemas/model_parameters/nsb_offaxis.schema.yml +2 -2
  165. simtools/schemas/model_parameters/nsb_pixel_rate.schema.yml +2 -2
  166. simtools/schemas/model_parameters/num_gains.schema.yml +2 -2
  167. simtools/schemas/model_parameters/only_triggered_telescopes.schema.yml +2 -2
  168. simtools/schemas/model_parameters/optics_properties.schema.yml +2 -2
  169. simtools/schemas/model_parameters/pedestal_events.schema.yml +7 -3
  170. simtools/schemas/model_parameters/photon_delay.schema.yml +2 -2
  171. simtools/schemas/model_parameters/pixeltrg_time_step.schema.yml +2 -2
  172. simtools/schemas/model_parameters/pm_average_gain.schema.yml +2 -2
  173. simtools/schemas/model_parameters/pm_collection_efficiency.schema.yml +2 -2
  174. simtools/schemas/model_parameters/pm_gain_index.schema.yml +2 -2
  175. simtools/schemas/model_parameters/pm_photoelectron_spectrum.schema.yml +2 -2
  176. simtools/schemas/model_parameters/pm_transit_time.schema.yml +2 -2
  177. simtools/schemas/model_parameters/pm_voltage_variation.schema.yml +2 -2
  178. simtools/schemas/model_parameters/primary_mirror_degraded_map.schema.yml +2 -2
  179. simtools/schemas/model_parameters/qe_variation.schema.yml +2 -2
  180. simtools/schemas/model_parameters/quantum_efficiency.schema.yml +2 -2
  181. simtools/schemas/model_parameters/random_focal_length.schema.yml +2 -2
  182. simtools/schemas/model_parameters/random_generator.schema.yml +2 -2
  183. simtools/schemas/model_parameters/random_mono_probability.schema.yml +2 -2
  184. simtools/schemas/model_parameters/sampled_output.schema.yml +2 -2
  185. simtools/schemas/model_parameters/save_pe_with_amplitude.schema.yml +2 -2
  186. simtools/schemas/model_parameters/store_photoelectrons.schema.yml +2 -2
  187. simtools/schemas/model_parameters/tailcut_scale.schema.yml +2 -2
  188. simtools/schemas/model_parameters/telescope_axis_height.schema.yml +2 -2
  189. simtools/schemas/model_parameters/telescope_random_angle.schema.yml +2 -2
  190. simtools/schemas/model_parameters/telescope_random_error.schema.yml +2 -2
  191. simtools/schemas/model_parameters/telescope_sphere_radius.schema.yml +2 -2
  192. simtools/schemas/model_parameters/telescope_transmission.schema.yml +2 -2
  193. simtools/schemas/model_parameters/teltrig_min_sigsum.schema.yml +2 -2
  194. simtools/schemas/model_parameters/teltrig_min_time.schema.yml +2 -2
  195. simtools/schemas/model_parameters/transit_time_calib_error.schema.yml +2 -2
  196. simtools/schemas/model_parameters/transit_time_compensate_error.schema.yml +2 -2
  197. simtools/schemas/model_parameters/transit_time_compensate_step.schema.yml +2 -2
  198. simtools/schemas/model_parameters/transit_time_error.schema.yml +2 -2
  199. simtools/schemas/model_parameters/transit_time_jitter.schema.yml +2 -2
  200. simtools/schemas/model_parameters/trigger_current_limit.schema.yml +2 -2
  201. simtools/schemas/model_parameters/trigger_delay_compensation.schema.yml +2 -2
  202. simtools/schemas/model_parameters/trigger_pixels.schema.yml +2 -2
  203. simtools/simtel/simtel_config_reader.py +21 -17
  204. simtools/simtel/simtel_config_writer.py +237 -65
  205. simtools/simtel/simtel_io_file_info.py +57 -0
  206. simtools/simtel/simtel_io_histogram.py +10 -14
  207. simtools/simtel/simtel_io_histograms.py +2 -2
  208. simtools/simtel/simtel_io_metadata.py +91 -0
  209. simtools/simtel/simulator_array.py +26 -12
  210. simtools/simtel/simulator_camera_efficiency.py +12 -6
  211. simtools/simtel/simulator_light_emission.py +6 -11
  212. simtools/simtel/simulator_ray_tracing.py +14 -4
  213. simtools/simulator.py +230 -66
  214. simtools/testing/configuration.py +5 -0
  215. simtools/testing/helpers.py +18 -0
  216. simtools/testing/sim_telarray_metadata.py +212 -0
  217. simtools/testing/validate_output.py +12 -5
  218. simtools/utils/general.py +18 -27
  219. simtools/utils/names.py +27 -5
  220. simtools/visualization/visualize.py +2 -2
  221. simtools/applications/production_scale_events.py +0 -185
  222. {gammasimtools-0.15.0.dist-info → gammasimtools-0.16.0.dist-info}/licenses/LICENSE +0 -0
  223. {gammasimtools-0.15.0.dist-info → gammasimtools-0.16.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
@@ -17,6 +18,7 @@ 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
19
20
  from simtools.simtel.simulator_array import SimulatorArray
21
+ from simtools.testing.sim_telarray_metadata import assert_sim_telarray_metadata
20
22
 
21
23
  __all__ = [
22
24
  "InvalidRunsToSimulateError",
@@ -74,7 +76,14 @@ class Simulator:
74
76
  self._submit_options = self.args_dict.get("submit_options", None)
75
77
  self._extra_commands = extra_commands
76
78
 
77
- self.array_model = self._initialize_array_model(mongo_db_config)
79
+ self.sim_telarray_seeds = {
80
+ "seed": self.args_dict.get("sim_telarray_instrument_seeds"),
81
+ "random_instrument_instances": self.args_dict.get(
82
+ "sim_telarray_random_instrument_instances"
83
+ ),
84
+ "seed_file_name": "sim_telarray_instrument_seeds.txt", # name only; no directory
85
+ }
86
+ self.array_models = self._initialize_array_models(mongo_db_config)
78
87
  self._simulation_runner = self._initialize_simulation_runner(mongo_db_config)
79
88
 
80
89
  @property
@@ -89,8 +98,8 @@ class Simulator:
89
98
 
90
99
  Parameters
91
100
  ----------
92
- simulation_software: choices: [simtel, corsika, corsika_simtel]
93
- implemented are sim_telarray and CORSIKA or corsika_simtel
101
+ simulation_software: choices: [sim_telarray, corsika, corsika_sim_telarray]
102
+ implemented are sim_telarray and CORSIKA or corsika_sim_telarray
94
103
  (running CORSIKA and piping it directly to sim_telarray)
95
104
 
96
105
  Raises
@@ -98,14 +107,14 @@ class Simulator:
98
107
  gen.InvalidConfigDataError
99
108
 
100
109
  """
101
- if simulation_software not in ["simtel", "corsika", "corsika_simtel"]:
110
+ if simulation_software not in ["sim_telarray", "corsika", "corsika_sim_telarray"]:
102
111
  self._logger.error(f"Invalid simulation software: {simulation_software}")
103
112
  raise gen.InvalidConfigDataError
104
113
  self._simulation_software = simulation_software.lower()
105
114
 
106
- def _initialize_array_model(self, mongo_db_config):
115
+ def _initialize_array_models(self, mongo_db_config):
107
116
  """
108
- Initialize array simulation model.
117
+ Initialize array simulation models.
109
118
 
110
119
  Parameters
111
120
  ----------
@@ -114,16 +123,59 @@ class Simulator:
114
123
 
115
124
  Returns
116
125
  -------
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
- )
126
+ list
127
+ List of ArrayModel objects.
128
+ """
129
+ model_version = self.args_dict.get("model_version", [])
130
+ versions = [model_version] if not isinstance(model_version, list) else model_version
131
+
132
+ return [
133
+ ArrayModel(
134
+ label=self.label,
135
+ site=self.args_dict.get("site"),
136
+ layout_name=self.args_dict.get("array_layout_name"),
137
+ mongo_db_config=mongo_db_config,
138
+ model_version=version,
139
+ sim_telarray_seeds={
140
+ "seed": self._get_seed_for_random_instrument_instances(
141
+ self.sim_telarray_seeds["seed"], version
142
+ ),
143
+ "random_instrument_instances": self.sim_telarray_seeds[
144
+ "random_instrument_instances"
145
+ ],
146
+ "seed_file_name": self.sim_telarray_seeds["seed_file_name"],
147
+ },
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
  """
@@ -214,22 +266,33 @@ class Simulator:
214
266
  CorsikaRunner or SimulatorArray or CorsikaSimtelRunner
215
267
  Simulation runner object.
216
268
  """
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
- )
269
+ corsika_configurations = []
270
+ for array_model in self.array_models:
271
+ corsika_configurations.append(
272
+ CorsikaConfig(
273
+ array_model=array_model,
274
+ label=self.label,
275
+ args_dict=self.args_dict,
276
+ db_config=db_config,
277
+ )
278
+ )
223
279
 
224
280
  runner_class = {
225
281
  "corsika": CorsikaRunner,
226
- "simtel": SimulatorArray,
227
- "corsika_simtel": CorsikaSimtelRunner,
282
+ "sim_telarray": SimulatorArray,
283
+ "corsika_sim_telarray": CorsikaSimtelRunner,
228
284
  }.get(self.simulation_software)
229
285
 
286
+ # In case of CorsikaSimtelRunner we should pass all corsika_configurations
287
+ # to the runner, since we define multiple configurations to run in a single job
288
+ # using multipipe. In other cases we pass only the first one (there's only one anyway).
230
289
  runner_args = {
231
290
  "label": self.label,
232
- "corsika_config": corsika_config,
291
+ "corsika_config": (
292
+ corsika_configurations
293
+ if runner_class is CorsikaSimtelRunner
294
+ else corsika_configurations[0]
295
+ ),
233
296
  "simtel_path": self.args_dict.get("simtel_path"),
234
297
  "use_multipipe": runner_class is CorsikaSimtelRunner,
235
298
  }
@@ -237,7 +300,7 @@ class Simulator:
237
300
  if runner_class is not SimulatorArray:
238
301
  runner_args["keep_seeds"] = self.args_dict.get("corsika_test_seeds", False)
239
302
  if runner_class is not CorsikaRunner:
240
- runner_args["sim_telarray_seeds"] = self.args_dict.get("sim_telarray_seeds")
303
+ runner_args["sim_telarray_seeds"] = self.sim_telarray_seeds
241
304
 
242
305
  return runner_class(**runner_args)
243
306
 
@@ -325,10 +388,10 @@ class Simulator:
325
388
  _runs_and_files = {}
326
389
  self._logger.debug(f"Getting runs and files to submit ({input_file_list})")
327
390
 
328
- if self.simulation_software == "simtel":
391
+ if self.simulation_software == "sim_telarray":
329
392
  input_file_list = self._enforce_list_type(input_file_list)
330
393
  _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"]:
394
+ elif self.simulation_software in ["corsika", "corsika_sim_telarray"]:
332
395
  _runs_and_files = dict.fromkeys(self._get_runs_to_simulate())
333
396
  if len(_runs_and_files) == 0:
334
397
  raise ValueError("No runs to submit.")
@@ -392,39 +455,66 @@ class Simulator:
392
455
 
393
456
  """
394
457
  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
403
- )
404
- )
458
+ results = {key: [] for key in keys}
405
459
 
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
410
- )
411
- )
412
- results["input"] = str(file)
413
- results["hist"] = str(
460
+ if "sim_telarray" in self.simulation_software:
461
+ results["input"].append(str(file))
462
+
463
+ results["sub_out"].append(
464
+ str(
414
465
  self._simulation_runner.get_file_name(
415
- file_type="histogram", simulation_software="simtel", run_number=run_number
466
+ file_type="sub_log",
467
+ mode="out",
468
+ run_number=run_number,
416
469
  )
417
470
  )
471
+ )
418
472
 
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
473
+ for model_version_index, _ in enumerate(self.array_models):
474
+ results["output"].append(
475
+ str(
476
+ self._simulation_runner.get_file_name(
477
+ file_type="output",
478
+ run_number=run_number,
479
+ model_version_index=model_version_index,
480
+ )
423
481
  )
424
482
  )
483
+ if "sim_telarray" in self.simulation_software:
484
+ results["log"].append(
485
+ str(
486
+ self._simulation_runner.get_file_name(
487
+ file_type="log",
488
+ simulation_software="sim_telarray",
489
+ run_number=run_number,
490
+ model_version_index=model_version_index,
491
+ )
492
+ )
493
+ )
494
+ results["hist"].append(
495
+ str(
496
+ self._simulation_runner.get_file_name(
497
+ file_type="histogram",
498
+ simulation_software="sim_telarray",
499
+ run_number=run_number,
500
+ model_version_index=model_version_index,
501
+ )
502
+ )
503
+ )
504
+ if "corsika" in self.simulation_software:
505
+ results["corsika_log"].append(
506
+ str(
507
+ self._simulation_runner.get_file_name(
508
+ file_type="corsika_log",
509
+ simulation_software="corsika",
510
+ run_number=run_number,
511
+ model_version_index=model_version_index,
512
+ )
513
+ )
514
+ )
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
519
  def get_file_list(self, file_type="output"):
430
520
  """
@@ -563,6 +653,8 @@ class Simulator:
563
653
  """
564
654
  Pack simulation output files for registering on the grid.
565
655
 
656
+ Creates separate tarballs for each model version's log files.
657
+
566
658
  Parameters
567
659
  ----------
568
660
  directory_for_grid_upload: str
@@ -576,7 +668,7 @@ class Simulator:
576
668
  log_files = self.get_file_list(file_type="log")
577
669
  corsika_log_files = self.get_file_list(file_type="corsika_log")
578
670
  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")
671
+
580
672
  directory_for_grid_upload = (
581
673
  Path(directory_for_grid_upload)
582
674
  if directory_for_grid_upload
@@ -586,24 +678,96 @@ class Simulator:
586
678
  )
587
679
  directory_for_grid_upload.mkdir(parents=True, exist_ok=True)
588
680
 
589
- tar_file_name = directory_for_grid_upload.joinpath(tar_file_name)
681
+ # If there are more than one model version,
682
+ # duplicate the corsika log file to have one for each model version with the "right name".
683
+ if len(self.array_models) > 1 and corsika_log_files:
684
+ self._copy_corsika_log_file_for_all_versions(corsika_log_files)
590
685
 
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)
686
+ # Group files by model version
687
+ for model in self.array_models:
688
+ model_version = model.model_version
689
+
690
+ # Filter files for this model version
691
+ model_logs = [f for f in log_files if model_version in f]
692
+ model_hists = [f for f in histogram_files if model_version in f]
693
+ model_corsika_logs = [f for f in corsika_log_files if model_version in f]
599
694
 
600
- for file_to_move in [*output_files]:
695
+ if model_logs:
696
+ tar_file_name = Path(model_logs[0]).name.replace("log.gz", "log_hist.tar.gz")
697
+ tar_file_path = directory_for_grid_upload.joinpath(tar_file_name)
698
+
699
+ with tarfile.open(tar_file_path, "w:gz") as tar:
700
+ files_to_tar = model_logs + model_hists + model_corsika_logs
701
+ for file_to_tar in files_to_tar:
702
+ tar.add(file_to_tar, arcname=Path(file_to_tar).name)
703
+
704
+ for file_to_move in output_files:
601
705
  source_file = Path(file_to_move)
602
706
  destination_file = directory_for_grid_upload / source_file.name
603
707
  if destination_file.exists():
604
708
  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
709
  shutil.move(source_file, destination_file)
710
+
609
711
  self._logger.info(f"Output files for the grid placed in {directory_for_grid_upload!s}")
712
+
713
+ def validate_metadata(self):
714
+ """Validate metadata in the sim_telarray output files."""
715
+ if "sim_telarray" not in self.simulation_software:
716
+ self._logger.info("No sim_telarray files to validate.")
717
+ return
718
+
719
+ for model in self.array_models:
720
+ files = self.get_file_list(file_type="output")
721
+ output_file = next((f for f in files if model.model_version in f), None)
722
+ if output_file:
723
+ self._logger.info(f"Validating metadata for {output_file}")
724
+ assert_sim_telarray_metadata(output_file, model)
725
+ self._logger.info(f"Metadata for sim_telarray file {output_file} is valid.")
726
+ else:
727
+ self._logger.warning(
728
+ f"No sim_telarray file found for model version {model.model_version}: {files}"
729
+ )
730
+
731
+ def _copy_corsika_log_file_for_all_versions(self, corsika_log_files):
732
+ """
733
+ Create copies of the CORSIKA log file for each model version.
734
+
735
+ Adds a header comment to each copy explaining its relationship to the original.
736
+
737
+ Parameters
738
+ ----------
739
+ corsika_log_files: list
740
+ List containing the original CORSIKA log file path.
741
+
742
+ """
743
+ original_log = Path(corsika_log_files[0])
744
+ # Find which model version the original log belongs to
745
+ original_version = next(
746
+ model.model_version
747
+ for model in self.array_models
748
+ if re.search(
749
+ rf"(?<![0-9A-Za-z]){re.escape(model.model_version)}(?![0-9A-Za-z])",
750
+ original_log.name,
751
+ )
752
+ )
753
+
754
+ for model in self.array_models:
755
+ if model.model_version == original_version:
756
+ continue
757
+
758
+ new_log = original_log.parent / original_log.name.replace(
759
+ original_version, model.model_version
760
+ )
761
+
762
+ with gzip.open(new_log, "wt") as new_file:
763
+ new_file.write(
764
+ f"###############################################################\n"
765
+ f"Copy of CORSIKA log file from model version {original_version}.\n"
766
+ f"Applicable also for {model.model_version} (same CORSIKA configuration,\n"
767
+ f"different sim_telarray model versions in the same run).\n"
768
+ f"###############################################################\n\n"
769
+ )
770
+ with gzip.open(original_log, "rt") as orig_file:
771
+ shutil.copyfileobj(orig_file, new_file)
772
+
773
+ 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
@@ -0,0 +1,212 @@
1
+ """Test consistency of sim_telarray metadata."""
2
+
3
+ import logging
4
+
5
+ import numpy as np
6
+
7
+ from simtools.simtel.simtel_config_reader import SimtelConfigReader
8
+ from simtools.simtel.simtel_config_writer import sim_telarray_random_seeds
9
+ from simtools.simtel.simtel_io_file_info import get_corsika_run_number
10
+ from simtools.simtel.simtel_io_metadata import (
11
+ get_sim_telarray_telescope_id,
12
+ read_sim_telarray_metadata,
13
+ )
14
+
15
+ _logger = logging.getLogger(__name__)
16
+
17
+
18
+ def assert_sim_telarray_metadata(file, array_model):
19
+ """
20
+ Assert consistency of sim_telarray metadata with given array model.
21
+
22
+ Parameters
23
+ ----------
24
+ file: Path
25
+ Path to the sim_telarray file.
26
+ array_model: ArrayModel
27
+ Array model to compare with.
28
+ """
29
+ global_meta, telescope_meta = read_sim_telarray_metadata(file)
30
+ _logger.info(f"Found metadata in sim_telarray file for {len(telescope_meta)} telescopes")
31
+ site_parameter_mismatch = _assert_model_parameters(global_meta, array_model.site_model)
32
+ sim_telarray_seed_mismatch = _assert_sim_telarray_seed(
33
+ global_meta, array_model.sim_telarray_seeds, file
34
+ )
35
+ if sim_telarray_seed_mismatch:
36
+ site_parameter_mismatch.append(sim_telarray_seed_mismatch)
37
+
38
+ if len(telescope_meta) != len(array_model.telescope_model):
39
+ raise ValueError(
40
+ f"Number of telescopes in sim_telarray file ({len(telescope_meta)}) does not match "
41
+ f"number of telescopes in array model ({len(array_model.telescope_model)})"
42
+ )
43
+
44
+ for telescope_name in array_model.telescope_model.keys():
45
+ if not get_sim_telarray_telescope_id(telescope_name, file):
46
+ raise ValueError(f"Telescope {telescope_name} not found in sim_telarray file metadata")
47
+
48
+ telescope_parameter_mismatch = [
49
+ _assert_model_parameters(telescope_meta[i], model)
50
+ for i, model in enumerate(array_model.telescope_model.values(), start=1)
51
+ ]
52
+
53
+ # ensure printout of all mismatches, not only those found first
54
+ telescope_parameter_mismatch.append(site_parameter_mismatch)
55
+ if any(len(m) > 0 for m in telescope_parameter_mismatch):
56
+ mismatches = [m for m in telescope_parameter_mismatch if len(m) > 0]
57
+ raise ValueError(
58
+ f"Telescope or site model parameters do not match sim_telarray metadata: {mismatches}"
59
+ )
60
+
61
+
62
+ def _assert_model_parameters(metadata, model):
63
+ """
64
+ Assert that model parameter values matches the values in the sim_telarray metadata.
65
+
66
+ Parameters
67
+ ----------
68
+ metadata: dict
69
+ Metadata dictionary.
70
+ model: SiteModel, TelescopeModel
71
+ Model to compare with.
72
+
73
+ Returns
74
+ -------
75
+ invalid_parameter_list: list
76
+ List of parameters that do not match.
77
+
78
+ """
79
+ config_reader = SimtelConfigReader()
80
+
81
+ invalid_parameter_list = []
82
+
83
+ for param in model.parameters:
84
+ sim_telarray_name = _sim_telarray_name_from_parameter_name(param)
85
+ if sim_telarray_name in metadata.keys():
86
+ parameter_type = model.parameters[param]["type"]
87
+ if parameter_type not in ("string", "dict", "boolean"):
88
+ value, _ = config_reader.extract_value_from_sim_telarray_column(
89
+ [metadata[sim_telarray_name]], parameter_type
90
+ )
91
+ else:
92
+ value = metadata[sim_telarray_name]
93
+ value = (int)(value) if value.isnumeric() else value
94
+
95
+ if not is_equal(value, model.parameters[param]["value"], parameter_type):
96
+ invalid_parameter_list.append(
97
+ f"Parameter {param} mismatch between sim_telarray file: {value}, "
98
+ f"and model: {model.parameters[param]['value']}"
99
+ )
100
+
101
+ return invalid_parameter_list
102
+
103
+
104
+ def _assert_sim_telarray_seed(metadata, sim_telarray_seeds, file=None):
105
+ """
106
+ Assert that sim_telarray seed matches the values in the sim_telarray metadata.
107
+
108
+ Regenerate seeds using the sim_telarray_random_seeds function and compare with the metadata.
109
+
110
+ Parameters
111
+ ----------
112
+ metadata: dict
113
+ Metadata dictionary.
114
+ sim_telarray_seeds: dict
115
+ Dictionary of sim_telarray seeds.
116
+ file : Path
117
+ Path to the sim_telarray file.
118
+
119
+ Returns
120
+ -------
121
+ invalid_parameter_list: list
122
+ Error message if sim_telarray seeds do not match.
123
+
124
+ """
125
+ if not sim_telarray_seeds or not metadata:
126
+ return None
127
+
128
+ if "instrument_seed" in metadata.keys() and "instrument_instances" in metadata.keys():
129
+ if str(metadata.get("instrument_seed")) != str(sim_telarray_seeds.get("seed")):
130
+ return (
131
+ "Parameter instrument_seed mismatch between sim_telarray file: "
132
+ f"{metadata['instrument_seed']}, and model: {sim_telarray_seeds.get('seed')}"
133
+ )
134
+ _logger.info(
135
+ f"sim_telarray_seed in sim_telarray file: {metadata['instrument_seed']}, "
136
+ f"and model: {sim_telarray_seeds.get('seed')}"
137
+ )
138
+ if file:
139
+ run_number_modified = get_corsika_run_number(file) - 1
140
+ test_seeds = sim_telarray_random_seeds(
141
+ int(metadata["instrument_seed"]), int(metadata["instrument_instances"])
142
+ )
143
+ # no +1 as in sim_telarray (as we count from 0)
144
+ seed_used = run_number_modified % int(metadata["instrument_instances"])
145
+ if str(metadata.get("rng_select_seed")) != str(test_seeds[seed_used]):
146
+ return (
147
+ "Parameter rng_select_seed mismatch between sim_telarray file: "
148
+ f"{metadata['rng_select_seed']}, and model: {test_seeds[seed_used]}"
149
+ )
150
+
151
+ return None
152
+
153
+
154
+ def _sim_telarray_name_from_parameter_name(parameter_name):
155
+ """Return sim_telarray parameter name. Some specific fine tuning."""
156
+ # parameters like "reference_point_latitude"
157
+ sim_telarray_name = parameter_name.replace("reference_point_", "")
158
+
159
+ if sim_telarray_name == "altitude":
160
+ return "corsika_observation_level"
161
+ if sim_telarray_name == "array_triggers":
162
+ return None
163
+
164
+ return sim_telarray_name
165
+
166
+
167
+ def is_equal(value1, value2, value_type):
168
+ """
169
+ Check if two values are equal based on their type.
170
+
171
+ The complexity of this function reflects the complexity of the sim_telarray
172
+ metadata output.
173
+
174
+ Parameters
175
+ ----------
176
+ value1: any
177
+ First value to compare.
178
+ value2: any
179
+ Second value to compare.
180
+ value_type: str
181
+ Type of the values ('string', 'dict', etc.).
182
+
183
+ Returns
184
+ -------
185
+ bool
186
+ True if the values are equal, False otherwise.
187
+ """
188
+ value1 = value1[0] if isinstance(value1, tuple) else value1
189
+ value2 = value2[0] if isinstance(value2, tuple) else value2
190
+ if value1 is None or value2 is None:
191
+ if value1 in ("none", None) and value2 in ("none", None):
192
+ return True
193
+ if value_type == "string":
194
+ return str(value1).strip() == str(value2).strip()
195
+ if value_type == "dict":
196
+ return value1 == value2
197
+ if value_type == "boolean":
198
+ return bool(value1) == bool(value2)
199
+ return _is_equal_floats_or_ints(value1, value2)
200
+
201
+
202
+ def _is_equal_floats_or_ints(value1, value2):
203
+ """Check if floats and ints are equal."""
204
+ if isinstance(value1, np.ndarray | list) and isinstance(value2, np.ndarray | list):
205
+ return bool(np.allclose(np.array(value1), np.array(value2), rtol=1e-10))
206
+ if isinstance(value1, list) and isinstance(value2, float | int | np.integer | np.floating):
207
+ if all(x == value1[0] for x in value1):
208
+ return bool(np.isclose(float(value1[0]), float(value2), rtol=1e-10))
209
+ if isinstance(value2, list) and isinstance(value1, float | int | np.integer | np.floating):
210
+ if all(x == value2[0] for x in value2):
211
+ return bool(np.isclose(float(value1), float(value2[0]), rtol=1e-10))
212
+ return bool(np.isclose(float(value1), float(value2), rtol=1e-10))
@@ -64,11 +64,18 @@ def _validate_output_files(config, integration_test):
64
64
 
65
65
  def _test_simtel_cfg_files(config, integration_test, from_command_line, from_config_file):
66
66
  """Test simtel cfg files."""
67
- test_simtel_cfg_file = integration_test.get("TEST_SIMTEL_CFG_FILES", {}).get(
68
- from_command_line or from_config_file
69
- )
70
- if test_simtel_cfg_file:
71
- _validate_simtel_cfg_files(config, test_simtel_cfg_file)
67
+ cfg_files = integration_test.get("TEST_SIMTEL_CFG_FILES", {})
68
+ if isinstance(from_command_line, list):
69
+ sources = from_command_line
70
+ elif isinstance(from_config_file, list):
71
+ sources = from_config_file
72
+ else:
73
+ sources = [from_command_line or from_config_file]
74
+ for version in sources:
75
+ cfg = cfg_files.get(version)
76
+ if cfg:
77
+ _validate_simtel_cfg_files(config, cfg)
78
+ break
72
79
 
73
80
 
74
81
  def _validate_reference_output_file(config, integration_test):