gammasimtools 0.16.0__py3-none-any.whl → 0.17.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {gammasimtools-0.16.0.dist-info → gammasimtools-0.17.0.dist-info}/METADATA +4 -2
- {gammasimtools-0.16.0.dist-info → gammasimtools-0.17.0.dist-info}/RECORD +60 -54
- {gammasimtools-0.16.0.dist-info → gammasimtools-0.17.0.dist-info}/WHEEL +1 -1
- {gammasimtools-0.16.0.dist-info → gammasimtools-0.17.0.dist-info}/entry_points.txt +3 -1
- simtools/_version.py +2 -2
- simtools/applications/derive_ctao_array_layouts.py +5 -5
- simtools/applications/generate_simtel_event_data.py +36 -46
- simtools/applications/merge_tables.py +104 -0
- simtools/applications/plot_array_layout.py +145 -258
- simtools/applications/production_derive_corsika_limits.py +35 -220
- simtools/applications/production_derive_statistics.py +77 -43
- simtools/applications/simulate_light_emission.py +1 -0
- simtools/applications/simulate_prod.py +30 -18
- simtools/applications/simulate_prod_htcondor_generator.py +0 -1
- simtools/applications/submit_array_layouts.py +93 -0
- simtools/applications/verify_simulation_model_production_tables.py +52 -0
- simtools/camera/camera_efficiency.py +3 -3
- simtools/configuration/commandline_parser.py +28 -34
- simtools/configuration/configurator.py +0 -4
- simtools/corsika/corsika_config.py +17 -12
- simtools/corsika/primary_particle.py +46 -13
- simtools/data_model/metadata_collector.py +7 -3
- simtools/db/db_handler.py +11 -11
- simtools/db/db_model_upload.py +2 -2
- simtools/io_operations/io_handler.py +2 -2
- simtools/io_operations/io_table_handler.py +345 -0
- simtools/job_execution/htcondor_script_generator.py +2 -2
- simtools/job_execution/job_manager.py +7 -121
- simtools/layout/array_layout_utils.py +385 -0
- simtools/model/array_model.py +5 -0
- simtools/model/model_repository.py +134 -0
- simtools/production_configuration/{calculate_statistical_errors_grid_point.py → calculate_statistical_uncertainties_grid_point.py} +101 -112
- simtools/production_configuration/derive_corsika_limits.py +239 -111
- simtools/production_configuration/derive_corsika_limits_grid.py +189 -0
- simtools/production_configuration/derive_production_statistics.py +57 -26
- simtools/production_configuration/derive_production_statistics_handler.py +70 -37
- simtools/production_configuration/interpolation_handler.py +296 -94
- simtools/ray_tracing/ray_tracing.py +7 -6
- simtools/reporting/docs_read_parameters.py +104 -62
- simtools/runners/corsika_simtel_runner.py +4 -1
- simtools/runners/runner_services.py +5 -4
- simtools/schemas/model_parameters/dsum_threshold.schema.yml +41 -0
- simtools/schemas/production_configuration_metrics.schema.yml +2 -2
- simtools/simtel/simtel_config_writer.py +34 -14
- simtools/simtel/simtel_io_event_reader.py +301 -194
- simtools/simtel/simtel_io_event_writer.py +207 -227
- simtools/simtel/simtel_io_file_info.py +9 -4
- simtools/simtel/simtel_io_metadata.py +20 -5
- simtools/simtel/simulator_array.py +2 -2
- simtools/simtel/simulator_light_emission.py +79 -34
- simtools/simtel/simulator_ray_tracing.py +2 -2
- simtools/simulator.py +101 -68
- simtools/testing/validate_output.py +4 -1
- simtools/utils/general.py +1 -1
- simtools/utils/names.py +5 -5
- simtools/visualization/plot_array_layout.py +242 -0
- simtools/visualization/plot_pixels.py +681 -0
- simtools/visualization/visualize.py +3 -219
- simtools/applications/production_generate_simulation_config.py +0 -152
- simtools/layout/ctao_array_layouts.py +0 -172
- simtools/production_configuration/generate_simulation_config.py +0 -158
- {gammasimtools-0.16.0.dist-info → gammasimtools-0.17.0.dist-info}/licenses/LICENSE +0 -0
- {gammasimtools-0.16.0.dist-info → gammasimtools-0.17.0.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
"""Handle interpolation between multiple
|
|
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
|
-
"""
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
57
|
-
self.
|
|
104
|
+
for i, (energy_grid, production_statistics) in enumerate(
|
|
105
|
+
zip(self.energy_grids, self.production_statistics)
|
|
58
106
|
):
|
|
59
|
-
az =
|
|
60
|
-
zen =
|
|
61
|
-
nsb =
|
|
62
|
-
offset =
|
|
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
|
-
"""
|
|
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 >
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
95
|
-
|
|
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
|
|
253
|
+
Interpolated values.
|
|
101
254
|
"""
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
method=
|
|
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
|
|
268
|
+
def _perform_interpolation_with_energy(self):
|
|
116
269
|
"""
|
|
117
|
-
|
|
270
|
+
Perform energy-dependent interpolation.
|
|
118
271
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
127
|
-
Interpolated
|
|
315
|
+
np.ndarray
|
|
316
|
+
Interpolated values at the query points.
|
|
128
317
|
"""
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
142
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
326
|
+
self.interpolated_production_statistics = self._perform_interpolation(
|
|
327
|
+
grid_points_no_energy, production_statistic, reduced_production_grid_points
|
|
328
|
+
)
|
|
147
329
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
|
335
|
+
return self.interpolated_production_statistics
|
|
158
336
|
|
|
159
|
-
def plot_comparison(self,
|
|
337
|
+
def plot_comparison(self, grid_point_index=0):
|
|
160
338
|
"""
|
|
161
|
-
Plot a comparison between
|
|
339
|
+
Plot a comparison between interpolated production statistics and reconstructed events.
|
|
162
340
|
|
|
163
341
|
Parameters
|
|
164
342
|
----------
|
|
165
|
-
|
|
166
|
-
|
|
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=
|
|
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
|
-
|
|
374
|
+
_, ax = plt.subplots()
|
|
171
375
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
|
|
386
|
+
self.evaluators[0].data["event_energies_reco"],
|
|
387
|
+
bins=self.evaluators[0].data["bin_edges_low"],
|
|
188
388
|
)
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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="
|
|
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="
|
|
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="
|
|
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="
|
|
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="
|
|
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=
|
|
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,
|