gammasimtools 0.14.0__py3-none-any.whl → 0.16.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. {gammasimtools-0.14.0.dist-info → gammasimtools-0.16.0.dist-info}/METADATA +2 -32
  2. {gammasimtools-0.14.0.dist-info → gammasimtools-0.16.0.dist-info}/RECORD +222 -214
  3. {gammasimtools-0.14.0.dist-info → gammasimtools-0.16.0.dist-info}/WHEEL +1 -1
  4. {gammasimtools-0.14.0.dist-info → gammasimtools-0.16.0.dist-info}/entry_points.txt +5 -2
  5. simtools/_version.py +2 -2
  6. simtools/applications/calculate_trigger_rate.py +10 -10
  7. simtools/applications/convert_all_model_parameters_from_simtel.py +16 -16
  8. simtools/applications/convert_model_parameter_from_simtel.py +1 -1
  9. simtools/applications/derive_psf_parameters.py +12 -9
  10. simtools/applications/docs_produce_array_element_report.py +3 -3
  11. simtools/applications/docs_produce_calibration_reports.py +49 -0
  12. simtools/applications/docs_produce_simulation_configuration_report.py +50 -0
  13. simtools/applications/{generate_simtel_array_histograms.py → generate_sim_telarray_histograms.py} +2 -2
  14. simtools/applications/production_derive_corsika_limits.py +63 -10
  15. simtools/applications/production_derive_statistics.py +125 -0
  16. simtools/applications/production_generate_grid.py +197 -0
  17. simtools/applications/production_generate_simulation_config.py +0 -10
  18. simtools/applications/simulate_light_emission.py +5 -13
  19. simtools/applications/simulate_prod.py +16 -4
  20. simtools/applications/validate_cumulative_psf.py +6 -4
  21. simtools/applications/validate_file_using_schema.py +7 -3
  22. simtools/applications/validate_optics.py +5 -4
  23. simtools/camera/camera_efficiency.py +14 -39
  24. simtools/configuration/commandline_parser.py +4 -3
  25. simtools/configuration/configurator.py +10 -0
  26. simtools/corsika/corsika_config.py +103 -5
  27. simtools/data_model/format_checkers.py +9 -0
  28. simtools/data_model/model_data_writer.py +3 -0
  29. simtools/data_model/schema.py +27 -16
  30. simtools/data_model/validate_data.py +27 -7
  31. simtools/db/db_handler.py +10 -4
  32. simtools/layout/array_layout.py +1 -0
  33. simtools/model/array_model.py +63 -29
  34. simtools/model/model_parameter.py +76 -51
  35. simtools/model/model_utils.py +43 -1
  36. simtools/model/site_model.py +3 -2
  37. simtools/model/telescope_model.py +4 -4
  38. simtools/production_configuration/calculate_statistical_errors_grid_point.py +0 -4
  39. simtools/production_configuration/{event_scaler.py → derive_production_statistics.py} +24 -20
  40. simtools/production_configuration/derive_production_statistics_handler.py +119 -0
  41. simtools/production_configuration/generate_production_grid.py +364 -0
  42. simtools/production_configuration/generate_simulation_config.py +9 -9
  43. simtools/production_configuration/interpolation_handler.py +16 -11
  44. simtools/ray_tracing/mirror_panel_psf.py +16 -20
  45. simtools/ray_tracing/psf_analysis.py +2 -2
  46. simtools/ray_tracing/ray_tracing.py +5 -1
  47. simtools/reporting/docs_read_parameters.py +361 -58
  48. simtools/runners/corsika_runner.py +11 -1
  49. simtools/runners/corsika_simtel_runner.py +80 -89
  50. simtools/runners/runner_services.py +17 -4
  51. simtools/runners/simtel_runner.py +27 -10
  52. simtools/schemas/model_parameter.metaschema.yml +4 -0
  53. simtools/schemas/model_parameter_and_data_schema.metaschema.yml +1 -0
  54. simtools/schemas/model_parameters/adjust_gain.schema.yml +2 -2
  55. simtools/schemas/model_parameters/array_element_position_ground.schema.yml +2 -2
  56. simtools/schemas/model_parameters/array_element_position_utm.schema.yml +2 -2
  57. simtools/schemas/model_parameters/array_window.schema.yml +2 -2
  58. simtools/schemas/model_parameters/asum_offset.schema.yml +2 -2
  59. simtools/schemas/model_parameters/asum_shaping.schema.yml +2 -2
  60. simtools/schemas/model_parameters/asum_threshold.schema.yml +2 -2
  61. simtools/schemas/model_parameters/axes_offsets.schema.yml +2 -2
  62. simtools/schemas/model_parameters/camera_body_diameter.schema.yml +2 -2
  63. simtools/schemas/model_parameters/camera_body_shape.schema.yml +2 -2
  64. simtools/schemas/model_parameters/camera_config_file.schema.yml +2 -2
  65. simtools/schemas/model_parameters/camera_config_rotate.schema.yml +2 -2
  66. simtools/schemas/model_parameters/camera_degraded_efficiency.schema.yml +2 -2
  67. simtools/schemas/model_parameters/camera_degraded_map.schema.yml +2 -2
  68. simtools/schemas/model_parameters/camera_depth.schema.yml +2 -2
  69. simtools/schemas/model_parameters/camera_filter.schema.yml +2 -2
  70. simtools/schemas/model_parameters/camera_pixels.schema.yml +2 -2
  71. simtools/schemas/model_parameters/camera_transmission.schema.yml +2 -2
  72. simtools/schemas/model_parameters/channels_per_chip.schema.yml +2 -2
  73. simtools/schemas/model_parameters/correct_nsb_spectrum_to_telescope_altitude.schema.yml +2 -2
  74. simtools/schemas/model_parameters/corsika_starting_grammage.schema.yml +90 -1
  75. simtools/schemas/model_parameters/default_trigger.schema.yml +2 -2
  76. simtools/schemas/model_parameters/design_model.schema.yml +2 -2
  77. simtools/schemas/model_parameters/disc_ac_coupled.schema.yml +2 -2
  78. simtools/schemas/model_parameters/disc_bins.schema.yml +2 -2
  79. simtools/schemas/model_parameters/disc_start.schema.yml +2 -2
  80. simtools/schemas/model_parameters/discriminator_amplitude.schema.yml +2 -2
  81. simtools/schemas/model_parameters/discriminator_fall_time.schema.yml +2 -2
  82. simtools/schemas/model_parameters/discriminator_gate_length.schema.yml +2 -2
  83. simtools/schemas/model_parameters/discriminator_hysteresis.schema.yml +2 -2
  84. simtools/schemas/model_parameters/discriminator_output_amplitude.schema.yml +2 -2
  85. simtools/schemas/model_parameters/discriminator_output_var_percent.schema.yml +2 -2
  86. simtools/schemas/model_parameters/discriminator_pulse_shape.schema.yml +2 -2
  87. simtools/schemas/model_parameters/discriminator_rise_time.schema.yml +2 -2
  88. simtools/schemas/model_parameters/discriminator_scale_threshold.schema.yml +2 -2
  89. simtools/schemas/model_parameters/discriminator_sigsum_over_threshold.schema.yml +2 -2
  90. simtools/schemas/model_parameters/discriminator_threshold.schema.yml +2 -2
  91. simtools/schemas/model_parameters/discriminator_time_over_threshold.schema.yml +2 -2
  92. simtools/schemas/model_parameters/discriminator_var_gate_length.schema.yml +2 -2
  93. simtools/schemas/model_parameters/discriminator_var_sigsum_over_threshold.schema.yml +2 -2
  94. simtools/schemas/model_parameters/discriminator_var_threshold.schema.yml +2 -2
  95. simtools/schemas/model_parameters/discriminator_var_time_over_threshold.schema.yml +2 -2
  96. simtools/schemas/model_parameters/dish_shape_length.schema.yml +2 -2
  97. simtools/schemas/model_parameters/dsum_clipping.schema.yml +2 -2
  98. simtools/schemas/model_parameters/dsum_ignore_below.schema.yml +2 -2
  99. simtools/schemas/model_parameters/dsum_offset.schema.yml +2 -2
  100. simtools/schemas/model_parameters/dsum_pedsub.schema.yml +2 -2
  101. simtools/schemas/model_parameters/dsum_pre_clipping.schema.yml +2 -2
  102. simtools/schemas/model_parameters/dsum_prescale.schema.yml +2 -2
  103. simtools/schemas/model_parameters/dsum_presum_max.schema.yml +2 -2
  104. simtools/schemas/model_parameters/dsum_presum_shift.schema.yml +2 -2
  105. simtools/schemas/model_parameters/dsum_shaping.schema.yml +2 -2
  106. simtools/schemas/model_parameters/dsum_shaping_renormalize.schema.yml +2 -2
  107. simtools/schemas/model_parameters/dsum_threshold.schema.yml +3 -3
  108. simtools/schemas/model_parameters/dsum_zero_clip.schema.yml +2 -2
  109. simtools/schemas/model_parameters/effective_focal_length.schema.yml +2 -2
  110. simtools/schemas/model_parameters/fadc_ac_coupled.schema.yml +2 -2
  111. simtools/schemas/model_parameters/fadc_amplitude.schema.yml +2 -2
  112. simtools/schemas/model_parameters/fadc_bins.schema.yml +2 -2
  113. simtools/schemas/model_parameters/fadc_compensate_pedestal.schema.yml +2 -2
  114. simtools/schemas/model_parameters/fadc_err_compensate_pedestal.schema.yml +2 -2
  115. simtools/schemas/model_parameters/fadc_err_pedestal.schema.yml +2 -2
  116. simtools/schemas/model_parameters/fadc_lg_amplitude.schema.yml +2 -2
  117. simtools/schemas/model_parameters/fadc_lg_compensate_pedestal.schema.yml +2 -2
  118. simtools/schemas/model_parameters/fadc_lg_err_compensate_pedestal.schema.yml +2 -2
  119. simtools/schemas/model_parameters/fadc_lg_err_pedestal.schema.yml +2 -2
  120. simtools/schemas/model_parameters/fadc_lg_max_signal.schema.yml +2 -2
  121. simtools/schemas/model_parameters/fadc_lg_noise.schema.yml +2 -2
  122. simtools/schemas/model_parameters/fadc_lg_pedestal.schema.yml +2 -2
  123. simtools/schemas/model_parameters/fadc_lg_sensitivity.schema.yml +2 -2
  124. simtools/schemas/model_parameters/fadc_lg_sysvar_pedestal.schema.yml +2 -2
  125. simtools/schemas/model_parameters/fadc_lg_var_pedestal.schema.yml +2 -2
  126. simtools/schemas/model_parameters/fadc_lg_var_sensitivity.schema.yml +2 -2
  127. simtools/schemas/model_parameters/fadc_max_signal.schema.yml +2 -2
  128. simtools/schemas/model_parameters/fadc_mhz.schema.yml +2 -2
  129. simtools/schemas/model_parameters/fadc_noise.schema.yml +2 -2
  130. simtools/schemas/model_parameters/fadc_pedestal.schema.yml +2 -2
  131. simtools/schemas/model_parameters/fadc_pulse_shape.schema.yml +2 -2
  132. simtools/schemas/model_parameters/fadc_sensitivity.schema.yml +2 -2
  133. simtools/schemas/model_parameters/fadc_sum_bins.schema.yml +2 -2
  134. simtools/schemas/model_parameters/fadc_sum_offset.schema.yml +2 -2
  135. simtools/schemas/model_parameters/fadc_sysvar_pedestal.schema.yml +2 -2
  136. simtools/schemas/model_parameters/fadc_var_pedestal.schema.yml +2 -2
  137. simtools/schemas/model_parameters/fadc_var_sensitivity.schema.yml +2 -2
  138. simtools/schemas/model_parameters/fake_mirror_list.schema.yml +1 -1
  139. simtools/schemas/model_parameters/flatfielding.schema.yml +2 -2
  140. simtools/schemas/model_parameters/focal_length.schema.yml +2 -2
  141. simtools/schemas/model_parameters/focus_offset.schema.yml +2 -2
  142. simtools/schemas/model_parameters/gain_variation.schema.yml +2 -2
  143. simtools/schemas/model_parameters/hg_lg_variation.schema.yml +2 -2
  144. simtools/schemas/model_parameters/iobuf_maximum.schema.yml +2 -2
  145. simtools/schemas/model_parameters/iobuf_output_maximum.schema.yml +2 -2
  146. simtools/schemas/model_parameters/laser_events.schema.yml +1 -1
  147. simtools/schemas/model_parameters/lightguide_efficiency_vs_incidence_angle.schema.yml +2 -2
  148. simtools/schemas/model_parameters/lightguide_efficiency_vs_wavelength.schema.yml +2 -2
  149. simtools/schemas/model_parameters/min_photoelectrons.schema.yml +2 -2
  150. simtools/schemas/model_parameters/min_photons.schema.yml +2 -2
  151. simtools/schemas/model_parameters/mirror_align_random_distance.schema.yml +2 -2
  152. simtools/schemas/model_parameters/mirror_align_random_horizontal.schema.yml +2 -2
  153. simtools/schemas/model_parameters/mirror_align_random_vertical.schema.yml +2 -2
  154. simtools/schemas/model_parameters/mirror_class.schema.yml +2 -2
  155. simtools/schemas/model_parameters/mirror_degraded_reflection.schema.yml +2 -2
  156. simtools/schemas/model_parameters/mirror_focal_length.schema.yml +2 -2
  157. simtools/schemas/model_parameters/mirror_list.schema.yml +2 -2
  158. simtools/schemas/model_parameters/mirror_offset.schema.yml +2 -2
  159. simtools/schemas/model_parameters/mirror_reflection_random_angle.schema.yml +2 -2
  160. simtools/schemas/model_parameters/mirror_reflectivity.schema.yml +2 -2
  161. simtools/schemas/model_parameters/multiplicity_offset.schema.yml +2 -2
  162. simtools/schemas/model_parameters/muon_mono_threshold.schema.yml +2 -2
  163. simtools/schemas/model_parameters/nsb_autoscale_airmass.schema.yml +2 -2
  164. simtools/schemas/model_parameters/nsb_offaxis.schema.yml +2 -2
  165. simtools/schemas/model_parameters/nsb_pixel_rate.schema.yml +2 -2
  166. simtools/schemas/model_parameters/num_gains.schema.yml +2 -2
  167. simtools/schemas/model_parameters/only_triggered_telescopes.schema.yml +2 -2
  168. simtools/schemas/model_parameters/optics_properties.schema.yml +2 -2
  169. simtools/schemas/model_parameters/pedestal_events.schema.yml +7 -3
  170. simtools/schemas/model_parameters/photon_delay.schema.yml +2 -2
  171. simtools/schemas/model_parameters/pixeltrg_time_step.schema.yml +2 -2
  172. simtools/schemas/model_parameters/pm_average_gain.schema.yml +2 -2
  173. simtools/schemas/model_parameters/pm_collection_efficiency.schema.yml +2 -2
  174. simtools/schemas/model_parameters/pm_gain_index.schema.yml +2 -2
  175. simtools/schemas/model_parameters/pm_photoelectron_spectrum.schema.yml +2 -2
  176. simtools/schemas/model_parameters/pm_transit_time.schema.yml +2 -2
  177. simtools/schemas/model_parameters/pm_voltage_variation.schema.yml +2 -2
  178. simtools/schemas/model_parameters/primary_mirror_degraded_map.schema.yml +2 -2
  179. simtools/schemas/model_parameters/qe_variation.schema.yml +2 -2
  180. simtools/schemas/model_parameters/quantum_efficiency.schema.yml +2 -2
  181. simtools/schemas/model_parameters/random_focal_length.schema.yml +2 -2
  182. simtools/schemas/model_parameters/random_generator.schema.yml +2 -2
  183. simtools/schemas/model_parameters/random_mono_probability.schema.yml +2 -2
  184. simtools/schemas/model_parameters/sampled_output.schema.yml +2 -2
  185. simtools/schemas/model_parameters/save_pe_with_amplitude.schema.yml +2 -2
  186. simtools/schemas/model_parameters/store_photoelectrons.schema.yml +2 -2
  187. simtools/schemas/model_parameters/tailcut_scale.schema.yml +2 -2
  188. simtools/schemas/model_parameters/telescope_axis_height.schema.yml +2 -2
  189. simtools/schemas/model_parameters/telescope_random_angle.schema.yml +2 -2
  190. simtools/schemas/model_parameters/telescope_random_error.schema.yml +2 -2
  191. simtools/schemas/model_parameters/telescope_sphere_radius.schema.yml +2 -2
  192. simtools/schemas/model_parameters/telescope_transmission.schema.yml +2 -2
  193. simtools/schemas/model_parameters/teltrig_min_sigsum.schema.yml +2 -2
  194. simtools/schemas/model_parameters/teltrig_min_time.schema.yml +2 -2
  195. simtools/schemas/model_parameters/transit_time_calib_error.schema.yml +2 -2
  196. simtools/schemas/model_parameters/transit_time_compensate_error.schema.yml +2 -2
  197. simtools/schemas/model_parameters/transit_time_compensate_step.schema.yml +2 -2
  198. simtools/schemas/model_parameters/transit_time_error.schema.yml +2 -2
  199. simtools/schemas/model_parameters/transit_time_jitter.schema.yml +2 -2
  200. simtools/schemas/model_parameters/trigger_current_limit.schema.yml +2 -2
  201. simtools/schemas/model_parameters/trigger_delay_compensation.schema.yml +2 -2
  202. simtools/schemas/model_parameters/trigger_pixels.schema.yml +2 -2
  203. simtools/simtel/simtel_config_reader.py +21 -17
  204. simtools/simtel/simtel_config_writer.py +237 -65
  205. simtools/simtel/simtel_io_file_info.py +57 -0
  206. simtools/simtel/simtel_io_histogram.py +10 -14
  207. simtools/simtel/simtel_io_histograms.py +2 -2
  208. simtools/simtel/simtel_io_metadata.py +91 -0
  209. simtools/simtel/simulator_array.py +26 -12
  210. simtools/simtel/simulator_camera_efficiency.py +12 -6
  211. simtools/simtel/simulator_light_emission.py +6 -11
  212. simtools/simtel/simulator_ray_tracing.py +14 -4
  213. simtools/simulator.py +230 -66
  214. simtools/testing/configuration.py +12 -6
  215. simtools/testing/helpers.py +18 -0
  216. simtools/testing/sim_telarray_metadata.py +212 -0
  217. simtools/testing/validate_output.py +12 -5
  218. simtools/utils/general.py +18 -27
  219. simtools/utils/names.py +27 -5
  220. simtools/visualization/visualize.py +2 -2
  221. simtools/applications/production_scale_events.py +0 -185
  222. {gammasimtools-0.14.0.dist-info → gammasimtools-0.16.0.dist-info}/licenses/LICENSE +0 -0
  223. {gammasimtools-0.14.0.dist-info → gammasimtools-0.16.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,364 @@
1
+ """
2
+ Module defines the `GridGeneration` class.
3
+
4
+ Used to generate a grid of simulation points based on flexible axes definitions such
5
+ azimuth, zenith angle, night-sky background, and camera offset.
6
+ The module handles axis binning, scaling and interpolation of energy thresholds, viewcone,
7
+ and radius limits from a lookup table.
8
+ Additionally, it allows for converting between Altitude/Azimuth and Right Ascension
9
+ Declination coordinates. The resulting grid points are saved to a file.
10
+ """
11
+
12
+ import json
13
+ import logging
14
+
15
+ import numpy as np
16
+ from astropy import units as u
17
+ from astropy.coordinates import AltAz, EarthLocation, SkyCoord
18
+ from astropy.table import Table
19
+ from astropy.units import Quantity
20
+ from scipy.interpolate import griddata
21
+
22
+
23
+ class GridGeneration:
24
+ """
25
+ Defines and generates a grid of simulation points based on flexible axes definitions.
26
+
27
+ This class generates a grid of points for a simulation based on parameters such as
28
+ azimuth, zenith angle, night-sky background, and camera offset,
29
+ taking into account axis definitions, scaling, and units and interpolating values
30
+ for simulations from a lookup table.
31
+ """
32
+
33
+ def __init__(
34
+ self,
35
+ axes: dict,
36
+ coordinate_system: str = "zenith_azimuth",
37
+ observing_location=None,
38
+ observing_time=None,
39
+ lookup_table: str | None = None,
40
+ telescope_ids: list | None = None,
41
+ ):
42
+ """
43
+ Initialize the grid with the given axes and coordinate system.
44
+
45
+ Parameters
46
+ ----------
47
+ axes : dict
48
+ Dictionary where each key is the axis name and the value is a dictionary
49
+ defining the axis properties (range, binning, scaling, etc.).
50
+ coordinate_system : str, optional
51
+ The coordinate system for the grid generation (default is 'zenith_azimuth').
52
+ observing_location : EarthLocation, optional
53
+ The location of the observation (latitude, longitude, height).
54
+ observing_time : Time, optional
55
+ The time of the observation. If None, coordinate conversion to RA/Dec not working.
56
+ lookup_table : str, optional
57
+ Path to the lookup table file (ECSV format).
58
+ telescope_ids : list of int, optional
59
+ List of telescope IDs to get the limits for.
60
+ """
61
+ self._logger = logging.getLogger(__name__)
62
+
63
+ self.axes = axes["axes"] if "axes" in axes else axes
64
+ self.coordinate_system = coordinate_system
65
+ self.observing_location = (
66
+ observing_location
67
+ if observing_location is not None
68
+ else EarthLocation(lat=0.0 * u.deg, lon=0.0 * u.deg, height=0 * u.m)
69
+ )
70
+ self.observing_time = observing_time
71
+ self.lookup_table = lookup_table
72
+ self.telescope_ids = telescope_ids
73
+
74
+ # Store target values for each axis
75
+ self.target_values = self._generate_target_values()
76
+
77
+ if self.lookup_table:
78
+ self._apply_lookup_table_limits()
79
+
80
+ def _generate_target_values(self):
81
+ """
82
+ Generate target axis values and store them as Quantities.
83
+
84
+ Returns
85
+ -------
86
+ dict
87
+ Dictionary of target values for each axis, stored as Quantity objects.
88
+ """
89
+ target_values = {}
90
+ for axis_name, axis in self.axes.items():
91
+ axis_range = axis["range"]
92
+ binning = axis["binning"]
93
+ scaling = axis.get("scaling", "linear")
94
+ units = axis.get("units", None)
95
+
96
+ if axis_name == "azimuth":
97
+ # Use circular binning for azimuth
98
+ values = self.create_circular_binning(axis_range, binning)
99
+ elif scaling == "log":
100
+ # Log scaling
101
+ values = np.logspace(np.log10(axis_range[0]), np.log10(axis_range[1]), binning)
102
+ elif scaling == "1/cos":
103
+ # 1/cos scaling
104
+ cos_min = np.cos(np.radians(axis_range[0]))
105
+ cos_max = np.cos(np.radians(axis_range[1]))
106
+ inv_cos_values = np.linspace(1 / cos_min, 1 / cos_max, binning)
107
+ values = np.degrees(np.arccos(1 / inv_cos_values))
108
+ else:
109
+ # Linear scaling
110
+ values = np.linspace(axis_range[0], axis_range[1], binning)
111
+
112
+ if units:
113
+ values = values * u.Unit(units)
114
+
115
+ target_values[axis_name] = values
116
+
117
+ return target_values
118
+
119
+ def _apply_lookup_table_limits(self):
120
+ """Apply limits from the lookup table and interpolate values."""
121
+ lookup_table = Table.read(self.lookup_table, format="ascii.ecsv")
122
+
123
+ matching_rows = [
124
+ row for row in lookup_table if set(self.telescope_ids) == set(row["telescope_ids"])
125
+ ]
126
+
127
+ if not matching_rows:
128
+ raise ValueError(
129
+ f"No matching rows in the lookup table for telescope_ids: {self.telescope_ids}"
130
+ )
131
+
132
+ def extract_array(field, transform=lambda x: x):
133
+ return np.array([transform(row[field]) for row in matching_rows])
134
+
135
+ zeniths = extract_array("zenith")
136
+ azimuths = extract_array("azimuth", lambda x: x % 360)
137
+ nsb_values = extract_array("nsb", lambda x: 1 if x == "dark" else 5)
138
+ lower_energy_thresholds = extract_array("lower_energy_threshold")
139
+ upper_radius_thresholds = extract_array("upper_radius_threshold")
140
+ viewcone_radii = extract_array("viewcone_radius")
141
+
142
+ # Wrap azimuths and repeat others
143
+ azimuths_wrapped = np.concatenate([azimuths + shift for shift in (0, 360, -360)])
144
+
145
+ def repeat_3(arr):
146
+ """Repeat an array three times."""
147
+ return np.tile(arr, 3)
148
+
149
+ points = np.column_stack(
150
+ (
151
+ repeat_3(zeniths),
152
+ azimuths_wrapped,
153
+ repeat_3(nsb_values),
154
+ )
155
+ )
156
+
157
+ target_grid = (
158
+ np.array(
159
+ np.meshgrid(
160
+ self.target_values["zenith_angle"].value,
161
+ self.target_values["azimuth"].value,
162
+ self.target_values["nsb"].value,
163
+ indexing="ij",
164
+ )
165
+ )
166
+ .reshape(3, -1)
167
+ .T
168
+ )
169
+
170
+ def interpolate(values):
171
+ return griddata(
172
+ points, repeat_3(values), target_grid, method="linear", fill_value=np.nan
173
+ ).reshape(
174
+ len(self.target_values["zenith_angle"]),
175
+ len(self.target_values["azimuth"]),
176
+ len(self.target_values["nsb"]),
177
+ )
178
+
179
+ self.interpolated_limits = {
180
+ "energy": interpolate(lower_energy_thresholds),
181
+ "radius": interpolate(upper_radius_thresholds),
182
+ "viewcone": interpolate(viewcone_radii),
183
+ }
184
+
185
+ def create_circular_binning(self, azimuth_range, num_bins):
186
+ """
187
+ Create bin centers for azimuth angles, handling circular wraparound (0 deg to 360 deg).
188
+
189
+ Parameters
190
+ ----------
191
+ azimuth_range : tuple
192
+ (min_azimuth, max_azimuth), can wrap around 0 deg.
193
+ num_bins : int
194
+ Number of bins.
195
+
196
+ Returns
197
+ -------
198
+ np.ndarray
199
+ Array of bin centers.
200
+ """
201
+ azimuth_min, azimuth_max = azimuth_range
202
+ azimuth_min %= 360 # Normalize to [0, 360)
203
+ azimuth_max %= 360
204
+
205
+ clockwise_distance = (azimuth_max - azimuth_min) % 360
206
+ counterclockwise_distance = (azimuth_min - azimuth_max) % 360
207
+
208
+ if clockwise_distance <= counterclockwise_distance:
209
+ bin_centers = (
210
+ np.linspace(azimuth_min, azimuth_min + clockwise_distance, num_bins, endpoint=True)
211
+ % 360
212
+ )
213
+ else:
214
+ bin_centers = (
215
+ np.linspace(
216
+ azimuth_min, azimuth_min - counterclockwise_distance, num_bins, endpoint=True
217
+ )
218
+ % 360
219
+ )
220
+
221
+ return bin_centers
222
+
223
+ def generate_grid(self) -> list[dict]:
224
+ """
225
+ Generate the grid based on the required axes and include interpolated limits.
226
+
227
+ Takes energy threshold, viewcone, and radius from the interpolated lookup table.
228
+
229
+ Returns
230
+ -------
231
+ list of dict
232
+ A list of grid points, each represented as a dictionary with axis names
233
+ as keys and axis values as values. Axis values may include units where defined.
234
+ """
235
+ value_arrays = [value.value for value in self.target_values.values()]
236
+ units = [value.unit for value in self.target_values.values()]
237
+ grid = np.meshgrid(*value_arrays, indexing="ij")
238
+ combinations = np.vstack(list(map(np.ravel, grid))).T
239
+ grid_points = []
240
+ for combination in combinations:
241
+ grid_point = {
242
+ key: Quantity(combination[i], units[i])
243
+ for i, key in enumerate(self.target_values.keys())
244
+ }
245
+
246
+ if "energy" in self.interpolated_limits:
247
+ zenith_idx = np.searchsorted(
248
+ self.target_values["zenith_angle"].value, grid_point["zenith_angle"].value
249
+ )
250
+ azimuth_idx = np.searchsorted(
251
+ self.target_values["azimuth"].value, grid_point["azimuth"].value
252
+ )
253
+ nsb_idx = np.searchsorted(self.target_values["nsb"].value, grid_point["nsb"].value)
254
+ energy_lower = self.interpolated_limits["energy"][zenith_idx, azimuth_idx, nsb_idx]
255
+ grid_point["energy_threshold"] = {"lower": energy_lower * u.TeV}
256
+
257
+ if "radius" in self.interpolated_limits:
258
+ radius_value = self.interpolated_limits["radius"][zenith_idx, azimuth_idx, nsb_idx]
259
+ grid_point["radius"] = radius_value * u.m
260
+
261
+ if "viewcone" in self.interpolated_limits:
262
+ viewcone_value = self.interpolated_limits["viewcone"][
263
+ zenith_idx, azimuth_idx, nsb_idx
264
+ ]
265
+ grid_point["viewcone"] = viewcone_value * u.deg
266
+
267
+ grid_points.append(grid_point)
268
+
269
+ return grid_points
270
+
271
+ def convert_altaz_to_radec(self, alt, az):
272
+ """
273
+ Convert Altitude/Azimuth (AltAz) coordinates to Right Ascension/Declination (RA/Dec).
274
+
275
+ Parameters
276
+ ----------
277
+ alt : float
278
+ Altitude angle in degrees.
279
+ az : float
280
+ Azimuth angle in degrees.
281
+
282
+ Returns
283
+ -------
284
+ SkyCoord
285
+ SkyCoord object containing the RA/Dec coordinates.
286
+
287
+ Raises
288
+ ------
289
+ ValueError
290
+ If observing_time is not set.
291
+ """
292
+ if self.observing_time is None:
293
+ raise ValueError(
294
+ "Observing time is not set. "
295
+ "Please provide an observing_time to convert coordinates."
296
+ )
297
+
298
+ alt_rad = alt.to(u.rad)
299
+ az_rad = az.to(u.rad)
300
+ aa = AltAz(
301
+ alt=alt_rad,
302
+ az=az_rad,
303
+ location=self.observing_location,
304
+ obstime=self.observing_time,
305
+ )
306
+ skycoord = SkyCoord(aa)
307
+ return skycoord.icrs # Return RA/Dec in ICRS frame
308
+
309
+ def convert_coordinates(self, grid_points: list[dict]) -> list[dict]:
310
+ """
311
+ Convert the grid points to RA/Dec coordinates if necessary.
312
+
313
+ Parameters
314
+ ----------
315
+ grid_points : list of dict
316
+ List of grid points, each represented as a dictionary with axis
317
+ names as keys and values.
318
+
319
+ Returns
320
+ -------
321
+ list of dict
322
+ The grid points with converted RA/Dec coordinates.
323
+ """
324
+ if self.coordinate_system == "ra_dec":
325
+ for point in grid_points:
326
+ if "zenith_angle" in point and "azimuth" in point:
327
+ alt = (90.0 * u.deg) - point.pop("zenith_angle")
328
+ az = point.pop("azimuth")
329
+ radec = self.convert_altaz_to_radec(alt, az)
330
+ point["ra"] = radec.ra.deg * u.deg
331
+ point["dec"] = radec.dec.deg * u.deg
332
+ return grid_points
333
+
334
+ def serialize_grid_points(self, grid_points, output_file=None):
335
+ """Serialize the grid output and save to a file or print to the console."""
336
+ cleaned_points = []
337
+
338
+ for point in grid_points:
339
+ cleaned_point = {}
340
+ for key, value in point.items():
341
+ if isinstance(value, dict):
342
+ # Nested dictionaries
343
+ cleaned_point[key] = {k: self.serialize_quantity(v) for k, v in value.items()}
344
+ else:
345
+ cleaned_point[key] = self.serialize_quantity(value)
346
+
347
+ cleaned_points.append(cleaned_point)
348
+
349
+ output_data = json.dumps(cleaned_points, indent=4)
350
+
351
+ if output_file:
352
+ with open(output_file, "w", encoding="utf-8") as f:
353
+ f.write(output_data)
354
+ self._logger.info(f"Output saved to {output_file}")
355
+ else:
356
+ self._logger.info(output_data)
357
+ return output_data
358
+
359
+ def serialize_quantity(self, value):
360
+ """Serialize Quantity."""
361
+ if isinstance(value, u.Quantity):
362
+ return {"value": value.value, "unit": str(value.unit)}
363
+ self._logger.warning(f"Unsupported type {type(value)} for serialization. Returning as is.")
364
+ return value
@@ -3,7 +3,9 @@
3
3
  from simtools.production_configuration.calculate_statistical_errors_grid_point import (
4
4
  StatisticalErrorEvaluator,
5
5
  )
6
- from simtools.production_configuration.event_scaler import EventScaler
6
+ from simtools.production_configuration.derive_production_statistics import (
7
+ ProductionStatisticsDerivator,
8
+ )
7
9
 
8
10
  __all__ = ["SimulationConfig"]
9
11
 
@@ -18,8 +20,6 @@ class SimulationConfig:
18
20
  Dictionary representing a grid point with azimuth, elevation, and night sky background.
19
21
  file_path : str
20
22
  Path to the DL2 MC event file for statistical uncertainty evaluation.
21
- file_type : str
22
- Type of the DL2 MC event file ('point-like' or 'cone').
23
23
  metrics : dict, optional
24
24
  Dictionary of metrics to evaluate.
25
25
  """
@@ -28,16 +28,16 @@ class SimulationConfig:
28
28
  self,
29
29
  grid_point: dict[str, float],
30
30
  file_path: str,
31
- file_type: str,
32
31
  metrics: dict[str, float] | None = None,
33
32
  ):
34
33
  """Initialize the simulation configuration for a grid point."""
35
34
  self.grid_point = grid_point
36
35
  self.file_path = file_path
37
- self.file_type = file_type
38
36
  self.metrics = metrics or {}
39
- self.evaluator = StatisticalErrorEvaluator(file_path, file_type, metrics)
40
- self.event_scaler = EventScaler(self.evaluator, self.metrics)
37
+ self.evaluator = StatisticalErrorEvaluator(file_path, metrics)
38
+ self.derive_production_statistics = ProductionStatisticsDerivator(
39
+ self.evaluator, self.metrics
40
+ )
41
41
  self.simulation_params = {}
42
42
 
43
43
  def configure_simulation(self) -> dict[str, float]:
@@ -61,14 +61,14 @@ class SimulationConfig:
61
61
  """
62
62
  Calculate the required number of simulated events based on statistical error metrics.
63
63
 
64
- Uses the EventScaler to scale the events.
64
+ Uses the ProductionStatisticsDerivator to scale the events.
65
65
 
66
66
  Returns
67
67
  -------
68
68
  int
69
69
  The number of simulated events required.
70
70
  """
71
- return self.event_scaler.scale_events()
71
+ return self.derive_production_statistics.derive_statistics()
72
72
 
73
73
  def _calculate_core_scatter_area(self) -> float:
74
74
  """
@@ -1,10 +1,12 @@
1
- """Interpolates between instances of StatisticalErrorEvaluator using EventScaler."""
1
+ """Handle interpolation between multiple StatisticalErrorEvaluator instances."""
2
2
 
3
3
  import astropy.units as u
4
4
  import numpy as np
5
5
  from scipy.interpolate import griddata
6
6
 
7
- from simtools.production_configuration.event_scaler import EventScaler
7
+ from simtools.production_configuration.derive_production_statistics import (
8
+ ProductionStatisticsDerivator,
9
+ )
8
10
 
9
11
  __all__ = ["InterpolationHandler"]
10
12
 
@@ -15,7 +17,9 @@ class InterpolationHandler:
15
17
  def __init__(self, evaluators, metrics: dict):
16
18
  self.evaluators = evaluators
17
19
  self.metrics = metrics
18
- self.event_scalers = [EventScaler(e, self.metrics) for e in self.evaluators]
20
+ self.derive_production_statistics = [
21
+ ProductionStatisticsDerivator(e, self.metrics) for e in self.evaluators
22
+ ]
19
23
 
20
24
  self.azimuths = [e.grid_point[1].to(u.deg).value for e in self.evaluators]
21
25
  self.zeniths = [e.grid_point[2].to(u.deg).value for e in self.evaluators]
@@ -26,8 +30,9 @@ class InterpolationHandler:
26
30
  (e.data["bin_edges_low"][:-1] + e.data["bin_edges_high"][:-1]) / 2
27
31
  for e in self.evaluators
28
32
  ]
29
- self.scaled_events = [
30
- scaler.scale_events(return_sum=False) for scaler in self.event_scalers
33
+ self.production_statistics = [
34
+ derivator.derive_statistics(return_sum=False)
35
+ for derivator in self.derive_production_statistics
31
36
  ]
32
37
  self.energy_thresholds = np.array([e.energy_threshold for e in self.evaluators])
33
38
 
@@ -48,8 +53,8 @@ class InterpolationHandler:
48
53
  flat_data_list = []
49
54
  flat_grid_points = []
50
55
 
51
- for e, energy_grid, scaled_events in zip(
52
- self.evaluators, self.energy_grids, self.scaled_events
56
+ for e, energy_grid, production_statistics in zip(
57
+ self.evaluators, self.energy_grids, self.production_statistics
53
58
  ):
54
59
  az = np.full(len(energy_grid), e.grid_point[1].to(u.deg).value)
55
60
  zen = np.full(len(energy_grid), e.grid_point[2].to(u.deg).value)
@@ -59,7 +64,7 @@ class InterpolationHandler:
59
64
  # Combine grid points and data
60
65
  grid_points = np.column_stack([energy_grid.to(u.TeV).value, az, zen, nsb, offset])
61
66
  flat_grid_points.append(grid_points)
62
- flat_data_list.append(scaled_events)
67
+ flat_data_list.append(production_statistics)
63
68
 
64
69
  # Flatten the list and convert to numpy arrays
65
70
  flat_grid_points = np.vstack(flat_grid_points)
@@ -153,7 +158,7 @@ class InterpolationHandler:
153
158
 
154
159
  def plot_comparison(self, evaluator):
155
160
  """
156
- Plot a comparison between the simulated, scaled, and reconstructed events.
161
+ Plot a comparison between the simulated, derived, and reconstructed events.
157
162
 
158
163
  Parameters
159
164
  ----------
@@ -176,7 +181,7 @@ class InterpolationHandler:
176
181
 
177
182
  self.interpolate(self.grid_points)
178
183
 
179
- plt.plot(midpoints, evaluator.scaled_events, label="Scaled")
184
+ plt.plot(midpoints, evaluator.production_statistics, label="Derived")
180
185
 
181
186
  reconstructed_event_histogram, _ = np.histogram(
182
187
  evaluator.data["event_energies_reco"], bins=evaluator.data["bin_edges_low"]
@@ -187,5 +192,5 @@ class InterpolationHandler:
187
192
  plt.xscale("log")
188
193
  plt.xlabel("Energy (Midpoint of Bin Edges)")
189
194
  plt.ylabel("Event Count")
190
- plt.title("Comparison of Simulated, scaled, and reconstructed events")
195
+ plt.title("Comparison of simulated, derived, and reconstructed events")
191
196
  plt.show()
@@ -9,7 +9,7 @@ from astropy.table import QTable, Table
9
9
  import simtools.data_model.model_data_writer as writer
10
10
  import simtools.utils.general as gen
11
11
  from simtools.data_model.metadata_collector import MetadataCollector
12
- from simtools.model.telescope_model import TelescopeModel
12
+ from simtools.model.model_utils import initialize_simulation_models
13
13
  from simtools.ray_tracing.ray_tracing import RayTracing
14
14
 
15
15
 
@@ -38,7 +38,7 @@ class MirrorPanelPSF:
38
38
  self._logger.debug("Initializing MirrorPanelPSF")
39
39
 
40
40
  self.args_dict = args_dict
41
- self.telescope_model = self._define_telescope_model(label, db_config)
41
+ self.telescope_model, self.site_model = self._define_telescope_model(label, db_config)
42
42
 
43
43
  if self.args_dict["test"]:
44
44
  self.args_dict["number_of_mirrors_to_test"] = 2
@@ -63,37 +63,32 @@ class MirrorPanelPSF:
63
63
  This includes updating the configuration with mirror list and/or random focal length given
64
64
  as input.
65
65
 
66
- Attributes
67
- ----------
68
- label: str
69
- Application label.
70
- db_config:
71
- Dictionary with database configuration.
72
-
73
66
  Returns
74
67
  -------
75
- tel TelescopeModel
76
- telescope model
77
-
68
+ tel : TelescopeModel
69
+ The telescope model.
70
+ site_model : SiteModel
71
+ The site model.
78
72
  """
79
- tel = TelescopeModel(
73
+ tel_model, site_model = initialize_simulation_models(
74
+ label=label,
75
+ db_config=db_config,
80
76
  site=self.args_dict["site"],
81
77
  telescope_name=self.args_dict["telescope"],
82
78
  model_version=self.args_dict["model_version"],
83
- mongo_db_config=db_config,
84
- label=label,
85
79
  )
86
80
  if self.args_dict["mirror_list"] is not None:
87
81
  mirror_list_file = gen.find_file(
88
82
  name=self.args_dict["mirror_list"], loc=self.args_dict["model_path"]
89
83
  )
90
- tel.change_parameter("mirror_list", self.args_dict["mirror_list"])
91
- tel.export_parameter_file("mirror_list", mirror_list_file)
84
+ tel_model.change_parameter("mirror_list", self.args_dict["mirror_list"])
85
+ tel_model.export_parameter_file("mirror_list", mirror_list_file)
92
86
  if self.args_dict["random_focal_length"] is not None:
93
- tel.change_parameter("random_focal_length", str(self.args_dict["random_focal_length"]))
94
- tel.export_model_files()
87
+ tel_model.change_parameter(
88
+ "random_focal_length", str(self.args_dict["random_focal_length"])
89
+ )
95
90
 
96
- return tel
91
+ return tel_model, site_model
97
92
 
98
93
  def _get_psf_containment(self):
99
94
  """Read measured single-mirror point-spread function from file and return mean and sigma."""
@@ -229,6 +224,7 @@ class MirrorPanelPSF:
229
224
  self.telescope_model.change_parameter("mirror_reflection_random_angle", rnda)
230
225
  ray = RayTracing(
231
226
  telescope_model=self.telescope_model,
227
+ site_model=self.site_model,
232
228
  simtel_path=self.args_dict.get("simtel_path", None),
233
229
  single_mirror_mode=True,
234
230
  mirror_numbers=(
@@ -91,7 +91,7 @@ class PSFImage:
91
91
 
92
92
  def _process_simtel_file_using_rx(self, photon_file):
93
93
  """
94
- Process a simtel file with photon lists using the RX method.
94
+ Process a sim_telarray file with photon lists using the RX method.
95
95
 
96
96
  Parameters
97
97
  ----------
@@ -152,7 +152,7 @@ class PSFImage:
152
152
  self._process_simtel_line(line)
153
153
 
154
154
  if not self._is_photon_positions_ok():
155
- msg = "Problems reading Simtel file - invalid data"
155
+ msg = "Problems reading sim_telarray file - invalid data"
156
156
  self._logger.error(msg)
157
157
  raise RuntimeError(msg)
158
158
 
@@ -33,6 +33,8 @@ class RayTracing:
33
33
  ----------
34
34
  telescope_model: TelescopeModel
35
35
  telescope model
36
+ site_model: SiteModel
37
+ site model
36
38
  simtel_path: str (or Path)
37
39
  Location of sim_telarray installation.
38
40
  label: str
@@ -61,6 +63,7 @@ class RayTracing:
61
63
  def __init__(
62
64
  self,
63
65
  telescope_model,
66
+ site_model,
64
67
  simtel_path,
65
68
  label=None,
66
69
  zenith_angle=20.0 * u.deg,
@@ -77,7 +80,7 @@ class RayTracing:
77
80
  self.simtel_path = Path(simtel_path)
78
81
  self._io_handler = io_handler.IOHandler()
79
82
 
80
- self.telescope_model = telescope_model
83
+ self.telescope_model, self.site_model = telescope_model, site_model
81
84
  self.label = label if label is not None else self.telescope_model.label
82
85
 
83
86
  self.zenith_angle = zenith_angle.to("deg").value
@@ -211,6 +214,7 @@ class RayTracing:
211
214
  simtel = SimulatorRayTracing(
212
215
  simtel_path=self.simtel_path,
213
216
  telescope_model=self.telescope_model,
217
+ site_model=self.site_model,
214
218
  test=test,
215
219
  config_data={
216
220
  "zenith_angle": self.zenith_angle,