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
@@ -124,9 +124,15 @@ class CorsikaConfig:
124
124
  if db_config is None: # all following parameter require DB
125
125
  return config
126
126
 
127
- db_model_parameters = ModelParameter(
128
- mongo_db_config=db_config, model_version=args_dict["model_version"]
129
- )
127
+ # If the user provided multiple model versions, we take the first one
128
+ # because for CORSIKA config we need only one and it doesn't matter which
129
+ model_versions = args_dict.get("model_version", None)
130
+ if not isinstance(model_versions, list):
131
+ model_versions = [model_versions]
132
+ self.assert_corsika_configurations_match(model_versions, db_config=db_config)
133
+ model_version = model_versions[0]
134
+ self._logger.debug(f"Using model version {model_version} for CORSIKA parameters")
135
+ db_model_parameters = ModelParameter(mongo_db_config=db_config, model_version=model_version)
130
136
  parameters_from_db = db_model_parameters.get_simulation_software_parameters("corsika")
131
137
 
132
138
  config["INTERACTION_FLAGS"] = self._corsika_configuration_interaction_flags(
@@ -140,6 +146,60 @@ class CorsikaConfig:
140
146
 
141
147
  return config
142
148
 
149
+ def assert_corsika_configurations_match(self, model_versions, db_config=None):
150
+ """
151
+ Assert that CORSIKA configurations match across all model versions.
152
+
153
+ Parameters
154
+ ----------
155
+ model_versions : list
156
+ List of model versions to check.
157
+ db_config : dict, optional
158
+ Database configuration.
159
+
160
+ Raises
161
+ ------
162
+ InvalidCorsikaInputError
163
+ If CORSIKA parameters are not identical across all model versions.
164
+ """
165
+ if len(model_versions) < 2:
166
+ return
167
+
168
+ parameters_from_db_list = []
169
+
170
+ # Get parameters for all model versions
171
+ for model_version in model_versions:
172
+ db_model_parameters = ModelParameter(
173
+ mongo_db_config=db_config, model_version=model_version
174
+ )
175
+ parameters_from_db_list.append(
176
+ db_model_parameters.get_simulation_software_parameters("corsika")
177
+ )
178
+
179
+ # Parameters that can differ between model versions (e.g., i/o buffer size)
180
+ skip_parameters = ["corsika_iact_io_buffer", "corsika_iact_split_auto"]
181
+
182
+ # Check if all parameters match
183
+ for i in range(len(parameters_from_db_list) - 1):
184
+ for key in parameters_from_db_list[i]:
185
+ if key in skip_parameters:
186
+ continue
187
+
188
+ current_value = parameters_from_db_list[i][key]["value"]
189
+ next_value = parameters_from_db_list[i + 1][key]["value"]
190
+
191
+ if current_value != next_value:
192
+ self._logger.warning(
193
+ f"Parameter '{key}' mismatch between model versions:\n"
194
+ f" {model_versions[i]}: {current_value}\n"
195
+ f" {model_versions[i + 1]}: {next_value}"
196
+ )
197
+ raise InvalidCorsikaInputError(
198
+ f"CORSIKA parameter '{key}' differs between model versions "
199
+ f"{model_versions[i]} and {model_versions[i + 1]}. "
200
+ f"Values are {current_value} and {next_value} respectively."
201
+ )
202
+
143
203
  def _corsika_configuration_from_user_input(self, args_dict):
144
204
  """
145
205
  Get CORSIKA configuration from user input.
@@ -236,7 +296,45 @@ class CorsikaConfig:
236
296
 
237
297
  def _input_config_corsika_starting_grammage(self, entry):
238
298
  """Return FIXCHI parameter CORSIKA format."""
239
- return f"{entry['value'] * u.Unit(entry['unit']).to('g/cm2')}"
299
+ value = self._get_starting_grammage_value(entry["value"])
300
+ return f"{value * u.Unit(entry['unit']).to('g/cm2')}"
301
+
302
+ def _get_starting_grammage_value(self, value_entry):
303
+ """
304
+ Get appropriate starting grammage value from entry values.
305
+
306
+ Parameters
307
+ ----------
308
+ value_entry : float or list
309
+ Value or list of grammage configurations
310
+
311
+ Returns
312
+ -------
313
+ float
314
+ Selected grammage value
315
+ """
316
+ if not isinstance(value_entry, list):
317
+ return value_entry
318
+
319
+ tel_types = {tel.design_model for tel in self.array_model.telescope_model.values()}
320
+ particle = self.primary_particle.name
321
+ matched_values = self._get_matching_grammage_values(value_entry, tel_types, particle)
322
+
323
+ return min(matched_values) if matched_values else 0
324
+
325
+ def _get_matching_grammage_values(self, configs, tel_types, particle):
326
+ """Get list of matching grammage values for particle and telescope types."""
327
+ matched = []
328
+ defaults = []
329
+
330
+ for config in configs:
331
+ if config.get("instrument") is None or config.get("instrument") in tel_types:
332
+ if config["primary_particle"] == particle:
333
+ matched.append(config["value"])
334
+ elif config["primary_particle"] == "default":
335
+ defaults.append(config["value"])
336
+
337
+ return matched if matched else defaults
240
338
 
241
339
  def _input_config_corsika_particle_kinetic_energy_cutoff(self, entry):
242
340
  """Return ECUTS parameter CORSIKA format."""
@@ -540,6 +638,9 @@ class CorsikaConfig:
540
638
  ValueError
541
639
  If file_type is unknown or if the run number is not given for file_type==config_tmp.
542
640
  """
641
+ if file_type == "config_tmp" and run_number is None:
642
+ raise ValueError("Must provide a run number for a temporary CORSIKA config file")
643
+
543
644
  file_label = f"_{self.label}" if self.label is not None else ""
544
645
 
545
646
  _vc_low = self.get_config_parameter("VIEWCONE")[0]
@@ -548,25 +649,27 @@ class CorsikaConfig:
548
649
  f"_cone{int(_vc_low):d}-{int(_vc_high):d}" if _vc_low != 0 or _vc_high != 0 else ""
549
650
  )
550
651
 
652
+ run_number_in_file_name = ""
653
+ if file_type == "output_generic":
654
+ # The XXXXXX will be replaced by the run number after the pfp step with sed
655
+ run_number_in_file_name = "runXXXXXX_"
656
+ if file_type == "config_tmp":
657
+ run_number_in_file_name = f"run{run_number:06}_"
658
+
551
659
  base_name = (
552
- f"{self.primary_particle.name}_{self.array_model.site}_{self.array_model.layout_name}_"
553
- f"za{int(self.get_config_parameter('THETAP')[0]):03}-"
554
- f"azm{self.azimuth_angle:03}deg"
555
- f"{view_cone}{file_label}"
660
+ f"{self.primary_particle.name}_{run_number_in_file_name}"
661
+ f"za{int(self.get_config_parameter('THETAP')[0]):03}deg_"
662
+ f"azm{self.azimuth_angle:03}deg{view_cone}_"
663
+ f"{self.array_model.site}_{self.array_model.layout_name}_"
664
+ f"{self.array_model.model_version}{file_label}"
556
665
  )
557
666
 
558
667
  if file_type == "config_tmp":
559
- if run_number is None:
560
- raise ValueError("Must provide a run number for a temporary CORSIKA config file")
561
- return f"corsika_config_run{run_number:06}_{base_name}.txt"
668
+ return f"corsika_config_{base_name}.txt"
562
669
  if file_type == "config":
563
670
  return f"corsika_config_{base_name}.input"
564
671
  if file_type == "output_generic":
565
- # The XXXXXX will be replaced by the run number after the pfp step with sed
566
- return (
567
- f"runXXXXXX_{base_name}_{self.array_model.site}_"
568
- f"{self.array_model.layout_name}{file_label}.zst"
569
- )
672
+ return f"{base_name}.zst"
570
673
  if file_type == "multipipe":
571
674
  return f"multi_cta-{self.array_model.site}-{self.array_model.layout_name}.cfg"
572
675
 
@@ -587,7 +690,7 @@ class CorsikaConfig:
587
690
  str
588
691
  Output file name.
589
692
  """
590
- sub_dir = "corsika_simtel" if use_multipipe else "corsika"
693
+ sub_dir = "corsika_sim_telarray" if use_multipipe else "corsika"
591
694
  config_file_name = self.get_corsika_config_file_name(file_type="config")
592
695
  file_directory = self.io_handler.get_output_directory(label=self.label, sub_dir=sub_dir)
593
696
  self._logger.debug(f"Creating directory {file_directory}")
@@ -7,7 +7,7 @@ from particle import Corsika7ID, InvalidParticle, Particle
7
7
 
8
8
  class PrimaryParticle:
9
9
  """
10
- Primary particle definition using CORSIKA7 or PDG ID.
10
+ Primary particle definition using CORSIKA7, eventio, or PDG ID.
11
11
 
12
12
  Uses a dictionary to map particle common names to CORSIKA7 IDs.
13
13
  Particles not found in the dictionary are searched in the PDG particle database.
@@ -20,27 +20,31 @@ class PrimaryParticle:
20
20
  The actual ID of the primary particle.
21
21
  """
22
22
 
23
+ _valid_types = {
24
+ "corsika7_id": "corsika7_id",
25
+ "common_name": "name",
26
+ "pdg_id": "pdg_id",
27
+ "eventio_id": "eventio_id",
28
+ }
29
+
23
30
  def __init__(self, particle_id_type=None, particle_id=None):
24
31
  self._logger = logging.getLogger(__name__)
25
-
26
32
  self._corsika7_id = None
27
33
  self._name = None
28
34
  self._pdg_id = None
29
35
  self._pdg_name = None
36
+ self._eventio_id = None
30
37
 
31
- if bool(particle_id_type) != bool(particle_id):
38
+ if (particle_id_type is None) != (particle_id is None):
32
39
  raise ValueError("Both 'particle_id_type' and 'particle_id' must be provided together.")
33
40
 
34
- valid_id_types = {"corsika7_id", "common_name", "pdg_id"}
35
- if particle_id_type and particle_id_type not in valid_id_types:
36
- raise ValueError(f"Particle ID type must be one of {valid_id_types}")
37
-
38
- if particle_id_type == "corsika7_id":
39
- self.corsika7_id = particle_id
40
- elif particle_id_type == "common_name":
41
- self.name = particle_id
42
- elif particle_id_type == "pdg_id":
43
- self.pdg_id = particle_id
41
+ if particle_id_type:
42
+ try:
43
+ setattr(self, self._valid_types[particle_id_type], particle_id)
44
+ except KeyError as exc:
45
+ raise ValueError(
46
+ f"Particle ID type must be one of {set(self._valid_types)}"
47
+ ) from exc
44
48
 
45
49
  def __str__(self):
46
50
  """Return a string representation of the primary particle."""
@@ -70,6 +74,35 @@ class PrimaryParticle:
70
74
  raise ValueError(f"Invalid CORSIKA7 ID: {value}") from exc
71
75
  self._corsika7_id = int(value)
72
76
 
77
+ @property
78
+ def eventio_id(self):
79
+ """
80
+ EventIO ID of the primary particle.
81
+
82
+ 0 (gamma), 1(e-), 2(mu-), 100*A+Z for nucleons and nuclei, negative for antimatter.
83
+ """
84
+ return self._eventio_id
85
+
86
+ @eventio_id.setter
87
+ def eventio_id(self, value):
88
+ """Set EventIO ID of the primary particle."""
89
+ mapping = {
90
+ 0: 1,
91
+ 1: 3,
92
+ -1: 2,
93
+ 2: 6,
94
+ -2: 5,
95
+ 100: 13,
96
+ 101: 14,
97
+ -101: 15,
98
+ }
99
+
100
+ try:
101
+ self.corsika7_id = mapping.get(value, value)
102
+ except (ValueError, InvalidParticle) as exc:
103
+ raise ValueError(f"Invalid EventIO ID: {value}") from exc
104
+ self._eventio_id = value
105
+
73
106
  @property
74
107
  def name(self):
75
108
  """Common name of the primary particle."""
@@ -5,6 +5,7 @@ import re
5
5
  import astropy.units as u
6
6
  import jsonschema
7
7
 
8
+ from simtools.corsika.primary_particle import PrimaryParticle
8
9
  from simtools.utils import names
9
10
 
10
11
  format_checker = jsonschema.FormatChecker()
@@ -50,3 +51,11 @@ def check_array_triggers_name(name):
50
51
  raise ValueError(f"Array trigger name '{name}' does not match pattern '{pattern}'")
51
52
  names.validate_array_element_type(re.match(pattern, name).group(1))
52
53
  return True
54
+
55
+
56
+ @format_checker.checks("common_particle_name")
57
+ def check_common_particle_name(name):
58
+ """Validate common particle names for jsonschema."""
59
+ if name in PrimaryParticle.particle_names() or name == "default":
60
+ return True
61
+ raise ValueError(f"Invalid common particle name: '{name}'")
@@ -13,7 +13,6 @@ from pathlib import Path
13
13
 
14
14
  import yaml
15
15
 
16
- import simtools.constants
17
16
  import simtools.utils.general as gen
18
17
  import simtools.version
19
18
  from simtools.constants import METADATA_JSON_SCHEMA
@@ -209,7 +208,7 @@ class MetadataCollector:
209
208
  url = self.input_metadata[0][self.observatory]["product"]["data"]["model"]["url"]
210
209
  self._logger.debug(f"Schema file from input metadata: {url}")
211
210
  return url
212
- except (KeyError, TypeError):
211
+ except (KeyError, TypeError, IndexError):
213
212
  pass
214
213
 
215
214
  self._logger.warning("No schema file found.")
@@ -254,7 +253,7 @@ class MetadataCollector:
254
253
  )
255
254
  if _site is not None:
256
255
  return names.validate_site_name(_site)
257
- except (KeyError, TypeError):
256
+ except (KeyError, TypeError, IndexError):
258
257
  pass
259
258
  return None
260
259
 
@@ -360,6 +359,11 @@ class MetadataCollector:
360
359
  _input_metadata = self._read_input_metadata_from_yml_or_json(metadata_file)
361
360
  elif Path(metadata_file).suffix == ".ecsv":
362
361
  _input_metadata = self._read_input_metadata_from_ecsv(metadata_file)
362
+ elif Path(metadata_file).name.endswith((".simtel.zst", ".simtel")):
363
+ self._logger.warning(
364
+ "Metadata extraction from sim_telarray files is not supported yet."
365
+ )
366
+ continue
363
367
  else:
364
368
  raise gen.InvalidConfigDataError(f"Unknown metadata file format: {metadata_file}")
365
369
 
@@ -270,6 +270,9 @@ class ModelDataWriter:
270
270
  "type": self._get_parameter_type(),
271
271
  "file": self._parameter_is_a_file(),
272
272
  "meta_parameter": False,
273
+ "model_parameter_schema_version": self.schema_dict.get(
274
+ "model_parameter_schema_version", "0.1.0"
275
+ ),
273
276
  }
274
277
  return self.validate_and_transform(
275
278
  product_data_dict=data_dict,
@@ -18,7 +18,7 @@ from simtools.utils import names
18
18
  _logger = logging.getLogger(__name__)
19
19
 
20
20
 
21
- def get_get_model_parameter_schema_files(schema_directory=MODEL_PARAMETER_SCHEMA_PATH):
21
+ def get_model_parameter_schema_files(schema_directory=MODEL_PARAMETER_SCHEMA_PATH):
22
22
  """
23
23
  Return list of parameters and schema files located in schema file directory.
24
24
 
@@ -35,7 +35,8 @@ def get_get_model_parameter_schema_files(schema_directory=MODEL_PARAMETER_SCHEMA
35
35
  raise FileNotFoundError(f"No schema files found in {schema_directory}")
36
36
  parameters = []
37
37
  for schema_file in schema_files:
38
- schema_dict = gen.collect_data_from_file(file_name=schema_file)
38
+ # reading parameter 'name' only - first document in schema file should be ok
39
+ schema_dict = gen.collect_data_from_file(file_name=schema_file, yaml_document=0)
39
40
  parameters.append(schema_dict.get("name"))
40
41
  return parameters, schema_files
41
42
 
@@ -182,6 +183,15 @@ def load_schema(schema_file=None, schema_version=None):
182
183
  return schema
183
184
 
184
185
 
186
+ def _get_array_element_list():
187
+ """Build complete list of array elements including design types."""
188
+ elements = set(names.array_elements().keys())
189
+ for array_element in names.array_elements():
190
+ for design_type in names.array_element_design_types(array_element):
191
+ elements.add(f"{array_element}-{design_type}")
192
+ return sorted(elements)
193
+
194
+
185
195
  def _add_array_elements(key, schema):
186
196
  """
187
197
  Add list of array elements to schema.
@@ -200,22 +210,23 @@ def _add_array_elements(key, schema):
200
210
  -------
201
211
  dict
202
212
  Schema dictionary with added array elements.
203
-
204
213
  """
205
- _list_of_array_elements = sorted(names.array_elements().keys())
206
-
207
- def recursive_search(sub_schema, key):
208
- if key in sub_schema:
209
- if "enum" in sub_schema[key] and isinstance(sub_schema[key]["enum"], list):
210
- sub_schema[key]["enum"] = list(
211
- set(sub_schema[key]["enum"] + _list_of_array_elements)
212
- )
213
- else:
214
- sub_schema[key]["enum"] = _list_of_array_elements
214
+ array_elements = _get_array_element_list()
215
+
216
+ def update_enum(sub_schema):
217
+ if "enum" in sub_schema and isinstance(sub_schema["enum"], list):
218
+ sub_schema["enum"] = list(set(sub_schema["enum"] + array_elements))
215
219
  else:
216
- for _, v in sub_schema.items():
217
- if isinstance(v, dict):
218
- recursive_search(v, key)
220
+ sub_schema["enum"] = array_elements
221
+
222
+ def recursive_search(sub_schema, target_key):
223
+ if target_key in sub_schema:
224
+ update_enum(sub_schema[target_key])
225
+ return
226
+
227
+ for v in sub_schema.values():
228
+ if isinstance(v, dict):
229
+ recursive_search(v, target_key)
219
230
 
220
231
  recursive_search(schema, key)
221
232
  return schema
@@ -177,7 +177,11 @@ class DataValidator:
177
177
  if is_model_parameter:
178
178
  self._prepare_model_parameter()
179
179
 
180
- self._data_description = self._read_validation_schema(self.schema_file_name)
180
+ self._data_description = self._read_validation_schema(
181
+ self.schema_file_name,
182
+ # use 0.1.0 as default; this corresponds to the first definition of the schema
183
+ self.data_dict.get("model_parameter_schema_version", "0.1.0"),
184
+ )
181
185
 
182
186
  value_as_list, unit_as_list = self._get_value_and_units_as_lists()
183
187
 
@@ -701,14 +705,20 @@ class DataValidator:
701
705
 
702
706
  return False
703
707
 
704
- def _read_validation_schema(self, schema_file):
708
+ def _read_validation_schema(self, schema_file, schema_version=None):
705
709
  """
706
710
  Read validation schema from file.
707
711
 
712
+ The schema file can be a yaml file with several documents, each document
713
+ describing a different schema version. Returns first document if no
714
+ schema version is requested.
715
+
708
716
  Parameters
709
717
  ----------
710
718
  schema_file: Path
711
719
  Schema file describing input data.
720
+ schema_version: str
721
+ Version of the schema to be used.
712
722
 
713
723
  Returns
714
724
  -------
@@ -719,11 +729,21 @@ class DataValidator:
719
729
  ------
720
730
  KeyError
721
731
  if 'data' can not be read from dict in schema file
722
- """
723
- try:
724
- return gen.collect_data_from_file(file_name=schema_file)["data"]
725
- except KeyError as exc:
726
- raise KeyError(f"Error reading validation schema from {schema_file}") from exc
732
+ ValueError
733
+ if schema version is not found in schema file
734
+ """
735
+ schema_data = gen.collect_data_from_file(file_name=schema_file)
736
+ entries = schema_data if isinstance(schema_data, list) else [schema_data]
737
+
738
+ for entry in entries:
739
+ if not schema_version or entry.get("version") == schema_version:
740
+ try:
741
+ return entry["data"]
742
+ except KeyError as exc:
743
+ raise KeyError(f"Error reading validation schema from {schema_file}") from exc
744
+ raise ValueError(
745
+ f"Schema version {schema_version} not found in schema file {schema_file}. "
746
+ )
727
747
 
728
748
  def _get_data_description(self, column_name=None, status_test=False):
729
749
  """
simtools/db/db_handler.py CHANGED
@@ -122,9 +122,11 @@ class DatabaseHandler:
122
122
  port=self.mongo_db_config["db_api_port"],
123
123
  username=self.mongo_db_config["db_api_user"],
124
124
  password=self.mongo_db_config["db_api_pw"],
125
- authSource=self.mongo_db_config.get("db_api_authentication_database")
126
- if self.mongo_db_config.get("db_api_authentication_database")
127
- else "admin",
125
+ authSource=(
126
+ self.mongo_db_config.get("db_api_authentication_database")
127
+ if self.mongo_db_config.get("db_api_authentication_database")
128
+ else "admin"
129
+ ),
128
130
  directConnection=direct_connection,
129
131
  ssl=not direct_connection,
130
132
  tlsallowinvalidhostnames=True,
@@ -211,7 +213,11 @@ class DatabaseHandler:
211
213
  """
212
214
  collection_name = names.get_collection_name_from_parameter_name(parameter)
213
215
  if model_version:
214
- production_table = self._read_production_table_from_mongo_db(
216
+ if isinstance(model_version, list):
217
+ raise ValueError(
218
+ "Only one model version can be passed to get_model_parameter, not a list."
219
+ )
220
+ production_table = self.read_production_table_from_mongo_db(
215
221
  collection_name, model_version
216
222
  )
217
223
  array_element_list = self._get_array_element_list(
@@ -257,7 +263,7 @@ class DatabaseHandler:
257
263
  dict containing the parameters
258
264
  """
259
265
  pars = {}
260
- production_table = self._read_production_table_from_mongo_db(collection, model_version)
266
+ production_table = self.read_production_table_from_mongo_db(collection, model_version)
261
267
  array_element_list = self._get_array_element_list(
262
268
  array_element_name, site, production_table, collection
263
269
  )
@@ -512,9 +518,9 @@ class DatabaseHandler:
512
518
  parameters[par_now]["entry_date"] = ObjectId(post["_id"]).generation_time
513
519
  return {k: parameters[k] for k in sorted(parameters)}
514
520
 
515
- def _read_production_table_from_mongo_db(self, collection_name, model_version):
521
+ def read_production_table_from_mongo_db(self, collection_name, model_version):
516
522
  """
517
- Read production table from MongoDB.
523
+ Read production table for the given collection from MongoDB.
518
524
 
519
525
  Parameters
520
526
  ----------
@@ -585,7 +591,7 @@ class DatabaseHandler:
585
591
  list
586
592
  Sorted list of all array elements found in collection
587
593
  """
588
- production_table = self._read_production_table_from_mongo_db(collection, model_version)
594
+ production_table = self.read_production_table_from_mongo_db(collection, model_version)
589
595
  return sorted([entry for entry in production_table["parameters"] if "-design" not in entry])
590
596
 
591
597
  def get_design_model(self, model_version, array_element_name, collection="telescopes"):
@@ -607,7 +613,7 @@ class DatabaseHandler:
607
613
  str
608
614
  Design model for a given array element.
609
615
  """
610
- production_table = self._read_production_table_from_mongo_db(collection, model_version)
616
+ production_table = self.read_production_table_from_mongo_db(collection, model_version)
611
617
  try:
612
618
  return production_table["design_model"][array_element_name]
613
619
  except KeyError:
@@ -635,7 +641,7 @@ class DatabaseHandler:
635
641
  list
636
642
  Sorted list of all array element names found in collection
637
643
  """
638
- production_table = self._read_production_table_from_mongo_db(collection, model_version)
644
+ production_table = self.read_production_table_from_mongo_db(collection, model_version)
639
645
  all_array_elements = production_table["parameters"]
640
646
  return sorted(
641
647
  [
@@ -678,7 +684,7 @@ class DatabaseHandler:
678
684
  model_version=model_version,
679
685
  collection="configuration_corsika",
680
686
  )
681
- if simulation_software == "simtel":
687
+ if simulation_software == "sim_telarray":
682
688
  return (
683
689
  self.get_model_parameters(
684
690
  site,
@@ -753,7 +759,7 @@ class DatabaseHandler:
753
759
  """
754
760
  db_name = self._get_db_name(db_name)
755
761
  collection = self.get_collection(db_name, "production_tables")
756
- self._logger.info(f"Adding production for {production_table.get('collection')} to to DB")
762
+ self._logger.debug(f"Adding production for {production_table.get('collection')} to to DB")
757
763
  collection.insert_one(production_table)
758
764
  DatabaseHandler.production_table_cached.clear()
759
765
 
@@ -803,13 +809,13 @@ class DatabaseHandler:
803
809
  raise ValueError(f"File is not UTF-8 encoded: {file_path}")
804
810
  files_to_add_to_db.add(f"{file_path}")
805
811
 
806
- self._logger.info(
812
+ self._logger.debug(
807
813
  f"Adding a new entry to DB {db_name} and collection {collection_name}:\n{par_dict}"
808
814
  )
809
815
  collection.insert_one(par_dict)
810
816
 
811
817
  for file_to_insert_now in files_to_add_to_db:
812
- self._logger.info(f"Will also add the file {file_to_insert_now} to the DB")
818
+ self._logger.debug(f"Will also add the file {file_to_insert_now} to the DB")
813
819
  self.insert_file_to_db(file_to_insert_now, db_name)
814
820
 
815
821
  self._reset_parameter_cache()
@@ -958,7 +964,7 @@ class DatabaseHandler:
958
964
  return [array_element_name]
959
965
  if collection == "configuration_sim_telarray":
960
966
  # get design model from 'telescope' or 'calibration_device' production tables
961
- production_table = self._read_production_table_from_mongo_db(
967
+ production_table = self.read_production_table_from_mongo_db(
962
968
  names.get_collection_name_from_array_element_name(array_element_name),
963
969
  production_table["model_version"],
964
970
  )
@@ -27,7 +27,7 @@ def add_values_from_json_to_db(file, collection, db, db_name, file_prefix):
27
27
  Path to location of all additional files to be uploaded.
28
28
  """
29
29
  par_dict = gen.collect_data_from_file(file_name=file)
30
- logger.info(
30
+ logger.debug(
31
31
  f"Adding the following parameter to the DB: {par_dict['parameter']} "
32
32
  f"version {par_dict['parameter_version']} "
33
33
  f"(collection {collection} in database {db_name})"
@@ -120,7 +120,7 @@ def _read_production_table(model_dict, file, model_name):
120
120
  },
121
121
  )
122
122
  parameter_dict = gen.collect_data_from_file(file_name=file)
123
- logger.info(f"Reading production table for {array_element} (collection {collection})")
123
+ logger.debug(f"Reading production table for {array_element} (collection {collection})")
124
124
  try:
125
125
  if array_element in ("configuration_corsika", "configuration_sim_telarray"):
126
126
  model_dict[collection]["parameters"] = parameter_dict["parameters"]
@@ -72,7 +72,7 @@ class IOHandler(metaclass=IOHandlerSingleton):
72
72
  label: str
73
73
  Instance label.
74
74
  sub_dir: str
75
- Name of the subdirectory (ray-tracing, model etc)
75
+ Name of the subdirectory (ray_tracing, model etc)
76
76
 
77
77
  Returns
78
78
  -------
@@ -116,7 +116,7 @@ class IOHandler(metaclass=IOHandlerSingleton):
116
116
  label: str
117
117
  Instance label.
118
118
  sub_dir: str
119
- Name of the subdirectory (ray-tracing, model etc)
119
+ Name of the subdirectory (ray_tracing, model etc)
120
120
 
121
121
  Returns
122
122
  -------