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
@@ -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):
@@ -95,7 +102,10 @@ def _validate_output_path_and_file(config, integration_file_tests):
95
102
 
96
103
  output_file_path = Path(output_path) / file_test["FILE"]
97
104
  _logger.info(f"Checking path: {output_file_path}")
98
- assert output_file_path.exists()
105
+ try:
106
+ assert output_file_path.exists()
107
+ except AssertionError as exc:
108
+ raise AssertionError(f"Output file {output_file_path} does not exist. ") from exc
99
109
 
100
110
  if "EXPECTED_OUTPUT" in file_test:
101
111
  assert assertions.check_output_from_sim_telarray(
simtools/utils/general.py CHANGED
@@ -133,28 +133,13 @@ def collect_data_from_http(url):
133
133
  try:
134
134
  with tempfile.NamedTemporaryFile(mode="w+t") as tmp_file:
135
135
  urllib.request.urlretrieve(url, tmp_file.name)
136
- if url.endswith("yml") or url.endswith("yaml"):
137
- try:
138
- data = yaml.safe_load(tmp_file)
139
- except yaml.constructor.ConstructorError:
140
- data = _load_yaml_using_astropy(tmp_file)
141
- elif url.endswith("json"):
142
- data = json.load(tmp_file)
143
- elif url.endswith("list"):
144
- lines = tmp_file.readlines()
145
- data = [line.strip() for line in lines]
146
- else:
147
- msg = f"File extension of {url} not supported (should be json or yaml)"
148
- _logger.error(msg)
149
- raise TypeError(msg)
136
+ data = _collect_data_from_different_file_types(
137
+ tmp_file, url, Path(url).suffix.lower(), None
138
+ )
150
139
  except TypeError as exc:
151
- msg = "Invalid url {url}"
152
- _logger.error(msg)
153
- raise TypeError(msg) from exc
140
+ raise TypeError(f"Invalid url {url}") from exc
154
141
  except urllib.error.HTTPError as exc:
155
- msg = f"Failed to download file from {url}"
156
- _logger.error(msg)
157
- raise FileNotFoundError(msg) from exc
142
+ raise FileNotFoundError(f"Failed to download file from {url}") from exc
158
143
 
159
144
  _logger.debug(f"Downloaded file from {url}")
160
145
  return data
@@ -182,20 +167,26 @@ def collect_data_from_file(file_name, yaml_document=None):
182
167
  suffix = Path(file_name).suffix.lower()
183
168
  try:
184
169
  with open(file_name, encoding="utf-8") as file:
185
- if suffix == ".json":
186
- return json.load(file)
187
- if suffix == ".list":
188
- return [line.strip() for line in file.readlines()]
189
- if suffix in [".yml", ".yaml"]:
190
- return _collect_data_from_yaml_file(file, file_name, yaml_document)
170
+ return _collect_data_from_different_file_types(file, file_name, suffix, yaml_document)
191
171
  # broad exception to catch all possible errors in reading the file
192
172
  except Exception as exc: # pylint: disable=broad-except
193
173
  raise type(exc)(f"Failed to read file {file_name}: {exc}") from exc
194
174
  return None
195
175
 
196
176
 
177
+ def _collect_data_from_different_file_types(file, file_name, suffix, yaml_document):
178
+ """Collect data from different file types."""
179
+ if suffix == ".json":
180
+ return json.load(file)
181
+ if suffix in (".list", ".txt"):
182
+ return [line.strip() for line in file.readlines()]
183
+ if suffix in [".yml", ".yaml"]:
184
+ return _collect_data_from_yaml_file(file, file_name, yaml_document)
185
+ raise TypeError(f"File type {suffix} not supported.")
186
+
187
+
197
188
  def _collect_data_from_yaml_file(file, file_name, yaml_document):
198
- """Collect data from a yaml file."""
189
+ """Collect data from a yaml file (allow for multi-document yaml files)."""
199
190
  try:
200
191
  return yaml.safe_load(file)
201
192
  except yaml.constructor.ConstructorError:
simtools/utils/names.py CHANGED
@@ -19,7 +19,11 @@ from pathlib import Path
19
19
 
20
20
  import yaml
21
21
 
22
- from simtools.constants import MODEL_PARAMETER_SCHEMA_PATH, SCHEMA_PATH
22
+ from simtools.constants import (
23
+ MODEL_PARAMETER_DESCRIPTION_METASCHEMA,
24
+ MODEL_PARAMETER_SCHEMA_PATH,
25
+ SCHEMA_PATH,
26
+ )
23
27
 
24
28
  _logger = logging.getLogger(__name__)
25
29
 
@@ -59,6 +63,21 @@ def array_elements():
59
63
  return yaml.safe_load(file)["data"]
60
64
 
61
65
 
66
+ @cache
67
+ def simulation_software():
68
+ """
69
+ Get simulation software names from the meta schema definition.
70
+
71
+ Returns
72
+ -------
73
+ list
74
+ List of simulation software names.
75
+ """
76
+ with open(Path(MODEL_PARAMETER_DESCRIPTION_METASCHEMA), encoding="utf-8") as file:
77
+ schema = yaml.safe_load(file)
78
+ return schema["definitions"]["SimulationSoftwareName"]["enum"]
79
+
80
+
62
81
  @cache
63
82
  def site_names():
64
83
  """
@@ -129,15 +148,18 @@ def _load_model_parameters():
129
148
  """
130
149
  Get model parameters properties from schema files.
131
150
 
151
+ For schema files including multiple schemas, only the first one is returned
152
+ (as this is the most recent definition).
153
+
132
154
  Returns
133
155
  -------
134
156
  dict
135
157
  Model parameters definitions for all model parameters.
136
158
  """
137
159
  _parameters = {}
138
- for schema_file in list(Path(MODEL_PARAMETER_SCHEMA_PATH).rglob("*.yml")):
160
+ for schema_file in Path(MODEL_PARAMETER_SCHEMA_PATH).rglob("*.yml"):
139
161
  with open(schema_file, encoding="utf-8") as f:
140
- data = yaml.safe_load(f)
162
+ data = next(yaml.safe_load_all(f))
141
163
  _parameters[data["name"]] = data
142
164
  return _parameters
143
165
 
@@ -512,7 +534,7 @@ def get_collection_name_from_parameter_name(parameter_name):
512
534
 
513
535
  def get_simulation_software_name_from_parameter_name(
514
536
  parameter_name,
515
- simulation_software="sim_telarray",
537
+ software_name="sim_telarray",
516
538
  set_meta_parameter=False,
517
539
  ):
518
540
  """
@@ -541,7 +563,7 @@ def get_simulation_software_name_from_parameter_name(
541
563
 
542
564
  for software in _parameter.get("simulation_software", []):
543
565
  if (
544
- software.get("name") == simulation_software
566
+ software.get("name") == software_name
545
567
  and software.get("set_meta_parameter", False) is set_meta_parameter
546
568
  ):
547
569
  return software.get("internal_parameter_name", parameter_name)
@@ -636,7 +658,7 @@ def generate_file_name(
636
658
  """
637
659
  Generate a file name for output, config, or plotting.
638
660
 
639
- Used e.g., to generate camera-efficiency and ray-tracing output files.
661
+ Used e.g., to generate camera_efficiency and ray_tracing output files.
640
662
 
641
663
  Parameters
642
664
  ----------
@@ -668,10 +690,10 @@ def generate_file_name(
668
690
  str
669
691
  File name.
670
692
  """
671
- name = f"{file_type}-{site}-{telescope_model_name}"
672
- name += f"-d{source_distance:.1f}km" if source_distance is not None else ""
673
- name += f"-za{float(zenith_angle):.1f}deg"
674
- name += f"-off{off_axis_angle:.3f}deg" if off_axis_angle is not None else ""
693
+ name = f"{file_type}_{site}_{telescope_model_name}"
694
+ name += f"_d{source_distance:.1f}km" if source_distance is not None else ""
695
+ name += f"_za{float(zenith_angle):.1f}deg"
696
+ name += f"_off{off_axis_angle:.3f}deg" if off_axis_angle is not None else ""
675
697
  name += f"_azm{round(azimuth_angle):03}deg" if azimuth_angle is not None else ""
676
698
  name += f"_mirror{mirror_number}" if mirror_number is not None else ""
677
699
  name += f"_{label}" if label is not None else ""
@@ -0,0 +1,242 @@
1
+ #!/usr/bin/python3
2
+ """Plot array elements for a layout."""
3
+
4
+ from collections import Counter
5
+
6
+ import astropy.units as u
7
+ import matplotlib.patches as mpatches
8
+ import matplotlib.pyplot as plt
9
+ from astropy.table import Column
10
+ from matplotlib.collections import PatchCollection
11
+
12
+ from simtools.utils import geometry as transf
13
+ from simtools.utils import names
14
+ from simtools.visualization import legend_handlers as leg_h
15
+
16
+ __all__ = ["get_telescope_patch", "plot_array_layout"]
17
+
18
+
19
+ def plot_array_layout(
20
+ telescopes,
21
+ show_tel_label=False,
22
+ axes_range=None,
23
+ marker_scaling=1.0,
24
+ background_telescopes=None,
25
+ ):
26
+ """
27
+ Plot telescope array layout.
28
+
29
+ Parameters
30
+ ----------
31
+ telescopes : Table
32
+ Telescope data table.
33
+ show_tel_label : bool
34
+ Show telescope labels (default False).
35
+ axes_range : float or None
36
+ Axis range, auto if None.
37
+ marker_scaling : float
38
+ Marker size scale factor.
39
+ background_telescopes : Table or None
40
+ Optional background telescope table.
41
+
42
+ Returns
43
+ -------
44
+ fig : Figure
45
+ Matplotlib figure object.
46
+ """
47
+ fig, ax = plt.subplots(1)
48
+
49
+ patches, plot_range = get_patches(ax, telescopes, show_tel_label, axes_range, marker_scaling)
50
+
51
+ if background_telescopes is not None:
52
+ bg_patches, bg_range = get_patches(
53
+ ax, background_telescopes, False, axes_range, marker_scaling
54
+ )
55
+ ax.add_collection(PatchCollection(bg_patches, match_original=True, alpha=0.1))
56
+ if axes_range is None:
57
+ plot_range = max(plot_range, bg_range)
58
+
59
+ update_legend(ax, telescopes)
60
+ finalize_plot(ax, patches, "Easting [m]", "Northing [m]", plot_range)
61
+
62
+ return fig
63
+
64
+
65
+ def get_patches(ax, telescopes, show_tel_label, axes_range, marker_scaling):
66
+ """
67
+ Get plot patches and axis range.
68
+
69
+ Returns
70
+ -------
71
+ patches : list
72
+ List of telescope patches.
73
+ axes_range : float
74
+ Calculated or input axis range.
75
+ """
76
+ pos_x, pos_y = get_positions(telescopes)
77
+ telescopes["pos_x_rotated"] = Column(pos_x)
78
+ telescopes["pos_y_rotated"] = Column(pos_y)
79
+
80
+ patches, radii = create_patches(telescopes, marker_scaling, show_tel_label, ax)
81
+
82
+ if axes_range:
83
+ return patches, axes_range
84
+
85
+ r = max(radii).value
86
+ max_x = max(abs(pos_x.min().value), abs(pos_x.max().value)) + r
87
+ max_y = max(abs(pos_y.min().value), abs(pos_y.max().value)) + r
88
+ updated_axes_range = max(max_x, max_y) * 1.1
89
+
90
+ return patches, updated_axes_range
91
+
92
+
93
+ @u.quantity_input(x=u.m, y=u.m, radius=u.m)
94
+ def get_telescope_patch(name, x, y, radius):
95
+ """
96
+ Create patch for a telescope.
97
+
98
+ Returns
99
+ -------
100
+ patch : Patch
101
+ Circle or rectangle patch.
102
+ """
103
+ tel_obj = leg_h.TelescopeHandler()
104
+ tel_type = names.get_array_element_type_from_name(name)
105
+ x, y, r = x.to(u.m), y.to(u.m), radius.to(u.m)
106
+
107
+ if tel_type == "SCTS":
108
+ return mpatches.Rectangle(
109
+ ((x - r / 2).value, (y - r / 2).value),
110
+ width=r.value,
111
+ height=r.value,
112
+ fill=False,
113
+ color=tel_obj.colors_dict[tel_type],
114
+ )
115
+
116
+ return mpatches.Circle(
117
+ (x.value, y.value),
118
+ radius=r.value,
119
+ fill=tel_type.startswith("MST"),
120
+ color=tel_obj.colors_dict[tel_type],
121
+ )
122
+
123
+
124
+ def get_positions(telescopes):
125
+ """
126
+ Get X/Y positions depending on coordinate system.
127
+
128
+ For ground coordinates, rotates the positions by 90 degrees.
129
+
130
+ Returns
131
+ -------
132
+ x_rot, y_rot : Quantity
133
+ Position coordinates.
134
+ """
135
+ if "position_x" in telescopes.colnames:
136
+ x, y = telescopes["position_x"], telescopes["position_y"]
137
+ locale_rotate_angle = 90 * u.deg
138
+ elif "utm_east" in telescopes.colnames:
139
+ x, y = telescopes["utm_east"], telescopes["utm_north"]
140
+ locale_rotate_angle = 0 * u.deg
141
+ else:
142
+ raise ValueError("Missing required position columns.")
143
+
144
+ return transf.rotate(x, y, locale_rotate_angle) if locale_rotate_angle != 0 else (x, y)
145
+
146
+
147
+ def create_patches(telescopes, scale, show_label, ax):
148
+ """
149
+ Create telescope patches and labels.
150
+
151
+ Returns
152
+ -------
153
+ patches : list
154
+ Shape patches.
155
+ radii : list
156
+ Telescope radii.
157
+ """
158
+ patches, radii = [], []
159
+ fontsize, scale_factor = (4, 2) if len(telescopes) > 30 else (8, 1)
160
+
161
+ for tel in telescopes:
162
+ name = get_telescope_name(tel)
163
+ radius = get_sphere_radius(tel)
164
+ radii.append(radius)
165
+ tel_type = names.get_array_element_type_from_name(name)
166
+
167
+ patches.append(
168
+ get_telescope_patch(
169
+ tel_type,
170
+ tel["pos_x_rotated"],
171
+ tel["pos_y_rotated"],
172
+ scale_factor * radius * scale,
173
+ )
174
+ )
175
+
176
+ if show_label:
177
+ ax.text(
178
+ tel["pos_x_rotated"].value,
179
+ tel["pos_y_rotated"].value + scale_factor * radius.value,
180
+ name,
181
+ ha="center",
182
+ va="bottom",
183
+ fontsize=fontsize,
184
+ )
185
+
186
+ return patches, radii
187
+
188
+
189
+ def get_telescope_name(tel):
190
+ """
191
+ Get telescope name.
192
+
193
+ Returns
194
+ -------
195
+ name : str
196
+ Telescope name or fallback identifier.
197
+ """
198
+ if "telescope_name" in tel.colnames:
199
+ return tel["telescope_name"]
200
+ if "asset_code" in tel.colnames and "sequence_number" in tel.colnames:
201
+ return f"{tel['asset_code']}-{tel['sequence_number']}"
202
+ return f"tel_{tel.index}"
203
+
204
+
205
+ def get_sphere_radius(tel):
206
+ """
207
+ Get telescope sphere radius.
208
+
209
+ Returns
210
+ -------
211
+ radius : Quantity
212
+ Radius with units.
213
+ """
214
+ return tel["sphere_radius"] if "sphere_radius" in tel.colnames else 10.0 * u.m
215
+
216
+
217
+ def update_legend(ax, telescopes):
218
+ """Add legend for telescope types and counts."""
219
+ names_list = [get_telescope_name(tel) for tel in telescopes]
220
+ types = [names.get_array_element_type_from_name(n) for n in names_list]
221
+ counts = Counter(types)
222
+
223
+ objs, labels = [], []
224
+ for t in names.get_list_of_array_element_types():
225
+ if counts[t]:
226
+ objs.append(leg_h.all_telescope_objects[t]())
227
+ labels.append(f"{t} ({counts[t]})")
228
+
229
+ handler_map = {k: v() for k, v in leg_h.legend_handler_map.items()}
230
+ ax.legend(objs, labels, handler_map=handler_map, prop={"size": 11}, loc="best")
231
+
232
+
233
+ def finalize_plot(ax, patches, x_title, y_title, axes_range):
234
+ """Finalize plot appearance and limits."""
235
+ ax.add_collection(PatchCollection(patches, match_original=True))
236
+ ax.set(xlabel=x_title, ylabel=y_title)
237
+ ax.tick_params(labelsize=8)
238
+ ax.axis("square")
239
+ if axes_range:
240
+ ax.set_xlim(-axes_range, axes_range)
241
+ ax.set_ylim(-axes_range, axes_range)
242
+ plt.tight_layout()