gammasimtools 0.16.0__py3-none-any.whl → 0.18.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 (85) hide show
  1. {gammasimtools-0.16.0.dist-info → gammasimtools-0.18.0.dist-info}/METADATA +5 -2
  2. {gammasimtools-0.16.0.dist-info → gammasimtools-0.18.0.dist-info}/RECORD +82 -74
  3. {gammasimtools-0.16.0.dist-info → gammasimtools-0.18.0.dist-info}/WHEEL +1 -1
  4. {gammasimtools-0.16.0.dist-info → gammasimtools-0.18.0.dist-info}/entry_points.txt +4 -1
  5. simtools/_version.py +2 -2
  6. simtools/applications/db_add_simulation_model_from_repository_to_db.py +10 -1
  7. simtools/applications/derive_ctao_array_layouts.py +5 -5
  8. simtools/applications/derive_mirror_rnda.py +1 -1
  9. simtools/applications/generate_simtel_event_data.py +128 -46
  10. simtools/applications/merge_tables.py +102 -0
  11. simtools/applications/plot_array_layout.py +145 -258
  12. simtools/applications/plot_tabular_data.py +12 -1
  13. simtools/applications/plot_tabular_data_for_model_parameter.py +103 -0
  14. simtools/applications/production_derive_corsika_limits.py +78 -225
  15. simtools/applications/production_derive_statistics.py +77 -43
  16. simtools/applications/simulate_light_emission.py +1 -0
  17. simtools/applications/simulate_prod.py +30 -18
  18. simtools/applications/simulate_prod_htcondor_generator.py +0 -1
  19. simtools/applications/submit_array_layouts.py +93 -0
  20. simtools/applications/verify_simulation_model_production_tables.py +52 -0
  21. simtools/camera/camera_efficiency.py +3 -3
  22. simtools/configuration/commandline_parser.py +30 -35
  23. simtools/configuration/configurator.py +0 -4
  24. simtools/constants.py +2 -0
  25. simtools/corsika/corsika_config.py +17 -12
  26. simtools/corsika/primary_particle.py +46 -13
  27. simtools/data_model/metadata_collector.py +7 -3
  28. simtools/data_model/schema.py +15 -1
  29. simtools/db/db_handler.py +16 -11
  30. simtools/db/db_model_upload.py +2 -2
  31. simtools/io_operations/io_handler.py +2 -2
  32. simtools/io_operations/io_table_handler.py +345 -0
  33. simtools/job_execution/htcondor_script_generator.py +2 -2
  34. simtools/job_execution/job_manager.py +7 -121
  35. simtools/layout/array_layout_utils.py +389 -0
  36. simtools/model/array_model.py +10 -1
  37. simtools/model/model_repository.py +134 -0
  38. simtools/production_configuration/{calculate_statistical_errors_grid_point.py → calculate_statistical_uncertainties_grid_point.py} +101 -112
  39. simtools/production_configuration/derive_corsika_limits.py +239 -111
  40. simtools/production_configuration/derive_corsika_limits_grid.py +232 -0
  41. simtools/production_configuration/derive_production_statistics.py +57 -26
  42. simtools/production_configuration/derive_production_statistics_handler.py +70 -37
  43. simtools/production_configuration/interpolation_handler.py +296 -94
  44. simtools/ray_tracing/ray_tracing.py +7 -6
  45. simtools/reporting/docs_read_parameters.py +104 -62
  46. simtools/resources/array-element-ids.json +126 -0
  47. simtools/runners/corsika_simtel_runner.py +4 -1
  48. simtools/runners/runner_services.py +5 -4
  49. simtools/schemas/model_parameter_and_data_schema.metaschema.yml +5 -1
  50. simtools/schemas/model_parameters/atmospheric_profile.schema.yml +41 -0
  51. simtools/schemas/model_parameters/atmospheric_transmission.schema.yml +43 -0
  52. simtools/schemas/model_parameters/camera_filter.schema.yml +10 -0
  53. simtools/schemas/model_parameters/camera_filter_incidence_angle.schema.yml +10 -0
  54. simtools/schemas/model_parameters/discriminator_pulse_shape.schema.yml +31 -0
  55. simtools/schemas/model_parameters/dsum_threshold.schema.yml +41 -0
  56. simtools/schemas/model_parameters/fadc_pulse_shape.schema.yml +12 -0
  57. simtools/schemas/model_parameters/lightguide_efficiency_vs_incidence_angle.schema.yml +10 -0
  58. simtools/schemas/model_parameters/mirror_reflectivity.schema.yml +10 -0
  59. simtools/schemas/model_parameters/nsb_reference_spectrum.schema.yml +12 -0
  60. simtools/schemas/model_parameters/pm_photoelectron_spectrum.schema.yml +19 -0
  61. simtools/schemas/model_parameters/quantum_efficiency.schema.yml +10 -0
  62. simtools/schemas/plot_configuration.metaschema.yml +46 -57
  63. simtools/schemas/production_configuration_metrics.schema.yml +2 -2
  64. simtools/simtel/simtel_config_writer.py +34 -14
  65. simtools/simtel/simtel_io_event_reader.py +301 -194
  66. simtools/simtel/simtel_io_event_writer.py +237 -221
  67. simtools/simtel/simtel_io_file_info.py +9 -4
  68. simtools/simtel/simtel_io_metadata.py +119 -8
  69. simtools/simtel/simulator_array.py +2 -2
  70. simtools/simtel/simulator_light_emission.py +79 -34
  71. simtools/simtel/simulator_ray_tracing.py +2 -2
  72. simtools/simulator.py +101 -68
  73. simtools/testing/validate_output.py +4 -1
  74. simtools/utils/general.py +1 -3
  75. simtools/utils/names.py +76 -7
  76. simtools/visualization/plot_array_layout.py +242 -0
  77. simtools/visualization/plot_pixels.py +680 -0
  78. simtools/visualization/plot_tables.py +81 -2
  79. simtools/visualization/visualize.py +3 -219
  80. simtools/applications/production_generate_simulation_config.py +0 -152
  81. simtools/layout/ctao_array_layouts.py +0 -172
  82. simtools/production_configuration/generate_simulation_config.py +0 -158
  83. {gammasimtools-0.16.0.dist-info → gammasimtools-0.18.0.dist-info}/licenses/LICENSE +0 -0
  84. {gammasimtools-0.16.0.dist-info → gammasimtools-0.18.0.dist-info}/top_level.txt +0 -0
  85. /simtools/{schemas → resources}/array_elements.yml +0 -0
@@ -1,4 +1,6 @@
1
- """Handle interpolation between multiple StatisticalErrorEvaluator instances."""
1
+ """Handle interpolation between multiple StatisticalUncertaintyEvaluator instances."""
2
+
3
+ import logging
2
4
 
3
5
  import astropy.units as u
4
6
  import numpy as np
@@ -12,31 +14,75 @@ __all__ = ["InterpolationHandler"]
12
14
 
13
15
 
14
16
  class InterpolationHandler:
15
- """Handle interpolation between multiple StatisticalErrorEvaluator instances."""
17
+ """
18
+ Calculate the required events for production via interpolation from a grid.
19
+
20
+ This class provides methods to interpolate production statistics across a grid of
21
+ parameter values (azimuth, zenith, NSB, offset) and energy.
22
+ """
23
+
24
+ def __init__(self, evaluators, metrics: dict, grid_points_production: list):
25
+ """
26
+ Initialize the InterpolationHandler.
16
27
 
17
- def __init__(self, evaluators, metrics: dict):
28
+ Parameters
29
+ ----------
30
+ evaluators : list
31
+ List of StatisticalUncertaintyEvaluator instances.
32
+ metrics : dict
33
+ Dictionary of metrics to use for production statistics.
34
+ grid_points_production : list
35
+ List of grid points for interpolation, each being a dictionary with keys
36
+ 'azimuth', 'zenith_angle', 'nsb', 'offset' etc.
37
+ """
38
+ self._logger = logging.getLogger(__name__)
18
39
  self.evaluators = evaluators
19
40
  self.metrics = metrics
41
+ self.grid_points_production = grid_points_production
42
+
43
+ self._initialize_derivators()
44
+ self._extract_grid_properties()
45
+
46
+ self.data, self.grid_points = self._build_data_array()
47
+ self.interpolated_production_statistics = None
48
+ self.interpolated_production_statistics_with_energy = None
49
+ self._non_flat_mask = None
50
+
51
+ def _initialize_derivators(self):
52
+ """Initialize production statistics derivators for all evaluators."""
20
53
  self.derive_production_statistics = [
21
54
  ProductionStatisticsDerivator(e, self.metrics) for e in self.evaluators
22
55
  ]
23
56
 
57
+ self.production_statistics = [
58
+ derivator.derive_statistics(return_sum=False)
59
+ for derivator in self.derive_production_statistics
60
+ ]
61
+ self.production_statistics_sum = [
62
+ derivator.derive_statistics(return_sum=True)
63
+ for derivator in self.derive_production_statistics
64
+ ]
65
+
66
+ def _extract_grid_properties(self):
67
+ """Extract grid properties from evaluators."""
24
68
  self.azimuths = [e.grid_point[1].to(u.deg).value for e in self.evaluators]
25
69
  self.zeniths = [e.grid_point[2].to(u.deg).value for e in self.evaluators]
26
70
  self.nsbs = [e.grid_point[3] for e in self.evaluators]
27
71
  self.offsets = [e.grid_point[4].to(u.deg).value for e in self.evaluators]
28
-
29
72
  self.energy_grids = [
30
73
  (e.data["bin_edges_low"][:-1] + e.data["bin_edges_high"][:-1]) / 2
31
74
  for e in self.evaluators
32
75
  ]
33
- self.production_statistics = [
34
- derivator.derive_statistics(return_sum=False)
35
- for derivator in self.derive_production_statistics
36
- ]
37
76
  self.energy_thresholds = np.array([e.energy_threshold for e in self.evaluators])
38
77
 
39
- self.data, self.grid_points = self._build_data_array()
78
+ # Check if energy grids are consistent
79
+ if self.energy_grids and not all(
80
+ np.array_equal(self.energy_grids[0], grid) for grid in self.energy_grids
81
+ ):
82
+ self._logger.warning(
83
+ "Energy grids are not identical across evaluators. "
84
+ "Using the first evaluator's energy grid for interpolation."
85
+ )
40
86
 
41
87
  def _build_data_array(self):
42
88
  """
@@ -49,148 +95,304 @@ class InterpolationHandler:
49
95
  np.ndarray
50
96
  The corresponding grid points.
51
97
  """
52
- # Flatten the energy grid and other dimensions into a combined array
98
+ if not self.evaluators:
99
+ return np.array([]), np.array([])
100
+
53
101
  flat_data_list = []
54
102
  flat_grid_points = []
55
103
 
56
- for e, energy_grid, production_statistics in zip(
57
- self.evaluators, self.energy_grids, self.production_statistics
104
+ for i, (energy_grid, production_statistics) in enumerate(
105
+ zip(self.energy_grids, self.production_statistics)
58
106
  ):
59
- az = np.full(len(energy_grid), e.grid_point[1].to(u.deg).value)
60
- zen = np.full(len(energy_grid), e.grid_point[2].to(u.deg).value)
61
- nsb = np.full(len(energy_grid), e.grid_point[3])
62
- offset = np.full(len(energy_grid), e.grid_point[4].to(u.deg).value)
107
+ az = self.azimuths[i]
108
+ zen = self.zeniths[i]
109
+ nsb = self.nsbs[i]
110
+ offset = self.offsets[i]
111
+
112
+ az_array = np.full(len(energy_grid), az)
113
+ zen_array = np.full(len(energy_grid), zen)
114
+ nsb_array = np.full(len(energy_grid), nsb)
115
+ offset_array = np.full(len(energy_grid), offset)
116
+
117
+ grid_points = np.column_stack(
118
+ [energy_grid.to(u.TeV).value, az_array, zen_array, nsb_array, offset_array]
119
+ )
63
120
 
64
- # Combine grid points and data
65
- grid_points = np.column_stack([energy_grid.to(u.TeV).value, az, zen, nsb, offset])
66
121
  flat_grid_points.append(grid_points)
67
122
  flat_data_list.append(production_statistics)
68
123
 
69
- # Flatten the list and convert to numpy arrays
70
124
  flat_grid_points = np.vstack(flat_grid_points)
71
125
  flat_data = np.hstack(flat_data_list)
72
126
 
73
- # Sort the grid points and corresponding data by energy
74
127
  sorted_indices = np.argsort(flat_grid_points[:, 0])
75
128
  sorted_grid_points = flat_grid_points[sorted_indices]
76
129
  sorted_data = flat_data[sorted_indices]
77
130
 
78
131
  return sorted_data, sorted_grid_points
79
132
 
80
- def _remove_flat_dimensions(self, grid_points):
81
- """Identify and remove flat dimensions (dimensions with no variance)."""
133
+ def _remove_flat_dimensions(self, grid_points, threshold=1e-6):
134
+ """
135
+ Identify and remove flat dimensions (dimensions with no variance).
136
+
137
+ Parameters
138
+ ----------
139
+ grid_points : np.ndarray
140
+ Grid points to analyze.
141
+ threshold : float, optional
142
+ Threshold for determining flatness, by default 1e-6
143
+
144
+ Returns
145
+ -------
146
+ tuple
147
+ (reduced_grid_points, non_flat_mask)
148
+ """
149
+ if grid_points.size == 0:
150
+ return grid_points, np.array([], dtype=bool)
151
+
82
152
  variance = np.var(grid_points, axis=0)
83
- non_flat_mask = variance > 1e-6 # Threshold for determining flatness
153
+ non_flat_mask = variance > threshold
154
+
155
+ if not np.any(non_flat_mask):
156
+ self._logger.warning(
157
+ "All dimensions are flat. Keeping all dimensions for interpolation."
158
+ )
159
+ return grid_points, np.ones_like(variance, dtype=bool)
160
+
84
161
  reduced_grid_points = grid_points[:, non_flat_mask]
85
162
  return reduced_grid_points, non_flat_mask
86
163
 
87
- def interpolate(self, query_points: np.ndarray) -> np.ndarray:
164
+ def build_grid_points_no_energy(self):
165
+ """
166
+ Build grid points without energy dimension.
167
+
168
+ Returns
169
+ -------
170
+ tuple
171
+ (production_statistics, grid_points_no_energy)
88
172
  """
89
- Interpolate the number of simulated events given query points.
173
+ if not self.evaluators:
174
+ self._logger.error("No evaluators available for grid point building.")
175
+ return np.array([]), np.array([])
176
+
177
+ flat_data_list = []
178
+ flat_grid_points = []
179
+
180
+ for i, production_statistics_sum in enumerate(self.production_statistics_sum):
181
+ az = self.azimuths[i]
182
+ zen = self.zeniths[i]
183
+ nsb = self.nsbs[i]
184
+ offset = self.offsets[i]
185
+
186
+ flat_data_list.append(float(production_statistics_sum.value))
187
+
188
+ grid_point = np.array([[az, zen, nsb, offset]])
189
+ flat_grid_points.append(grid_point)
190
+
191
+ flat_grid_points = np.vstack(flat_grid_points)
192
+ return flat_data_list, flat_grid_points
193
+
194
+ def _prepare_energy_independent_data(self):
195
+ """
196
+ Prepare data for energy-independent interpolation.
197
+
198
+ Returns
199
+ -------
200
+ tuple
201
+ (production_statistic, grid_points_no_energy)
202
+ """
203
+ production_statistic, grid_points_no_energy = self.build_grid_points_no_energy()
204
+ production_statistic = np.array(production_statistic, dtype=float)
205
+ grid_points_no_energy, non_flat_mask = self._remove_flat_dimensions(grid_points_no_energy)
206
+
207
+ self._non_flat_mask = non_flat_mask # Store for later use
208
+ return production_statistic, grid_points_no_energy
209
+
210
+ def _prepare_production_grid_points(self):
211
+ """
212
+ Convert grid_points_production to a format suitable for interpolation.
213
+
214
+ Returns
215
+ -------
216
+ np.ndarray
217
+ Reduced production grid points.
218
+ """
219
+ production_grid_points = []
220
+
221
+ for point in self.grid_points_production:
222
+ production_grid_points.append(
223
+ [
224
+ point["azimuth"]["value"],
225
+ point["zenith_angle"]["value"],
226
+ point["nsb"]["value"],
227
+ point["offset"]["value"],
228
+ ]
229
+ )
230
+
231
+ production_grid_points = np.array(production_grid_points)
232
+
233
+ return production_grid_points[:, self._non_flat_mask]
234
+
235
+ def _perform_interpolation(self, grid_points, values, query_points, method="linear"):
236
+ """
237
+ Perform interpolation using griddata.
90
238
 
91
239
  Parameters
92
240
  ----------
241
+ grid_points : np.ndarray
242
+ Grid points for interpolation.
243
+ values : np.ndarray
244
+ Values at the grid points.
93
245
  query_points : np.ndarray
94
- Array of query points with shape (n, 5), where n is the number of points,
95
- and 5 represents (energy, azimuth, zenith, nsb, offset).
246
+ Query points for interpolation.
247
+ method : str, optional
248
+ Interpolation method, by default "linear".
96
249
 
97
250
  Returns
98
251
  -------
99
252
  np.ndarray
100
- Interpolated values at the query points.
253
+ Interpolated values.
101
254
  """
102
- reduced_grid_points, non_flat_mask = self._remove_flat_dimensions(self.grid_points)
103
- reduced_query_points = query_points[:, non_flat_mask]
255
+ self._logger.debug(f"Grid points shape: {grid_points.shape}")
256
+ self._logger.debug(f"Values shape: {values.shape}")
257
+ self._logger.debug(f"Query points shape: {query_points.shape}")
104
258
 
105
- # Interpolate using the reduced dimensions
106
259
  return griddata(
107
- reduced_grid_points,
108
- self.data,
109
- reduced_query_points,
110
- method="linear",
260
+ grid_points,
261
+ values,
262
+ query_points,
263
+ method=method,
111
264
  fill_value=np.nan,
112
265
  rescale=True,
113
266
  )
114
267
 
115
- def interpolate_energy_threshold(self, query_point: np.ndarray) -> float:
268
+ def _perform_interpolation_with_energy(self):
116
269
  """
117
- Interpolate the energy threshold for a given grid point.
270
+ Perform energy-dependent interpolation.
118
271
 
119
- Parameters
120
- ----------
121
- query_point : np.ndarray
122
- Array specifying the grid point (energy, azimuth, zenith, NSB, offset).
272
+ Returns
273
+ -------
274
+ np.ndarray
275
+ Energy-dependent interpolated values.
276
+ """
277
+ # Get grid points with energy dimension
278
+ grid_points_energy = self.grid_points
279
+ grid_points_energy, _ = self._remove_flat_dimensions(grid_points_energy)
280
+
281
+ # Build energy query grid
282
+ reduced_production_grid_points = self._prepare_production_grid_points()
283
+ energy_grid = self.energy_grids[0] if self.energy_grids else []
284
+
285
+ energy_query_grid = []
286
+ for energy in energy_grid:
287
+ for grid_point in reduced_production_grid_points:
288
+ energy_query_grid.append(np.hstack([energy.to(u.TeV).value, grid_point]))
289
+
290
+ energy_query_grid = np.array(energy_query_grid)
291
+
292
+ self._logger.debug(f"Grid points with energy shape: {grid_points_energy.shape}")
293
+ self._logger.debug(f"Data shape: {self.data.shape}")
294
+ self._logger.debug(f"Energy query grid shape: {energy_query_grid.shape}")
295
+
296
+ interpolated_values = self._perform_interpolation(
297
+ grid_points_energy, self.data, energy_query_grid
298
+ )
299
+
300
+ reshaped = interpolated_values.reshape(
301
+ len(reduced_production_grid_points), len(energy_grid)
302
+ )
303
+ return np.array([reshaped])
304
+
305
+ def interpolate(self) -> np.ndarray:
306
+ """
307
+ Interpolate production statistics at the grid points specified in grid_points_production.
308
+
309
+ This method performs two types of interpolation:
310
+ 1. Energy-independent interpolation using the sum of production statistics
311
+ 2. Energy-dependent interpolation for each energy bin
123
312
 
124
313
  Returns
125
314
  -------
126
- float
127
- Interpolated energy threshold.
315
+ np.ndarray
316
+ Interpolated values at the query points.
128
317
  """
129
- flat_grid_points = []
130
- flat_energy_thresholds = []
131
-
132
- for e in self.evaluators:
133
- az = e.grid_point[1].to(u.deg).value
134
- zen = e.grid_point[2].to(u.deg).value
135
- nsb = e.grid_point[3]
136
- offset = e.grid_point[4].to(u.deg).value
137
- grid_point = np.array([az, zen, nsb, offset])
138
- flat_grid_points.append(grid_point)
139
- flat_energy_thresholds.append(e.energy_threshold)
318
+ if not self.evaluators:
319
+ self._logger.error("No evaluators available for interpolation.")
320
+ return np.array([])
140
321
 
141
- flat_grid_points = np.array(flat_grid_points)
142
- flat_energy_thresholds = np.array(flat_energy_thresholds)
322
+ # Energy-independent interpolation
323
+ production_statistic, grid_points_no_energy = self._prepare_energy_independent_data()
324
+ reduced_production_grid_points = self._prepare_production_grid_points()
143
325
 
144
- reduced_grid_points, non_flat_mask = self._remove_flat_dimensions(flat_grid_points)
145
- full_non_flat_mask = np.concatenate(([False], non_flat_mask))
146
- reduced_query_point = query_point[0][full_non_flat_mask]
326
+ self.interpolated_production_statistics = self._perform_interpolation(
327
+ grid_points_no_energy, production_statistic, reduced_production_grid_points
328
+ )
147
329
 
148
- interpolated_threshold = griddata(
149
- reduced_grid_points,
150
- flat_energy_thresholds,
151
- reduced_query_point,
152
- method="linear",
153
- fill_value=np.nan,
154
- rescale=False,
330
+ # Energy-dependent interpolation
331
+ self.interpolated_production_statistics_with_energy = (
332
+ self._perform_interpolation_with_energy()
155
333
  )
156
334
 
157
- return interpolated_threshold.item()
335
+ return self.interpolated_production_statistics
158
336
 
159
- def plot_comparison(self, evaluator):
337
+ def plot_comparison(self, grid_point_index=0):
160
338
  """
161
- Plot a comparison between the simulated, derived, and reconstructed events.
339
+ Plot a comparison between interpolated production statistics and reconstructed events.
162
340
 
163
341
  Parameters
164
342
  ----------
165
- evaluator : StatisticalErrorEvaluator
166
- The evaluator for which to plot the comparison.
343
+ grid_point_index : int, optional
344
+ Index of the grid point to plot, by default 0
345
+
346
+ Returns
347
+ -------
348
+ matplotlib.axes.Axes
349
+ The Axes object containing the plot.
167
350
  """
168
- import matplotlib.pyplot as plt # pylint: disable=import-outside-toplevel
351
+ import matplotlib.pyplot as plt # pylint: disable=C0415
352
+
353
+ if not self.evaluators:
354
+ self._logger.error("No evaluators available for plotting.")
355
+ _, ax = plt.subplots()
356
+ ax.text(0.5, 0.5, "No data available", ha="center", va="center")
357
+ return ax
358
+
359
+ # Use first evaluator for energy bins
360
+ bin_edges_low = self.evaluators[0].data["bin_edges_low"][:-1]
361
+ bin_edges_high = self.evaluators[0].data["bin_edges_high"][:-1]
362
+ midpoints = (bin_edges_low + bin_edges_high) / 2
363
+
364
+ if (
365
+ self.interpolated_production_statistics_with_energy is None
366
+ or len(self.interpolated_production_statistics_with_energy) == 0
367
+ or len(self.interpolated_production_statistics_with_energy[0]) <= grid_point_index
368
+ ):
369
+ self._logger.warning(
370
+ f"Invalid grid point index {grid_point_index}. Using index 0 instead."
371
+ )
372
+ grid_point_index = 0
169
373
 
170
- midpoints = 0.5 * (evaluator.data["bin_edges_high"] + evaluator.data["bin_edges_low"])
374
+ _, ax = plt.subplots()
171
375
 
172
- self.grid_points = np.column_stack(
173
- [
174
- midpoints,
175
- np.full_like(midpoints, evaluator.grid_point[1]),
176
- np.full_like(midpoints, evaluator.grid_point[2]),
177
- np.full_like(midpoints, evaluator.grid_point[3]),
178
- np.full_like(midpoints, evaluator.grid_point[4]),
376
+ if (
377
+ self.interpolated_production_statistics_with_energy is not None
378
+ and len(self.interpolated_production_statistics_with_energy) > 0
379
+ ):
380
+ interpolated_stats = self.interpolated_production_statistics_with_energy[0][
381
+ grid_point_index
179
382
  ]
180
- )
181
-
182
- self.interpolate(self.grid_points)
183
-
184
- plt.plot(midpoints, evaluator.production_statistics, label="Derived")
383
+ ax.plot(midpoints, interpolated_stats, label="Interpolated Production Statistics")
185
384
 
186
385
  reconstructed_event_histogram, _ = np.histogram(
187
- evaluator.data["event_energies_reco"], bins=evaluator.data["bin_edges_low"]
386
+ self.evaluators[0].data["event_energies_reco"],
387
+ bins=self.evaluators[0].data["bin_edges_low"],
188
388
  )
189
- plt.plot(midpoints[:-1], reconstructed_event_histogram, label="Reconstructed")
190
-
191
- plt.legend()
192
- plt.xscale("log")
193
- plt.xlabel("Energy (Midpoint of Bin Edges)")
194
- plt.ylabel("Event Count")
195
- plt.title("Comparison of simulated, derived, and reconstructed events")
196
- plt.show()
389
+ ax.plot(midpoints, reconstructed_event_histogram, label="Reconstructed Events")
390
+
391
+ ax.legend()
392
+ ax.set_xscale("log")
393
+ ax.set_yscale("log")
394
+ ax.set_xlabel("Energy (TeV)")
395
+ ax.set_ylabel("Event Count")
396
+ ax.set_title("Comparison of Interpolated and Reconstructed Events")
397
+
398
+ return ax
@@ -89,11 +89,11 @@ class RayTracing:
89
89
  self.use_random_focal_length = use_random_focal_length
90
90
  self.mirrors = self._initialize_mirror_configuration(source_distance, mirror_numbers)
91
91
  self.output_directory = self._io_handler.get_output_directory(
92
- label=self.label, sub_dir="ray-tracing"
92
+ label=self.label, sub_dir="ray_tracing"
93
93
  )
94
94
  self.output_directory.joinpath("results").mkdir(parents=True, exist_ok=True)
95
95
  self._file_results = self.output_directory.joinpath("results").joinpath(
96
- self._generate_file_name(file_type="ray-tracing", suffix=".ecsv")
96
+ self._generate_file_name(file_type="ray_tracing", suffix=".ecsv")
97
97
  )
98
98
  self._psf_images = {}
99
99
  self._results = None
@@ -513,7 +513,7 @@ class RayTracing:
513
513
 
514
514
  if save:
515
515
  plot_file_name = self._generate_file_name(
516
- file_type="ray-tracing",
516
+ file_type="ray_tracing",
517
517
  suffix=".pdf",
518
518
  extra_label=key,
519
519
  )
@@ -524,7 +524,7 @@ class RayTracing:
524
524
 
525
525
  for off_axis_key, image in self._psf_images.items():
526
526
  image_file_name = self._generate_file_name(
527
- file_type="ray-tracing",
527
+ file_type="ray_tracing",
528
528
  off_axis_angle=off_axis_key,
529
529
  suffix=".pdf",
530
530
  extra_label=f"image_{key}",
@@ -534,7 +534,7 @@ class RayTracing:
534
534
  image.plot_image(file_name=image_file)
535
535
 
536
536
  image_cumulative_file_name = self._generate_file_name(
537
- file_type="ray-tracing",
537
+ file_type="ray_tracing",
538
538
  off_axis_angle=off_axis_key,
539
539
  suffix=".pdf",
540
540
  extra_label=f"cumulative_psf_{key}",
@@ -636,8 +636,9 @@ class RayTracing:
636
636
  self, file_type, suffix, off_axis_angle=None, mirror_number=None, extra_label=None
637
637
  ):
638
638
  """Generate file name for output files."""
639
+ file_type_prefix = file_type if file_type == "ray_tracing" else f"ray_tracing_{file_type}"
639
640
  return names.generate_file_name(
640
- file_type=file_type,
641
+ file_type=file_type_prefix,
641
642
  suffix=suffix,
642
643
  site=self.telescope_model.site,
643
644
  telescope_model_name=self.telescope_model.name,