gammasimtools 0.13.0__py3-none-any.whl → 0.14.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 (226) hide show
  1. {gammasimtools-0.13.0.dist-info → gammasimtools-0.14.0.dist-info}/METADATA +3 -3
  2. {gammasimtools-0.13.0.dist-info → gammasimtools-0.14.0.dist-info}/RECORD +224 -217
  3. {gammasimtools-0.13.0.dist-info → gammasimtools-0.14.0.dist-info}/WHEEL +1 -1
  4. {gammasimtools-0.13.0.dist-info → gammasimtools-0.14.0.dist-info}/entry_points.txt +3 -1
  5. simtools/_version.py +2 -2
  6. simtools/applications/db_add_file_to_db.py +15 -0
  7. simtools/applications/db_add_value_from_json_to_db.py +18 -1
  8. simtools/applications/derive_ctao_array_layouts.py +120 -0
  9. simtools/applications/derive_photon_electron_spectrum.py +29 -1
  10. simtools/applications/docs_produce_array_element_report.py +41 -16
  11. simtools/applications/docs_produce_model_parameter_reports.py +28 -8
  12. simtools/applications/{production_extract_mc_event_data.py → generate_simtel_event_data.py} +12 -20
  13. simtools/applications/print_version.py +81 -0
  14. simtools/applications/production_derive_corsika_limits.py +172 -39
  15. simtools/applications/production_scale_events.py +59 -36
  16. simtools/applications/run_application.py +41 -11
  17. simtools/applications/simulate_light_emission.py +115 -247
  18. simtools/applications/simulate_prod_htcondor_generator.py +2 -2
  19. simtools/camera/single_photon_electron_spectrum.py +163 -15
  20. simtools/data_model/model_data_writer.py +7 -6
  21. simtools/db/db_handler.py +14 -8
  22. simtools/dependencies.py +38 -3
  23. simtools/layout/array_layout.py +1 -0
  24. simtools/layout/ctao_array_layouts.py +172 -0
  25. simtools/model/array_model.py +3 -4
  26. simtools/model/model_parameter.py +30 -87
  27. simtools/production_configuration/derive_corsika_limits.py +222 -154
  28. simtools/production_configuration/event_scaler.py +2 -2
  29. simtools/reporting/docs_auto_report_generator.py +217 -0
  30. simtools/reporting/docs_read_parameters.py +192 -110
  31. simtools/schemas/application_workflow.metaschema.yml +3 -0
  32. simtools/schemas/model_parameter.metaschema.yml +13 -0
  33. simtools/schemas/model_parameter_and_data_schema.metaschema.yml +6 -0
  34. simtools/schemas/model_parameters/adjust_gain.schema.yml +1 -1
  35. simtools/schemas/model_parameters/altitude.schema.yml +1 -1
  36. simtools/schemas/model_parameters/array_coordinates_UTM.schema.yml +3 -3
  37. simtools/schemas/model_parameters/array_element_position_ground.schema.yml +3 -3
  38. simtools/schemas/model_parameters/array_element_position_utm.schema.yml +3 -3
  39. simtools/schemas/model_parameters/array_window.schema.yml +1 -1
  40. simtools/schemas/model_parameters/asum_clipping.schema.yml +1 -1
  41. simtools/schemas/model_parameters/asum_offset.schema.yml +1 -1
  42. simtools/schemas/model_parameters/asum_threshold.schema.yml +1 -1
  43. simtools/schemas/model_parameters/axes_offsets.schema.yml +2 -2
  44. simtools/schemas/model_parameters/camera_body_diameter.schema.yml +1 -1
  45. simtools/schemas/model_parameters/camera_body_shape.schema.yml +1 -1
  46. simtools/schemas/model_parameters/camera_config_rotate.schema.yml +1 -1
  47. simtools/schemas/model_parameters/camera_degraded_efficiency.schema.yml +1 -1
  48. simtools/schemas/model_parameters/camera_depth.schema.yml +1 -1
  49. simtools/schemas/model_parameters/camera_pixels.schema.yml +1 -1
  50. simtools/schemas/model_parameters/camera_transmission.schema.yml +1 -1
  51. simtools/schemas/model_parameters/channels_per_chip.schema.yml +1 -1
  52. simtools/schemas/model_parameters/corsika_iact_io_buffer.schema.yml +1 -1
  53. simtools/schemas/model_parameters/corsika_iact_max_bunches.schema.yml +1 -1
  54. simtools/schemas/model_parameters/corsika_iact_split_auto.schema.yml +1 -1
  55. simtools/schemas/model_parameters/corsika_observation_level.schema.yml +1 -1
  56. simtools/schemas/model_parameters/dark_events.schema.yml +1 -1
  57. simtools/schemas/model_parameters/disc_bins.schema.yml +1 -1
  58. simtools/schemas/model_parameters/disc_start.schema.yml +1 -1
  59. simtools/schemas/model_parameters/discriminator_amplitude.schema.yml +1 -1
  60. simtools/schemas/model_parameters/discriminator_fall_time.schema.yml +1 -1
  61. simtools/schemas/model_parameters/discriminator_gate_length.schema.yml +1 -1
  62. simtools/schemas/model_parameters/discriminator_hysteresis.schema.yml +1 -1
  63. simtools/schemas/model_parameters/discriminator_output_amplitude.schema.yml +1 -1
  64. simtools/schemas/model_parameters/discriminator_output_var_percent.schema.yml +1 -1
  65. simtools/schemas/model_parameters/discriminator_rise_time.schema.yml +1 -1
  66. simtools/schemas/model_parameters/discriminator_scale_threshold.schema.yml +1 -1
  67. simtools/schemas/model_parameters/discriminator_sigsum_over_threshold.schema.yml +1 -1
  68. simtools/schemas/model_parameters/discriminator_threshold.schema.yml +1 -1
  69. simtools/schemas/model_parameters/discriminator_time_over_threshold.schema.yml +1 -1
  70. simtools/schemas/model_parameters/discriminator_var_gate_length.schema.yml +1 -1
  71. simtools/schemas/model_parameters/discriminator_var_sigsum_over_threshold.schema.yml +1 -1
  72. simtools/schemas/model_parameters/discriminator_var_threshold.schema.yml +1 -1
  73. simtools/schemas/model_parameters/discriminator_var_time_over_threshold.schema.yml +1 -1
  74. simtools/schemas/model_parameters/dish_shape_length.schema.yml +1 -1
  75. simtools/schemas/model_parameters/dsum_clipping.schema.yml +1 -1
  76. simtools/schemas/model_parameters/dsum_ignore_below.schema.yml +1 -1
  77. simtools/schemas/model_parameters/dsum_offset.schema.yml +1 -1
  78. simtools/schemas/model_parameters/dsum_pre_clipping.schema.yml +1 -1
  79. simtools/schemas/model_parameters/dsum_prescale.schema.yml +2 -2
  80. simtools/schemas/model_parameters/dsum_presum_max.schema.yml +1 -1
  81. simtools/schemas/model_parameters/dsum_presum_shift.schema.yml +1 -1
  82. simtools/schemas/model_parameters/dsum_threshold.schema.yml +1 -1
  83. simtools/schemas/model_parameters/dsum_zero_clip.schema.yml +1 -1
  84. simtools/schemas/model_parameters/effective_focal_length.schema.yml +5 -5
  85. simtools/schemas/model_parameters/epsg_code.schema.yml +1 -1
  86. simtools/schemas/model_parameters/fadc_amplitude.schema.yml +1 -1
  87. simtools/schemas/model_parameters/fadc_bins.schema.yml +1 -1
  88. simtools/schemas/model_parameters/fadc_compensate_pedestal.schema.yml +1 -1
  89. simtools/schemas/model_parameters/fadc_dev_pedestal.schema.yml +1 -1
  90. simtools/schemas/model_parameters/fadc_err_compensate_pedestal.schema.yml +1 -1
  91. simtools/schemas/model_parameters/fadc_err_pedestal.schema.yml +1 -1
  92. simtools/schemas/model_parameters/fadc_lg_amplitude.schema.yml +1 -1
  93. simtools/schemas/model_parameters/fadc_lg_compensate_pedestal.schema.yml +1 -1
  94. simtools/schemas/model_parameters/fadc_lg_dev_pedestal.schema.yml +1 -1
  95. simtools/schemas/model_parameters/fadc_lg_err_compensate_pedestal.schema.yml +1 -1
  96. simtools/schemas/model_parameters/fadc_lg_err_pedestal.schema.yml +1 -1
  97. simtools/schemas/model_parameters/fadc_lg_max_signal.schema.yml +1 -1
  98. simtools/schemas/model_parameters/fadc_lg_max_sum.schema.yml +1 -1
  99. simtools/schemas/model_parameters/fadc_lg_noise.schema.yml +1 -1
  100. simtools/schemas/model_parameters/fadc_lg_pedestal.schema.yml +1 -1
  101. simtools/schemas/model_parameters/fadc_lg_sensitivity.schema.yml +1 -1
  102. simtools/schemas/model_parameters/fadc_lg_sysvar_pedestal.schema.yml +1 -1
  103. simtools/schemas/model_parameters/fadc_lg_var_pedestal.schema.yml +1 -1
  104. simtools/schemas/model_parameters/fadc_lg_var_sensitivity.schema.yml +1 -1
  105. simtools/schemas/model_parameters/fadc_max_signal.schema.yml +1 -1
  106. simtools/schemas/model_parameters/fadc_max_sum.schema.yml +1 -1
  107. simtools/schemas/model_parameters/fadc_mhz.schema.yml +1 -1
  108. simtools/schemas/model_parameters/fadc_noise.schema.yml +1 -1
  109. simtools/schemas/model_parameters/fadc_pedestal.schema.yml +1 -1
  110. simtools/schemas/model_parameters/fadc_sensitivity.schema.yml +1 -1
  111. simtools/schemas/model_parameters/fadc_sum_bins.schema.yml +1 -1
  112. simtools/schemas/model_parameters/fadc_sum_offset.schema.yml +1 -1
  113. simtools/schemas/model_parameters/fadc_sysvar_pedestal.schema.yml +1 -1
  114. simtools/schemas/model_parameters/fadc_var_pedestal.schema.yml +1 -1
  115. simtools/schemas/model_parameters/fadc_var_sensitivity.schema.yml +1 -1
  116. simtools/schemas/model_parameters/focal_length.schema.yml +1 -1
  117. simtools/schemas/model_parameters/focal_surface_parameters.schema.yml +20 -20
  118. simtools/schemas/model_parameters/focal_surface_ref_radius.schema.yml +1 -1
  119. simtools/schemas/model_parameters/focus_offset.schema.yml +4 -4
  120. simtools/schemas/model_parameters/gain_variation.schema.yml +1 -1
  121. simtools/schemas/model_parameters/geomag_horizontal.schema.yml +1 -1
  122. simtools/schemas/model_parameters/geomag_rotation.schema.yml +1 -1
  123. simtools/schemas/model_parameters/geomag_vertical.schema.yml +1 -1
  124. simtools/schemas/model_parameters/hg_lg_variation.schema.yml +1 -1
  125. simtools/schemas/model_parameters/iobuf_maximum.schema.yml +1 -1
  126. simtools/schemas/model_parameters/iobuf_output_maximum.schema.yml +1 -1
  127. simtools/schemas/model_parameters/laser_events.schema.yml +1 -1
  128. simtools/schemas/model_parameters/laser_external_trigger.schema.yml +1 -1
  129. simtools/schemas/model_parameters/laser_photons.schema.yml +1 -1
  130. simtools/schemas/model_parameters/laser_pulse_exptime.schema.yml +1 -1
  131. simtools/schemas/model_parameters/laser_pulse_offset.schema.yml +1 -1
  132. simtools/schemas/model_parameters/laser_pulse_sigtime.schema.yml +1 -1
  133. simtools/schemas/model_parameters/laser_pulse_twidth.schema.yml +1 -1
  134. simtools/schemas/model_parameters/laser_var_photons.schema.yml +1 -1
  135. simtools/schemas/model_parameters/laser_wavelength.schema.yml +1 -1
  136. simtools/schemas/model_parameters/led_events.schema.yml +1 -1
  137. simtools/schemas/model_parameters/led_photons.schema.yml +1 -1
  138. simtools/schemas/model_parameters/led_pulse_offset.schema.yml +1 -1
  139. simtools/schemas/model_parameters/led_pulse_sigtime.schema.yml +1 -1
  140. simtools/schemas/model_parameters/led_var_photons.schema.yml +1 -1
  141. simtools/schemas/model_parameters/min_photoelectrons.schema.yml +1 -1
  142. simtools/schemas/model_parameters/min_photons.schema.yml +1 -1
  143. simtools/schemas/model_parameters/mirror_align_random_distance.schema.yml +1 -1
  144. simtools/schemas/model_parameters/mirror_align_random_horizontal.schema.yml +4 -4
  145. simtools/schemas/model_parameters/mirror_align_random_vertical.schema.yml +4 -4
  146. simtools/schemas/model_parameters/mirror_class.schema.yml +1 -1
  147. simtools/schemas/model_parameters/mirror_degraded_reflection.schema.yml +1 -1
  148. simtools/schemas/model_parameters/mirror_focal_length.schema.yml +1 -1
  149. simtools/schemas/model_parameters/mirror_offset.schema.yml +1 -1
  150. simtools/schemas/model_parameters/mirror_panel_2f_measurements.schema.yml +3 -3
  151. simtools/schemas/model_parameters/mirror_reflection_random_angle.schema.yml +3 -3
  152. simtools/schemas/model_parameters/multiplicity_offset.schema.yml +1 -1
  153. simtools/schemas/model_parameters/muon_mono_threshold.schema.yml +45 -0
  154. simtools/schemas/model_parameters/nsb_autoscale_airmass.schema.yml +2 -2
  155. simtools/schemas/model_parameters/nsb_gain_drop_scale.schema.yml +1 -1
  156. simtools/schemas/model_parameters/nsb_offaxis.schema.yml +5 -5
  157. simtools/schemas/model_parameters/nsb_pixel_rate.schema.yml +1 -1
  158. simtools/schemas/model_parameters/nsb_reference_value.schema.yml +1 -1
  159. simtools/schemas/model_parameters/nsb_scaling_factor.schema.yml +1 -1
  160. simtools/schemas/model_parameters/nsb_spectrum.schema.yml +2 -2
  161. simtools/schemas/model_parameters/num_gains.schema.yml +1 -1
  162. simtools/schemas/model_parameters/pedestal_events.schema.yml +1 -1
  163. simtools/schemas/model_parameters/photon_delay.schema.yml +1 -1
  164. simtools/schemas/model_parameters/photons_per_run.schema.yml +1 -1
  165. simtools/schemas/model_parameters/pixel_cells.schema.yml +1 -1
  166. simtools/schemas/model_parameters/pixels_parallel.schema.yml +1 -1
  167. simtools/schemas/model_parameters/pixeltrg_time_step.schema.yml +1 -1
  168. simtools/schemas/model_parameters/pm_average_gain.schema.yml +1 -1
  169. simtools/schemas/model_parameters/pm_collection_efficiency.schema.yml +1 -1
  170. simtools/schemas/model_parameters/pm_gain_index.schema.yml +1 -1
  171. simtools/schemas/model_parameters/pm_transit_time.schema.yml +4 -4
  172. simtools/schemas/model_parameters/pm_voltage_variation.schema.yml +1 -1
  173. simtools/schemas/model_parameters/primary_mirror_degraded_map.schema.yml +7 -3
  174. simtools/schemas/model_parameters/primary_mirror_diameter.schema.yml +1 -1
  175. simtools/schemas/model_parameters/primary_mirror_hole_diameter.schema.yml +1 -1
  176. simtools/schemas/model_parameters/primary_mirror_parameters.schema.yml +20 -20
  177. simtools/schemas/model_parameters/primary_mirror_ref_radius.schema.yml +1 -1
  178. simtools/schemas/model_parameters/qe_variation.schema.yml +1 -1
  179. simtools/schemas/model_parameters/random_focal_length.schema.yml +2 -2
  180. simtools/schemas/model_parameters/random_mono_probability.schema.yml +38 -0
  181. simtools/schemas/model_parameters/reference_point_altitude.schema.yml +1 -1
  182. simtools/schemas/model_parameters/reference_point_latitude.schema.yml +4 -1
  183. simtools/schemas/model_parameters/reference_point_longitude.schema.yml +4 -1
  184. simtools/schemas/model_parameters/reference_point_utm_east.schema.yml +1 -1
  185. simtools/schemas/model_parameters/reference_point_utm_north.schema.yml +1 -1
  186. simtools/schemas/model_parameters/secondary_mirror_baffle.schema.yml +5 -5
  187. simtools/schemas/model_parameters/secondary_mirror_degraded_reflection.schema.yml +1 -1
  188. simtools/schemas/model_parameters/secondary_mirror_diameter.schema.yml +1 -1
  189. simtools/schemas/model_parameters/secondary_mirror_hole_diameter.schema.yml +1 -1
  190. simtools/schemas/model_parameters/secondary_mirror_parameters.schema.yml +20 -20
  191. simtools/schemas/model_parameters/secondary_mirror_ref_radius.schema.yml +1 -1
  192. simtools/schemas/model_parameters/secondary_mirror_shadow_diameter.schema.yml +1 -1
  193. simtools/schemas/model_parameters/secondary_mirror_shadow_offset.schema.yml +1 -1
  194. simtools/schemas/model_parameters/stars.schema.yml +36 -0
  195. simtools/schemas/model_parameters/store_photoelectrons.schema.yml +1 -1
  196. simtools/schemas/model_parameters/tailcut_scale.schema.yml +1 -1
  197. simtools/schemas/model_parameters/telescope_axis_height.schema.yml +1 -1
  198. simtools/schemas/model_parameters/telescope_random_angle.schema.yml +1 -1
  199. simtools/schemas/model_parameters/telescope_random_error.schema.yml +1 -1
  200. simtools/schemas/model_parameters/telescope_sphere_radius.schema.yml +1 -1
  201. simtools/schemas/model_parameters/telescope_transmission.schema.yml +6 -6
  202. simtools/schemas/model_parameters/teltrig_min_sigsum.schema.yml +1 -1
  203. simtools/schemas/model_parameters/teltrig_min_time.schema.yml +1 -1
  204. simtools/schemas/model_parameters/transit_time_calib_error.schema.yml +1 -1
  205. simtools/schemas/model_parameters/transit_time_compensate_error.schema.yml +1 -1
  206. simtools/schemas/model_parameters/transit_time_compensate_step.schema.yml +1 -1
  207. simtools/schemas/model_parameters/transit_time_error.schema.yml +1 -1
  208. simtools/schemas/model_parameters/transit_time_jitter.schema.yml +1 -1
  209. simtools/schemas/model_parameters/trigger_current_limit.schema.yml +1 -1
  210. simtools/schemas/model_parameters/trigger_delay_compensation.schema.yml +4 -4
  211. simtools/schemas/model_parameters/trigger_pixels.schema.yml +1 -1
  212. simtools/simtel/simtel_config_writer.py +70 -38
  213. simtools/simtel/simtel_io_event_reader.py +278 -0
  214. simtools/simtel/simtel_io_event_writer.py +317 -0
  215. simtools/simtel/simulator_light_emission.py +181 -67
  216. simtools/simulator.py +2 -2
  217. simtools/testing/configuration.py +17 -0
  218. simtools/utils/general.py +33 -7
  219. simtools/utils/geometry.py +19 -0
  220. simtools/utils/names.py +7 -1
  221. simtools/visualization/visualize.py +2 -2
  222. simtools/production_configuration/extract_mc_event_data.py +0 -253
  223. simtools/simtel/simtel_io_events.py +0 -265
  224. {gammasimtools-0.13.0.dist-info → gammasimtools-0.14.0.dist-info/licenses}/LICENSE +0 -0
  225. {gammasimtools-0.13.0.dist-info → gammasimtools-0.14.0.dist-info}/top_level.txt +0 -0
  226. /simtools/schemas/model_parameters/{nsb_skymap.schema.yml → nsb_sky_map.schema.yml} +0 -0
@@ -0,0 +1,317 @@
1
+ """Generate a reduced dataset from given simulation event list and save the output to file."""
2
+
3
+ import logging
4
+ from dataclasses import dataclass, field
5
+
6
+ import numpy as np
7
+ import tables
8
+ from eventio import EventIOFile
9
+ from eventio.simtel import (
10
+ ArrayEvent,
11
+ MCEvent,
12
+ MCRunHeader,
13
+ MCShower,
14
+ TrackingPosition,
15
+ TriggerInformation,
16
+ )
17
+
18
+ from simtools.utils.geometry import calculate_circular_mean
19
+
20
+ DEFAULT_FILTERS = tables.Filters(complevel=5, complib="zlib", shuffle=True, bitshuffle=False)
21
+
22
+
23
+ @dataclass
24
+ class ShowerEventData:
25
+ """Shower event data."""
26
+
27
+ simulated_energy: list = field(default_factory=list)
28
+ x_core: list = field(default_factory=list)
29
+ y_core: list = field(default_factory=list)
30
+ shower_azimuth: list = field(default_factory=list)
31
+ shower_altitude: list = field(default_factory=list)
32
+ shower_id: list = field(default_factory=list)
33
+ area_weight: list = field(default_factory=list)
34
+
35
+ x_core_shower: list = field(default_factory=list)
36
+ y_core_shower: list = field(default_factory=list)
37
+ core_distance_shower: list = field(default_factory=list)
38
+
39
+
40
+ @dataclass
41
+ class TriggeredEventData:
42
+ """Triggered event data."""
43
+
44
+ triggered_id: list = field(default_factory=list)
45
+ array_altitudes: list = field(default_factory=list)
46
+ array_azimuths: list = field(default_factory=list)
47
+ trigger_telescope_list_list: list = field(default_factory=list)
48
+ angular_distance: list = field(default_factory=list)
49
+
50
+
51
+ class SimtelIOEventDataWriter:
52
+ """
53
+ Generate a reduced dataset from given simulation event list and save the output to file.
54
+
55
+ Attributes
56
+ ----------
57
+ input_files : list
58
+ List of input file paths to process.
59
+ output_file : str
60
+ Path to the output file.
61
+ max_files : int, optional
62
+ Maximum number of files to process.
63
+ """
64
+
65
+ def __init__(self, input_files, output_file, max_files=100):
66
+ """Initialize class."""
67
+ self._logger = logging.getLogger(__name__)
68
+ self.input_files = input_files
69
+ self.output_file = output_file
70
+ try:
71
+ self.max_files = max_files if max_files < len(input_files) else len(input_files)
72
+ except TypeError as exc:
73
+ raise TypeError("No input files provided.") from exc
74
+ self.shower = None
75
+ self.n_use = None
76
+ self.shower_id_offset = 0
77
+ self.event_data = ShowerEventData()
78
+ self.triggered_data = TriggeredEventData()
79
+ self.file_names = []
80
+
81
+ def process_files(self):
82
+ """Process the input files and store them in an file."""
83
+ self.shower_id_offset = 0
84
+
85
+ for i, file in enumerate(self.input_files[: self.max_files], start=1):
86
+ self._logger.info(f"Processing file {i}/{self.max_files}: {file}")
87
+ self._process_file(file)
88
+ if i == 1 or len(self.event_data.simulated_energy) >= 1e7:
89
+ self._write_data(mode="w" if i == 1 else "a")
90
+ self.shower_id_offset += len(self.event_data.simulated_energy)
91
+ self._reset_data()
92
+
93
+ self._write_data(mode="a")
94
+
95
+ def get_event_data(self):
96
+ """
97
+ Return shower and triggered event data.
98
+
99
+ Returns
100
+ -------
101
+ ShowerEventData, TriggeredEventData
102
+ Shower and triggered event data.
103
+ """
104
+ return self.event_data, self.triggered_data
105
+
106
+ def _process_file(self, file):
107
+ """Process a single file and update data lists."""
108
+ with EventIOFile(file) as f:
109
+ for eventio_object in f:
110
+ if isinstance(eventio_object, MCRunHeader):
111
+ self._process_mc_run_header(eventio_object)
112
+ elif isinstance(eventio_object, MCShower):
113
+ self._process_mc_shower(eventio_object)
114
+ elif isinstance(eventio_object, MCEvent):
115
+ self._process_mc_event(eventio_object)
116
+ elif isinstance(eventio_object, ArrayEvent):
117
+ self._process_array_event(eventio_object)
118
+ self.file_names.append(str(file))
119
+
120
+ def _process_mc_run_header(self, eventio_object):
121
+ """Process MC run header and update data lists."""
122
+ mc_head = eventio_object.parse()
123
+ self.n_use = mc_head["n_use"] # reuse factor n_use needed to extend the values below
124
+ self._logger.info(f"Shower reuse factor: {self.n_use} (viewcone: {mc_head['viewcone']})")
125
+
126
+ def _process_mc_shower(self, eventio_object):
127
+ """
128
+ Process MC shower and update shower event list.
129
+
130
+ Duplicated entries 'self.n_use' times to match the number simulated events with
131
+ different core positions.
132
+ """
133
+ self.shower = eventio_object.parse()
134
+
135
+ self.event_data.simulated_energy.extend([self.shower["energy"]] * self.n_use)
136
+ self.event_data.shower_azimuth.extend([self.shower["azimuth"]] * self.n_use)
137
+ self.event_data.shower_altitude.extend([self.shower["altitude"]] * self.n_use)
138
+
139
+ def _process_mc_event(self, eventio_object):
140
+ """Process MC event and update shower event list."""
141
+ event = eventio_object.parse()
142
+
143
+ self.event_data.shower_id.append(event["shower_num"])
144
+ self.event_data.x_core.append(event["xcore"])
145
+ self.event_data.y_core.append(event["ycore"])
146
+ self.event_data.area_weight.append(event["aweight"])
147
+
148
+ def _process_array_event(self, eventio_object):
149
+ """Process array event and update triggered event list."""
150
+ tracking_positions = []
151
+
152
+ for _, obj in enumerate(eventio_object):
153
+ if isinstance(obj, TriggerInformation):
154
+ self._process_trigger_information(obj)
155
+
156
+ if isinstance(obj, TrackingPosition):
157
+ tracking_position = obj.parse()
158
+ tracking_positions.append(
159
+ {
160
+ "altitude": tracking_position["altitude_raw"],
161
+ "azimuth": tracking_position["azimuth_raw"],
162
+ }
163
+ )
164
+
165
+ if tracking_positions:
166
+ self._process_tracking_positions(tracking_positions)
167
+
168
+ def _process_tracking_positions(self, tracking_positions):
169
+ """
170
+ Process collected tracking positions and update triggered event list.
171
+
172
+ Use mean telescope tracking positions, averaged over all triggered telescopes.
173
+ """
174
+ altitudes = [pos["altitude"] for pos in tracking_positions]
175
+ azimuths = [pos["azimuth"] for pos in tracking_positions]
176
+
177
+ self.triggered_data.array_altitudes.append(np.mean(altitudes))
178
+ self.triggered_data.array_azimuths.append(calculate_circular_mean(azimuths))
179
+
180
+ def _process_trigger_information(self, trigger_info):
181
+ """Process trigger information and update triggered event list."""
182
+ trigger_info = trigger_info.parse()
183
+ telescopes = trigger_info["triggered_telescopes"]
184
+ if len(telescopes) > 0:
185
+ # add offset to obtain unique shower IDs among all files
186
+ self.triggered_data.triggered_id.append(self.shower["shower"] + self.shower_id_offset)
187
+ self.triggered_data.trigger_telescope_list_list.append(
188
+ np.array(telescopes, dtype=np.int16)
189
+ )
190
+
191
+ def _table_descriptions(self):
192
+ """HDF5 table descriptions for shower data, triggered data, and file names."""
193
+ shower_data_desc = {
194
+ "shower_id": tables.Int32Col(),
195
+ "simulated_energy": tables.Float32Col(),
196
+ "x_core": tables.Float32Col(),
197
+ "y_core": tables.Float32Col(),
198
+ "area_weight": tables.Float32Col(),
199
+ "shower_azimuth": tables.Float32Col(),
200
+ "shower_altitude": tables.Float32Col(),
201
+ }
202
+ triggered_data_desc = {
203
+ "triggered_id": tables.Int32Col(),
204
+ "array_altitudes": tables.Float32Col(),
205
+ "array_azimuths": tables.Float32Col(),
206
+ "telescope_list_index": tables.Int32Col(), # Index into VLArray
207
+ }
208
+ file_names_desc = {
209
+ "file_names": tables.StringCol(256),
210
+ }
211
+ return shower_data_desc, triggered_data_desc, file_names_desc
212
+
213
+ def _tables(self, output_file, data_group, mode="a"):
214
+ """Create or get HDF5 tables."""
215
+ descriptions = self._table_descriptions()
216
+ table_names = ["reduced_data", "triggered_data", "file_names"]
217
+
218
+ table_dict = {}
219
+ for name, desc in zip(table_names, descriptions):
220
+ path = f"/data/{name}"
221
+ table_dict[name] = (
222
+ output_file.create_table(
223
+ data_group, name, desc, name.replace("_", " ").title(), filters=DEFAULT_FILTERS
224
+ )
225
+ if mode == "w" or path not in output_file
226
+ else output_file.get_node(path)
227
+ )
228
+
229
+ return table_dict["reduced_data"], table_dict["triggered_data"], table_dict["file_names"]
230
+
231
+ def _write_event_data(self, reduced_table):
232
+ """Fill event data tables."""
233
+ if len(self.event_data.simulated_energy) == 0:
234
+ return
235
+ row = reduced_table.row
236
+ for i, energy in enumerate(self.event_data.simulated_energy):
237
+ row["shower_id"] = (
238
+ self.event_data.shower_id[i] if i < len(self.event_data.shower_id) else 0
239
+ )
240
+ row["simulated_energy"] = energy
241
+ row["x_core"] = self.event_data.x_core[i] if i < len(self.event_data.x_core) else 0
242
+ row["y_core"] = self.event_data.y_core[i] if i < len(self.event_data.y_core) else 0
243
+ row["area_weight"] = (
244
+ self.event_data.area_weight[i] if i < len(self.event_data.area_weight) else 0
245
+ )
246
+ row["shower_azimuth"] = (
247
+ self.event_data.shower_azimuth[i] if i < len(self.event_data.shower_azimuth) else 0
248
+ )
249
+ row["shower_altitude"] = (
250
+ self.event_data.shower_altitude[i]
251
+ if i < len(self.event_data.shower_altitude)
252
+ else 0
253
+ )
254
+ row.append()
255
+ reduced_table.flush()
256
+
257
+ def _writer_triggered_data(self, triggered_table, vlarray):
258
+ """Fill triggered event data tables."""
259
+ # Get or create VLArray for telescope lists
260
+ if len(self.triggered_data.triggered_id) == 0:
261
+ return
262
+ row = triggered_table.row
263
+ start_idx = vlarray.nrows
264
+ for i, triggered_id in enumerate(self.triggered_data.triggered_id):
265
+ row["triggered_id"] = triggered_id
266
+ row["array_altitudes"] = (
267
+ self.triggered_data.array_altitudes[i]
268
+ if i < len(self.triggered_data.array_altitudes)
269
+ else 0
270
+ )
271
+ row["array_azimuths"] = (
272
+ self.triggered_data.array_azimuths[i]
273
+ if i < len(self.triggered_data.array_azimuths)
274
+ else 0
275
+ )
276
+ row["telescope_list_index"] = start_idx + i # Index into the VLArray
277
+ row.append()
278
+ vlarray.append(
279
+ self.triggered_data.trigger_telescope_list_list[i]
280
+ if i < len(self.triggered_data.trigger_telescope_list_list)
281
+ else []
282
+ )
283
+ triggered_table.flush()
284
+
285
+ def _write_data(self, mode="a"):
286
+ """Write data to HDF5 file."""
287
+ with tables.open_file(self.output_file, mode=mode) as f:
288
+ data_group = (
289
+ f.create_group("/", "data", "Data group")
290
+ if mode == "w" or "/data" not in f
291
+ else f.get_node("/data")
292
+ )
293
+
294
+ reduced_table, triggered_table, file_names_table = self._tables(f, data_group, mode)
295
+ self._write_event_data(reduced_table)
296
+
297
+ vlarray = (
298
+ f.create_vlarray(
299
+ data_group,
300
+ "trigger_telescope_list_list",
301
+ tables.Int16Atom(),
302
+ "List of triggered telescope IDs",
303
+ )
304
+ if mode == "w" or "/data/trigger_telescope_list_list" not in f
305
+ else f.get_node("/data/trigger_telescope_list_list")
306
+ )
307
+ self._writer_triggered_data(triggered_table, vlarray)
308
+
309
+ if self.file_names:
310
+ file_names_table.append([[name] for name in self.file_names])
311
+ file_names_table.flush()
312
+
313
+ def _reset_data(self):
314
+ """Reset data structures for batch processing."""
315
+ self.event_data = ShowerEventData()
316
+ self.triggered_data = TriggeredEventData()
317
+ self.file_names = []
@@ -2,14 +2,17 @@
2
2
 
3
3
  import logging
4
4
  import stat
5
+ import subprocess
5
6
  from pathlib import Path
6
7
 
7
8
  import astropy.units as u
8
9
  import numpy as np
9
10
 
11
+ from simtools.corsika.corsika_histograms_visualize import save_figs_to_pdf
10
12
  from simtools.io_operations import io_handler
11
13
  from simtools.runners.simtel_runner import SimtelRunner
12
14
  from simtools.utils.general import clear_default_sim_telarray_cfg_directories
15
+ from simtools.visualization.visualize import plot_simtel_ctapipe
13
16
 
14
17
  __all__ = ["SimulatorLightEmission"]
15
18
 
@@ -18,36 +21,7 @@ class SimulatorLightEmission(SimtelRunner):
18
21
  """
19
22
  Interface with sim_telarray to perform light emission package simulations.
20
23
 
21
- The light emission package is used to simulate a artificial light source, used for calibration.
22
-
23
- The angle and pointing vector calculations use the convention north (x) towards
24
- east (-y).
25
-
26
- Parameters
27
- ----------
28
- telescope_model:
29
- Instance of the TelescopeModel class.
30
- calibration_model:
31
- CalibrationModel instance to define calibration device.
32
- site_model:
33
- SiteModel instance to define the site specific parameters.
34
- default_le_config: dict
35
- defines parameters for running the sim_telarray light emission application.
36
- le_application: str
37
- Name of the application. Default sim_telarray application running
38
- the sim_telarray LightEmission package is xyzls.
39
- simtel_path: str or Path
40
- Location of sim_telarray installation.
41
- light_source_type: str
42
- The light source type.
43
- label: str
44
- Label for output directory.
45
- config_data: dict
46
- Dict containing the configurable parameters.
47
- config_file: str or Path
48
- Path of the yaml file containing the configurable parameters.
49
- test: bool
50
- Test flag.
24
+ The light emission package is used to simulate an artificial light source, used for calibration.
51
25
  """
52
26
 
53
27
  def __init__(
@@ -55,7 +29,7 @@ class SimulatorLightEmission(SimtelRunner):
55
29
  telescope_model,
56
30
  calibration_model,
57
31
  site_model,
58
- default_le_config,
32
+ light_emission_config,
59
33
  le_application,
60
34
  simtel_path,
61
35
  light_source_type,
@@ -86,8 +60,8 @@ class SimulatorLightEmission(SimtelRunner):
86
60
  )
87
61
 
88
62
  self.le_application = le_application
89
- self.default_le_config = default_le_config
90
- self.distance = self.telescope_calibration_device_distance()
63
+ self.light_emission_config = light_emission_config
64
+ self.distance = None
91
65
  self.light_source_type = light_source_type
92
66
  self._telescope_model.export_config_file()
93
67
  self.test = test
@@ -179,36 +153,6 @@ class SimulatorLightEmission(SimtelRunner):
179
153
  )
180
154
  return pointing_vector.tolist(), [tel_theta, tel_phi, laser_theta, laser_phi]
181
155
 
182
- def telescope_calibration_device_distance(self):
183
- """
184
- Calculate the distance between the telescope and the calibration device.
185
-
186
- Returns
187
- -------
188
- astropy Quantity
189
- The distance between the telescope and the calibration device.
190
- """
191
- if not self.default_le_config:
192
- x_cal, y_cal, z_cal = self._calibration_model.get_parameter_value(
193
- "array_element_position_ground"
194
- )
195
- x_tel, y_tel, z_tel = self._telescope_model.get_parameter_value(
196
- "array_element_position_ground"
197
- )
198
-
199
- else:
200
- x_tel = self.default_le_config["x_pos"]["default"].to(u.m).value
201
- y_tel = self.default_le_config["y_pos"]["default"].to(u.m).value
202
- z_tel = self.default_le_config["z_pos"]["default"].to(u.m).value
203
-
204
- x_cal, y_cal, z_cal = 0, 0, 0
205
-
206
- tel_vect = np.array([x_tel, y_tel, z_tel])
207
- cal_vect = np.array([x_cal, y_cal, z_cal])
208
- distance = np.linalg.norm(cal_vect - tel_vect)
209
-
210
- return distance * u.m
211
-
212
156
  def _make_light_emission_script(self):
213
157
  """
214
158
  Create the light emission script to run the light emission package.
@@ -235,11 +179,11 @@ class SimulatorLightEmission(SimtelRunner):
235
179
 
236
180
  if self.light_source_type == "led":
237
181
  if self.le_application[1] == "variable":
238
- command += f" -x {self.default_le_config['x_pos']['default'].to(u.cm).value}"
239
- command += f" -y {self.default_le_config['y_pos']['default'].to(u.cm).value}"
240
- command += f" -z {self.default_le_config['z_pos']['default'].to(u.cm).value}"
182
+ command += f" -x {self.light_emission_config['x_pos']['default'].to(u.cm).value}"
183
+ command += f" -y {self.light_emission_config['y_pos']['default'].to(u.cm).value}"
184
+ command += f" -z {self.light_emission_config['z_pos']['default'].to(u.cm).value}"
241
185
  command += (
242
- f" -d {','.join(map(str, self.default_le_config['direction']['default']))}"
186
+ f" -d {','.join(map(str, self.light_emission_config['direction']['default']))}"
243
187
  )
244
188
  command += f" -n {self.photons_per_run}"
245
189
 
@@ -470,3 +414,173 @@ class SimulatorLightEmission(SimtelRunner):
470
414
 
471
415
  _script_file.chmod(_script_file.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP)
472
416
  return _script_file
417
+
418
+ def process_simulation_output(self, args_dict, figures):
419
+ """Process the simulation output, including plotting and saving figures."""
420
+ try:
421
+ filename = self._get_simulation_output_filename()
422
+ distance = self._get_distance_for_plotting()
423
+
424
+ fig = self._plot_simulation_output(
425
+ filename,
426
+ args_dict["boundary_thresh"],
427
+ args_dict["picture_thresh"],
428
+ args_dict["min_neighbors"],
429
+ distance,
430
+ args_dict["return_cleaned"],
431
+ )
432
+ figures.append(fig)
433
+
434
+ except AttributeError:
435
+ msg = (
436
+ f"Telescope not triggered at distance of "
437
+ f"{self.light_emission_config['z_pos']['default']}"
438
+ )
439
+ self._logger.warning(msg)
440
+
441
+ def _get_simulation_output_filename(self):
442
+ """Get the filename of the simulation output."""
443
+ return (
444
+ f"{self.output_directory}/{self.le_application[0]}_{self.le_application[1]}.simtel.gz"
445
+ )
446
+
447
+ def _get_distance_for_plotting(self):
448
+ """Get the distance to be used for plotting."""
449
+ try:
450
+ return self.light_emission_config["z_pos"]["default"]
451
+ except KeyError:
452
+ return round(self.distance, 2)
453
+
454
+ def _plot_simulation_output(
455
+ self, filename, boundary_thresh, picture_thresh, min_neighbors, distance, return_cleaned
456
+ ):
457
+ """Plot the simulation output."""
458
+ return plot_simtel_ctapipe(
459
+ filename,
460
+ cleaning_args=[boundary_thresh, picture_thresh, min_neighbors],
461
+ distance=distance,
462
+ return_cleaned=return_cleaned,
463
+ )
464
+
465
+ def save_figures_to_pdf(self, figures, telescope):
466
+ """Save the generated figures to a PDF file."""
467
+ save_figs_to_pdf(
468
+ figures,
469
+ f"{self.output_directory}/"
470
+ f"{telescope}_{self.le_application[0]}_{self.le_application[1]}.pdf",
471
+ )
472
+
473
+ def run_simulation(self, args_dict, figures):
474
+ """Run the light emission simulation."""
475
+ run_script = self.prepare_script(generate_postscript=True, **args_dict)
476
+ log_file = Path(self.output_directory) / "logfile.log"
477
+ with open(log_file, "w", encoding="utf-8") as log_file:
478
+ subprocess.run(
479
+ run_script,
480
+ shell=False,
481
+ check=False,
482
+ text=True,
483
+ stdout=log_file,
484
+ stderr=log_file,
485
+ )
486
+ self.process_simulation_output(args_dict, figures)
487
+
488
+ def distance_list(self, arg):
489
+ """
490
+ Convert distance list to astropy quantities.
491
+
492
+ Parameters
493
+ ----------
494
+ arg: list
495
+ List of distances.
496
+
497
+ Returns
498
+ -------
499
+ values: list
500
+ List of distances as astropy quantities.
501
+ """
502
+ try:
503
+ return [float(x) * u.m for x in arg]
504
+ except ValueError as exc:
505
+ raise ValueError("Distances must be numeric values") from exc
506
+
507
+ def update_light_emission_config(self, key: str, value):
508
+ """
509
+ Update the light emission configuration.
510
+
511
+ Parameters
512
+ ----------
513
+ key : str
514
+ The key in the configuration to update.
515
+ value : Any
516
+ The new value to set for the key.
517
+ """
518
+ if key in self.light_emission_config:
519
+ self.light_emission_config[key]["default"] = value
520
+ else:
521
+ raise KeyError(f"Key '{key}' not found in light emission configuration.")
522
+
523
+ def calculate_distance_telescope_calibration_device(self):
524
+ """
525
+ Calculate the distance(s) between the telescope and the calibration device.
526
+
527
+ Returns
528
+ -------
529
+ list of astropy.Quantity
530
+ A list of distances for variable positions or a single distance for layout positions.
531
+ """
532
+ if not self.light_emission_config:
533
+ # Layout positions: Use DB coordinates
534
+ x_cal, y_cal, z_cal = self._calibration_model.get_parameter_value(
535
+ "array_element_position_ground"
536
+ )
537
+ x_tel, y_tel, z_tel = self._telescope_model.get_parameter_value(
538
+ "array_element_position_ground"
539
+ )
540
+ tel_vect = np.array([x_tel, y_tel, z_tel])
541
+ cal_vect = np.array([x_cal, y_cal, z_cal])
542
+ distance = np.linalg.norm(cal_vect - tel_vect)
543
+ return [distance * u.m]
544
+
545
+ # Variable positions: Calculate distances for all positions
546
+ x_tel = self.light_emission_config["x_pos"]["default"].to(u.m).value
547
+ y_tel = self.light_emission_config["y_pos"]["default"].to(u.m).value
548
+ z_positions = self.light_emission_config["z_pos"]["default"]
549
+
550
+ distances = []
551
+ for z in z_positions:
552
+ tel_vect = np.array([x_tel, y_tel, z.to(u.m).value])
553
+ cal_vect = np.array([0, 0, 0]) # Calibration device at origin
554
+ distances.append(np.linalg.norm(cal_vect - tel_vect) * u.m)
555
+ return distances
556
+
557
+ def simulate_variable_distances(self, args_dict):
558
+ """Simulate light emission for variable distances."""
559
+ if args_dict["distances_ls"] is not None:
560
+ self.update_light_emission_config(
561
+ "z_pos", self.distance_list(args_dict["distances_ls"])
562
+ )
563
+ self._logger.info(
564
+ f"Simulating for distances: {self.light_emission_config['z_pos']['default']}"
565
+ )
566
+
567
+ figures = []
568
+ distances = self.calculate_distance_telescope_calibration_device()
569
+
570
+ for current_distance, z_pos in zip(
571
+ distances, self.light_emission_config["z_pos"]["default"]
572
+ ):
573
+ self.update_light_emission_config("z_pos", z_pos)
574
+ self.distance = current_distance
575
+ self.run_simulation(args_dict, figures)
576
+
577
+ self.save_figures_to_pdf(figures, args_dict["telescope"])
578
+
579
+ def simulate_layout_positions(self, args_dict):
580
+ """Simulate light emission for layout positions."""
581
+ figures = []
582
+ self.distance = self.calculate_distance_telescope_calibration_device()[
583
+ 0
584
+ ] # Single distance for layout
585
+ self.run_simulation(args_dict, figures)
586
+ self.save_figures_to_pdf(figures, args_dict["telescope"])
simtools/simulator.py CHANGED
@@ -329,7 +329,7 @@ class Simulator:
329
329
  input_file_list = self._enforce_list_type(input_file_list)
330
330
  _runs_and_files = {self._guess_run_from_file(file): file for file in input_file_list}
331
331
  elif self.simulation_software in ["corsika", "corsika_simtel"]:
332
- _runs_and_files = {run: None for run in self._get_runs_to_simulate()}
332
+ _runs_and_files = dict.fromkeys(self._get_runs_to_simulate())
333
333
  if len(_runs_and_files) == 0:
334
334
  raise ValueError("No runs to submit.")
335
335
  return _runs_and_files
@@ -392,7 +392,7 @@ class Simulator:
392
392
 
393
393
  """
394
394
  keys = ["output", "sub_out", "log", "input", "hist", "corsika_log"]
395
- defaults = {key: None for key in keys}
395
+ defaults = dict.fromkeys(keys)
396
396
  results = {key: defaults[key] for key in keys}
397
397
  results["output"] = str(
398
398
  self._simulation_runner.get_file_name(file_type="output", run_number=run_number)
@@ -1,6 +1,7 @@
1
1
  """Integration test configuration."""
2
2
 
3
3
  import logging
4
+ import os
4
5
  from pathlib import Path
5
6
 
6
7
  import yaml
@@ -14,6 +15,10 @@ class VersionError(Exception):
14
15
  """Raise if model version requested is not supported."""
15
16
 
16
17
 
18
+ class ProductionDBError(Exception):
19
+ """Raise if production db is used."""
20
+
21
+
17
22
  def get_list_of_test_configurations(config_files):
18
23
  """
19
24
  Return list of test configuration dictionaries or test names.
@@ -100,6 +105,7 @@ def configure(config, tmp_test_directory, request):
100
105
 
101
106
  if "CONFIGURATION" in config:
102
107
  _skip_test_for_model_version(config, model_version_requested)
108
+ _skip_test_for_production_db(config)
103
109
 
104
110
  config_file, config_string, config_file_model_version = _prepare_test_options(
105
111
  config["CONFIGURATION"],
@@ -130,6 +136,17 @@ def _skip_test_for_model_version(config, model_version_requested):
130
136
  )
131
137
 
132
138
 
139
+ def _skip_test_for_production_db(config):
140
+ """Skip test if production db is used."""
141
+ simtools_db_server = os.environ.get("SIMTOOLS_DB_SERVER")
142
+ if (
143
+ simtools_db_server
144
+ and config.get("SKIP_FOR_PRODUCTION_DB")
145
+ and "db.zeuthen.desy.de" in simtools_db_server
146
+ ):
147
+ raise ProductionDBError("Production database used for this test")
148
+
149
+
133
150
  def _prepare_test_options(config, output_path, model_version=None):
134
151
  """
135
152
  Prepare test configuration.