gammasimtools 0.24.0__py3-none-any.whl → 0.26.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.24.0.dist-info → gammasimtools-0.26.0.dist-info}/METADATA +2 -1
- {gammasimtools-0.24.0.dist-info → gammasimtools-0.26.0.dist-info}/RECORD +134 -130
- {gammasimtools-0.24.0.dist-info → gammasimtools-0.26.0.dist-info}/entry_points.txt +3 -1
- {gammasimtools-0.24.0.dist-info → gammasimtools-0.26.0.dist-info}/licenses/LICENSE +1 -1
- simtools/_version.py +2 -2
- simtools/application_control.py +78 -0
- simtools/applications/calculate_incident_angles.py +0 -2
- simtools/applications/convert_geo_coordinates_of_array_elements.py +1 -2
- simtools/applications/db_add_file_to_db.py +1 -1
- simtools/applications/db_add_simulation_model_from_repository_to_db.py +1 -1
- simtools/applications/db_add_value_from_json_to_db.py +1 -1
- simtools/applications/db_generate_compound_indexes.py +1 -1
- simtools/applications/db_get_array_layouts_from_db.py +2 -6
- simtools/applications/db_get_file_from_db.py +1 -1
- simtools/applications/db_get_parameter_from_db.py +1 -1
- simtools/applications/db_inspect_databases.py +1 -1
- simtools/applications/db_upload_model_repository.py +1 -1
- simtools/applications/derive_ctao_array_layouts.py +1 -2
- simtools/applications/derive_mirror_rnda.py +1 -3
- simtools/applications/derive_psf_parameters.py +5 -1
- simtools/applications/derive_pulse_shape_parameters.py +194 -0
- simtools/applications/derive_trigger_rates.py +1 -1
- simtools/applications/docs_produce_array_element_report.py +2 -8
- simtools/applications/docs_produce_calibration_reports.py +1 -3
- simtools/applications/docs_produce_model_parameter_reports.py +0 -2
- simtools/applications/docs_produce_simulation_configuration_report.py +1 -3
- simtools/applications/generate_array_config.py +0 -1
- simtools/applications/generate_corsika_histograms.py +48 -235
- simtools/applications/generate_regular_arrays.py +5 -35
- simtools/applications/generate_simtel_event_data.py +2 -2
- simtools/applications/maintain_simulation_model_add_production.py +2 -2
- simtools/applications/maintain_simulation_model_write_array_element_positions.py +87 -0
- simtools/applications/plot_array_layout.py +64 -108
- simtools/applications/plot_simulated_event_distributions.py +57 -0
- simtools/applications/plot_tabular_data.py +0 -1
- simtools/applications/plot_tabular_data_for_model_parameter.py +1 -6
- simtools/applications/production_derive_corsika_limits.py +1 -1
- simtools/applications/production_generate_grid.py +0 -1
- simtools/applications/run_application.py +1 -1
- simtools/applications/simulate_flasher.py +3 -4
- simtools/applications/simulate_illuminator.py +0 -1
- simtools/applications/simulate_pedestals.py +2 -6
- simtools/applications/simulate_prod.py +9 -28
- simtools/applications/simulate_prod_htcondor_generator.py +8 -1
- simtools/applications/submit_array_layouts.py +7 -7
- simtools/applications/submit_model_parameter_from_external.py +1 -3
- simtools/applications/validate_camera_efficiency.py +0 -1
- simtools/applications/validate_camera_fov.py +0 -1
- simtools/applications/validate_cumulative_psf.py +0 -2
- simtools/applications/validate_file_using_schema.py +49 -123
- simtools/applications/validate_optics.py +0 -13
- simtools/camera/camera_efficiency.py +1 -6
- simtools/camera/single_photon_electron_spectrum.py +2 -1
- simtools/configuration/commandline_parser.py +43 -8
- simtools/configuration/configurator.py +6 -11
- simtools/corsika/corsika_config.py +204 -99
- simtools/corsika/corsika_histograms.py +411 -1735
- simtools/corsika/primary_particle.py +1 -1
- simtools/data_model/metadata_collector.py +5 -2
- simtools/data_model/metadata_model.py +0 -4
- simtools/data_model/model_data_writer.py +27 -17
- simtools/data_model/schema.py +112 -5
- simtools/data_model/validate_data.py +80 -48
- simtools/db/db_handler.py +19 -8
- simtools/db/db_model_upload.py +2 -1
- simtools/db/mongo_db.py +133 -42
- simtools/dependencies.py +83 -44
- simtools/io/ascii_handler.py +4 -2
- simtools/io/table_handler.py +1 -1
- simtools/job_execution/htcondor_script_generator.py +0 -2
- simtools/layout/array_layout.py +4 -12
- simtools/layout/array_layout_utils.py +227 -58
- simtools/model/array_model.py +37 -18
- simtools/model/calibration_model.py +0 -4
- simtools/model/legacy_model_parameter.py +134 -0
- simtools/model/model_parameter.py +24 -14
- simtools/model/model_repository.py +18 -5
- simtools/model/model_utils.py +1 -6
- simtools/model/site_model.py +0 -4
- simtools/model/telescope_model.py +6 -11
- simtools/production_configuration/derive_corsika_limits.py +6 -11
- simtools/production_configuration/interpolation_handler.py +16 -16
- simtools/ray_tracing/incident_angles.py +5 -11
- simtools/ray_tracing/mirror_panel_psf.py +3 -7
- simtools/ray_tracing/psf_analysis.py +29 -27
- simtools/ray_tracing/psf_parameter_optimisation.py +822 -680
- simtools/ray_tracing/ray_tracing.py +6 -15
- simtools/reporting/docs_auto_report_generator.py +8 -13
- simtools/reporting/docs_read_parameters.py +70 -16
- simtools/runners/corsika_runner.py +15 -10
- simtools/runners/corsika_simtel_runner.py +9 -8
- simtools/runners/runner_services.py +17 -7
- simtools/runners/simtel_runner.py +11 -58
- simtools/runners/simtools_runner.py +2 -4
- simtools/schemas/model_parameters/flasher_pulse_exp_decay.schema.yml +2 -0
- simtools/schemas/model_parameters/flasher_pulse_shape.schema.yml +50 -0
- simtools/schemas/model_parameters/flasher_pulse_width.schema.yml +2 -0
- simtools/schemas/simulation_models_info.schema.yml +2 -0
- simtools/settings.py +154 -0
- simtools/sim_events/file_info.py +128 -0
- simtools/{simtel/simtel_io_event_histograms.py → sim_events/histograms.py} +25 -15
- simtools/{simtel/simtel_io_event_reader.py → sim_events/reader.py} +20 -17
- simtools/{simtel/simtel_io_event_writer.py → sim_events/writer.py} +84 -25
- simtools/simtel/pulse_shapes.py +273 -0
- simtools/simtel/simtel_config_writer.py +146 -22
- simtools/simtel/simtel_table_reader.py +6 -4
- simtools/simtel/simulator_array.py +62 -23
- simtools/simtel/simulator_camera_efficiency.py +4 -6
- simtools/simtel/simulator_light_emission.py +101 -19
- simtools/simtel/simulator_ray_tracing.py +4 -10
- simtools/simulator.py +360 -353
- simtools/telescope_trigger_rates.py +3 -4
- simtools/testing/assertions.py +115 -8
- simtools/testing/configuration.py +2 -3
- simtools/testing/helpers.py +2 -3
- simtools/testing/log_inspector.py +5 -1
- simtools/testing/sim_telarray_metadata.py +1 -1
- simtools/testing/validate_output.py +69 -23
- simtools/utils/general.py +37 -0
- simtools/utils/geometry.py +0 -77
- simtools/utils/names.py +7 -9
- simtools/version.py +37 -0
- simtools/visualization/legend_handlers.py +21 -10
- simtools/visualization/plot_array_layout.py +312 -41
- simtools/visualization/plot_corsika_histograms.py +143 -605
- simtools/visualization/plot_mirrors.py +834 -0
- simtools/visualization/plot_pixels.py +2 -4
- simtools/visualization/plot_psf.py +0 -1
- simtools/visualization/plot_simtel_event_histograms.py +4 -4
- simtools/visualization/plot_simtel_events.py +6 -11
- simtools/visualization/plot_tables.py +8 -19
- simtools/visualization/visualize.py +22 -2
- simtools/applications/db_development_tools/write_array_elements_positions_to_repository.py +0 -160
- simtools/applications/print_version.py +0 -53
- simtools/io/hdf5_handler.py +0 -139
- simtools/simtel/simtel_io_file_info.py +0 -62
- {gammasimtools-0.24.0.dist-info → gammasimtools-0.26.0.dist-info}/WHEEL +0 -0
- {gammasimtools-0.24.0.dist-info → gammasimtools-0.26.0.dist-info}/top_level.txt +0 -0
|
@@ -1,52 +1,26 @@
|
|
|
1
|
-
"""Extract Cherenkov photons
|
|
1
|
+
"""Extract Cherenkov photons from a CORSIKA IACT file and fill histograms."""
|
|
2
2
|
|
|
3
3
|
import functools
|
|
4
4
|
import logging
|
|
5
5
|
import operator
|
|
6
|
-
import re
|
|
7
|
-
import time
|
|
8
6
|
from pathlib import Path
|
|
9
7
|
|
|
10
8
|
import boost_histogram as bh
|
|
11
9
|
import numpy as np
|
|
12
10
|
from astropy import units as u
|
|
13
|
-
from astropy.io.misc import yaml
|
|
14
|
-
from astropy.units import cds
|
|
15
|
-
from corsikaio.subblocks import event_header, get_units_from_fields, run_header
|
|
16
|
-
from ctapipe.io import write_table
|
|
17
11
|
from eventio import IACTFile
|
|
18
12
|
|
|
19
|
-
from simtools import
|
|
20
|
-
from simtools.io import io_handler
|
|
21
|
-
from simtools.io.ascii_handler import collect_data_from_file
|
|
22
|
-
from simtools.io.hdf5_handler import fill_hdf5_table
|
|
23
|
-
from simtools.utils.geometry import convert_2d_to_radial_distr, rotate
|
|
24
|
-
from simtools.utils.names import sanitize_name
|
|
25
|
-
from simtools.visualization import plot_corsika_histograms as visualize
|
|
26
|
-
|
|
27
|
-
X_AXIS_STRING = "x axis"
|
|
28
|
-
Y_AXIS_STRING = "y axis"
|
|
29
|
-
Z_AXIS_STRING = "z axis"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
class HistogramNotCreatedError(Exception):
|
|
33
|
-
"""Exception for histogram not created."""
|
|
13
|
+
from simtools.utils.geometry import rotate
|
|
34
14
|
|
|
35
15
|
|
|
36
16
|
class CorsikaHistograms:
|
|
37
17
|
"""
|
|
38
|
-
|
|
18
|
+
Extract Cherenkov photons from a CORSIKA IACT file and fill histograms.
|
|
39
19
|
|
|
40
20
|
Parameters
|
|
41
21
|
----------
|
|
42
22
|
input_file: str or Path
|
|
43
|
-
CORSIKA IACT file
|
|
44
|
-
label: str
|
|
45
|
-
Instance label.
|
|
46
|
-
output_path: str
|
|
47
|
-
Path where to save the output of the class methods.
|
|
48
|
-
hdf5_file_name: str
|
|
49
|
-
HDF5 file name for histogram storage.
|
|
23
|
+
CORSIKA IACT file.
|
|
50
24
|
|
|
51
25
|
Raises
|
|
52
26
|
------
|
|
@@ -54,569 +28,106 @@ class CorsikaHistograms:
|
|
|
54
28
|
if the input file given does not exist.
|
|
55
29
|
"""
|
|
56
30
|
|
|
57
|
-
def __init__(self, input_file
|
|
58
|
-
self.label = label
|
|
31
|
+
def __init__(self, input_file):
|
|
59
32
|
self._logger = logging.getLogger(__name__)
|
|
60
33
|
self._logger.debug("Init CorsikaHistograms")
|
|
61
|
-
self.input_file = input_file
|
|
62
|
-
|
|
63
|
-
self.input_file = Path(self.input_file)
|
|
34
|
+
self.input_file = Path(input_file)
|
|
64
35
|
if not self.input_file.exists():
|
|
65
|
-
|
|
66
|
-
self._logger.error(msg)
|
|
67
|
-
raise FileNotFoundError
|
|
68
|
-
|
|
69
|
-
self.io_handler = io_handler.IOHandler()
|
|
70
|
-
_default_output_path = self.io_handler.get_output_directory("corsika")
|
|
71
|
-
|
|
72
|
-
if output_path is None:
|
|
73
|
-
self.output_path = _default_output_path
|
|
74
|
-
else:
|
|
75
|
-
self.output_path = Path(output_path)
|
|
76
|
-
|
|
77
|
-
if hdf5_file_name is None:
|
|
78
|
-
self.hdf5_file_name = re.split(r"\.", self.input_file.name)[0] + ".hdf5"
|
|
79
|
-
else:
|
|
80
|
-
self.hdf5_file_name = hdf5_file_name
|
|
81
|
-
|
|
82
|
-
self._telescope_indices = None
|
|
83
|
-
self._telescope_positions = None
|
|
84
|
-
self.num_events = None
|
|
85
|
-
self.num_of_hist = None
|
|
86
|
-
self.num_telescopes = None
|
|
87
|
-
self._num_photons_per_event_per_telescope = None
|
|
88
|
-
self._num_photons_per_event = None
|
|
89
|
-
self._num_photons_per_telescope = None
|
|
90
|
-
self.__meta_dict = None
|
|
91
|
-
self._dict_2d_distributions = None
|
|
92
|
-
self._dict_1d_distributions = None
|
|
93
|
-
self._event_azimuth_angles = None
|
|
94
|
-
self._event_zenith_angles = None
|
|
95
|
-
self._hist_config = None
|
|
96
|
-
self._total_num_photons = None
|
|
97
|
-
self._magnetic_field_x = None
|
|
98
|
-
self._magnetic_field_z = None
|
|
99
|
-
self._event_total_energies = None
|
|
100
|
-
self._event_first_interaction_heights = None
|
|
101
|
-
self._corsika_version = None
|
|
102
|
-
self.event_information = None
|
|
103
|
-
self._individual_telescopes = None
|
|
104
|
-
self._allowed_histograms = {"hist_position", "hist_direction", "hist_time_altitude"}
|
|
105
|
-
self._allowed_1d_labels = {"wavelength", "time", "altitude"}
|
|
106
|
-
self._allowed_2d_labels = {"counts", "density", "direction", "time_altitude"}
|
|
107
|
-
self._header = None
|
|
108
|
-
self.hist_position = None
|
|
109
|
-
self.hist_direction = None
|
|
110
|
-
self.hist_time_altitude = None
|
|
111
|
-
|
|
112
|
-
self.read_event_information()
|
|
113
|
-
self._initialize_header()
|
|
114
|
-
|
|
115
|
-
def parse_telescope_indices(self, indices_arg):
|
|
116
|
-
"""Return telescope indices as ndarray[int] or None.
|
|
117
|
-
|
|
118
|
-
Accepts None, a sequence of strings/ints. Raises ValueError on invalid input.
|
|
119
|
-
"""
|
|
120
|
-
if indices_arg is None:
|
|
121
|
-
return None
|
|
122
|
-
try:
|
|
123
|
-
return np.array(indices_arg).astype(int)
|
|
124
|
-
except ValueError as exc:
|
|
125
|
-
msg = (
|
|
126
|
-
f"{indices_arg} not a valid input. Please use integer numbers for telescope_indices"
|
|
127
|
-
)
|
|
128
|
-
self._logger.error(msg)
|
|
129
|
-
raise ValueError(msg) from exc
|
|
130
|
-
|
|
131
|
-
def should_overwrite(
|
|
132
|
-
self, write_hdf5: bool, event1d: list | None, event2d: list | None
|
|
133
|
-
) -> bool:
|
|
134
|
-
"""Return True if output HDF5 exists and any writing flag is requested."""
|
|
135
|
-
exists = Path(self.hdf5_file_name).exists()
|
|
136
|
-
if exists and (write_hdf5 or bool(event1d) or bool(event2d)):
|
|
137
|
-
self._logger.warning(
|
|
138
|
-
f"Output hdf5 file {self.hdf5_file_name} already exists. Overwriting it."
|
|
139
|
-
)
|
|
140
|
-
return True
|
|
141
|
-
return False
|
|
142
|
-
|
|
143
|
-
def run_export_pipeline(
|
|
144
|
-
self,
|
|
145
|
-
*,
|
|
146
|
-
individual_telescopes: bool,
|
|
147
|
-
hist_config,
|
|
148
|
-
indices_arg,
|
|
149
|
-
write_pdf: bool,
|
|
150
|
-
write_hdf5: bool,
|
|
151
|
-
event1d: list | None,
|
|
152
|
-
event2d: list | None,
|
|
153
|
-
test: bool = False,
|
|
154
|
-
) -> dict:
|
|
155
|
-
"""Run the full histogram export pipeline and return output artifact paths.
|
|
156
|
-
|
|
157
|
-
Returns a dict with optional keys: pdf_photons, pdf_event1d, pdf_event2d.
|
|
158
|
-
"""
|
|
159
|
-
outputs: dict[str, Path | None] = {
|
|
160
|
-
"pdf_photons": None,
|
|
161
|
-
"pdf_event_1d": None,
|
|
162
|
-
"pdf_event_2d": None,
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
indices = self.parse_telescope_indices(indices_arg)
|
|
166
|
-
overwrite = self.should_overwrite(write_hdf5, event1d, event2d)
|
|
167
|
-
|
|
168
|
-
self.set_histograms(
|
|
169
|
-
telescope_indices=indices,
|
|
170
|
-
individual_telescopes=individual_telescopes,
|
|
171
|
-
hist_config=hist_config,
|
|
172
|
-
)
|
|
173
|
-
|
|
174
|
-
if write_pdf:
|
|
175
|
-
pdf_path = visualize.export_all_photon_figures_pdf(self, test=test)
|
|
176
|
-
outputs["pdf_photons"] = pdf_path
|
|
177
|
-
if write_hdf5:
|
|
178
|
-
self.export_histograms(overwrite=overwrite)
|
|
179
|
-
|
|
180
|
-
if event1d is not None:
|
|
181
|
-
outputs["pdf_event_1d"] = visualize.derive_event_1d_histograms(
|
|
182
|
-
self, event1d, pdf=write_pdf, hdf5=write_hdf5, overwrite=not write_hdf5
|
|
183
|
-
)
|
|
184
|
-
if event2d is not None:
|
|
185
|
-
outputs["pdf_event_2d"] = visualize.derive_event_2d_histograms(
|
|
186
|
-
self,
|
|
187
|
-
event2d,
|
|
188
|
-
pdf=write_pdf,
|
|
189
|
-
hdf5=write_hdf5,
|
|
190
|
-
overwrite=not (write_hdf5 or bool(event1d)),
|
|
191
|
-
)
|
|
192
|
-
|
|
193
|
-
return outputs
|
|
194
|
-
|
|
195
|
-
@property
|
|
196
|
-
def hdf5_file_name(self):
|
|
197
|
-
"""
|
|
198
|
-
Property for the hdf5 file name.
|
|
199
|
-
|
|
200
|
-
The idea of this property is to allow setting (or changing) the name of the hdf5 file
|
|
201
|
-
even after creating the class instance.
|
|
202
|
-
"""
|
|
203
|
-
return self._hdf5_file_name
|
|
204
|
-
|
|
205
|
-
@hdf5_file_name.setter
|
|
206
|
-
def hdf5_file_name(self, hdf5_file_name):
|
|
207
|
-
"""
|
|
208
|
-
Set the hdf5_file_name to the argument passed.
|
|
209
|
-
|
|
210
|
-
Parameters
|
|
211
|
-
----------
|
|
212
|
-
hdf5_file_name: str
|
|
213
|
-
The name of hdf5 file to be set.
|
|
214
|
-
"""
|
|
215
|
-
self._hdf5_file_name = Path(self.output_path).joinpath(hdf5_file_name).absolute().as_posix()
|
|
216
|
-
|
|
217
|
-
@property
|
|
218
|
-
def corsika_version(self):
|
|
219
|
-
"""
|
|
220
|
-
Get the version of the CORSIKA IACT file.
|
|
221
|
-
|
|
222
|
-
Returns
|
|
223
|
-
-------
|
|
224
|
-
float:
|
|
225
|
-
The version of CORSIKA used to produce the CORSIKA IACT file given by self.input_file.
|
|
226
|
-
"""
|
|
227
|
-
if self._corsika_version is None:
|
|
228
|
-
all_corsika_versions = list(run_header.run_header_types.keys())
|
|
229
|
-
header = list(self.iact_file.header)
|
|
230
|
-
|
|
231
|
-
for i_version in reversed(all_corsika_versions):
|
|
232
|
-
# Get the event header for this software version being tested.
|
|
233
|
-
single_run_header = run_header.run_header_types[i_version]
|
|
234
|
-
# Get the position in the dictionary, where the version is.
|
|
235
|
-
version_index_position = np.argwhere(
|
|
236
|
-
np.array(list(single_run_header.names)) == "version"
|
|
237
|
-
)[0]
|
|
238
|
-
|
|
239
|
-
# Check if version tested is the same as the version written in the file header.
|
|
240
|
-
if i_version == np.trunc(float(header[version_index_position[0]]) * 10) / 10:
|
|
241
|
-
# If the version found is the same as the initial guess, leave the loop,
|
|
242
|
-
# otherwise, iterate until we find the correct version.
|
|
243
|
-
self._corsika_version = np.around(float(header[version_index_position[0]]), 3)
|
|
244
|
-
break
|
|
245
|
-
return self._corsika_version
|
|
246
|
-
|
|
247
|
-
def _initialize_header(self):
|
|
248
|
-
"""Initialize the header."""
|
|
249
|
-
self.all_run_keys = list(
|
|
250
|
-
run_header.run_header_types[np.around(self.corsika_version, 1)].names
|
|
251
|
-
)
|
|
252
|
-
self._header = {}
|
|
253
|
-
|
|
254
|
-
# Get units of the header
|
|
255
|
-
all_run_units = get_units_from_fields(
|
|
256
|
-
run_header.run_header_fields[np.trunc(self.corsika_version * 10) / 10]
|
|
257
|
-
)
|
|
258
|
-
all_header_astropy_units = self._get_header_astropy_units(self.all_run_keys, all_run_units)
|
|
259
|
-
|
|
260
|
-
# Fill the header dictionary
|
|
261
|
-
for i_key, key in enumerate(self.all_run_keys[1:]): # starting at the second
|
|
262
|
-
# element to avoid the non-numeric key.
|
|
263
|
-
self._header[key] = self.iact_file.header[i_key + 1] * all_header_astropy_units[key]
|
|
264
|
-
|
|
265
|
-
@property
|
|
266
|
-
def header(self):
|
|
267
|
-
"""
|
|
268
|
-
Get the run header.
|
|
269
|
-
|
|
270
|
-
Returns
|
|
271
|
-
-------
|
|
272
|
-
dict:
|
|
273
|
-
The run header.
|
|
274
|
-
"""
|
|
275
|
-
return self._header
|
|
276
|
-
|
|
277
|
-
def read_event_information(self):
|
|
278
|
-
"""
|
|
279
|
-
Read the information about the events from their headers and save as a class instance.
|
|
280
|
-
|
|
281
|
-
The main information can also be fetched individually through the functions below.
|
|
282
|
-
For the remaining information (such as px, py, pz), use this function.
|
|
36
|
+
raise FileNotFoundError(f"File {self.input_file} does not exist.")
|
|
283
37
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
self.telescope_positions = np.array(self.iact_file.telescope_positions)
|
|
288
|
-
self.num_telescopes = np.size(self.telescope_positions, axis=0)
|
|
289
|
-
self.all_event_keys = list(
|
|
290
|
-
event_header.event_header_types[np.trunc(self.corsika_version * 10) / 10].names
|
|
291
|
-
)
|
|
292
|
-
all_event_units = get_units_from_fields(
|
|
293
|
-
event_header.event_header_fields[np.trunc(self.corsika_version * 10) / 10]
|
|
294
|
-
)
|
|
295
|
-
|
|
296
|
-
self.event_information = {key: [] for key in self.all_event_keys}
|
|
297
|
-
|
|
298
|
-
self.num_events = 0
|
|
299
|
-
# Build a dictionary with the parameters for the events.
|
|
300
|
-
for event in self.iact_file:
|
|
301
|
-
for i_key, key in enumerate(self.all_event_keys[1:]):
|
|
302
|
-
self.event_information[key].append(event.header[i_key + 1])
|
|
303
|
-
|
|
304
|
-
self.num_events += 1
|
|
305
|
-
|
|
306
|
-
all_event_astropy_units = self._get_header_astropy_units(
|
|
307
|
-
self.all_event_keys, all_event_units
|
|
308
|
-
)
|
|
309
|
-
|
|
310
|
-
# Add the units to dictionary with the parameters and turn it into
|
|
311
|
-
# astropy.Quantities.
|
|
312
|
-
for i_key, key in enumerate(self.all_event_keys[1:]): # starting at the second
|
|
313
|
-
# element to avoid the non-numeric (e.g. 'EVTH') key.
|
|
314
|
-
self.event_information[key] = (
|
|
315
|
-
np.array(self.event_information[key]) * all_event_astropy_units[key]
|
|
316
|
-
)
|
|
317
|
-
|
|
318
|
-
def _get_header_astropy_units(self, parameters, non_astropy_units):
|
|
319
|
-
"""
|
|
320
|
-
Return the dictionary with astropy units from the given list of parameters.
|
|
321
|
-
|
|
322
|
-
Parameters
|
|
323
|
-
----------
|
|
324
|
-
parameters: list
|
|
325
|
-
The list of parameters to extract the astropy units.
|
|
326
|
-
non_astropy_units: dict
|
|
327
|
-
A dictionary with the parameter units (in strings).
|
|
328
|
-
|
|
329
|
-
Returns
|
|
330
|
-
-------
|
|
331
|
-
dict:
|
|
332
|
-
A dictionary with the astropy units.
|
|
333
|
-
"""
|
|
334
|
-
# Build a dictionary with astropy units for the unit of the event's (header's) parameters.
|
|
335
|
-
all_event_astropy_units = {}
|
|
336
|
-
for key in parameters[1:]: # starting at the second
|
|
337
|
-
# element to avoid the non-numeric (e.g. 'EVTH') key.
|
|
338
|
-
|
|
339
|
-
# We extract the astropy unit (dimensionless in case no unit is provided).
|
|
340
|
-
if key in non_astropy_units:
|
|
341
|
-
with cds.enable():
|
|
342
|
-
unit = u.Unit(non_astropy_units[key])
|
|
343
|
-
else:
|
|
344
|
-
unit = u.dimensionless_unscaled
|
|
345
|
-
all_event_astropy_units[key] = unit
|
|
346
|
-
return all_event_astropy_units
|
|
38
|
+
self.events = None
|
|
39
|
+
self.hist = self._set_2d_distributions()
|
|
40
|
+
self.hist.update(self._set_1d_distributions())
|
|
347
41
|
|
|
348
|
-
|
|
349
|
-
def telescope_indices(self):
|
|
42
|
+
def fill(self):
|
|
350
43
|
"""
|
|
351
|
-
|
|
44
|
+
Fill Cherenkov photons histograms.
|
|
352
45
|
|
|
353
46
|
Returns
|
|
354
47
|
-------
|
|
355
|
-
list:
|
|
356
|
-
The indices of the telescopes of interest.
|
|
357
|
-
"""
|
|
358
|
-
return self._telescope_indices
|
|
359
|
-
|
|
360
|
-
@telescope_indices.setter
|
|
361
|
-
def telescope_indices(self, telescope_new_indices):
|
|
362
|
-
"""
|
|
363
|
-
Set the telescope index (or indices).
|
|
364
|
-
|
|
365
|
-
If self.individual_telescopes is True, the indices of the telescopes passed are analyzed
|
|
366
|
-
individually (different histograms for each telescope) even if all telescopes are listed.
|
|
367
|
-
|
|
368
|
-
Parameters
|
|
369
|
-
----------
|
|
370
|
-
telescope_new_indices: int or list of int or np.array of int
|
|
371
|
-
The indices of the specific telescopes to be inspected. If not specified, all telescopes
|
|
372
|
-
are treated together in one histogram and the value of self._telescope_indices is a list
|
|
373
|
-
of all telescope indices.
|
|
48
|
+
list: list of boost_histogram.Histogram instances.
|
|
374
49
|
|
|
375
50
|
Raises
|
|
376
51
|
------
|
|
377
|
-
|
|
378
|
-
if
|
|
379
|
-
"""
|
|
380
|
-
if telescope_new_indices is None:
|
|
381
|
-
self._telescope_indices = np.arange(self.num_telescopes)
|
|
382
|
-
else:
|
|
383
|
-
if not isinstance(telescope_new_indices, list | np.ndarray):
|
|
384
|
-
telescope_new_indices = np.array([telescope_new_indices])
|
|
385
|
-
for i_telescope in telescope_new_indices:
|
|
386
|
-
if not isinstance(i_telescope, int | np.integer):
|
|
387
|
-
msg = "The index or indices given are not of type int."
|
|
388
|
-
self._logger.error(msg)
|
|
389
|
-
raise TypeError
|
|
390
|
-
self._telescope_indices = np.sort(telescope_new_indices)
|
|
391
|
-
|
|
392
|
-
@property
|
|
393
|
-
def hist_config(self):
|
|
394
|
-
"""
|
|
395
|
-
The configuration of the histograms.
|
|
396
|
-
|
|
397
|
-
Returns
|
|
398
|
-
-------
|
|
399
|
-
dict:
|
|
400
|
-
the dictionary with the histogram configuration.
|
|
52
|
+
AttributeError:
|
|
53
|
+
if event has not photon saved.
|
|
401
54
|
"""
|
|
402
|
-
|
|
403
|
-
msg = (
|
|
404
|
-
"No histogram configuration was defined before. The default config is being "
|
|
405
|
-
"created now."
|
|
406
|
-
)
|
|
407
|
-
self._logger.warning(msg)
|
|
408
|
-
self._hist_config = self._create_histogram_default_config()
|
|
409
|
-
return self._hist_config
|
|
55
|
+
self._read_event_headers()
|
|
410
56
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
57
|
+
with IACTFile(self.input_file) as f:
|
|
58
|
+
telescope_positions = np.array(f.telescope_positions)
|
|
59
|
+
for event_counter, event in enumerate(f):
|
|
60
|
+
if hasattr(event, "photon_bunches"):
|
|
61
|
+
photons = list(event.photon_bunches.values())
|
|
62
|
+
self._fill_histograms(photons, event_counter, telescope_positions, True)
|
|
63
|
+
|
|
64
|
+
self._update_distributions()
|
|
65
|
+
|
|
66
|
+
def _read_event_headers(self):
|
|
67
|
+
"""Read event information from headers."""
|
|
68
|
+
event_dtype = np.dtype(
|
|
69
|
+
[
|
|
70
|
+
("particle_id", "i4"),
|
|
71
|
+
("total_energy", "f8"),
|
|
72
|
+
("azimuth_deg", "f8"),
|
|
73
|
+
("zenith_deg", "f8"),
|
|
74
|
+
("num_photons", "f8"),
|
|
75
|
+
]
|
|
76
|
+
)
|
|
415
77
|
|
|
416
|
-
|
|
417
|
-
|
|
78
|
+
with IACTFile(self.input_file) as iact_file:
|
|
79
|
+
records = [
|
|
80
|
+
(
|
|
81
|
+
event.header["particle_id"],
|
|
82
|
+
event.header["total_energy"],
|
|
83
|
+
np.rad2deg(event.header["azimuth"]),
|
|
84
|
+
np.rad2deg(event.header["zenith"]),
|
|
85
|
+
0.0, # filled later when reading photon bunches
|
|
86
|
+
)
|
|
87
|
+
for event in iact_file
|
|
88
|
+
]
|
|
418
89
|
|
|
419
|
-
|
|
420
|
-
----------
|
|
421
|
-
input_config: str, Path, dict or NoneType
|
|
422
|
-
yaml file with the configuration parameters to create the histograms. For the correct
|
|
423
|
-
format, please look at the docstring at _create_histogram_default_config.
|
|
424
|
-
Alternatively, it can be a dictionary with the configuration parameters to create
|
|
425
|
-
the histograms.
|
|
426
|
-
"""
|
|
427
|
-
if isinstance(input_config, dict):
|
|
428
|
-
self._hist_config = input_config
|
|
429
|
-
else:
|
|
430
|
-
self._hist_config = collect_data_from_file(input_config) if input_config else None
|
|
90
|
+
self.events = np.array(records, dtype=event_dtype)
|
|
431
91
|
|
|
432
|
-
def
|
|
92
|
+
def _create_regular_axes(self, hist, axes):
|
|
433
93
|
"""
|
|
434
|
-
|
|
94
|
+
Create regular axis for a single histogram.
|
|
435
95
|
|
|
436
96
|
Parameters
|
|
437
97
|
----------
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
if file_name is None:
|
|
443
|
-
file_name = "hist_config"
|
|
444
|
-
file_name = Path(file_name).with_suffix(".yml")
|
|
445
|
-
output_config_file_name = Path(self.output_path).joinpath(file_name)
|
|
446
|
-
with open(output_config_file_name, "w", encoding="utf-8") as file:
|
|
447
|
-
yaml.dump(self.hist_config, file)
|
|
448
|
-
|
|
449
|
-
def _create_histogram_default_config(self):
|
|
450
|
-
"""
|
|
451
|
-
Create a dictionary with the configuration necessary to create the histograms.
|
|
452
|
-
|
|
453
|
-
It is used only in case the configuration is not provided in a yaml file or dict.
|
|
454
|
-
|
|
455
|
-
Three histograms are created: hist_position with 3 dimensions (x, y positions and the
|
|
456
|
-
wavelength), hist_direction with 2 dimensions (direction cosines in x and y directions),
|
|
457
|
-
hist_time_altitude with 2 dimensions (time and altitude of emission).
|
|
458
|
-
|
|
459
|
-
Four arguments are passed to each dimension in the dictionary:
|
|
460
|
-
|
|
461
|
-
"bins": the number of bins,
|
|
462
|
-
"start": the first element of the histogram,
|
|
463
|
-
"stop": the last element of the histogram, and
|
|
464
|
-
"scale" to define the scale of the bins which can be "linear" or "log". If "log", the
|
|
465
|
-
common logarithm (log10) is applied to the axes. The start and stop values have to be
|
|
466
|
-
valid, i.e., >0.
|
|
98
|
+
hist: dict
|
|
99
|
+
Histogram dictionary.
|
|
100
|
+
axes: list
|
|
101
|
+
List of axis names (e.g. ["x_bins", "y_bins"]).
|
|
467
102
|
|
|
468
103
|
Returns
|
|
469
104
|
-------
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
"""
|
|
473
|
-
if self.individual_telescopes is False:
|
|
474
|
-
xy_maximum = 1000 * u.m
|
|
475
|
-
xy_bin = 100
|
|
476
|
-
|
|
477
|
-
else:
|
|
478
|
-
xy_maximum = 16 * u.m
|
|
479
|
-
xy_bin = 64
|
|
480
|
-
return {
|
|
481
|
-
"hist_position": {
|
|
482
|
-
X_AXIS_STRING: {
|
|
483
|
-
"bins": xy_bin,
|
|
484
|
-
"start": -xy_maximum,
|
|
485
|
-
"stop": xy_maximum,
|
|
486
|
-
"scale": "linear",
|
|
487
|
-
},
|
|
488
|
-
Y_AXIS_STRING: {
|
|
489
|
-
"bins": xy_bin,
|
|
490
|
-
"start": -xy_maximum,
|
|
491
|
-
"stop": xy_maximum,
|
|
492
|
-
"scale": "linear",
|
|
493
|
-
},
|
|
494
|
-
Z_AXIS_STRING: {
|
|
495
|
-
"bins": 80,
|
|
496
|
-
"start": 200 * u.nm,
|
|
497
|
-
"stop": 1000 * u.nm,
|
|
498
|
-
"scale": "linear",
|
|
499
|
-
},
|
|
500
|
-
},
|
|
501
|
-
"hist_direction": {
|
|
502
|
-
X_AXIS_STRING: {
|
|
503
|
-
"bins": 100,
|
|
504
|
-
"start": -1,
|
|
505
|
-
"stop": 1,
|
|
506
|
-
"scale": "linear",
|
|
507
|
-
},
|
|
508
|
-
Y_AXIS_STRING: {
|
|
509
|
-
"bins": 100,
|
|
510
|
-
"start": -1,
|
|
511
|
-
"stop": 1,
|
|
512
|
-
"scale": "linear",
|
|
513
|
-
},
|
|
514
|
-
},
|
|
515
|
-
"hist_time_altitude": {
|
|
516
|
-
X_AXIS_STRING: {
|
|
517
|
-
"bins": 100,
|
|
518
|
-
"start": -2000 * u.ns,
|
|
519
|
-
"stop": 2000 * u.ns,
|
|
520
|
-
"scale": "linear",
|
|
521
|
-
},
|
|
522
|
-
Y_AXIS_STRING: {
|
|
523
|
-
"bins": 100,
|
|
524
|
-
"start": 120 * u.km,
|
|
525
|
-
"stop": 0 * u.km,
|
|
526
|
-
"scale": "linear",
|
|
527
|
-
},
|
|
528
|
-
},
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
def _create_regular_axes(self, label):
|
|
532
|
-
"""
|
|
533
|
-
Create regular axis for histograms.
|
|
534
|
-
|
|
535
|
-
Parameters
|
|
536
|
-
----------
|
|
537
|
-
label: str
|
|
538
|
-
Label to identify to which histogram the new axis belongs.
|
|
539
|
-
|
|
540
|
-
Raises
|
|
541
|
-
------
|
|
542
|
-
ValueError:
|
|
543
|
-
if label is not valid.
|
|
105
|
+
list:
|
|
106
|
+
List of boost_histogram axis instances.
|
|
544
107
|
"""
|
|
545
108
|
transform = {"log": bh.axis.transform.log, "linear": None}
|
|
546
109
|
|
|
547
|
-
if label not in self._allowed_histograms:
|
|
548
|
-
msg = f"allowed labels must be one of the following: {self._allowed_histograms}"
|
|
549
|
-
self._logger.error(msg)
|
|
550
|
-
raise ValueError(msg)
|
|
551
|
-
|
|
552
|
-
all_axes = [X_AXIS_STRING, Y_AXIS_STRING]
|
|
553
|
-
if label == "hist_position":
|
|
554
|
-
all_axes.append(Z_AXIS_STRING)
|
|
555
|
-
|
|
556
110
|
boost_axes = []
|
|
557
|
-
for axis in
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
start = self.hist_config[label][axis]["start"]
|
|
563
|
-
stop = self.hist_config[label][axis]["stop"]
|
|
111
|
+
for axis in axes:
|
|
112
|
+
bins, start, stop = hist[axis][:3]
|
|
113
|
+
scale = hist[axis][3] if len(hist[axis]) > 3 else "linear"
|
|
114
|
+
if isinstance(start, u.quantity.Quantity):
|
|
115
|
+
start, stop = start.value, stop.value
|
|
564
116
|
boost_axes.append(
|
|
565
117
|
bh.axis.Regular(
|
|
566
|
-
bins=
|
|
118
|
+
bins=bins,
|
|
567
119
|
start=start,
|
|
568
120
|
stop=stop,
|
|
569
|
-
transform=transform[
|
|
121
|
+
transform=transform[scale],
|
|
570
122
|
)
|
|
571
123
|
)
|
|
572
124
|
return boost_axes
|
|
573
125
|
|
|
574
|
-
def
|
|
575
|
-
"""
|
|
576
|
-
Create the histogram instances.
|
|
577
|
-
|
|
578
|
-
Parameters
|
|
579
|
-
----------
|
|
580
|
-
individual_telescopes: bool
|
|
581
|
-
if False, the histograms are filled for all given telescopes together.
|
|
582
|
-
if True, one histogram is set for each telescope separately.
|
|
583
|
-
"""
|
|
584
|
-
self.individual_telescopes = individual_telescopes
|
|
585
|
-
self.num_of_hist = len(self.telescope_indices) if self.individual_telescopes is True else 1
|
|
586
|
-
|
|
587
|
-
self.hist_position, self.hist_direction, self.hist_time_altitude = [], [], []
|
|
588
|
-
|
|
589
|
-
for _ in range(self.num_of_hist):
|
|
590
|
-
boost_axes_position = self._create_regular_axes("hist_position")
|
|
591
|
-
self.hist_position.append(
|
|
592
|
-
bh.Histogram(
|
|
593
|
-
boost_axes_position[0],
|
|
594
|
-
boost_axes_position[1],
|
|
595
|
-
boost_axes_position[2],
|
|
596
|
-
)
|
|
597
|
-
)
|
|
598
|
-
boost_axes_direction = self._create_regular_axes("hist_direction")
|
|
599
|
-
self.hist_direction.append(
|
|
600
|
-
bh.Histogram(
|
|
601
|
-
boost_axes_direction[0],
|
|
602
|
-
boost_axes_direction[1],
|
|
603
|
-
)
|
|
604
|
-
)
|
|
605
|
-
|
|
606
|
-
boost_axes_time_altitude = self._create_regular_axes("hist_time_altitude")
|
|
607
|
-
self.hist_time_altitude.append(
|
|
608
|
-
bh.Histogram(
|
|
609
|
-
boost_axes_time_altitude[0],
|
|
610
|
-
boost_axes_time_altitude[1],
|
|
611
|
-
)
|
|
612
|
-
)
|
|
613
|
-
|
|
614
|
-
def _fill_histograms(self, photons, rotation_around_z_axis=None, rotation_around_y_axis=None):
|
|
126
|
+
def _fill_histograms(self, photons, event_counter, telescope_positions, rotate_photons=True):
|
|
615
127
|
"""
|
|
616
|
-
Fill
|
|
128
|
+
Fill Cherenkov photon histograms.
|
|
617
129
|
|
|
618
|
-
|
|
619
|
-
filled in the plane perpendicular to the incoming direction of the particle.
|
|
130
|
+
For rotate_photons, the Cherenkov photon's coordinates are filled in the shower plane.
|
|
620
131
|
|
|
621
132
|
Parameters
|
|
622
133
|
----------
|
|
@@ -630,1240 +141,405 @@ class CorsikaHistograms:
|
|
|
630
141
|
incoming direction and the x axis,
|
|
631
142
|
cy: direction cosine in the y direction, i.e., the cosine of the angle between the
|
|
632
143
|
incoming direction and the y axis,
|
|
633
|
-
time: time of arrival of the photon in ns.
|
|
634
|
-
the top of the atmosphere (CORSIKA-defined) if self.event_first_interaction_heights
|
|
635
|
-
is positive or at first interaction if otherwise.
|
|
144
|
+
time: time of arrival of the photon in ns.
|
|
636
145
|
zem: altitude where the photon was generated in cm,
|
|
637
146
|
photons: number of photons associated to this bunch,
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
hist_num = 0
|
|
657
|
-
for i_tel_info, photons_info in np.array(
|
|
658
|
-
list(zip(self.telescope_positions, photons)), dtype=object
|
|
659
|
-
)[self.telescope_indices]:
|
|
660
|
-
if rotation_around_z_axis is None or rotation_around_y_axis is None:
|
|
661
|
-
photon_x, photon_y = photons_info["x"], photons_info["y"]
|
|
662
|
-
else:
|
|
663
|
-
photon_x, photon_y = rotate(
|
|
664
|
-
photons_info["x"],
|
|
665
|
-
photons_info["y"],
|
|
666
|
-
rotation_around_z_axis,
|
|
667
|
-
rotation_around_y_axis,
|
|
147
|
+
event_counter: int
|
|
148
|
+
Event counter.
|
|
149
|
+
telescope_positions: numpy.array
|
|
150
|
+
Array with the telescope positions with shape (M, 2), where M is the number of
|
|
151
|
+
telescopes in the array. The two columns are the x and y positions of the telescopes
|
|
152
|
+
in the CORSIKA coordinate system.
|
|
153
|
+
rotate_photons: bool
|
|
154
|
+
If True, the photon's coordinates are rotated to the plane perpendicular to the
|
|
155
|
+
incoming direction of the primary particle.
|
|
156
|
+
"""
|
|
157
|
+
hist_str = "histogram"
|
|
158
|
+
for photon, telescope in zip(photons, telescope_positions):
|
|
159
|
+
if rotate_photons:
|
|
160
|
+
px, py = rotate(
|
|
161
|
+
photon["x"],
|
|
162
|
+
photon["y"],
|
|
163
|
+
self.events["azimuth_deg"][event_counter],
|
|
164
|
+
self.events["zenith_deg"][event_counter],
|
|
668
165
|
)
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
)
|
|
681
|
-
|
|
682
|
-
self.
|
|
683
|
-
self.
|
|
684
|
-
|
|
166
|
+
else:
|
|
167
|
+
px, py = photon["x"], photon["y"]
|
|
168
|
+
|
|
169
|
+
px -= telescope["x"]
|
|
170
|
+
py -= telescope["y"]
|
|
171
|
+
w = photon["photons"]
|
|
172
|
+
|
|
173
|
+
pxm = px * u.cm.to(u.m)
|
|
174
|
+
pym = py * u.cm.to(u.m)
|
|
175
|
+
zem = (photon["zem"] * u.cm).to(u.km)
|
|
176
|
+
|
|
177
|
+
self.hist["counts_xy"][hist_str].fill(pxm, pym, weight=w)
|
|
178
|
+
self.hist["density_xy"][hist_str].fill(pxm, pym, weight=w)
|
|
179
|
+
self.hist["direction_xy"][hist_str].fill(photon["cx"], photon["cy"], weight=w)
|
|
180
|
+
self.hist["time_altitude"][hist_str].fill(photon["time"] * u.ns, zem, weight=w)
|
|
181
|
+
self.hist["wavelength_altitude"][hist_str].fill(
|
|
182
|
+
np.abs(photon["wavelength"]) * u.nm, zem, weight=w
|
|
685
183
|
)
|
|
686
|
-
if self.individual_telescopes is True:
|
|
687
|
-
hist_num += 1
|
|
688
|
-
|
|
689
|
-
def set_histograms(self, telescope_indices=None, individual_telescopes=None, hist_config=None):
|
|
690
|
-
"""
|
|
691
|
-
Create and fill Cherenkov photons histograms using information from the CORSIKA IACT file.
|
|
692
|
-
|
|
693
|
-
Parameters
|
|
694
|
-
----------
|
|
695
|
-
telescope_indices: int or list of int
|
|
696
|
-
The indices of the specific telescopes to be inspected.
|
|
697
|
-
individual_telescopes: bool
|
|
698
|
-
if False, the histograms are supposed to be filled for all telescopes. Default is False.
|
|
699
|
-
if True, one histogram is set for each telescope separately.
|
|
700
|
-
hist_config:
|
|
701
|
-
yaml file with the configuration parameters to create the histograms. For the correct
|
|
702
|
-
format, please look at the docstring of _create_histogram_default_config.
|
|
703
|
-
Alternatively, it can be a dictionary with the configuration parameters to create
|
|
704
|
-
the histograms.
|
|
705
|
-
|
|
706
|
-
Returns
|
|
707
|
-
-------
|
|
708
|
-
list: list of boost_histogram.Histogram instances.
|
|
709
|
-
|
|
710
|
-
Raises
|
|
711
|
-
------
|
|
712
|
-
AttributeError:
|
|
713
|
-
if event has not photon saved.
|
|
714
|
-
"""
|
|
715
|
-
self.telescope_indices = telescope_indices
|
|
716
|
-
self.individual_telescopes = individual_telescopes
|
|
717
|
-
self.hist_config = hist_config
|
|
718
|
-
self._create_histograms(individual_telescopes=self.individual_telescopes)
|
|
719
|
-
|
|
720
|
-
num_photons_per_event_per_telescope_to_set = []
|
|
721
|
-
start_time = time.time()
|
|
722
|
-
self._logger.debug(f"Starting reading the file at {start_time}.")
|
|
723
|
-
with IACTFile(self.input_file) as f:
|
|
724
|
-
event_counter = 0
|
|
725
|
-
for event in f:
|
|
726
|
-
for i_telescope in self.telescope_indices:
|
|
727
|
-
if hasattr(event, "photon_bunches"):
|
|
728
|
-
photons = list(event.photon_bunches.values())
|
|
729
|
-
else:
|
|
730
|
-
msg = "The event has no associated photon bunches saved. "
|
|
731
|
-
self._logger.error(msg)
|
|
732
|
-
raise AttributeError
|
|
733
|
-
|
|
734
|
-
# Count photons only from the telescopes given by self.telescope_indices.
|
|
735
|
-
num_photons_per_event_per_telescope_to_set.append(event.n_photons[i_telescope])
|
|
736
|
-
self._fill_histograms(
|
|
737
|
-
photons,
|
|
738
|
-
self.event_azimuth_angles[event_counter],
|
|
739
|
-
self.event_zenith_angles[event_counter],
|
|
740
|
-
)
|
|
741
|
-
event_counter += 1
|
|
742
|
-
self.num_photons_per_event_per_telescope = num_photons_per_event_per_telescope_to_set
|
|
743
|
-
self._logger.debug(
|
|
744
|
-
f"Finished reading the file and creating the histograms in {time.time() - start_time} "
|
|
745
|
-
f"seconds"
|
|
746
|
-
)
|
|
747
|
-
|
|
748
|
-
@property
|
|
749
|
-
def individual_telescopes(self):
|
|
750
|
-
"""Return the individual telescopes as property."""
|
|
751
|
-
return self._individual_telescopes
|
|
752
|
-
|
|
753
|
-
@individual_telescopes.setter
|
|
754
|
-
def individual_telescopes(self, new_individual_telescopes: bool):
|
|
755
|
-
"""
|
|
756
|
-
Define individual telescopes.
|
|
757
|
-
|
|
758
|
-
Parameters
|
|
759
|
-
----------
|
|
760
|
-
new_individual_telescopes: bool
|
|
761
|
-
if False, the histograms are supposed to be filled for all telescopes.
|
|
762
|
-
if True, one histogram is set for each telescope separately.
|
|
763
|
-
"""
|
|
764
|
-
if new_individual_telescopes is None:
|
|
765
|
-
self._individual_telescopes = False
|
|
766
|
-
else:
|
|
767
|
-
self._individual_telescopes = new_individual_telescopes
|
|
768
184
|
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
185
|
+
r = np.hypot(px, py) * u.cm.to(u.m)
|
|
186
|
+
self.hist["counts_r"][hist_str].fill(r, weight=w)
|
|
187
|
+
self.hist["density_r"][hist_str].fill(r, weight=w)
|
|
772
188
|
|
|
773
|
-
|
|
774
|
-
------
|
|
775
|
-
HistogramNotCreatedError:
|
|
776
|
-
if the histogram was not previously created.
|
|
777
|
-
"""
|
|
778
|
-
for histogram in self._allowed_histograms:
|
|
779
|
-
if not hasattr(self, histogram) or getattr(self, histogram) is None:
|
|
780
|
-
msg = (
|
|
781
|
-
"The histograms were not created. Please, use create_histograms to create "
|
|
782
|
-
"histograms from the CORSIKA output file."
|
|
783
|
-
)
|
|
784
|
-
self._logger.error(msg)
|
|
785
|
-
raise HistogramNotCreatedError
|
|
189
|
+
self.events["num_photons"][event_counter] += np.sum(w)
|
|
786
190
|
|
|
787
|
-
def
|
|
191
|
+
def get_hist_2d_projection(self, hist):
|
|
788
192
|
"""
|
|
789
193
|
Get 2D distributions.
|
|
790
194
|
|
|
791
195
|
Parameters
|
|
792
196
|
----------
|
|
793
|
-
|
|
794
|
-
|
|
197
|
+
hist: boost_histogram.Histogram
|
|
198
|
+
Histogram.
|
|
795
199
|
|
|
796
200
|
Returns
|
|
797
201
|
-------
|
|
798
202
|
numpy.ndarray
|
|
799
|
-
|
|
203
|
+
Histogram counts.
|
|
800
204
|
numpy.array
|
|
801
|
-
|
|
205
|
+
Histogram x bin edges.
|
|
802
206
|
numpy.array
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
207
|
+
Histogram y bin edges.
|
|
208
|
+
numpy.ndarray or None
|
|
209
|
+
Histogram uncertainties (sqrt of variance) if available.
|
|
210
|
+
"""
|
|
211
|
+
view = hist.view()
|
|
212
|
+
if self._check_for_all_attributes(view):
|
|
213
|
+
counts = np.asarray([view["value"].T])
|
|
214
|
+
uncertainties = np.asarray([np.sqrt(view["variance"].T)])
|
|
215
|
+
else:
|
|
216
|
+
counts = np.asarray([view.T])
|
|
217
|
+
uncertainties = None
|
|
218
|
+
|
|
219
|
+
x_edges = np.asarray([hist.axes.edges[0].flatten()])
|
|
220
|
+
y_edges = np.asarray([hist.axes.edges[1].flatten()])
|
|
221
|
+
|
|
222
|
+
return counts, x_edges, y_edges, uncertainties
|
|
223
|
+
|
|
224
|
+
def _get_hist_1d_from_numpy(self, label, hist):
|
|
225
|
+
"""Get 1D histogram from numpy histogram."""
|
|
226
|
+
bins = hist["x_bins"][0]
|
|
227
|
+
start = hist["x_bins"][1] if hist["x_bins"][1] else np.min(self.events[label])
|
|
228
|
+
stop = hist["x_bins"][2] if hist["x_bins"][2] is not None else np.max(self.events[label])
|
|
229
|
+
scale = hist["x_bins"][3] if len(hist["x_bins"]) > 3 else "linear"
|
|
230
|
+
if scale == "log":
|
|
231
|
+
bin_edges = np.logspace(np.log10(start), np.log10(stop), bins + 1)
|
|
232
|
+
else:
|
|
233
|
+
bin_edges = np.linspace(start, stop, bins + 1)
|
|
234
|
+
histo_1d, _ = np.histogram(self.events[label], bins=bin_edges)
|
|
235
|
+
uncertainties = np.sqrt(histo_1d)
|
|
236
|
+
return (
|
|
237
|
+
histo_1d.reshape(1, bins),
|
|
238
|
+
bin_edges.reshape(1, bins + 1),
|
|
239
|
+
uncertainties.reshape(1, bins),
|
|
818
240
|
)
|
|
819
241
|
|
|
820
|
-
|
|
821
|
-
for i_telescope in range(num_telescopes_to_fill):
|
|
822
|
-
mini_hist = None
|
|
823
|
-
if label == "counts":
|
|
824
|
-
mini_hist = self.hist_position[i_telescope][:, :, sum]
|
|
825
|
-
hist_values.append(mini_hist.view().T)
|
|
826
|
-
elif label == "density":
|
|
827
|
-
mini_hist = self.hist_position[i_telescope][:, :, sum]
|
|
828
|
-
areas = functools.reduce(operator.mul, mini_hist.axes.widths)
|
|
829
|
-
hist_values.append(mini_hist.view().T / areas)
|
|
830
|
-
elif label == "direction":
|
|
831
|
-
mini_hist = self.hist_direction[i_telescope]
|
|
832
|
-
hist_values.append(self.hist_direction[i_telescope].view().T)
|
|
833
|
-
elif label == "time_altitude":
|
|
834
|
-
mini_hist = self.hist_time_altitude[i_telescope]
|
|
835
|
-
hist_values.append(self.hist_time_altitude[i_telescope].view().T)
|
|
836
|
-
if mini_hist is not None:
|
|
837
|
-
x_bin_edges.append(mini_hist.axes.edges[0].flatten())
|
|
838
|
-
y_bin_edges.append(mini_hist.axes.edges[1].flatten())
|
|
839
|
-
|
|
840
|
-
return np.array(hist_values), np.array(x_bin_edges), np.array(y_bin_edges)
|
|
841
|
-
|
|
842
|
-
def get_2d_photon_position_distr(self):
|
|
242
|
+
def get_hist_1d_projection(self, label, hist):
|
|
843
243
|
"""
|
|
844
|
-
Get
|
|
244
|
+
Get 1D distributions from numpy or boost histograms (1D and 2D).
|
|
245
|
+
|
|
246
|
+
Parameters
|
|
247
|
+
----------
|
|
248
|
+
label: str
|
|
249
|
+
Histogram label.
|
|
250
|
+
hist: dict
|
|
251
|
+
Histogram dictionary.
|
|
845
252
|
|
|
846
253
|
Returns
|
|
847
254
|
-------
|
|
848
255
|
numpy.ndarray
|
|
849
|
-
|
|
256
|
+
Histogram counts.
|
|
850
257
|
numpy.array
|
|
851
|
-
|
|
852
|
-
numpy.
|
|
853
|
-
|
|
854
|
-
"""
|
|
855
|
-
|
|
258
|
+
Histogram x bin edges.
|
|
259
|
+
numpy.ndarray or None
|
|
260
|
+
Histogram uncertainties (if available).
|
|
261
|
+
"""
|
|
262
|
+
# plain numpy histogram
|
|
263
|
+
if (
|
|
264
|
+
hist.get("projection") is None
|
|
265
|
+
and hasattr(self, "events")
|
|
266
|
+
and label in self.events.dtype.names
|
|
267
|
+
):
|
|
268
|
+
return self._get_hist_1d_from_numpy(label, hist)
|
|
269
|
+
|
|
270
|
+
# boost 1D histogram
|
|
271
|
+
if hist.get("projection") is None:
|
|
272
|
+
# Use histogram from hist dict if available, otherwise from self.hist
|
|
273
|
+
if "histogram" in hist:
|
|
274
|
+
histo_1d = hist["histogram"]
|
|
275
|
+
else:
|
|
276
|
+
# No histogram available, return None values
|
|
277
|
+
return None, None, None
|
|
278
|
+
edges = histo_1d.axes.edges.T.flatten()[0]
|
|
279
|
+
view = histo_1d.view()
|
|
280
|
+
if self._check_for_all_attributes(view):
|
|
281
|
+
counts = np.asarray([view["value"].T])
|
|
282
|
+
uncertainties = np.asarray([np.sqrt(view["variance"].T)])
|
|
283
|
+
else:
|
|
284
|
+
counts = np.asarray([view.T])
|
|
285
|
+
uncertainties = None
|
|
286
|
+
return counts, np.asarray([edges]), uncertainties
|
|
287
|
+
|
|
288
|
+
# boost 2D histogram projection
|
|
289
|
+
histo_2d = self.hist[hist["projection"][0]]["histogram"]
|
|
290
|
+
if hist["projection"][1] == "x":
|
|
291
|
+
h = histo_2d[:, sum]
|
|
292
|
+
else:
|
|
293
|
+
h = histo_2d[sum, :]
|
|
294
|
+
edges = h.axes.edges.T.flatten()[0]
|
|
295
|
+
view = h.view()
|
|
296
|
+
if self._check_for_all_attributes(view):
|
|
297
|
+
counts = np.asarray([view["value"].T])
|
|
298
|
+
uncertainties = np.asarray([np.sqrt(view["variance"].T)])
|
|
299
|
+
else:
|
|
300
|
+
counts = np.asarray([view.T])
|
|
301
|
+
uncertainties = None
|
|
302
|
+
return counts, np.asarray([edges]), uncertainties
|
|
856
303
|
|
|
857
|
-
def
|
|
304
|
+
def _set_1d_distributions(self, r_max=2000 * u.m, bins=100):
|
|
858
305
|
"""
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
It returns the photon density per square meter.
|
|
306
|
+
Define 1D histograms.
|
|
862
307
|
|
|
863
308
|
Returns
|
|
864
309
|
-------
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
numpy.array
|
|
868
|
-
The x bin edges of the density/count histograms in x, usually in meters.
|
|
869
|
-
numpy.array
|
|
870
|
-
The y bin edges of the density/count histograms in y, usually in meters.
|
|
310
|
+
dict:
|
|
311
|
+
Dictionary with 1D histogram information.
|
|
871
312
|
"""
|
|
872
|
-
|
|
313
|
+
file_name = "file_name"
|
|
314
|
+
title = "title"
|
|
315
|
+
projection = "projection"
|
|
316
|
+
x_bins = "x_bins"
|
|
317
|
+
x_axis_unit = "x_axis_unit"
|
|
318
|
+
x_axis_title = "x_axis_title"
|
|
319
|
+
y_axis_unit = "y_axis_unit"
|
|
320
|
+
y_axis_title = "y_axis_title"
|
|
321
|
+
hist_1d = {
|
|
322
|
+
"wavelength": {
|
|
323
|
+
file_name: "hist_1d_photon_wavelength_distr",
|
|
324
|
+
title: "Photon wavelength distribution",
|
|
325
|
+
projection: ["wavelength_altitude", "x"],
|
|
326
|
+
},
|
|
327
|
+
"counts_r": {
|
|
328
|
+
file_name: "hist_1d_photon_radial_distr",
|
|
329
|
+
title: "Photon lateral distribution (ground level)",
|
|
330
|
+
x_bins: [bins, 0 * u.m, r_max, "linear"],
|
|
331
|
+
x_axis_title: "Distance to center",
|
|
332
|
+
x_axis_unit: u.m,
|
|
333
|
+
},
|
|
334
|
+
"density_r": {
|
|
335
|
+
file_name: "hist_1d_photon_density_distr",
|
|
336
|
+
title: "Photon lateral density distribution (ground level)",
|
|
337
|
+
x_bins: [bins, 0 * u.m, r_max, "linear"],
|
|
338
|
+
x_axis_title: "Distance to center",
|
|
339
|
+
x_axis_unit: u.m,
|
|
340
|
+
y_axis_title: "Photon density",
|
|
341
|
+
y_axis_unit: u.m**-2,
|
|
342
|
+
},
|
|
343
|
+
"density_x": {
|
|
344
|
+
file_name: "hist_1d_photon_density_x_distr",
|
|
345
|
+
title: "Photon lateral density x distribution (ground level)",
|
|
346
|
+
projection: ["density_xy", "x"],
|
|
347
|
+
},
|
|
348
|
+
"density_y": {
|
|
349
|
+
file_name: "hist_1d_photon_density_y_distr",
|
|
350
|
+
title: "Photon lateral density y distribution (ground level)",
|
|
351
|
+
projection: ["density_xy", "y"],
|
|
352
|
+
},
|
|
353
|
+
"time": {
|
|
354
|
+
file_name: "hist_1d_photon_time_distr",
|
|
355
|
+
title: "Photon arrival time distribution",
|
|
356
|
+
projection: ["time_altitude", "x"],
|
|
357
|
+
},
|
|
358
|
+
"altitude": {
|
|
359
|
+
file_name: "hist_1d_photon_altitude_distr",
|
|
360
|
+
title: "Photon emission altitude distribution",
|
|
361
|
+
projection: ["time_altitude", "y"],
|
|
362
|
+
},
|
|
363
|
+
"direction_cosine_x": {
|
|
364
|
+
file_name: "hist_1d_photon_direction_cosine_x_distr",
|
|
365
|
+
title: "Photon direction cosine x distribution",
|
|
366
|
+
projection: ["direction_xy", "x"],
|
|
367
|
+
},
|
|
368
|
+
"direction_cosine_y": {
|
|
369
|
+
file_name: "hist_1d_photon_direction_cosine_y_distr",
|
|
370
|
+
title: "Photon direction cosine y distribution",
|
|
371
|
+
projection: ["direction_xy", "y"],
|
|
372
|
+
},
|
|
373
|
+
"num_photons": {
|
|
374
|
+
file_name: "hist_1d_photon_per_event_distr",
|
|
375
|
+
title: "Photons per event distribution",
|
|
376
|
+
"event_type": True,
|
|
377
|
+
x_bins: [100, 0, None, "log"],
|
|
378
|
+
x_axis_title: "Cherenkov photons per event",
|
|
379
|
+
x_axis_unit: u.dimensionless_unscaled,
|
|
380
|
+
},
|
|
381
|
+
}
|
|
873
382
|
|
|
874
|
-
|
|
383
|
+
for value in hist_1d.values():
|
|
384
|
+
value["is_1d"] = True
|
|
385
|
+
value["log_y"] = True
|
|
386
|
+
value[y_axis_title] = (
|
|
387
|
+
"Counts" if value.get(y_axis_title) is None else value[y_axis_title]
|
|
388
|
+
)
|
|
389
|
+
value[y_axis_unit] = (
|
|
390
|
+
u.dimensionless_unscaled if value.get(y_axis_unit) is None else value[y_axis_unit]
|
|
391
|
+
)
|
|
392
|
+
if value.get("projection") is not None:
|
|
393
|
+
hist_2d_name = value["projection"][0]
|
|
394
|
+
if value["projection"][1] == "x":
|
|
395
|
+
value[x_bins] = self.hist[hist_2d_name]["x_bins"]
|
|
396
|
+
value[x_axis_title] = self.hist[hist_2d_name]["x_axis_title"]
|
|
397
|
+
value[x_axis_unit] = self.hist[hist_2d_name]["x_axis_unit"]
|
|
398
|
+
else:
|
|
399
|
+
value[x_bins] = self.hist[hist_2d_name]["y_bins"]
|
|
400
|
+
value[x_axis_title] = self.hist[hist_2d_name]["y_axis_title"]
|
|
401
|
+
value[x_axis_unit] = self.hist[hist_2d_name]["y_axis_unit"]
|
|
402
|
+
elif value.get("event_type", False) is False:
|
|
403
|
+
boost_axes = self._create_regular_axes(value, ["x_bins"])
|
|
404
|
+
value["histogram"] = bh.Histogram(boost_axes[0], storage=bh.storage.Weight())
|
|
405
|
+
return hist_1d
|
|
406
|
+
|
|
407
|
+
def _set_2d_distributions(self, xy_maximum=1000 * u.m, xy_bin=100):
|
|
875
408
|
"""
|
|
876
|
-
|
|
409
|
+
Define 2D histograms.
|
|
877
410
|
|
|
878
411
|
Returns
|
|
879
412
|
-------
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
numpy.array
|
|
883
|
-
The x bin edges of the direction histograms in cos(x).
|
|
884
|
-
numpy.array
|
|
885
|
-
The y bin edges of the direction histograms in cos(y)
|
|
413
|
+
dict:
|
|
414
|
+
Dictionary with 2D histogram information.
|
|
886
415
|
"""
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
def get_2d_photon_time_altitude_distr(self):
|
|
890
|
-
"""
|
|
891
|
-
Get 2D histograms of the time and altitude of the photon production.
|
|
892
|
-
|
|
893
|
-
Returns
|
|
894
|
-
-------
|
|
895
|
-
numpy.ndarray
|
|
896
|
-
The counts of the histogram.
|
|
897
|
-
numpy.array
|
|
898
|
-
The x bin edges of the time_altitude histograms, usually in ns.
|
|
899
|
-
numpy.array
|
|
900
|
-
The y bin edges of the time_altitude histograms, usually in km.
|
|
901
|
-
"""
|
|
902
|
-
return self._get_hist_2d_projection("time_altitude")
|
|
903
|
-
|
|
904
|
-
def get_2d_num_photons_distr(self):
|
|
905
|
-
"""
|
|
906
|
-
Get the distribution of Cherenkov photons per event per telescope.
|
|
907
|
-
|
|
908
|
-
It returns the 2D array accounting for the events from the telescopes given
|
|
909
|
-
by self.telescope_indices.
|
|
910
|
-
|
|
911
|
-
Returns
|
|
912
|
-
-------
|
|
913
|
-
numpy.ndarray
|
|
914
|
-
The counts of the histogram.
|
|
915
|
-
numpy.array
|
|
916
|
-
An array that counts the telescopes in self.telescope_indices
|
|
917
|
-
numpy.array
|
|
918
|
-
Number of photons per event per telescope in self.telescope_indices.
|
|
919
|
-
"""
|
|
920
|
-
num_events_array = np.arange(self.num_events + 1).reshape(1, self.num_events + 1)
|
|
921
|
-
# It counts only the telescope indices given by self.telescope_indices.
|
|
922
|
-
# The + 1 closes the last edge.
|
|
923
|
-
telescope_counter = np.arange(len(self.telescope_indices) + 1).reshape(
|
|
924
|
-
1, len(self.telescope_indices) + 1
|
|
925
|
-
)
|
|
926
|
-
hist_2d = np.array(self.num_photons_per_event_per_telescope)
|
|
927
|
-
hist_2d = hist_2d.reshape((1, len(self.telescope_indices), self.num_events))
|
|
928
|
-
return (hist_2d, num_events_array, telescope_counter)
|
|
929
|
-
|
|
930
|
-
def _get_hist_1d_projection(self, label):
|
|
931
|
-
"""
|
|
932
|
-
Get 1D distributions.
|
|
933
|
-
|
|
934
|
-
Parameters
|
|
935
|
-
----------
|
|
936
|
-
label: str
|
|
937
|
-
Label to indicate which histogram.
|
|
938
|
-
|
|
939
|
-
Returns
|
|
940
|
-
-------
|
|
941
|
-
numpy.ndarray
|
|
942
|
-
The counts of the histogram.
|
|
943
|
-
numpy.array
|
|
944
|
-
The bin edges of the histogram.
|
|
945
|
-
|
|
946
|
-
Raises
|
|
947
|
-
------
|
|
948
|
-
ValueError:
|
|
949
|
-
if label is not valid.
|
|
950
|
-
"""
|
|
951
|
-
if label not in self._allowed_1d_labels:
|
|
952
|
-
msg = f"{label} is not valid. Valid entries are {self._allowed_1d_labels}"
|
|
953
|
-
self._logger.error(msg)
|
|
954
|
-
raise ValueError(msg)
|
|
955
|
-
self._raise_if_no_histogram()
|
|
956
|
-
|
|
957
|
-
x_bin_edges_list, hist_1d_list = [], []
|
|
958
|
-
for i_hist, _ in enumerate(self.hist_position):
|
|
959
|
-
mini_hist = None
|
|
960
|
-
if label == "wavelength":
|
|
961
|
-
mini_hist = self.hist_position[i_hist][sum, sum, :]
|
|
962
|
-
elif label == "time":
|
|
963
|
-
mini_hist = self.hist_time_altitude[i_hist][:, sum]
|
|
964
|
-
elif label == "altitude":
|
|
965
|
-
mini_hist = self.hist_time_altitude[i_hist][sum, :]
|
|
966
|
-
|
|
967
|
-
x_bin_edges_list.append(mini_hist.axes.edges.T.flatten()[0])
|
|
968
|
-
hist_1d_list.append(mini_hist.view().T)
|
|
969
|
-
return np.array(hist_1d_list), np.array(x_bin_edges_list)
|
|
970
|
-
|
|
971
|
-
def _get_bins_max_dist(self, bins=None, max_dist=None):
|
|
972
|
-
"""
|
|
973
|
-
Get the number of bins and the max distance to generate the radial and density histograms.
|
|
974
|
-
|
|
975
|
-
Parameters
|
|
976
|
-
----------
|
|
977
|
-
bins: float
|
|
978
|
-
Number of bins of the radial distribution.
|
|
979
|
-
max_dist: float
|
|
980
|
-
Maximum distance to consider in the 1D histogram (in meters).
|
|
981
|
-
"""
|
|
982
|
-
hist_position = "hist_position"
|
|
983
|
-
if max_dist is None:
|
|
984
|
-
max_dist = np.amax(
|
|
985
|
-
[
|
|
986
|
-
self.hist_config[hist_position][X_AXIS_STRING]["start"].to(u.m).value,
|
|
987
|
-
self.hist_config[hist_position][X_AXIS_STRING]["stop"].to(u.m).value,
|
|
988
|
-
self.hist_config[hist_position][Y_AXIS_STRING]["start"].to(u.m).value,
|
|
989
|
-
self.hist_config[hist_position][Y_AXIS_STRING]["stop"].to(u.m).value,
|
|
990
|
-
]
|
|
991
|
-
)
|
|
992
|
-
if bins is None:
|
|
993
|
-
bins = (
|
|
994
|
-
np.amax(
|
|
995
|
-
[
|
|
996
|
-
self.hist_config[hist_position][X_AXIS_STRING]["bins"],
|
|
997
|
-
self.hist_config[hist_position][Y_AXIS_STRING]["bins"],
|
|
998
|
-
]
|
|
999
|
-
)
|
|
1000
|
-
// 2
|
|
1001
|
-
) # //2 because of the 2D array going into the negative and
|
|
1002
|
-
# positive axis
|
|
1003
|
-
return bins, max_dist
|
|
1004
|
-
|
|
1005
|
-
def get_photon_radial_distr(self, bins=None, max_dist=None):
|
|
1006
|
-
"""
|
|
1007
|
-
Get the phton radial distribution on the ground in relation to the center of the array.
|
|
1008
|
-
|
|
1009
|
-
Parameters
|
|
1010
|
-
----------
|
|
1011
|
-
bins: float
|
|
1012
|
-
Number of bins of the radial distribution.
|
|
1013
|
-
max_dist: float
|
|
1014
|
-
Maximum distance to consider in the 1D histogram (in meters).
|
|
1015
|
-
|
|
1016
|
-
Returns
|
|
1017
|
-
-------
|
|
1018
|
-
np.array
|
|
1019
|
-
The counts of the 1D histogram with size = int(max_dist/bin_size).
|
|
1020
|
-
np.array
|
|
1021
|
-
The bin edges of the 1D histogram in meters with size = int(max_dist/bin_size) + 1,
|
|
1022
|
-
usually in meter.
|
|
1023
|
-
"""
|
|
1024
|
-
bins, max_dist = self._get_bins_max_dist(bins=bins, max_dist=max_dist)
|
|
1025
|
-
bin_edges_1d_list, hist_1d_list = [], []
|
|
1026
|
-
|
|
1027
|
-
hist_2d_values_list, x_position_list, y_position_list = self.get_2d_photon_position_distr()
|
|
1028
|
-
|
|
1029
|
-
for i_hist, x_pos in enumerate(x_position_list):
|
|
1030
|
-
hist_1d, bin_edges_1d = convert_2d_to_radial_distr(
|
|
1031
|
-
hist_2d_values_list[i_hist],
|
|
1032
|
-
x_pos,
|
|
1033
|
-
y_position_list[i_hist],
|
|
1034
|
-
bins=bins,
|
|
1035
|
-
max_dist=max_dist,
|
|
1036
|
-
)
|
|
1037
|
-
bin_edges_1d_list.append(bin_edges_1d)
|
|
1038
|
-
hist_1d_list.append(hist_1d)
|
|
1039
|
-
return np.array(hist_1d_list), np.array(bin_edges_1d_list)
|
|
1040
|
-
|
|
1041
|
-
def get_photon_density_distr(self, bins=None, max_dist=None):
|
|
1042
|
-
"""
|
|
1043
|
-
Get the photon density distribution on the ground in relation to the center of the array.
|
|
1044
|
-
|
|
1045
|
-
Parameters
|
|
1046
|
-
----------
|
|
1047
|
-
bins: float
|
|
1048
|
-
Number of bins of the radial distribution.
|
|
1049
|
-
max_dist: float
|
|
1050
|
-
Maximum distance to consider in the 1D histogram (in meters).
|
|
1051
|
-
|
|
1052
|
-
Returns
|
|
1053
|
-
-------
|
|
1054
|
-
np.array
|
|
1055
|
-
The density distribution of the 1D histogram with size = int(max_dist/bin_size),
|
|
1056
|
-
usually in $m^{-2}$.
|
|
1057
|
-
np.array
|
|
1058
|
-
The bin edges of the 1D histogram in meters with size = int(max_dist/bin_size) + 1,
|
|
1059
|
-
usually in meter.
|
|
1060
|
-
"""
|
|
1061
|
-
bins, max_dist = self._get_bins_max_dist(bins=bins, max_dist=max_dist)
|
|
1062
|
-
bin_edges_1d_list, hist_1d_list = [], []
|
|
1063
|
-
|
|
1064
|
-
hist_2d_values_list, x_position_list, y_position_list = self.get_2d_photon_density_distr()
|
|
1065
|
-
|
|
1066
|
-
for i_hist, _ in enumerate(x_position_list):
|
|
1067
|
-
hist_1d, bin_edges_1d = convert_2d_to_radial_distr(
|
|
1068
|
-
hist_2d_values_list[i_hist],
|
|
1069
|
-
x_position_list[i_hist], # pylint: disable=unnecessary-list-index-lookup
|
|
1070
|
-
y_position_list[i_hist],
|
|
1071
|
-
bins=bins,
|
|
1072
|
-
max_dist=max_dist,
|
|
1073
|
-
)
|
|
1074
|
-
bin_edges_1d_list.append(bin_edges_1d)
|
|
1075
|
-
hist_1d_list.append(hist_1d)
|
|
1076
|
-
return np.array(hist_1d_list), np.array(bin_edges_1d_list)
|
|
1077
|
-
|
|
1078
|
-
def get_photon_wavelength_distr(self):
|
|
1079
|
-
"""
|
|
1080
|
-
Get histograms with the wavelengths of the photon bunches.
|
|
1081
|
-
|
|
1082
|
-
Returns
|
|
1083
|
-
-------
|
|
1084
|
-
np.array
|
|
1085
|
-
The counts of the wavelength histogram.
|
|
1086
|
-
np.array
|
|
1087
|
-
The bin edges of the wavelength histogram in nanometers.
|
|
1088
|
-
|
|
1089
|
-
"""
|
|
1090
|
-
return self._get_hist_1d_projection("wavelength")
|
|
1091
|
-
|
|
1092
|
-
def get_photon_time_of_emission_distr(self):
|
|
1093
|
-
"""
|
|
1094
|
-
Get the distribution of the emitted time of the Cherenkov photons.
|
|
1095
|
-
|
|
1096
|
-
The clock starts when the particle crosses the top of the atmosphere (CORSIKA-defined) if
|
|
1097
|
-
self.event_first_interaction_heights is positive or at first interaction if otherwise.
|
|
1098
|
-
|
|
1099
|
-
Returns
|
|
1100
|
-
-------
|
|
1101
|
-
numpy.ndarray
|
|
1102
|
-
The counts of the histogram.
|
|
1103
|
-
numpy.array
|
|
1104
|
-
The bin edges of the time histograms in ns.
|
|
1105
|
-
|
|
1106
|
-
"""
|
|
1107
|
-
return self._get_hist_1d_projection("time")
|
|
1108
|
-
|
|
1109
|
-
def get_photon_altitude_distr(self):
|
|
1110
|
-
"""
|
|
1111
|
-
Get the emission altitude of the Cherenkov photons.
|
|
1112
|
-
|
|
1113
|
-
Returns
|
|
1114
|
-
-------
|
|
1115
|
-
numpy.ndarray
|
|
1116
|
-
The counts of the histogram.
|
|
1117
|
-
numpy.array
|
|
1118
|
-
The bin edges of the photon altitude histograms in km.
|
|
1119
|
-
|
|
1120
|
-
"""
|
|
1121
|
-
return self._get_hist_1d_projection("altitude")
|
|
1122
|
-
|
|
1123
|
-
@property
|
|
1124
|
-
def num_photons_per_event_per_telescope(self):
|
|
1125
|
-
"""The number of photons per event per telescope."""
|
|
1126
|
-
return self._num_photons_per_event_per_telescope
|
|
1127
|
-
|
|
1128
|
-
@num_photons_per_event_per_telescope.setter
|
|
1129
|
-
def num_photons_per_event_per_telescope(self, num_photons_per_event_per_telescope_to_set):
|
|
1130
|
-
"""Set the number of photons per event per telescope."""
|
|
1131
|
-
self._num_photons_per_event_per_telescope = (
|
|
1132
|
-
np.array(num_photons_per_event_per_telescope_to_set)
|
|
1133
|
-
.reshape(self.num_events, len(self.telescope_indices))
|
|
1134
|
-
.T
|
|
1135
|
-
)
|
|
1136
|
-
|
|
1137
|
-
@property
|
|
1138
|
-
def num_photons_per_event(self):
|
|
1139
|
-
"""
|
|
1140
|
-
Get the the number of photons per events.
|
|
1141
|
-
|
|
1142
|
-
Includes the telescopes indicated by self.telescope_indices.
|
|
1143
|
-
|
|
1144
|
-
Returns
|
|
1145
|
-
-------
|
|
1146
|
-
numpy.array
|
|
1147
|
-
Number of photons per event.
|
|
1148
|
-
"""
|
|
1149
|
-
self._num_photons_per_event = np.sum(self.num_photons_per_event_per_telescope, axis=0)
|
|
1150
|
-
return self._num_photons_per_event
|
|
1151
|
-
|
|
1152
|
-
def get_num_photons_per_event_distr(self, bins=50, hist_range=None):
|
|
1153
|
-
"""
|
|
1154
|
-
Get the distribution of photons per event.
|
|
1155
|
-
|
|
1156
|
-
Parameters
|
|
1157
|
-
----------
|
|
1158
|
-
bins: float
|
|
1159
|
-
Number of bins for the histogram.
|
|
1160
|
-
hist_range: 2-tuple
|
|
1161
|
-
Tuple to define the range of the histogram.
|
|
1162
|
-
|
|
1163
|
-
Returns
|
|
1164
|
-
-------
|
|
1165
|
-
numpy.ndarray
|
|
1166
|
-
The counts of the histogram.
|
|
1167
|
-
numpy.array
|
|
1168
|
-
Number of photons per event.
|
|
1169
|
-
"""
|
|
1170
|
-
hist, bin_edges = np.histogram(self.num_photons_per_event, bins=bins, range=hist_range)
|
|
1171
|
-
return hist.reshape(1, bins), bin_edges.reshape(1, bins + 1)
|
|
1172
|
-
|
|
1173
|
-
def get_num_photons_per_telescope_distr(self, bins=50, hist_range=None):
|
|
1174
|
-
"""
|
|
1175
|
-
Get the distribution of photons per telescope.
|
|
1176
|
-
|
|
1177
|
-
Parameters
|
|
1178
|
-
----------
|
|
1179
|
-
bins: float
|
|
1180
|
-
Number of bins for the histogram.
|
|
1181
|
-
hist_range: 2-tuple
|
|
1182
|
-
Tuple to define the range of the histogram.
|
|
1183
|
-
|
|
1184
|
-
Returns
|
|
1185
|
-
-------
|
|
1186
|
-
numpy.ndarray
|
|
1187
|
-
The counts of the histogram.
|
|
1188
|
-
numpy.array
|
|
1189
|
-
Number of photons per telescope.
|
|
1190
|
-
"""
|
|
1191
|
-
hist, bin_edges = np.histogram(self.num_photons_per_telescope, bins=bins, range=hist_range)
|
|
1192
|
-
return hist.reshape(1, bins), bin_edges.reshape(1, bins + 1)
|
|
1193
|
-
|
|
1194
|
-
def export_histograms(self, overwrite=False):
|
|
1195
|
-
"""
|
|
1196
|
-
Export the histograms to hdf5 files.
|
|
1197
|
-
|
|
1198
|
-
Parameters
|
|
1199
|
-
----------
|
|
1200
|
-
overwrite: bool
|
|
1201
|
-
If True overwrites the histograms already saved in the hdf5 file.
|
|
1202
|
-
"""
|
|
1203
|
-
self._export_1d_histograms(overwrite=overwrite)
|
|
1204
|
-
self._export_2d_histograms(overwrite=False)
|
|
1205
|
-
|
|
1206
|
-
@property
|
|
1207
|
-
def _meta_dict(self):
|
|
1208
|
-
"""
|
|
1209
|
-
Define the meta dictionary for exporting the histograms.
|
|
1210
|
-
|
|
1211
|
-
Returns
|
|
1212
|
-
-------
|
|
1213
|
-
dict
|
|
1214
|
-
Meta dictionary for the hdf5 files with the histograms.
|
|
1215
|
-
"""
|
|
1216
|
-
if self.__meta_dict is None:
|
|
1217
|
-
self.__meta_dict = {
|
|
1218
|
-
"corsika_version": self.corsika_version,
|
|
1219
|
-
"simtools_version": version.__version__,
|
|
1220
|
-
"iact_file": self.input_file.name,
|
|
1221
|
-
"telescope_indices": list(self.telescope_indices),
|
|
1222
|
-
"individual_telescopes": self.individual_telescopes,
|
|
1223
|
-
"note": "Only lower bin edges are given.",
|
|
1224
|
-
}
|
|
1225
|
-
return self.__meta_dict
|
|
1226
|
-
|
|
1227
|
-
@property
|
|
1228
|
-
def dict_1d_distributions(self):
|
|
1229
|
-
"""
|
|
1230
|
-
Dictionary to label the 1D distributions according to the class methods.
|
|
1231
|
-
|
|
1232
|
-
Returns
|
|
1233
|
-
-------
|
|
1234
|
-
dict:
|
|
1235
|
-
The dictionary with information about the 1D distributions.
|
|
1236
|
-
"""
|
|
1237
|
-
fn_key = "function"
|
|
1238
|
-
file_name = "file name"
|
|
416
|
+
file_name = "file_name"
|
|
1239
417
|
title = "title"
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
418
|
+
x_bins, y_bins = "x_bins", "y_bins"
|
|
419
|
+
x_axis_title, x_axis_unit = "x_axis_title", "x_axis_unit"
|
|
420
|
+
y_axis_title, y_axis_unit = "y_axis_title", "y_axis_unit"
|
|
421
|
+
z_axis_title, z_axis_unit = "z_axis_title", "z_axis_unit"
|
|
422
|
+
|
|
423
|
+
hist_2d = {
|
|
424
|
+
"counts_xy": {
|
|
425
|
+
file_name: "hist_2d_photon_count_distr",
|
|
426
|
+
title: "Photon count distribution (ground level)",
|
|
427
|
+
x_bins: [xy_bin, -xy_maximum, xy_maximum, "linear"],
|
|
428
|
+
y_bins: [xy_bin, -xy_maximum, xy_maximum],
|
|
429
|
+
x_axis_title: "x position on the ground",
|
|
430
|
+
x_axis_unit: xy_maximum.unit,
|
|
431
|
+
y_axis_title: "y position on the ground",
|
|
432
|
+
y_axis_unit: xy_maximum.unit,
|
|
1249
433
|
},
|
|
1250
|
-
"
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
434
|
+
"density_xy": {
|
|
435
|
+
file_name: "hist_2d_photon_density_distr",
|
|
436
|
+
title: "Photon lateral density distribution (ground level)",
|
|
437
|
+
x_bins: [xy_bin, -xy_maximum, xy_maximum, "linear"],
|
|
438
|
+
y_bins: [xy_bin, -xy_maximum, xy_maximum, "linear"],
|
|
439
|
+
x_axis_title: "x position on the ground",
|
|
440
|
+
x_axis_unit: xy_maximum.unit,
|
|
441
|
+
y_axis_title: "y position on the ground",
|
|
442
|
+
y_axis_unit: xy_maximum.unit,
|
|
443
|
+
z_axis_title: "Photon density",
|
|
444
|
+
z_axis_unit: u.m**-2,
|
|
1256
445
|
},
|
|
1257
|
-
"
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
446
|
+
"direction_xy": {
|
|
447
|
+
file_name: "hist_2d_photon_direction_distr",
|
|
448
|
+
title: "Photon arrival direction",
|
|
449
|
+
x_bins: [100, -1, 1, "linear"],
|
|
450
|
+
y_bins: [100, -1, 1, "linear"],
|
|
451
|
+
x_axis_title: "x direction cosine",
|
|
452
|
+
x_axis_unit: u.dimensionless_unscaled,
|
|
453
|
+
y_axis_title: "y direction cosine",
|
|
454
|
+
y_axis_unit: u.dimensionless_unscaled,
|
|
1263
455
|
},
|
|
1264
|
-
"
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
456
|
+
"time_altitude": {
|
|
457
|
+
file_name: "hist_2d_photon_time_altitude_distr",
|
|
458
|
+
title: "Arrival time vs emission altitude",
|
|
459
|
+
x_bins: [100, -2000 * u.ns, 2000 * u.ns, "linear"],
|
|
460
|
+
y_bins: [100, 120 * u.km, 0 * u.km, "linear"],
|
|
461
|
+
x_axis_title: "Arrival time",
|
|
462
|
+
x_axis_unit: u.ns,
|
|
463
|
+
y_axis_title: "Emission altitude",
|
|
464
|
+
y_axis_unit: u.km,
|
|
1270
465
|
},
|
|
1271
|
-
"
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
file_name: "hist_1d_photon_per_event_distr",
|
|
1281
|
-
title: "Photons per event distribution",
|
|
1282
|
-
bin_edges: "Event counter",
|
|
1283
|
-
axis_unit: u.dimensionless_unscaled,
|
|
1284
|
-
},
|
|
1285
|
-
"num_photons_per_telescope": {
|
|
1286
|
-
fn_key: "get_num_photons_per_telescope_distr",
|
|
1287
|
-
file_name: "hist_1d_photon_per_telescope_distr",
|
|
1288
|
-
title: "Photons per telescope distribution",
|
|
1289
|
-
bin_edges: "Telescope counter",
|
|
1290
|
-
axis_unit: u.dimensionless_unscaled,
|
|
466
|
+
"wavelength_altitude": {
|
|
467
|
+
file_name: "hist_2d_photon_wavelength_altitude_distr",
|
|
468
|
+
title: "Wavelength vs emission altitude ",
|
|
469
|
+
x_bins: [100, 100 * u.nm, 1000 * u.nm, "linear"],
|
|
470
|
+
y_bins: [100, 120 * u.km, 0 * u.km, "linear"],
|
|
471
|
+
x_axis_title: "Wavelength",
|
|
472
|
+
x_axis_unit: u.nm,
|
|
473
|
+
y_axis_title: "Emission altitude",
|
|
474
|
+
y_axis_unit: u.km,
|
|
1291
475
|
},
|
|
1292
476
|
}
|
|
1293
|
-
return self._dict_1d_distributions
|
|
1294
|
-
|
|
1295
|
-
def _export_1d_histograms(self, overwrite=False):
|
|
1296
|
-
"""
|
|
1297
|
-
Auxiliary function to export only the 1D histograms.
|
|
1298
477
|
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
axis_unit = "axis unit"
|
|
1305
|
-
for _, function_dict in self.dict_1d_distributions.items():
|
|
1306
|
-
self._meta_dict["Title"] = sanitize_name(function_dict["title"])
|
|
1307
|
-
histogram_function = getattr(self, function_dict["function"])
|
|
1308
|
-
hist_1d_list, x_bin_edges_list = histogram_function()
|
|
1309
|
-
x_bin_edges_list = x_bin_edges_list * function_dict[axis_unit]
|
|
1310
|
-
if function_dict["function"] == "get_photon_density_distr":
|
|
1311
|
-
histogram_value_unit = 1 / (function_dict[axis_unit] ** 2)
|
|
1312
|
-
else:
|
|
1313
|
-
histogram_value_unit = u.dimensionless_unscaled
|
|
1314
|
-
hist_1d_list = hist_1d_list * histogram_value_unit
|
|
1315
|
-
for i_histogram, _ in enumerate(x_bin_edges_list):
|
|
1316
|
-
if self.individual_telescopes:
|
|
1317
|
-
hdf5_table_name = (
|
|
1318
|
-
f"/{function_dict['file name']}_"
|
|
1319
|
-
f"tel_index_{self.telescope_indices[i_histogram]}"
|
|
1320
|
-
)
|
|
1321
|
-
else:
|
|
1322
|
-
hdf5_table_name = f"/{function_dict['file name']}_all_tels"
|
|
1323
|
-
|
|
1324
|
-
table = fill_hdf5_table(
|
|
1325
|
-
hist=hist_1d_list[i_histogram],
|
|
1326
|
-
x_bin_edges=x_bin_edges_list[i_histogram],
|
|
1327
|
-
y_bin_edges=None,
|
|
1328
|
-
x_label=function_dict["bin edges"],
|
|
1329
|
-
y_label=None,
|
|
1330
|
-
meta_data=self._meta_dict,
|
|
1331
|
-
)
|
|
1332
|
-
self._logger.info(
|
|
1333
|
-
f"Writing 1D histogram with name {hdf5_table_name} to {self.hdf5_file_name}."
|
|
1334
|
-
)
|
|
1335
|
-
# overwrite takes precedence over append
|
|
1336
|
-
if overwrite is True:
|
|
1337
|
-
append = False
|
|
1338
|
-
else:
|
|
1339
|
-
append = True
|
|
1340
|
-
write_table(
|
|
1341
|
-
table, self.hdf5_file_name, hdf5_table_name, append=append, overwrite=overwrite
|
|
1342
|
-
)
|
|
1343
|
-
|
|
1344
|
-
@property
|
|
1345
|
-
def dict_2d_distributions(self):
|
|
1346
|
-
"""
|
|
1347
|
-
Dictionary to label the 2D distributions according to the class methods.
|
|
1348
|
-
|
|
1349
|
-
Returns
|
|
1350
|
-
-------
|
|
1351
|
-
dict:
|
|
1352
|
-
The dictionary with information about the 2D distributions.
|
|
1353
|
-
"""
|
|
1354
|
-
fn_key = "function"
|
|
1355
|
-
file_name = "file name"
|
|
1356
|
-
title = "title"
|
|
1357
|
-
x_bin_edges = "x bin edges"
|
|
1358
|
-
x_axis_unit = "x axis unit"
|
|
1359
|
-
y_bin_edges = "y bin edges"
|
|
1360
|
-
y_axis_unit = "y axis unit"
|
|
1361
|
-
if self._dict_2d_distributions is None:
|
|
1362
|
-
self._dict_2d_distributions = {
|
|
1363
|
-
"counts": {
|
|
1364
|
-
fn_key: "get_2d_photon_position_distr",
|
|
1365
|
-
file_name: "hist_2d_photon_count_distr",
|
|
1366
|
-
title: "Photon count distribution on the ground",
|
|
1367
|
-
x_bin_edges: "x position on the ground",
|
|
1368
|
-
x_axis_unit: self.hist_config["hist_position"][X_AXIS_STRING]["start"].unit,
|
|
1369
|
-
y_bin_edges: "y position on the ground",
|
|
1370
|
-
y_axis_unit: self.hist_config["hist_position"][Y_AXIS_STRING]["start"].unit,
|
|
1371
|
-
},
|
|
1372
|
-
"density": {
|
|
1373
|
-
fn_key: "get_2d_photon_density_distr",
|
|
1374
|
-
file_name: "hist_2d_photon_density_distr",
|
|
1375
|
-
title: "Photon density distribution on the ground",
|
|
1376
|
-
x_bin_edges: "x position on the ground",
|
|
1377
|
-
x_axis_unit: self.hist_config["hist_position"][X_AXIS_STRING]["start"].unit,
|
|
1378
|
-
y_bin_edges: "y position on the ground",
|
|
1379
|
-
y_axis_unit: self.hist_config["hist_position"][Y_AXIS_STRING]["start"].unit,
|
|
1380
|
-
},
|
|
1381
|
-
"direction": {
|
|
1382
|
-
fn_key: "get_2d_photon_direction_distr",
|
|
1383
|
-
file_name: "hist_2d_photon_direction_distr",
|
|
1384
|
-
title: "Photon arrival direction",
|
|
1385
|
-
x_bin_edges: "x direction cosine",
|
|
1386
|
-
x_axis_unit: u.dimensionless_unscaled,
|
|
1387
|
-
y_bin_edges: "y direction cosine",
|
|
1388
|
-
y_axis_unit: u.dimensionless_unscaled,
|
|
1389
|
-
},
|
|
1390
|
-
"time_altitude": {
|
|
1391
|
-
fn_key: "get_2d_photon_time_altitude_distr",
|
|
1392
|
-
file_name: "hist_2d_photon_time_altitude_distr",
|
|
1393
|
-
title: "Time of arrival vs altitude of emission",
|
|
1394
|
-
x_bin_edges: "Time of arrival",
|
|
1395
|
-
x_axis_unit: self.hist_config["hist_time_altitude"][X_AXIS_STRING][
|
|
1396
|
-
"start"
|
|
1397
|
-
].unit,
|
|
1398
|
-
y_bin_edges: "Altitude of emission",
|
|
1399
|
-
y_axis_unit: self.hist_config["hist_time_altitude"][Y_AXIS_STRING][
|
|
1400
|
-
"start"
|
|
1401
|
-
].unit,
|
|
1402
|
-
},
|
|
1403
|
-
"num_photons_per_telescope": {
|
|
1404
|
-
fn_key: "get_2d_num_photons_distr",
|
|
1405
|
-
file_name: "hist_2d_photon_telescope_event_distr",
|
|
1406
|
-
title: "Number of photons per telescope and per event",
|
|
1407
|
-
x_bin_edges: "Telescope counter",
|
|
1408
|
-
x_axis_unit: u.dimensionless_unscaled,
|
|
1409
|
-
y_bin_edges: "Event counter",
|
|
1410
|
-
y_axis_unit: u.dimensionless_unscaled,
|
|
1411
|
-
},
|
|
1412
|
-
}
|
|
1413
|
-
return self._dict_2d_distributions
|
|
1414
|
-
|
|
1415
|
-
def _export_2d_histograms(self, overwrite):
|
|
1416
|
-
"""
|
|
1417
|
-
Auxiliary function to export only the 2D histograms.
|
|
1418
|
-
|
|
1419
|
-
Parameters
|
|
1420
|
-
----------
|
|
1421
|
-
overwrite: bool
|
|
1422
|
-
If True overwrites the histograms already saved in the hdf5 file.
|
|
1423
|
-
"""
|
|
1424
|
-
x_axis_unit = "x axis unit"
|
|
1425
|
-
y_axis_unit = "y axis unit"
|
|
1426
|
-
|
|
1427
|
-
for property_name, function_dict in self.dict_2d_distributions.items():
|
|
1428
|
-
self._meta_dict["Title"] = sanitize_name(function_dict["title"])
|
|
1429
|
-
histogram_function = getattr(self, function_dict["function"])
|
|
1430
|
-
|
|
1431
|
-
hist_2d_list, x_bin_edges_list, y_bin_edges_list = histogram_function()
|
|
1432
|
-
if function_dict["function"] == "get_2d_photon_density_distr":
|
|
1433
|
-
histogram_value_unit = 1 / (
|
|
1434
|
-
self.dict_2d_distributions[property_name][x_axis_unit]
|
|
1435
|
-
* self.dict_2d_distributions[property_name][y_axis_unit]
|
|
1436
|
-
)
|
|
1437
|
-
else:
|
|
1438
|
-
histogram_value_unit = u.dimensionless_unscaled
|
|
1439
|
-
|
|
1440
|
-
hist_2d_list, x_bin_edges_list, y_bin_edges_list = (
|
|
1441
|
-
hist_2d_list * histogram_value_unit,
|
|
1442
|
-
x_bin_edges_list * self.dict_2d_distributions[property_name][x_axis_unit],
|
|
1443
|
-
y_bin_edges_list * self.dict_2d_distributions[property_name][y_axis_unit],
|
|
478
|
+
for value in hist_2d.values():
|
|
479
|
+
value["is_1d"] = False
|
|
480
|
+
value["log_z"] = True
|
|
481
|
+
value[z_axis_title] = (
|
|
482
|
+
"Counts" if value.get(z_axis_title) is None else value[z_axis_title]
|
|
1444
483
|
)
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
if self.individual_telescopes:
|
|
1448
|
-
hdf5_table_name = (
|
|
1449
|
-
f"/{self.dict_2d_distributions[property_name]['file name']}"
|
|
1450
|
-
f"_tel_index_{self.telescope_indices[i_histogram]}"
|
|
1451
|
-
)
|
|
1452
|
-
else:
|
|
1453
|
-
hdf5_table_name = (
|
|
1454
|
-
f"/{self.dict_2d_distributions[property_name]['file name']}_all_tels"
|
|
1455
|
-
)
|
|
1456
|
-
table = fill_hdf5_table(
|
|
1457
|
-
hist=hist_2d_list[i_histogram],
|
|
1458
|
-
x_bin_edges=x_bin_edges_list[i_histogram],
|
|
1459
|
-
y_bin_edges=y_bin_edges_list[i_histogram],
|
|
1460
|
-
x_label=function_dict["x bin edges"],
|
|
1461
|
-
y_label=function_dict["y bin edges"],
|
|
1462
|
-
meta_data=self._meta_dict,
|
|
1463
|
-
)
|
|
1464
|
-
|
|
1465
|
-
self._logger.info(
|
|
1466
|
-
f"Writing 2D histogram with name {hdf5_table_name} to {self.hdf5_file_name}."
|
|
1467
|
-
)
|
|
1468
|
-
# Always appending to table due to the file previously created
|
|
1469
|
-
# by self._export_1d_histograms.
|
|
1470
|
-
write_table(
|
|
1471
|
-
table, self.hdf5_file_name, hdf5_table_name, append=True, overwrite=overwrite
|
|
1472
|
-
)
|
|
1473
|
-
|
|
1474
|
-
def export_event_header_1d_histogram(
|
|
1475
|
-
self, event_header_element, bins=50, hist_range=None, overwrite=False
|
|
1476
|
-
):
|
|
1477
|
-
"""
|
|
1478
|
-
Export 'event_header_element' from CORSIKA to hd5 for a 1D histogram.
|
|
1479
|
-
|
|
1480
|
-
Parameters
|
|
1481
|
-
----------
|
|
1482
|
-
event_header_element: str
|
|
1483
|
-
The key to the CORSIKA event header element.
|
|
1484
|
-
Possible choices are stored in 'self.all_event_keys'.
|
|
1485
|
-
bins: float
|
|
1486
|
-
Number of bins for the histogram.
|
|
1487
|
-
hist_range: 2-tuple
|
|
1488
|
-
Tuple to define the range of the histogram.
|
|
1489
|
-
overwrite: bool
|
|
1490
|
-
If True overwrites the histograms already saved in the hdf5 file.
|
|
1491
|
-
"""
|
|
1492
|
-
hist, bin_edges = self.event_1d_histogram(
|
|
1493
|
-
event_header_element, bins=bins, hist_range=hist_range
|
|
1494
|
-
)
|
|
1495
|
-
bin_edges *= self.event_information[event_header_element].unit
|
|
1496
|
-
table = fill_hdf5_table(
|
|
1497
|
-
hist=hist,
|
|
1498
|
-
x_bin_edges=bin_edges,
|
|
1499
|
-
y_bin_edges=None,
|
|
1500
|
-
x_label=event_header_element,
|
|
1501
|
-
y_label=None,
|
|
1502
|
-
meta_data=self._meta_dict,
|
|
1503
|
-
)
|
|
1504
|
-
hdf5_table_name = f"/event_2d_histograms_{event_header_element}"
|
|
1505
|
-
|
|
1506
|
-
self._logger.info(
|
|
1507
|
-
f"Exporting histogram with name {hdf5_table_name} to {self.hdf5_file_name}."
|
|
1508
|
-
)
|
|
1509
|
-
# overwrite takes precedence over append
|
|
1510
|
-
if overwrite is True:
|
|
1511
|
-
append = False
|
|
1512
|
-
else:
|
|
1513
|
-
append = True
|
|
1514
|
-
write_table(table, self.hdf5_file_name, hdf5_table_name, append=append, overwrite=overwrite)
|
|
1515
|
-
|
|
1516
|
-
def export_event_header_2d_histogram(
|
|
1517
|
-
self,
|
|
1518
|
-
event_header_element_1,
|
|
1519
|
-
event_header_element_2,
|
|
1520
|
-
bins=50,
|
|
1521
|
-
hist_range=None,
|
|
1522
|
-
overwrite=False,
|
|
1523
|
-
):
|
|
1524
|
-
"""
|
|
1525
|
-
Export event_header of a 2D histogram to a hdf5 file.
|
|
1526
|
-
|
|
1527
|
-
Searches the 2D histogram for the key 'event_header_element_1' and
|
|
1528
|
-
'event_header_element_2'from the CORSIKA event header.
|
|
1529
|
-
|
|
1530
|
-
Parameters
|
|
1531
|
-
----------
|
|
1532
|
-
event_header_element_1: str
|
|
1533
|
-
The key to the CORSIKA event header element.
|
|
1534
|
-
event_header_element_2: str
|
|
1535
|
-
The key to the CORSIKA event header element.
|
|
1536
|
-
Possible choices for 'event_header_element_1' and 'event_header_element_2' are stored
|
|
1537
|
-
in 'self.all_event_keys'.
|
|
1538
|
-
bins: float
|
|
1539
|
-
Number of bins for the histogram.
|
|
1540
|
-
hist_range: 2-tuple
|
|
1541
|
-
Tuple to define the range of the histogram.
|
|
1542
|
-
overwrite: bool
|
|
1543
|
-
If True overwrites the histograms already saved in the hdf5 file.
|
|
1544
|
-
|
|
1545
|
-
"""
|
|
1546
|
-
hist, x_bin_edges, y_bin_edges = self.event_2d_histogram(
|
|
1547
|
-
event_header_element_1, event_header_element_2, bins=bins, hist_range=hist_range
|
|
1548
|
-
)
|
|
1549
|
-
x_bin_edges *= self.event_information[event_header_element_1].unit
|
|
1550
|
-
y_bin_edges *= self.event_information[event_header_element_2].unit
|
|
1551
|
-
|
|
1552
|
-
table = fill_hdf5_table(
|
|
1553
|
-
hist=hist,
|
|
1554
|
-
x_bin_edges=x_bin_edges,
|
|
1555
|
-
y_bin_edges=y_bin_edges,
|
|
1556
|
-
x_label=event_header_element_1,
|
|
1557
|
-
y_label=event_header_element_2,
|
|
1558
|
-
meta_data=self._meta_dict,
|
|
1559
|
-
)
|
|
1560
|
-
|
|
1561
|
-
hdf5_table_name = f"/event_2d_histograms_{event_header_element_1}_{event_header_element_2}"
|
|
1562
|
-
|
|
1563
|
-
self._logger.info(
|
|
1564
|
-
f"Exporting histogram with name {hdf5_table_name} to {self.hdf5_file_name}."
|
|
1565
|
-
)
|
|
1566
|
-
# overwrite takes precedence over append
|
|
1567
|
-
if overwrite is True:
|
|
1568
|
-
append = False
|
|
1569
|
-
else:
|
|
1570
|
-
append = True
|
|
1571
|
-
write_table(table, self.hdf5_file_name, hdf5_table_name, append=append, overwrite=overwrite)
|
|
1572
|
-
|
|
1573
|
-
@property
|
|
1574
|
-
def num_photons_per_telescope(self):
|
|
1575
|
-
"""
|
|
1576
|
-
The number of photons per event, considering the telescopes given by self.telescope_indices.
|
|
1577
|
-
|
|
1578
|
-
Returns
|
|
1579
|
-
-------
|
|
1580
|
-
numpy.array
|
|
1581
|
-
Number of photons per telescope.
|
|
1582
|
-
"""
|
|
1583
|
-
self._num_photons_per_telescope = np.sum(
|
|
1584
|
-
np.array(self.num_photons_per_event_per_telescope), axis=1
|
|
1585
|
-
)
|
|
1586
|
-
return self._num_photons_per_telescope
|
|
1587
|
-
|
|
1588
|
-
@property
|
|
1589
|
-
def total_num_photons(self):
|
|
1590
|
-
"""
|
|
1591
|
-
The total number of photons.
|
|
1592
|
-
|
|
1593
|
-
Returns
|
|
1594
|
-
-------
|
|
1595
|
-
float
|
|
1596
|
-
Total number photons.
|
|
1597
|
-
"""
|
|
1598
|
-
self._total_num_photons = np.sum(self.num_photons_per_event)
|
|
1599
|
-
return self._total_num_photons
|
|
1600
|
-
|
|
1601
|
-
@property
|
|
1602
|
-
def telescope_positions(self):
|
|
1603
|
-
"""
|
|
1604
|
-
The telescope positions found in the CORSIKA output file.
|
|
1605
|
-
|
|
1606
|
-
It does not depend on the telescope_indices attribute.
|
|
1607
|
-
|
|
1608
|
-
Returns
|
|
1609
|
-
-------
|
|
1610
|
-
numpy.ndarray
|
|
1611
|
-
x, y and z positions of the telescopes and their radius according to the CORSIKA
|
|
1612
|
-
spherical representation of the telescopes.
|
|
1613
|
-
"""
|
|
1614
|
-
return self._telescope_positions
|
|
1615
|
-
|
|
1616
|
-
@telescope_positions.setter
|
|
1617
|
-
def telescope_positions(self, new_positions):
|
|
1618
|
-
"""
|
|
1619
|
-
Set the telescope positions.
|
|
1620
|
-
|
|
1621
|
-
Parameters
|
|
1622
|
-
----------
|
|
1623
|
-
numpy.ndarray
|
|
1624
|
-
x, y and z positions of the telescopes and their radius according to the CORSIKA
|
|
1625
|
-
spherical representation of the telescopes.
|
|
1626
|
-
"""
|
|
1627
|
-
self._telescope_positions = new_positions
|
|
1628
|
-
|
|
1629
|
-
# In the next five functions, we provide dedicated functions to retrieve specific information
|
|
1630
|
-
# about the runs, i.e. zenith, azimuth, total energy, interaction height and Earth magnetic
|
|
1631
|
-
# field defined for the run. For other information, please use the get_event_parameter_info
|
|
1632
|
-
# function.
|
|
1633
|
-
@property
|
|
1634
|
-
def event_zenith_angles(self):
|
|
1635
|
-
"""
|
|
1636
|
-
Get the zenith angles of the simulated events in astropy units of degrees.
|
|
1637
|
-
|
|
1638
|
-
Returns
|
|
1639
|
-
-------
|
|
1640
|
-
astropy.Quantity
|
|
1641
|
-
The zenith angles for each event.
|
|
1642
|
-
"""
|
|
1643
|
-
if self._event_zenith_angles is None:
|
|
1644
|
-
self._event_zenith_angles = np.around(
|
|
1645
|
-
(self.event_information["zenith"]).to(u.deg),
|
|
1646
|
-
4,
|
|
484
|
+
value[z_axis_unit] = (
|
|
485
|
+
u.dimensionless_unscaled if value.get(z_axis_unit) is None else value[z_axis_unit]
|
|
1647
486
|
)
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
def event_azimuth_angles(self):
|
|
1652
|
-
"""
|
|
1653
|
-
Get the azimuth angles of the simulated events in astropy units of degrees.
|
|
1654
|
-
|
|
1655
|
-
Returns
|
|
1656
|
-
-------
|
|
1657
|
-
astropy.Quantity
|
|
1658
|
-
The azimuth angles for each event, usually in degrees.
|
|
1659
|
-
"""
|
|
1660
|
-
if self._event_azimuth_angles is None:
|
|
1661
|
-
self._event_azimuth_angles = np.around(
|
|
1662
|
-
(self.event_information["azimuth"]).to(u.deg),
|
|
1663
|
-
4,
|
|
1664
|
-
)
|
|
1665
|
-
return self._event_azimuth_angles
|
|
1666
|
-
|
|
1667
|
-
@property
|
|
1668
|
-
def event_energies(self):
|
|
1669
|
-
"""
|
|
1670
|
-
Get the energy of the simulated events in astropy units of TeV.
|
|
1671
|
-
|
|
1672
|
-
Returns
|
|
1673
|
-
-------
|
|
1674
|
-
astropy.Quantity
|
|
1675
|
-
The total energies of the incoming particles for each event, usually in TeV.
|
|
1676
|
-
"""
|
|
1677
|
-
if self._event_total_energies is None:
|
|
1678
|
-
self._event_total_energies = np.around(
|
|
1679
|
-
(self.event_information["total_energy"]).to(u.TeV),
|
|
1680
|
-
4,
|
|
487
|
+
boost_axes = self._create_regular_axes(value, ["x_bins", "y_bins"])
|
|
488
|
+
value["histogram"] = bh.Histogram(
|
|
489
|
+
boost_axes[0], boost_axes[1], storage=bh.storage.Weight()
|
|
1681
490
|
)
|
|
1682
|
-
return self._event_total_energies
|
|
1683
|
-
|
|
1684
|
-
@property
|
|
1685
|
-
def event_first_interaction_heights(self):
|
|
1686
|
-
"""
|
|
1687
|
-
Get the height of the first interaction in astropy units of km.
|
|
1688
|
-
|
|
1689
|
-
If negative, tracking starts at margin of atmosphere,
|
|
1690
|
-
see TSTART in the CORSIKA 7 user guide.
|
|
1691
491
|
|
|
1692
|
-
|
|
1693
|
-
-------
|
|
1694
|
-
astropy.Quantity
|
|
1695
|
-
The first interaction height for each event, usually in km.
|
|
1696
|
-
"""
|
|
1697
|
-
if self._event_first_interaction_heights is None:
|
|
1698
|
-
self._event_first_interaction_heights = np.around(
|
|
1699
|
-
(self.event_information["first_interaction_height"]).to(u.km),
|
|
1700
|
-
4,
|
|
1701
|
-
)
|
|
1702
|
-
return self._event_first_interaction_heights
|
|
1703
|
-
|
|
1704
|
-
@property
|
|
1705
|
-
def magnetic_field(self):
|
|
1706
|
-
"""
|
|
1707
|
-
Get the Earth magnetic field from the events header in astropy units of microT.
|
|
492
|
+
return hist_2d
|
|
1708
493
|
|
|
1709
|
-
|
|
494
|
+
def _update_distributions(self):
|
|
495
|
+
"""Update the distributions dictionary with the histogram values and bin edges."""
|
|
496
|
+
self._normalize_density_histograms()
|
|
1710
497
|
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
parameter: str
|
|
1736
|
-
The parameter of interest. Available options are to be found under
|
|
1737
|
-
self.all_event_keys.
|
|
1738
|
-
|
|
1739
|
-
Returns
|
|
1740
|
-
-------
|
|
1741
|
-
astropy.Quantity
|
|
1742
|
-
The array with the event information as required by the parameter.
|
|
1743
|
-
|
|
1744
|
-
Raises
|
|
1745
|
-
------
|
|
1746
|
-
KeyError:
|
|
1747
|
-
If parameter is not valid.
|
|
1748
|
-
"""
|
|
1749
|
-
if parameter not in self.all_event_keys:
|
|
1750
|
-
msg = f"key is not valid. Valid entries are {self.all_event_keys}"
|
|
1751
|
-
self._logger.error(msg)
|
|
1752
|
-
raise KeyError
|
|
1753
|
-
return self.event_information[parameter]
|
|
1754
|
-
|
|
1755
|
-
def get_run_info(self, parameter):
|
|
1756
|
-
"""
|
|
1757
|
-
Get specific information (i.e. any parameter) of the run.
|
|
1758
|
-
|
|
1759
|
-
The parameter is passed through the key word parameter.
|
|
1760
|
-
Available options are to be found under self.all_run_keys.
|
|
1761
|
-
The unit of the parameter, if any, is given according to the CORSIKA version
|
|
1762
|
-
(please see user guide in this case).
|
|
1763
|
-
|
|
1764
|
-
Parameters
|
|
1765
|
-
----------
|
|
1766
|
-
parameter: str
|
|
1767
|
-
The parameter of interest. Available options are to be found under
|
|
1768
|
-
self.all_run_keys.
|
|
1769
|
-
|
|
1770
|
-
Raises
|
|
1771
|
-
------
|
|
1772
|
-
KeyError:
|
|
1773
|
-
If parameter is not valid.
|
|
1774
|
-
"""
|
|
1775
|
-
if parameter not in self.all_run_keys:
|
|
1776
|
-
msg = f"key is not valid. Valid entries are {self.all_run_keys}"
|
|
1777
|
-
self._logger.error(msg)
|
|
1778
|
-
raise KeyError
|
|
1779
|
-
return self.header[parameter]
|
|
1780
|
-
|
|
1781
|
-
def event_1d_histogram(self, key, bins=50, hist_range=None):
|
|
1782
|
-
"""
|
|
1783
|
-
Create a histogram for the all events using key as parameter.
|
|
1784
|
-
|
|
1785
|
-
Valid keys are stored in self.all_event_keys (CORSIKA defined).
|
|
1786
|
-
|
|
1787
|
-
Parameters
|
|
1788
|
-
----------
|
|
1789
|
-
key: str
|
|
1790
|
-
The information from which to build the histogram, e.g. total_energy, zenith or
|
|
1791
|
-
first_interaction_height.
|
|
1792
|
-
bins: float
|
|
1793
|
-
Number of bins for the histogram.
|
|
1794
|
-
hist_range: 2-tuple
|
|
1795
|
-
Tuple to define the range of the histogram.
|
|
1796
|
-
|
|
1797
|
-
Returns
|
|
1798
|
-
-------
|
|
1799
|
-
numpy.ndarray
|
|
1800
|
-
The counts of the histogram.
|
|
1801
|
-
numpy.array
|
|
1802
|
-
Edges of the histogram.
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
Raises
|
|
1806
|
-
------
|
|
1807
|
-
KeyError:
|
|
1808
|
-
If key is not valid.
|
|
1809
|
-
"""
|
|
1810
|
-
if key not in self.all_event_keys:
|
|
1811
|
-
msg = f"key is not valid. Valid entries are {self.all_event_keys}"
|
|
1812
|
-
self._logger.error(msg)
|
|
1813
|
-
raise KeyError
|
|
1814
|
-
hist, bin_edges = np.histogram(
|
|
1815
|
-
self.event_information[key].value,
|
|
1816
|
-
bins=bins,
|
|
1817
|
-
range=hist_range,
|
|
1818
|
-
)
|
|
1819
|
-
return hist, bin_edges
|
|
498
|
+
for key, value in self.hist.items():
|
|
499
|
+
value["input_file_name"] = str(self.input_file)
|
|
500
|
+
if value["is_1d"]:
|
|
501
|
+
value["hist_values"], value["x_bin_edges"], value["uncertainties"] = (
|
|
502
|
+
self.get_hist_1d_projection(key, value)
|
|
503
|
+
)
|
|
504
|
+
else:
|
|
505
|
+
(
|
|
506
|
+
value["hist_values"],
|
|
507
|
+
value["x_bin_edges"],
|
|
508
|
+
value["y_bin_edges"],
|
|
509
|
+
value["uncertainties"],
|
|
510
|
+
) = self.get_hist_2d_projection(value["histogram"])
|
|
511
|
+
|
|
512
|
+
def _normalize_density_histograms(self):
|
|
513
|
+
"""Normalize the density histograms by the area of each bin."""
|
|
514
|
+
|
|
515
|
+
def normalize_histogram(hist, bin_areas):
|
|
516
|
+
view = hist.view()
|
|
517
|
+
if self._check_for_all_attributes(view):
|
|
518
|
+
view["value"] /= bin_areas
|
|
519
|
+
view["variance"] /= bin_areas**2
|
|
520
|
+
else:
|
|
521
|
+
hist /= bin_areas
|
|
1820
522
|
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
523
|
+
def normalize_histogram_1d(hist, bin_areas):
|
|
524
|
+
view = hist.view()
|
|
525
|
+
if self._check_for_all_attributes(view):
|
|
526
|
+
view["value"] /= bin_areas
|
|
527
|
+
view["variance"] /= bin_areas**2
|
|
528
|
+
else:
|
|
529
|
+
view /= bin_areas
|
|
1824
530
|
|
|
1825
|
-
|
|
531
|
+
density_xy_hist = self.hist["density_xy"]["histogram"]
|
|
532
|
+
density_r_hist = self.hist["density_r"]["histogram"]
|
|
1826
533
|
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
key_1: str
|
|
1830
|
-
The information from which to build the histogram, e.g. total_energy, zenith or
|
|
1831
|
-
first_interaction_height.
|
|
1832
|
-
key_2: str
|
|
1833
|
-
The information from which to build the histogram, e.g. total_energy, zenith or
|
|
1834
|
-
first_interaction_height.
|
|
1835
|
-
bins: float
|
|
1836
|
-
Number of bins for the histogram.
|
|
1837
|
-
hist_range: 2-tuple
|
|
1838
|
-
Tuple to define the range of the histogram.
|
|
1839
|
-
|
|
1840
|
-
Returns
|
|
1841
|
-
-------
|
|
1842
|
-
numpy.ndarray
|
|
1843
|
-
The counts of the histogram.
|
|
1844
|
-
numpy.array
|
|
1845
|
-
x Edges of the histogram.
|
|
1846
|
-
numpy.array
|
|
1847
|
-
y Edges of the histogram.
|
|
534
|
+
bin_areas_xy = functools.reduce(operator.mul, density_xy_hist.axes.widths)
|
|
535
|
+
normalize_histogram(density_xy_hist, bin_areas_xy)
|
|
1848
536
|
|
|
537
|
+
bin_edges_r = density_r_hist.axes.edges[0]
|
|
538
|
+
bin_areas_r = np.pi * (bin_edges_r[1:] ** 2 - bin_edges_r[:-1] ** 2)
|
|
539
|
+
normalize_histogram_1d(density_r_hist, bin_areas_r)
|
|
1849
540
|
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
for key in [key_1, key_2]:
|
|
1856
|
-
if key not in self.all_event_keys:
|
|
1857
|
-
msg = (
|
|
1858
|
-
f"At least one of the keys given is not valid. Valid entries are "
|
|
1859
|
-
f"{self.all_event_keys}"
|
|
1860
|
-
)
|
|
1861
|
-
self._logger.error(msg)
|
|
1862
|
-
raise KeyError
|
|
1863
|
-
hist, x_bin_edges, y_bin_edges = np.histogram2d(
|
|
1864
|
-
self.event_information[key_1].value,
|
|
1865
|
-
self.event_information[key_2].value,
|
|
1866
|
-
bins=bins,
|
|
1867
|
-
range=hist_range,
|
|
1868
|
-
)
|
|
1869
|
-
return hist, x_bin_edges, y_bin_edges
|
|
541
|
+
def _check_for_all_attributes(self, view):
|
|
542
|
+
"""Check if view has dtype fields ('value', 'variance')."""
|
|
543
|
+
if hasattr(view, "dtype") and view.dtype.names == ("value", "variance"):
|
|
544
|
+
return True
|
|
545
|
+
return False
|