gammasimtools 0.19.0__py3-none-any.whl → 0.20.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.19.0.dist-info → gammasimtools-0.20.0.dist-info}/METADATA +1 -3
- {gammasimtools-0.19.0.dist-info → gammasimtools-0.20.0.dist-info}/RECORD +43 -41
- {gammasimtools-0.19.0.dist-info → gammasimtools-0.20.0.dist-info}/entry_points.txt +2 -2
- simtools/_version.py +2 -2
- simtools/applications/calculate_incident_angles.py +182 -0
- simtools/applications/db_add_simulation_model_from_repository_to_db.py +17 -14
- simtools/applications/db_add_value_from_json_to_db.py +6 -9
- simtools/applications/db_generate_compound_indexes.py +7 -3
- simtools/applications/db_get_file_from_db.py +11 -23
- simtools/applications/derive_trigger_rates.py +91 -0
- simtools/applications/plot_simtel_events.py +73 -31
- simtools/applications/validate_file_using_schema.py +7 -4
- simtools/configuration/commandline_parser.py +17 -11
- simtools/data_model/validate_data.py +8 -3
- simtools/db/db_handler.py +83 -26
- simtools/db/db_model_upload.py +11 -16
- simtools/dependencies.py +10 -5
- simtools/layout/array_layout_utils.py +37 -5
- simtools/model/array_model.py +18 -1
- simtools/model/site_model.py +25 -0
- simtools/production_configuration/derive_corsika_limits.py +9 -34
- simtools/ray_tracing/incident_angles.py +706 -0
- simtools/schemas/model_parameter_and_data_schema.metaschema.yml +2 -2
- simtools/schemas/model_parameters/nsb_reference_spectrum.schema.yml +1 -1
- simtools/schemas/model_parameters/nsb_spectrum.schema.yml +22 -29
- simtools/schemas/model_parameters/stars.schema.yml +1 -1
- simtools/schemas/production_tables.schema.yml +5 -0
- simtools/simtel/simtel_config_writer.py +17 -19
- simtools/simtel/simtel_io_event_histograms.py +253 -516
- simtools/simtel/simtel_io_event_reader.py +51 -2
- simtools/simtel/simtel_io_event_writer.py +31 -11
- simtools/simtel/simtel_io_metadata.py +1 -1
- simtools/simtel/simtel_table_reader.py +3 -3
- simtools/telescope_trigger_rates.py +119 -0
- simtools/testing/log_inspector.py +13 -11
- simtools/utils/geometry.py +20 -0
- simtools/visualization/plot_incident_angles.py +431 -0
- simtools/visualization/plot_simtel_event_histograms.py +376 -0
- simtools/visualization/visualize.py +1 -3
- simtools/applications/calculate_trigger_rate.py +0 -187
- simtools/applications/generate_sim_telarray_histograms.py +0 -196
- simtools/simtel/simtel_io_histogram.py +0 -623
- simtools/simtel/simtel_io_histograms.py +0 -556
- {gammasimtools-0.19.0.dist-info → gammasimtools-0.20.0.dist-info}/WHEEL +0 -0
- {gammasimtools-0.19.0.dist-info → gammasimtools-0.20.0.dist-info}/licenses/LICENSE +0 -0
- {gammasimtools-0.19.0.dist-info → gammasimtools-0.20.0.dist-info}/top_level.txt +0 -0
- /simtools/visualization/{simtel_event_plots.py → plot_simtel_events.py} +0 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
"""Plot simtel event histograms filled with SimtelIOEventHistograms."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
import matplotlib.pyplot as plt
|
|
6
|
+
import numpy as np
|
|
7
|
+
from matplotlib.colors import LogNorm
|
|
8
|
+
|
|
9
|
+
from simtools.simtel.simtel_io_event_histograms import SimtelIOEventHistograms
|
|
10
|
+
|
|
11
|
+
_logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def plot(histograms, output_path=None, limits=None, rebin_factor=2, array_name=None):
|
|
15
|
+
"""
|
|
16
|
+
Plot simtel event histograms.
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
histograms: SimtelIOEventHistograms
|
|
21
|
+
Instance containing the histograms to plot.
|
|
22
|
+
output_path: Path or str, optional
|
|
23
|
+
Directory to save plots. If None, plots will be displayed.
|
|
24
|
+
limits: dict, optional
|
|
25
|
+
Dictionary containing limits for plotting. Keys can include:
|
|
26
|
+
- "upper_radius_limit": Upper limit for core distance
|
|
27
|
+
- "lower_energy_limit": Lower limit for energy
|
|
28
|
+
- "viewcone_radius": Radius for the viewcone
|
|
29
|
+
rebin_factor: int, optional
|
|
30
|
+
Factor by which to reduce the number of bins in 2D histograms for re-binned plots.
|
|
31
|
+
Default is 2 (merge every 2 bins). Set to 0 or 1 to disable re-binning.
|
|
32
|
+
array_name: str, optional
|
|
33
|
+
Name of the telescope array configuration.
|
|
34
|
+
"""
|
|
35
|
+
_logger.info(f"Plotting histograms written to {output_path}")
|
|
36
|
+
|
|
37
|
+
plots = _generate_plot_configurations(histograms, limits)
|
|
38
|
+
_execute_plotting_loop(plots, output_path, rebin_factor, array_name)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _get_limits(name, limits):
|
|
42
|
+
"""
|
|
43
|
+
Extract limits from the provided dictionary for plotting.
|
|
44
|
+
|
|
45
|
+
Fine tuned to expected histograms to be plotted.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def _safe_value(limits, key):
|
|
49
|
+
val = limits.get(key)
|
|
50
|
+
return getattr(val, "value", None)
|
|
51
|
+
|
|
52
|
+
mapping = {
|
|
53
|
+
"energy": {"x": _safe_value(limits, "lower_energy_limit")},
|
|
54
|
+
"core_distance": {"x": _safe_value(limits, "upper_radius_limit")},
|
|
55
|
+
"angular_distance": {"x": _safe_value(limits, "viewcone_radius")},
|
|
56
|
+
"core_vs_energy": {
|
|
57
|
+
"x": _safe_value(limits, "upper_radius_limit"),
|
|
58
|
+
"y": _safe_value(limits, "lower_energy_limit"),
|
|
59
|
+
},
|
|
60
|
+
"angular_distance_vs_energy": {
|
|
61
|
+
"x": _safe_value(limits, "viewcone_radius"),
|
|
62
|
+
"y": _safe_value(limits, "lower_energy_limit"),
|
|
63
|
+
},
|
|
64
|
+
"x_core_shower_vs_y_core_shower": {"r": _safe_value(limits, "upper_radius_limit")},
|
|
65
|
+
}
|
|
66
|
+
return mapping.get(name)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _generate_plot_configurations(histograms, limits):
|
|
70
|
+
"""Generate plot configurations for all histogram types."""
|
|
71
|
+
hist_1d_params = {"color": "tab:green", "edgecolor": "tab:green", "lw": 1}
|
|
72
|
+
hist_2d_params = {"norm": "log", "cmap": "viridis", "show_contour": False}
|
|
73
|
+
hist_2d_normalized_params = {"norm": "linear", "cmap": "viridis", "show_contour": True}
|
|
74
|
+
plots = {}
|
|
75
|
+
for name, hist in histograms.items():
|
|
76
|
+
if hist["histogram"] is None:
|
|
77
|
+
continue
|
|
78
|
+
if hist["1d"]:
|
|
79
|
+
plots[name] = _create_1d_plot_config(
|
|
80
|
+
hist, name=name, plot_params=hist_1d_params, limits=limits
|
|
81
|
+
)
|
|
82
|
+
else:
|
|
83
|
+
if "cumulative" in name or "efficiency" in name:
|
|
84
|
+
plot_params = hist_2d_normalized_params
|
|
85
|
+
else:
|
|
86
|
+
plot_params = hist_2d_params
|
|
87
|
+
|
|
88
|
+
plots[name] = _create_2d_plot_config(
|
|
89
|
+
hist, name=name, plot_params=plot_params, limits=limits
|
|
90
|
+
)
|
|
91
|
+
return plots
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _get_axis_title(axis_titles, axis):
|
|
95
|
+
"""Return axis title for given axis."""
|
|
96
|
+
if axis_titles is None:
|
|
97
|
+
return None
|
|
98
|
+
if axis == "x" and len(axis_titles) > 0:
|
|
99
|
+
return axis_titles[0]
|
|
100
|
+
if axis == "y" and len(axis_titles) > 1:
|
|
101
|
+
return axis_titles[1]
|
|
102
|
+
if axis == "z" and len(axis_titles) > 2:
|
|
103
|
+
return axis_titles[2]
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _create_1d_plot_config(histogram, name, plot_params, limits):
|
|
108
|
+
"""Create a 1D plot configuration."""
|
|
109
|
+
_logger.debug(f"Creating plot config for {name} with params: {plot_params}")
|
|
110
|
+
return {
|
|
111
|
+
"data": histogram["histogram"],
|
|
112
|
+
"bins": histogram["bin_edges"],
|
|
113
|
+
"plot_type": "histogram",
|
|
114
|
+
"plot_params": plot_params,
|
|
115
|
+
"labels": {
|
|
116
|
+
"x": _get_axis_title(histogram.get("axis_titles"), "x"),
|
|
117
|
+
"y": _get_axis_title(histogram.get("axis_titles"), "y"),
|
|
118
|
+
"title": f"{histogram['title']}: {name.replace('_', ' ')}",
|
|
119
|
+
},
|
|
120
|
+
"scales": histogram["plot_scales"],
|
|
121
|
+
"lines": _get_limits(name, limits) if limits else {},
|
|
122
|
+
"filename": name,
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _create_2d_plot_config(histogram, name, plot_params, limits):
|
|
127
|
+
"""Create a 2D plot configuration."""
|
|
128
|
+
_logger.debug(f"Creating plot config for {name} with params: {plot_params}")
|
|
129
|
+
return {
|
|
130
|
+
"data": histogram["histogram"],
|
|
131
|
+
"bins": [histogram["bin_edges"][0], histogram["bin_edges"][1]],
|
|
132
|
+
"plot_type": "histogram2d",
|
|
133
|
+
"plot_params": plot_params,
|
|
134
|
+
"labels": {
|
|
135
|
+
"x": _get_axis_title(histogram.get("axis_titles"), "x"),
|
|
136
|
+
"y": _get_axis_title(histogram.get("axis_titles"), "y"),
|
|
137
|
+
"title": f"{histogram['title']}: {name.replace('_', ' ')}",
|
|
138
|
+
},
|
|
139
|
+
"lines": _get_limits(name, limits) if limits else {},
|
|
140
|
+
"scales": histogram["plot_scales"],
|
|
141
|
+
"colorbar_label": _get_axis_title(histogram.get("axis_titles"), "z"),
|
|
142
|
+
"filename": name,
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _execute_plotting_loop(plots, output_path, rebin_factor, array_name):
|
|
147
|
+
"""Execute the main plotting loop for all plot configurations."""
|
|
148
|
+
for plot_key, plot_args in plots.items():
|
|
149
|
+
plot_filename = plot_args.pop("filename")
|
|
150
|
+
|
|
151
|
+
if plot_args.get("data") is None:
|
|
152
|
+
_logger.warning(f"Skipping plot {plot_key} - no data available")
|
|
153
|
+
continue
|
|
154
|
+
|
|
155
|
+
if array_name and plot_args.get("labels", {}).get("title"):
|
|
156
|
+
plot_args["labels"]["title"] += f" ({array_name} array)"
|
|
157
|
+
|
|
158
|
+
filename = _build_plot_filename(plot_filename, array_name)
|
|
159
|
+
output_file = output_path / filename if output_path else None
|
|
160
|
+
result = _create_plot(**plot_args, output_file=output_file)
|
|
161
|
+
|
|
162
|
+
# Skip re-binned plot if main plot failed
|
|
163
|
+
if result is None:
|
|
164
|
+
continue
|
|
165
|
+
|
|
166
|
+
if _should_create_rebinned_plot(rebin_factor, plot_args, plot_key):
|
|
167
|
+
_create_rebinned_plot(plot_args, filename, output_path, rebin_factor)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _build_plot_filename(base_filename, array_name=None):
|
|
171
|
+
"""
|
|
172
|
+
Build the full plot filename with appropriate extensions.
|
|
173
|
+
|
|
174
|
+
Parameters
|
|
175
|
+
----------
|
|
176
|
+
base_filename : str
|
|
177
|
+
The base filename without extension
|
|
178
|
+
array_name : str, optional
|
|
179
|
+
Name of the array to append to filename
|
|
180
|
+
|
|
181
|
+
Returns
|
|
182
|
+
-------
|
|
183
|
+
str
|
|
184
|
+
Complete filename with extension
|
|
185
|
+
"""
|
|
186
|
+
return f"{base_filename}_{array_name}.png" if array_name else f"{base_filename}.png"
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _should_create_rebinned_plot(rebin_factor, plot_args, plot_key):
|
|
190
|
+
"""
|
|
191
|
+
Check if a re-binned version of the plot should be created.
|
|
192
|
+
|
|
193
|
+
Parameters
|
|
194
|
+
----------
|
|
195
|
+
rebin_factor : int
|
|
196
|
+
Factor by which to rebin the energy axis
|
|
197
|
+
plot_args : dict
|
|
198
|
+
Plot arguments
|
|
199
|
+
plot_key : str
|
|
200
|
+
Key identifying the plot type
|
|
201
|
+
|
|
202
|
+
Returns
|
|
203
|
+
-------
|
|
204
|
+
bool
|
|
205
|
+
True if a re-binned plot should be created, False otherwise
|
|
206
|
+
"""
|
|
207
|
+
return (
|
|
208
|
+
rebin_factor > 1
|
|
209
|
+
and plot_args["plot_type"] == "histogram2d"
|
|
210
|
+
and plot_key.endswith("_cumulative")
|
|
211
|
+
and plot_args.get("plot_params", {}).get("norm") == "linear"
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _create_rebinned_plot(plot_args, filename, output_path, rebin_factor):
|
|
216
|
+
"""
|
|
217
|
+
Create a re-binned version of a 2D histogram plot.
|
|
218
|
+
|
|
219
|
+
Parameters
|
|
220
|
+
----------
|
|
221
|
+
plot_args : dict
|
|
222
|
+
Plot arguments for the original plot
|
|
223
|
+
filename : str
|
|
224
|
+
Filename of the original plot
|
|
225
|
+
output_path : Path or None
|
|
226
|
+
Path to save the plot to, or None
|
|
227
|
+
rebin_factor : int
|
|
228
|
+
Factor by which to rebin the energy axis
|
|
229
|
+
"""
|
|
230
|
+
data = plot_args["data"]
|
|
231
|
+
bins = plot_args["bins"]
|
|
232
|
+
|
|
233
|
+
rebinned_data, rebinned_x_bins, rebinned_y_bins = SimtelIOEventHistograms.rebin_2d_histogram(
|
|
234
|
+
data, bins[0], bins[1], rebin_factor
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
rebinned_plot_args = plot_args.copy()
|
|
238
|
+
rebinned_plot_args["data"] = rebinned_data
|
|
239
|
+
rebinned_plot_args["bins"] = [rebinned_x_bins, rebinned_y_bins]
|
|
240
|
+
|
|
241
|
+
if rebinned_plot_args.get("labels", {}).get("title"):
|
|
242
|
+
rebinned_plot_args["labels"]["title"] += f" (Energy rebinned {rebin_factor}x)"
|
|
243
|
+
|
|
244
|
+
rebinned_filename = f"{filename.replace('.png', '')}_rebinned.png"
|
|
245
|
+
rebinned_output_file = output_path / rebinned_filename if output_path else None
|
|
246
|
+
_create_plot(**rebinned_plot_args, output_file=rebinned_output_file)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _create_plot(
|
|
250
|
+
data,
|
|
251
|
+
bins=None,
|
|
252
|
+
plot_type="histogram",
|
|
253
|
+
plot_params=None,
|
|
254
|
+
labels=None,
|
|
255
|
+
scales=None,
|
|
256
|
+
colorbar_label=None,
|
|
257
|
+
output_file=None,
|
|
258
|
+
lines=None,
|
|
259
|
+
):
|
|
260
|
+
"""Create and save a plot with the given parameters."""
|
|
261
|
+
plot_params = plot_params or {}
|
|
262
|
+
labels = labels or {}
|
|
263
|
+
scales = scales or {}
|
|
264
|
+
lines = lines or {}
|
|
265
|
+
|
|
266
|
+
if not _has_data(data):
|
|
267
|
+
return None
|
|
268
|
+
|
|
269
|
+
fig, ax = plt.subplots(figsize=(8, 6))
|
|
270
|
+
_plot_data(ax, data, bins, plot_type, plot_params, colorbar_label)
|
|
271
|
+
_add_lines(ax, lines)
|
|
272
|
+
ax.set(
|
|
273
|
+
xlabel=labels.get("x", ""),
|
|
274
|
+
ylabel=labels.get("y", ""),
|
|
275
|
+
title=labels.get("title", ""),
|
|
276
|
+
xscale=scales.get("x", "linear"),
|
|
277
|
+
yscale=scales.get("y", "linear"),
|
|
278
|
+
)
|
|
279
|
+
if output_file:
|
|
280
|
+
_logger.info(f"Saving plot to {output_file}")
|
|
281
|
+
fig.savefig(output_file, dpi=300, bbox_inches="tight")
|
|
282
|
+
plt.close(fig)
|
|
283
|
+
else:
|
|
284
|
+
plt.tight_layout()
|
|
285
|
+
plt.show()
|
|
286
|
+
|
|
287
|
+
return fig
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _has_data(data):
|
|
291
|
+
"""Check that the data for plotting is not None or empty."""
|
|
292
|
+
if data is None or (isinstance(data, np.ndarray) and data.size == 0):
|
|
293
|
+
_logger.warning("No data available for plotting")
|
|
294
|
+
return False
|
|
295
|
+
return True
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def _plot_data(ax, data, bins, plot_type, plot_params, colorbar_label):
|
|
299
|
+
"""Plot the data on the given axes."""
|
|
300
|
+
if plot_type == "histogram":
|
|
301
|
+
ax.bar(bins[:-1], data, width=np.diff(bins), **plot_params)
|
|
302
|
+
elif plot_type == "histogram2d":
|
|
303
|
+
pcm = _create_2d_histogram_plot(data, bins, plot_params)
|
|
304
|
+
plt.colorbar(pcm, label=colorbar_label)
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def _add_lines(ax, lines):
|
|
308
|
+
"""Add reference lines to the plot."""
|
|
309
|
+
if lines.get("x") is not None:
|
|
310
|
+
ax.axvline(lines["x"], color="r", linestyle="--", linewidth=0.5)
|
|
311
|
+
if lines.get("y") is not None:
|
|
312
|
+
ax.axhline(lines["y"], color="r", linestyle="--", linewidth=0.5)
|
|
313
|
+
if lines.get("r") is not None:
|
|
314
|
+
ax.add_artist(
|
|
315
|
+
plt.Circle((0, 0), lines["r"], color="r", fill=False, linestyle="--", linewidth=0.5)
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _create_2d_histogram_plot(data, bins, plot_params):
|
|
320
|
+
"""
|
|
321
|
+
Create a 2D histogram plot with the given parameters.
|
|
322
|
+
|
|
323
|
+
Parameters
|
|
324
|
+
----------
|
|
325
|
+
data : np.ndarray
|
|
326
|
+
2D histogram data
|
|
327
|
+
bins : tuple of np.ndarray
|
|
328
|
+
Bin edges for x and y axes
|
|
329
|
+
plot_params : dict
|
|
330
|
+
Plot parameters including norm, cmap, and show_contour
|
|
331
|
+
|
|
332
|
+
Returns
|
|
333
|
+
-------
|
|
334
|
+
matplotlib.collections.QuadMesh
|
|
335
|
+
The created pcolormesh object for colorbar attachment
|
|
336
|
+
"""
|
|
337
|
+
if plot_params.get("norm") == "linear":
|
|
338
|
+
pcm = plt.pcolormesh(
|
|
339
|
+
bins[0],
|
|
340
|
+
bins[1],
|
|
341
|
+
data.T,
|
|
342
|
+
vmin=0,
|
|
343
|
+
vmax=1,
|
|
344
|
+
cmap=plot_params.get("cmap", "viridis"),
|
|
345
|
+
)
|
|
346
|
+
# Add contour line at value=1.0 for normalized histograms
|
|
347
|
+
if plot_params.get("show_contour", True):
|
|
348
|
+
x_centers = (bins[0][1:] + bins[0][:-1]) / 2
|
|
349
|
+
y_centers = (bins[1][1:] + bins[1][:-1]) / 2
|
|
350
|
+
x_mesh, y_mesh = np.meshgrid(x_centers, y_centers)
|
|
351
|
+
plt.contour(
|
|
352
|
+
x_mesh,
|
|
353
|
+
y_mesh,
|
|
354
|
+
data.T,
|
|
355
|
+
levels=[0.999999], # very close to 1 for floating point precision
|
|
356
|
+
colors=["tab:red"],
|
|
357
|
+
linestyles=["--"],
|
|
358
|
+
linewidths=[0.5],
|
|
359
|
+
)
|
|
360
|
+
else:
|
|
361
|
+
# Handle empty or invalid data for logarithmic scaling
|
|
362
|
+
data_max = data.max()
|
|
363
|
+
if data_max <= 0:
|
|
364
|
+
_logger.warning("No positive data found for logarithmic scaling, using linear scale")
|
|
365
|
+
pcm = plt.pcolormesh(
|
|
366
|
+
bins[0], bins[1], data.T, vmin=0, vmax=max(1, data_max), cmap="viridis"
|
|
367
|
+
)
|
|
368
|
+
else:
|
|
369
|
+
# Ensure vmin is less than vmax for LogNorm
|
|
370
|
+
vmin = max(1, data[data > 0].min()) if np.any(data > 0) else 1
|
|
371
|
+
vmax = max(vmin + 1, data_max)
|
|
372
|
+
pcm = plt.pcolormesh(
|
|
373
|
+
bins[0], bins[1], data.T, norm=LogNorm(vmin=vmin, vmax=vmax), cmap="viridis"
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
return pcm
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import logging
|
|
5
5
|
import re
|
|
6
6
|
from collections import OrderedDict
|
|
7
|
+
from pathlib import Path
|
|
7
8
|
|
|
8
9
|
import astropy.units as u
|
|
9
10
|
import matplotlib.pyplot as plt
|
|
@@ -641,9 +642,6 @@ def save_figure(fig, output_file, figure_format=None, log_title="", dpi="figure"
|
|
|
641
642
|
title: str
|
|
642
643
|
Title of the figure to be added to the log message.
|
|
643
644
|
"""
|
|
644
|
-
# pylint: disable=import-outside-toplevel
|
|
645
|
-
from pathlib import Path
|
|
646
|
-
|
|
647
645
|
figure_format = figure_format or ["pdf", "png"]
|
|
648
646
|
for fmt in figure_format:
|
|
649
647
|
_file = Path(output_file).with_suffix(f".{fmt}")
|
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/python3
|
|
2
|
-
|
|
3
|
-
r"""
|
|
4
|
-
Calculates array or single-telescope trigger rates.
|
|
5
|
-
|
|
6
|
-
The applications reads from a sim_telarray output file, a list of
|
|
7
|
-
sim_telarray output files ou from a file containing a list of sim_telarray files.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
Command line arguments
|
|
11
|
-
----------------------
|
|
12
|
-
simtel_file_names (str or list):
|
|
13
|
-
Path to the sim_telarray file or a list of sim_telarray output files.
|
|
14
|
-
Files can be generated in `simulate_prod` using the ``--save_file_lists`` option.
|
|
15
|
-
save_tables (bool):
|
|
16
|
-
If true, save the tables with the energy-dependent trigger rate to a ecsv file.
|
|
17
|
-
area_from_distribution (bool):
|
|
18
|
-
If true, the area thrown (the area in which the simulated events are distributed)
|
|
19
|
-
in the trigger rate calculation is estimated based on the event distribution.
|
|
20
|
-
The expected shape of the distribution of events as function of the core distance is triangular
|
|
21
|
-
up to the maximum distance. The weighted mean radius of the triangular distribution is 2/3 times
|
|
22
|
-
the upper edge. Therefore, when using the ``area_from_distribution`` flag, the mean distance
|
|
23
|
-
times 3/2, returns just the position of the upper edge in the triangle distribution with little
|
|
24
|
-
impact of the binning and little dependence on the scatter area defined in the simulation.
|
|
25
|
-
This is special useful when calculating trigger rate for individual telescopes.
|
|
26
|
-
If false, the area thrown is estimated based on the maximum distance as given in
|
|
27
|
-
the simulation configuration.
|
|
28
|
-
|
|
29
|
-
Example
|
|
30
|
-
-------
|
|
31
|
-
Calculate trigger rate from sim_telarray file
|
|
32
|
-
|
|
33
|
-
.. code-block:: console
|
|
34
|
-
|
|
35
|
-
simtools-calculate-trigger-rate --simtel_file_names tests/resources/ \\
|
|
36
|
-
run201_proton_za20deg_azm0deg_North_test_layout_test-prod.simtel.zst
|
|
37
|
-
|
|
38
|
-
Expected final print-out message:
|
|
39
|
-
|
|
40
|
-
.. code-block:: console
|
|
41
|
-
|
|
42
|
-
System trigger rate (Hz): 9.0064e+03 pm 9.0087e+03 Hz
|
|
43
|
-
|
|
44
|
-
"""
|
|
45
|
-
|
|
46
|
-
import logging
|
|
47
|
-
from pathlib import Path
|
|
48
|
-
|
|
49
|
-
import simtools.utils.general as gen
|
|
50
|
-
from simtools.configuration import configurator
|
|
51
|
-
from simtools.io import io_handler
|
|
52
|
-
from simtools.simtel.simtel_io_histograms import SimtelIOHistograms
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def _parse(label, description):
|
|
56
|
-
"""
|
|
57
|
-
Parse command line configuration.
|
|
58
|
-
|
|
59
|
-
Parameters
|
|
60
|
-
----------
|
|
61
|
-
label: str
|
|
62
|
-
Label describing the application.
|
|
63
|
-
description: str
|
|
64
|
-
Description of the application.
|
|
65
|
-
|
|
66
|
-
Returns
|
|
67
|
-
-------
|
|
68
|
-
CommandLineParser
|
|
69
|
-
Command line parser object
|
|
70
|
-
|
|
71
|
-
"""
|
|
72
|
-
config = configurator.Configurator(label=label, description=description)
|
|
73
|
-
|
|
74
|
-
config.parser.add_argument(
|
|
75
|
-
"--simtel_file_names",
|
|
76
|
-
help="Name of the sim_telarray output files to be calculate the trigger rate from or the "
|
|
77
|
-
"text file containing the list of sim_telarray output files.",
|
|
78
|
-
nargs="+",
|
|
79
|
-
required=True,
|
|
80
|
-
type=str,
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
config.parser.add_argument(
|
|
84
|
-
"--save_tables",
|
|
85
|
-
help="Save trigger rates per energy bin into ECSV files.",
|
|
86
|
-
action="store_true",
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
config.parser.add_argument(
|
|
90
|
-
"--area_from_distribution",
|
|
91
|
-
help="Calculate trigger rates using the event distribution.",
|
|
92
|
-
action="store_true",
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
config.parser.add_argument(
|
|
96
|
-
"--stack_files",
|
|
97
|
-
help="Stacks all histograms.",
|
|
98
|
-
action="store_true",
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
config_parser, _ = config.initialize(
|
|
102
|
-
db_config=False,
|
|
103
|
-
paths=True,
|
|
104
|
-
simulation_configuration={"corsika_configuration": ["energy_range", "view_cone"]},
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
return config_parser
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
def _get_simulation_parameters(config_parser):
|
|
111
|
-
"""
|
|
112
|
-
Get energy range and view cone in the correct form to use in the simtel classes.
|
|
113
|
-
|
|
114
|
-
Parameters
|
|
115
|
-
----------
|
|
116
|
-
CommandLineParser:
|
|
117
|
-
Command line parser object as defined by the _parse function.
|
|
118
|
-
|
|
119
|
-
Returns
|
|
120
|
-
-------
|
|
121
|
-
list:
|
|
122
|
-
The energy range used in the simulation.
|
|
123
|
-
list:
|
|
124
|
-
The view cone used in the simulation.
|
|
125
|
-
|
|
126
|
-
"""
|
|
127
|
-
|
|
128
|
-
def convert(param, unit):
|
|
129
|
-
return [param[0].to(unit).value, param[1].to(unit).value] if param else None
|
|
130
|
-
|
|
131
|
-
return convert(config_parser.get("energy_range"), "TeV"), convert(
|
|
132
|
-
config_parser.get("view_cone"), "deg"
|
|
133
|
-
)
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
def main(): # noqa: D103
|
|
137
|
-
label = Path(__file__).stem
|
|
138
|
-
description = (
|
|
139
|
-
"Calculates the simulated and triggered event rate based on sim_telarray output files."
|
|
140
|
-
)
|
|
141
|
-
config_parser = _parse(label, description)
|
|
142
|
-
|
|
143
|
-
logger = logging.getLogger()
|
|
144
|
-
logger.setLevel(gen.get_log_level_from_user(config_parser["log_level"]))
|
|
145
|
-
|
|
146
|
-
sim_telarray_files = gen.get_list_of_files_from_command_line(
|
|
147
|
-
config_parser["simtel_file_names"], [".zst", ".simtel", ".hdata"]
|
|
148
|
-
)
|
|
149
|
-
energy_range, view_cone = _get_simulation_parameters(config_parser)
|
|
150
|
-
|
|
151
|
-
histograms = SimtelIOHistograms(
|
|
152
|
-
sim_telarray_files,
|
|
153
|
-
area_from_distribution=config_parser["area_from_distribution"],
|
|
154
|
-
energy_range=energy_range,
|
|
155
|
-
view_cone=view_cone,
|
|
156
|
-
)
|
|
157
|
-
|
|
158
|
-
logger.info("Calculating simulated and triggered event rate")
|
|
159
|
-
(
|
|
160
|
-
sim_event_rates,
|
|
161
|
-
triggered_event_rates,
|
|
162
|
-
triggered_event_rate_uncertainties,
|
|
163
|
-
trigger_rate_in_tables,
|
|
164
|
-
) = histograms.calculate_trigger_rates(
|
|
165
|
-
print_info=True, stack_files=config_parser["stack_files"]
|
|
166
|
-
)
|
|
167
|
-
|
|
168
|
-
# Print out results
|
|
169
|
-
for i_hist, _ in enumerate(sim_event_rates):
|
|
170
|
-
print(f"\nFile {histograms.histogram_files[i_hist]}\n")
|
|
171
|
-
print(
|
|
172
|
-
f"System trigger rate (Hz): {triggered_event_rates[i_hist].value:.4e} \u00b1 "
|
|
173
|
-
f"{triggered_event_rate_uncertainties[i_hist].value:.4e} Hz"
|
|
174
|
-
)
|
|
175
|
-
if config_parser["save_tables"]:
|
|
176
|
-
io_handler_instance = io_handler.IOHandler()
|
|
177
|
-
output_path = io_handler_instance.get_output_directory(label, sub_dir="application-plots")
|
|
178
|
-
for i_table, table in enumerate(trigger_rate_in_tables):
|
|
179
|
-
output_file = (
|
|
180
|
-
str(output_path.joinpath(Path(sim_telarray_files[i_table]).stem)) + ".ecsv"
|
|
181
|
-
)
|
|
182
|
-
logger.info(f"Writing table {i_table + 1} to {output_file}")
|
|
183
|
-
table.write(output_file, overwrite=True)
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
if __name__ == "__main__":
|
|
187
|
-
main()
|