gammasimtools 0.25.0__py3-none-any.whl → 0.27.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.25.0.dist-info → gammasimtools-0.27.0.dist-info}/METADATA +6 -1
- {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/RECORD +135 -130
- {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/WHEEL +1 -1
- {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/entry_points.txt +3 -2
- {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/licenses/LICENSE +1 -1
- simtools/_version.py +2 -2
- simtools/application_control.py +35 -7
- simtools/applications/convert_geo_coordinates_of_array_elements.py +3 -3
- 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 +3 -7
- 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/{calculate_incident_angles.py → derive_incident_angle.py} +16 -18
- simtools/applications/derive_mirror_rnda.py +112 -180
- simtools/applications/derive_psf_parameters.py +0 -1
- simtools/applications/derive_pulse_shape_parameters.py +0 -1
- 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 +79 -229
- simtools/applications/generate_regular_arrays.py +76 -69
- 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 +5 -111
- 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 -15
- simtools/applications/simulate_illuminator.py +2 -11
- simtools/applications/simulate_pedestals.py +1 -5
- simtools/applications/simulate_prod.py +8 -11
- simtools/applications/simulate_prod_htcondor_generator.py +1 -1
- simtools/applications/submit_array_layouts.py +2 -4
- simtools/applications/submit_data_from_external.py +2 -1
- simtools/applications/submit_model_parameter_from_external.py +1 -3
- simtools/applications/validate_camera_efficiency.py +28 -28
- simtools/applications/validate_camera_fov.py +0 -1
- simtools/applications/validate_cumulative_psf.py +1 -5
- simtools/applications/validate_optics.py +2 -14
- simtools/atmosphere.py +83 -0
- simtools/camera/camera_efficiency.py +171 -53
- simtools/camera/single_photon_electron_spectrum.py +8 -7
- simtools/configuration/commandline_parser.py +82 -11
- simtools/configuration/configurator.py +6 -11
- simtools/constants.py +5 -0
- simtools/corsika/corsika_config.py +100 -202
- simtools/corsika/corsika_histograms.py +561 -1708
- 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 +59 -64
- simtools/data_model/schema.py +2 -0
- simtools/data_model/validate_data.py +1 -3
- simtools/db/db_handler.py +23 -10
- simtools/db/mongo_db.py +2 -2
- simtools/dependencies.py +81 -38
- simtools/io/ascii_handler.py +55 -5
- simtools/io/io_handler.py +23 -12
- simtools/io/table_handler.py +1 -1
- simtools/job_execution/job_manager.py +154 -79
- simtools/job_execution/process_pool.py +137 -0
- simtools/layout/array_layout.py +4 -13
- simtools/layout/array_layout_utils.py +348 -57
- simtools/model/array_model.py +23 -63
- simtools/model/calibration_model.py +4 -8
- simtools/model/legacy_model_parameter.py +134 -0
- simtools/model/model_parameter.py +147 -86
- simtools/model/model_utils.py +40 -6
- simtools/model/site_model.py +4 -8
- simtools/model/telescope_model.py +10 -16
- simtools/production_configuration/derive_corsika_limits.py +6 -11
- simtools/production_configuration/interpolation_handler.py +16 -16
- simtools/ray_tracing/incident_angles.py +92 -17
- simtools/ray_tracing/mirror_panel_psf.py +338 -222
- simtools/ray_tracing/psf_analysis.py +62 -48
- simtools/ray_tracing/psf_parameter_optimisation.py +3 -3
- simtools/ray_tracing/ray_tracing.py +43 -25
- simtools/reporting/docs_auto_report_generator.py +8 -13
- simtools/reporting/docs_read_parameters.py +2 -8
- simtools/runners/corsika_runner.py +52 -195
- simtools/runners/corsika_simtel_runner.py +77 -108
- simtools/runners/runner_services.py +214 -213
- simtools/runners/simtel_runner.py +27 -160
- simtools/runners/simtools_runner.py +11 -73
- simtools/schemas/application_workflow.metaschema.yml +8 -0
- simtools/settings.py +173 -0
- simtools/{io/eventio_handler.py → sim_events/file_info.py} +3 -3
- 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 +7 -2
- simtools/simtel/simtel_config_writer.py +79 -91
- simtools/simtel/simtel_seeds.py +184 -0
- simtools/simtel/simtel_table_reader.py +6 -4
- simtools/simtel/simulator_array.py +114 -109
- simtools/simtel/simulator_camera_efficiency.py +68 -46
- simtools/simtel/simulator_light_emission.py +164 -132
- simtools/simtel/simulator_ray_tracing.py +80 -71
- simtools/simulator.py +137 -355
- simtools/telescope_trigger_rates.py +3 -4
- simtools/testing/assertions.py +84 -33
- simtools/testing/configuration.py +1 -2
- simtools/testing/helpers.py +2 -3
- simtools/testing/log_inspector.py +1 -0
- simtools/testing/sim_telarray_metadata.py +14 -12
- simtools/testing/validate_output.py +121 -42
- simtools/utils/general.py +43 -17
- simtools/utils/geometry.py +0 -77
- simtools/utils/names.py +5 -5
- simtools/utils/random.py +36 -0
- simtools/visualization/legend_handlers.py +7 -6
- simtools/visualization/plot_array_layout.py +91 -16
- simtools/visualization/plot_corsika_histograms.py +145 -605
- simtools/visualization/plot_incident_angles.py +48 -1
- simtools/visualization/plot_mirrors.py +1 -4
- simtools/visualization/plot_pixels.py +2 -4
- simtools/visualization/plot_psf.py +160 -19
- 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
- {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/top_level.txt +0 -0
|
@@ -1,52 +1,27 @@
|
|
|
1
|
-
"""Extract Cherenkov photons
|
|
1
|
+
"""Extract Cherenkov photons from a CORSIKA IACT file and fill histograms."""
|
|
2
2
|
|
|
3
|
-
import functools
|
|
4
3
|
import logging
|
|
5
|
-
import operator
|
|
6
|
-
import re
|
|
7
|
-
import time
|
|
8
4
|
from pathlib import Path
|
|
9
5
|
|
|
10
6
|
import boost_histogram as bh
|
|
11
7
|
import numpy as np
|
|
12
8
|
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
9
|
from eventio import IACTFile
|
|
18
10
|
|
|
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."""
|
|
11
|
+
from simtools.utils.geometry import rotate
|
|
34
12
|
|
|
35
13
|
|
|
36
14
|
class CorsikaHistograms:
|
|
37
15
|
"""
|
|
38
|
-
|
|
16
|
+
Extract Cherenkov photons from a CORSIKA IACT file and fill histograms.
|
|
39
17
|
|
|
40
18
|
Parameters
|
|
41
19
|
----------
|
|
42
20
|
input_file: str or Path
|
|
43
|
-
CORSIKA IACT file
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
Path where to save the output of the class methods.
|
|
48
|
-
hdf5_file_name: str
|
|
49
|
-
HDF5 file name for histogram storage.
|
|
21
|
+
CORSIKA IACT file.
|
|
22
|
+
axis_distance: astropy.units.Quantity or float
|
|
23
|
+
Distance from the axis to consider when calculating the lateral density profiles
|
|
24
|
+
along x and y axes. If a float is given, it is assumed to be in meters.
|
|
50
25
|
|
|
51
26
|
Raises
|
|
52
27
|
------
|
|
@@ -54,569 +29,99 @@ class CorsikaHistograms:
|
|
|
54
29
|
if the input file given does not exist.
|
|
55
30
|
"""
|
|
56
31
|
|
|
57
|
-
def __init__(self, input_file,
|
|
58
|
-
self.label = label
|
|
32
|
+
def __init__(self, input_file, normalization_method="per-telescope", axis_distance=1000 * u.m):
|
|
59
33
|
self._logger = logging.getLogger(__name__)
|
|
60
|
-
self.
|
|
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
|
|
36
|
+
raise FileNotFoundError(f"File {self.input_file} does not exist.")
|
|
142
37
|
|
|
143
|
-
|
|
144
|
-
|
|
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,
|
|
38
|
+
self.axis_distance = (
|
|
39
|
+
axis_distance.to(u.m).value if isinstance(axis_distance, u.Quantity) else axis_distance
|
|
172
40
|
)
|
|
41
|
+
self.events = None
|
|
42
|
+
self.hist = self._set_2d_distributions()
|
|
43
|
+
self.hist.update(self._set_1d_distributions())
|
|
44
|
+
self._density_samples = []
|
|
45
|
+
self.normalization_method = normalization_method
|
|
173
46
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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 = {}
|
|
47
|
+
def fill(self):
|
|
48
|
+
"""Fill Cherenkov photons histograms."""
|
|
49
|
+
self._read_event_headers()
|
|
253
50
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
51
|
+
with IACTFile(self.input_file) as f:
|
|
52
|
+
telescope_positions = np.array(f.telescope_positions)
|
|
53
|
+
for event_counter, event in enumerate(f):
|
|
54
|
+
if hasattr(event, "photon_bunches"):
|
|
55
|
+
photons = list(event.photon_bunches.values())
|
|
56
|
+
self._fill_histograms(photons, event_counter, telescope_positions, False)
|
|
57
|
+
|
|
58
|
+
self._update_distributions()
|
|
59
|
+
|
|
60
|
+
def _read_event_headers(self):
|
|
61
|
+
"""Read event information from headers."""
|
|
62
|
+
event_dtype = np.dtype(
|
|
63
|
+
[
|
|
64
|
+
("particle_id", "i4"),
|
|
65
|
+
("total_energy", "f8"),
|
|
66
|
+
("azimuth_deg", "f8"),
|
|
67
|
+
("zenith_deg", "f8"),
|
|
68
|
+
("num_photons", "f8"),
|
|
69
|
+
]
|
|
257
70
|
)
|
|
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
71
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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.
|
|
283
|
-
|
|
284
|
-
"""
|
|
285
|
-
if self.event_information is None:
|
|
286
|
-
with IACTFile(self.input_file) as self.iact_file:
|
|
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]
|
|
72
|
+
with IACTFile(self.input_file) as iact_file:
|
|
73
|
+
records = [
|
|
74
|
+
(
|
|
75
|
+
event.header["particle_id"],
|
|
76
|
+
event.header["total_energy"],
|
|
77
|
+
np.rad2deg(event.header["azimuth"]),
|
|
78
|
+
np.rad2deg(event.header["zenith"]),
|
|
79
|
+
0.0, # filled later when reading photon bunches
|
|
294
80
|
)
|
|
81
|
+
for event in iact_file
|
|
82
|
+
]
|
|
295
83
|
|
|
296
|
-
|
|
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
|
|
84
|
+
self.events = np.array(records, dtype=event_dtype)
|
|
305
85
|
|
|
306
|
-
|
|
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):
|
|
86
|
+
def _create_regular_axes(self, hist, axes):
|
|
319
87
|
"""
|
|
320
|
-
|
|
88
|
+
Create regular axis for a single histogram.
|
|
321
89
|
|
|
322
90
|
Parameters
|
|
323
91
|
----------
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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
|
|
347
|
-
|
|
348
|
-
@property
|
|
349
|
-
def telescope_indices(self):
|
|
350
|
-
"""
|
|
351
|
-
The telescope index (or indices), which are considered for the production of the histograms.
|
|
92
|
+
hist: dict
|
|
93
|
+
Histogram dictionary.
|
|
94
|
+
axes: list
|
|
95
|
+
List of axis names (e.g. ["x_bins", "y_bins"]).
|
|
352
96
|
|
|
353
97
|
Returns
|
|
354
98
|
-------
|
|
355
99
|
list:
|
|
356
|
-
|
|
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.
|
|
374
|
-
|
|
375
|
-
Raises
|
|
376
|
-
------
|
|
377
|
-
TypeError:
|
|
378
|
-
if the indices passed through telescope_index are not of type int.
|
|
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.
|
|
401
|
-
"""
|
|
402
|
-
if self._hist_config is None:
|
|
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
|
|
410
|
-
|
|
411
|
-
@hist_config.setter
|
|
412
|
-
def hist_config(self, input_config):
|
|
413
|
-
"""
|
|
414
|
-
Set the configuration for the histograms (e.g., bin size, min and max values, etc).
|
|
415
|
-
|
|
416
|
-
The input is allowed either through a yaml file or a dictionary. If nothing is given,
|
|
417
|
-
the dictionary is created with default values.
|
|
418
|
-
|
|
419
|
-
Parameters
|
|
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
|
|
431
|
-
|
|
432
|
-
def hist_config_to_yaml(self, file_name=None):
|
|
433
|
-
"""
|
|
434
|
-
Save the histogram configuration dictionary to a yaml file.
|
|
435
|
-
|
|
436
|
-
Parameters
|
|
437
|
-
----------
|
|
438
|
-
file_name: str
|
|
439
|
-
Name of the output file, in which to save the histogram configuration.
|
|
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.
|
|
467
|
-
|
|
468
|
-
Returns
|
|
469
|
-
-------
|
|
470
|
-
dict:
|
|
471
|
-
Dictionary with the configuration parameters to create the histograms.
|
|
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.
|
|
100
|
+
List of boost_histogram axis instances.
|
|
544
101
|
"""
|
|
545
102
|
transform = {"log": bh.axis.transform.log, "linear": None}
|
|
546
103
|
|
|
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
104
|
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"]
|
|
105
|
+
for axis in axes:
|
|
106
|
+
bins, start, stop = hist[axis][:3]
|
|
107
|
+
scale = hist[axis][3] if len(hist[axis]) > 3 else "linear"
|
|
108
|
+
if isinstance(start, u.quantity.Quantity):
|
|
109
|
+
start, stop = start.value, stop.value
|
|
564
110
|
boost_axes.append(
|
|
565
111
|
bh.axis.Regular(
|
|
566
|
-
bins=
|
|
112
|
+
bins=bins,
|
|
567
113
|
start=start,
|
|
568
114
|
stop=stop,
|
|
569
|
-
transform=transform[
|
|
115
|
+
transform=transform[scale],
|
|
570
116
|
)
|
|
571
117
|
)
|
|
572
118
|
return boost_axes
|
|
573
119
|
|
|
574
|
-
def
|
|
120
|
+
def _fill_histograms(self, photons, event_counter, telescope_positions, rotate_photons=True):
|
|
575
121
|
"""
|
|
576
|
-
|
|
122
|
+
Fill Cherenkov photon histograms.
|
|
577
123
|
|
|
578
|
-
|
|
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):
|
|
615
|
-
"""
|
|
616
|
-
Fill histograms with the information of the photons on the ground.
|
|
617
|
-
|
|
618
|
-
if the azimuth and zenith angles are provided, the Cherenkov photon's coordinates are
|
|
619
|
-
filled in the plane perpendicular to the incoming direction of the particle.
|
|
124
|
+
For rotate_photons, the Cherenkov photon's coordinates are filled in the shower plane.
|
|
620
125
|
|
|
621
126
|
Parameters
|
|
622
127
|
----------
|
|
@@ -630,1240 +135,588 @@ class CorsikaHistograms:
|
|
|
630
135
|
incoming direction and the x axis,
|
|
631
136
|
cy: direction cosine in the y direction, i.e., the cosine of the angle between the
|
|
632
137
|
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.
|
|
138
|
+
time: time of arrival of the photon in ns.
|
|
636
139
|
zem: altitude where the photon was generated in cm,
|
|
637
140
|
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
|
-
|
|
657
|
-
|
|
658
|
-
|
|
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,
|
|
141
|
+
event_counter: int
|
|
142
|
+
Event counter.
|
|
143
|
+
telescope_positions: numpy.array
|
|
144
|
+
Array with the telescope positions with shape (M, 2), where M is the number of
|
|
145
|
+
telescopes in the array. The two columns are the x and y positions of the telescopes
|
|
146
|
+
in the CORSIKA coordinate system.
|
|
147
|
+
rotate_photons: bool
|
|
148
|
+
If True, the photon's coordinates are rotated to the plane perpendicular to the
|
|
149
|
+
incoming direction of the primary particle.
|
|
150
|
+
"""
|
|
151
|
+
hist_str = "histogram"
|
|
152
|
+
photons_per_telescope = np.zeros(len(telescope_positions))
|
|
153
|
+
zenith_rad = np.deg2rad(self.events["zenith_deg"][event_counter])
|
|
154
|
+
|
|
155
|
+
for tel_idx, (photon, telescope) in enumerate(zip(photons, telescope_positions)):
|
|
156
|
+
if rotate_photons:
|
|
157
|
+
px, py = rotate(
|
|
158
|
+
photon["x"],
|
|
159
|
+
photon["y"],
|
|
160
|
+
self.events["azimuth_deg"][event_counter],
|
|
161
|
+
self.events["zenith_deg"][event_counter],
|
|
668
162
|
)
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
163
|
+
else:
|
|
164
|
+
px, py = photon["x"], photon["y"]
|
|
165
|
+
|
|
166
|
+
px = px - telescope["x"]
|
|
167
|
+
py = py - telescope["y"]
|
|
168
|
+
w = photon["photons"]
|
|
169
|
+
|
|
170
|
+
pxm = px * u.cm.to(u.m)
|
|
171
|
+
pym = py * u.cm.to(u.m)
|
|
172
|
+
zem = (photon["zem"] * u.cm).to(u.km)
|
|
173
|
+
photons_per_telescope[tel_idx] += np.sum(w)
|
|
174
|
+
|
|
175
|
+
self.hist["counts_xy"][hist_str].fill(pxm, pym, weight=w)
|
|
176
|
+
self.hist["direction_xy"][hist_str].fill(photon["cx"], photon["cy"], weight=w)
|
|
177
|
+
self.hist["time_altitude"][hist_str].fill(photon["time"] * u.ns, zem, weight=w)
|
|
178
|
+
self.hist["wavelength_altitude"][hist_str].fill(
|
|
179
|
+
np.abs(photon["wavelength"]) * u.nm, zem, weight=w
|
|
680
180
|
)
|
|
681
181
|
|
|
682
|
-
|
|
683
|
-
self.
|
|
684
|
-
|
|
182
|
+
r = np.hypot(px, py) * u.cm.to(u.m)
|
|
183
|
+
self.hist["counts_r"][hist_str].fill(r, weight=w)
|
|
184
|
+
|
|
185
|
+
self.events["num_photons"][event_counter] += np.sum(w)
|
|
186
|
+
|
|
187
|
+
for tel_idx, telescope in enumerate(telescope_positions):
|
|
188
|
+
area = np.pi * (telescope["r"] ** 2) / np.cos(zenith_rad) / 1.0e4 # in m^2
|
|
189
|
+
n_photons = photons_per_telescope[tel_idx]
|
|
190
|
+
density = n_photons / area if area > 0 else 0.0
|
|
191
|
+
density_error = np.sqrt(n_photons) / area if area > 0 else 0.0
|
|
192
|
+
self._density_samples.append(
|
|
193
|
+
{
|
|
194
|
+
"x": telescope["x"] * u.cm.to(u.m),
|
|
195
|
+
"y": telescope["y"] * u.cm.to(u.m),
|
|
196
|
+
"density": density,
|
|
197
|
+
"density_error": density_error,
|
|
198
|
+
}
|
|
685
199
|
)
|
|
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
200
|
|
|
748
|
-
|
|
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
|
-
|
|
769
|
-
def _raise_if_no_histogram(self):
|
|
770
|
-
"""
|
|
771
|
-
Raise an error if the histograms were not created.
|
|
772
|
-
|
|
773
|
-
Raises
|
|
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
|
|
786
|
-
|
|
787
|
-
def _get_hist_2d_projection(self, label):
|
|
201
|
+
def get_hist_2d_projection(self, hist):
|
|
788
202
|
"""
|
|
789
203
|
Get 2D distributions.
|
|
790
204
|
|
|
791
205
|
Parameters
|
|
792
206
|
----------
|
|
793
|
-
|
|
794
|
-
|
|
207
|
+
hist: boost_histogram.Histogram
|
|
208
|
+
Histogram.
|
|
795
209
|
|
|
796
210
|
Returns
|
|
797
211
|
-------
|
|
798
212
|
numpy.ndarray
|
|
799
|
-
|
|
213
|
+
Histogram counts.
|
|
800
214
|
numpy.array
|
|
801
|
-
|
|
215
|
+
Histogram x bin edges.
|
|
802
216
|
numpy.array
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
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):
|
|
843
|
-
"""
|
|
844
|
-
Get 2D histograms of position of the Cherenkov photons on the ground.
|
|
845
|
-
|
|
846
|
-
Returns
|
|
847
|
-
-------
|
|
848
|
-
numpy.ndarray
|
|
849
|
-
The counts of the histogram.
|
|
850
|
-
numpy.array
|
|
851
|
-
The x bin edges of the count histograms in x, usually in meters.
|
|
852
|
-
numpy.array
|
|
853
|
-
The y bin edges of the count histograms in y, usually in meters.
|
|
854
|
-
"""
|
|
855
|
-
return self._get_hist_2d_projection("counts")
|
|
856
|
-
|
|
857
|
-
def get_2d_photon_density_distr(self):
|
|
858
|
-
"""
|
|
859
|
-
Get 2D histograms of position of the Cherenkov photons on the ground.
|
|
860
|
-
|
|
861
|
-
It returns the photon density per square meter.
|
|
862
|
-
|
|
863
|
-
Returns
|
|
864
|
-
-------
|
|
865
|
-
numpy.ndarray
|
|
866
|
-
The values of the histogram, usually in $m^{-2}$
|
|
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.
|
|
871
|
-
"""
|
|
872
|
-
return self._get_hist_2d_projection("density")
|
|
873
|
-
|
|
874
|
-
def get_2d_photon_direction_distr(self):
|
|
875
|
-
"""
|
|
876
|
-
Get 2D histograms of incoming direction of the Cherenkov photons on the ground.
|
|
877
|
-
|
|
878
|
-
Returns
|
|
879
|
-
-------
|
|
880
|
-
numpy.ndarray
|
|
881
|
-
The counts of the histogram.
|
|
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)
|
|
886
|
-
"""
|
|
887
|
-
return self._get_hist_2d_projection("direction")
|
|
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
|
|
217
|
+
Histogram y bin edges.
|
|
218
|
+
numpy.ndarray or None
|
|
219
|
+
Histogram uncertainties (sqrt of variance) if available.
|
|
220
|
+
"""
|
|
221
|
+
view = hist.view()
|
|
222
|
+
if self._check_for_all_attributes(view):
|
|
223
|
+
counts = np.asarray([view["value"].T])
|
|
224
|
+
uncertainties = np.asarray([np.sqrt(view["variance"].T)])
|
|
225
|
+
else:
|
|
226
|
+
counts = np.asarray([view.T])
|
|
227
|
+
uncertainties = None
|
|
228
|
+
|
|
229
|
+
x_edges = np.asarray([hist.axes.edges[0].flatten()])
|
|
230
|
+
y_edges = np.asarray([hist.axes.edges[1].flatten()])
|
|
231
|
+
|
|
232
|
+
return counts, x_edges, y_edges, uncertainties
|
|
233
|
+
|
|
234
|
+
def _get_hist_1d_from_numpy(self, label, hist):
|
|
235
|
+
"""Get 1D histogram from numpy histogram."""
|
|
236
|
+
bins = hist["x_bins"][0]
|
|
237
|
+
start = hist["x_bins"][1] if hist["x_bins"][1] else np.min(self.events[label])
|
|
238
|
+
stop = hist["x_bins"][2] if hist["x_bins"][2] is not None else np.max(self.events[label])
|
|
239
|
+
scale = hist["x_bins"][3] if len(hist["x_bins"]) > 3 else "linear"
|
|
240
|
+
if scale == "log":
|
|
241
|
+
bin_edges = np.logspace(np.log10(start), np.log10(stop), bins + 1)
|
|
242
|
+
else:
|
|
243
|
+
bin_edges = np.linspace(start, stop, bins + 1)
|
|
244
|
+
histo_1d, _ = np.histogram(self.events[label], bins=bin_edges)
|
|
245
|
+
uncertainties = np.sqrt(histo_1d)
|
|
246
|
+
return (
|
|
247
|
+
histo_1d.reshape(1, bins),
|
|
248
|
+
bin_edges.reshape(1, bins + 1),
|
|
249
|
+
uncertainties.reshape(1, bins),
|
|
925
250
|
)
|
|
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
251
|
|
|
930
|
-
def
|
|
252
|
+
def get_hist_1d_projection(self, label, hist):
|
|
931
253
|
"""
|
|
932
|
-
Get 1D distributions.
|
|
254
|
+
Get 1D distributions from numpy or boost histograms (1D and 2D).
|
|
933
255
|
|
|
934
256
|
Parameters
|
|
935
257
|
----------
|
|
936
258
|
label: str
|
|
937
|
-
|
|
259
|
+
Histogram label.
|
|
260
|
+
hist: dict
|
|
261
|
+
Histogram dictionary.
|
|
938
262
|
|
|
939
263
|
Returns
|
|
940
264
|
-------
|
|
941
265
|
numpy.ndarray
|
|
942
|
-
|
|
266
|
+
Histogram counts.
|
|
943
267
|
numpy.array
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
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
|
|
268
|
+
Histogram x bin edges.
|
|
269
|
+
numpy.ndarray or None
|
|
270
|
+
Histogram uncertainties (if available).
|
|
271
|
+
"""
|
|
272
|
+
# plain numpy histogram
|
|
273
|
+
if (
|
|
274
|
+
hist.get("projection") is None
|
|
275
|
+
and hasattr(self, "events")
|
|
276
|
+
and label in self.events.dtype.names
|
|
277
|
+
):
|
|
278
|
+
return self._get_hist_1d_from_numpy(label, hist)
|
|
279
|
+
|
|
280
|
+
# boost 1D histogram
|
|
281
|
+
if hist.get("projection") is None:
|
|
282
|
+
# Use histogram from hist dict if available, otherwise from self.hist
|
|
283
|
+
if "histogram" in hist:
|
|
284
|
+
histo_1d = hist["histogram"]
|
|
285
|
+
else:
|
|
286
|
+
# No histogram available, return None values
|
|
287
|
+
return None, None, None
|
|
288
|
+
edges = histo_1d.axes.edges.T.flatten()[0]
|
|
289
|
+
view = histo_1d.view()
|
|
290
|
+
if self._check_for_all_attributes(view):
|
|
291
|
+
counts = np.asarray([view["value"].T])
|
|
292
|
+
uncertainties = np.asarray([np.sqrt(view["variance"].T)])
|
|
293
|
+
else:
|
|
294
|
+
counts = np.asarray([view.T])
|
|
295
|
+
uncertainties = None
|
|
296
|
+
return counts, np.asarray([edges]), uncertainties
|
|
297
|
+
|
|
298
|
+
# boost 2D histogram projection
|
|
299
|
+
histo_2d = self.hist[hist["projection"][0]]["histogram"]
|
|
300
|
+
if hist["projection"][1] == "x":
|
|
301
|
+
h = histo_2d[:, sum]
|
|
302
|
+
else:
|
|
303
|
+
h = histo_2d[sum, :]
|
|
304
|
+
edges = h.axes.edges.T.flatten()[0]
|
|
305
|
+
view = h.view()
|
|
306
|
+
if self._check_for_all_attributes(view):
|
|
307
|
+
counts = np.asarray([view["value"].T])
|
|
308
|
+
uncertainties = np.asarray([np.sqrt(view["variance"].T)])
|
|
309
|
+
else:
|
|
310
|
+
counts = np.asarray([view.T])
|
|
311
|
+
uncertainties = None
|
|
312
|
+
return counts, np.asarray([edges]), uncertainties
|
|
1226
313
|
|
|
1227
|
-
|
|
1228
|
-
def dict_1d_distributions(self):
|
|
314
|
+
def _set_1d_distributions(self, r_max=2000 * u.m, bins=100):
|
|
1229
315
|
"""
|
|
1230
|
-
|
|
316
|
+
Define 1D histograms.
|
|
1231
317
|
|
|
1232
318
|
Returns
|
|
1233
319
|
-------
|
|
1234
320
|
dict:
|
|
1235
|
-
|
|
321
|
+
Dictionary with 1D histogram information.
|
|
1236
322
|
"""
|
|
1237
|
-
|
|
1238
|
-
file_name = "file name"
|
|
323
|
+
file_name = "file_name"
|
|
1239
324
|
title = "title"
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
325
|
+
projection = "projection"
|
|
326
|
+
x_bins = "x_bins"
|
|
327
|
+
x_axis_unit = "x_axis_unit"
|
|
328
|
+
x_axis_title = "x_axis_title"
|
|
329
|
+
y_axis_unit = "y_axis_unit"
|
|
330
|
+
y_axis_title = "y_axis_title"
|
|
331
|
+
log_y = "log_y"
|
|
332
|
+
photon_density = "Photon density"
|
|
333
|
+
distance_to_center = "Distance to center"
|
|
334
|
+
hist_1d = {
|
|
1243
335
|
"wavelength": {
|
|
1244
|
-
fn_key: "get_photon_wavelength_distr",
|
|
1245
336
|
file_name: "hist_1d_photon_wavelength_distr",
|
|
1246
337
|
title: "Photon wavelength distribution",
|
|
1247
|
-
|
|
1248
|
-
axis_unit: self.hist_config["hist_position"][Z_AXIS_STRING]["start"].unit,
|
|
338
|
+
projection: ["wavelength_altitude", "x"],
|
|
1249
339
|
},
|
|
1250
|
-
"
|
|
1251
|
-
fn_key: "get_photon_radial_distr",
|
|
340
|
+
"counts_r": {
|
|
1252
341
|
file_name: "hist_1d_photon_radial_distr",
|
|
1253
|
-
title: "
|
|
1254
|
-
|
|
1255
|
-
|
|
342
|
+
title: "Photon lateral distribution (ground level)",
|
|
343
|
+
x_bins: [bins, 0 * u.m, r_max, "linear"],
|
|
344
|
+
x_axis_title: distance_to_center,
|
|
345
|
+
x_axis_unit: u.m,
|
|
1256
346
|
},
|
|
1257
|
-
"
|
|
1258
|
-
fn_key: "get_photon_density_distr",
|
|
347
|
+
"density_r": {
|
|
1259
348
|
file_name: "hist_1d_photon_density_distr",
|
|
1260
|
-
title: "Photon density distribution
|
|
1261
|
-
|
|
1262
|
-
|
|
349
|
+
title: "Photon lateral density distribution (ground level)",
|
|
350
|
+
x_bins: [bins, 0 * u.m, r_max, "linear"],
|
|
351
|
+
x_axis_title: distance_to_center,
|
|
352
|
+
x_axis_unit: u.m,
|
|
353
|
+
y_axis_title: photon_density,
|
|
354
|
+
y_axis_unit: u.m**-2,
|
|
355
|
+
},
|
|
356
|
+
"density_r_from_counts": {
|
|
357
|
+
file_name: "hist_1d_photon_density_from_counts_distr",
|
|
358
|
+
title: "Photon lateral density from counts distribution (ground level)",
|
|
359
|
+
x_bins: [bins, 0 * u.m, r_max, "linear"],
|
|
360
|
+
x_axis_title: distance_to_center,
|
|
361
|
+
x_axis_unit: u.m,
|
|
362
|
+
y_axis_title: photon_density,
|
|
363
|
+
y_axis_unit: u.m**-2,
|
|
364
|
+
},
|
|
365
|
+
"density_x": {
|
|
366
|
+
file_name: "hist_1d_photon_density_x_distr",
|
|
367
|
+
title: "Photon lateral density x distribution (ground level)",
|
|
368
|
+
projection: ["counts_xy", "x"], # projection requires counts_xy histogram
|
|
369
|
+
x_axis_title: distance_to_center,
|
|
370
|
+
x_axis_unit: u.m,
|
|
371
|
+
y_axis_title: photon_density,
|
|
372
|
+
y_axis_unit: u.m**-2,
|
|
373
|
+
},
|
|
374
|
+
"density_y": {
|
|
375
|
+
file_name: "hist_1d_photon_density_y_distr",
|
|
376
|
+
title: "Photon lateral density y distribution (ground level)",
|
|
377
|
+
projection: ["counts_xy", "y"], # projection requires counts_xy histogram
|
|
378
|
+
y_axis_title: photon_density,
|
|
379
|
+
y_axis_unit: u.m**-2,
|
|
1263
380
|
},
|
|
1264
381
|
"time": {
|
|
1265
|
-
fn_key: "get_photon_time_of_emission_distr",
|
|
1266
382
|
file_name: "hist_1d_photon_time_distr",
|
|
1267
|
-
title: "Photon time
|
|
1268
|
-
|
|
1269
|
-
axis_unit: self.hist_config["hist_time_altitude"][X_AXIS_STRING]["start"].unit,
|
|
383
|
+
title: "Photon arrival time distribution",
|
|
384
|
+
projection: ["time_altitude", "x"],
|
|
1270
385
|
},
|
|
1271
386
|
"altitude": {
|
|
1272
|
-
fn_key: "get_photon_altitude_distr",
|
|
1273
387
|
file_name: "hist_1d_photon_altitude_distr",
|
|
1274
|
-
title: "Photon altitude
|
|
1275
|
-
|
|
1276
|
-
|
|
388
|
+
title: "Photon emission altitude distribution",
|
|
389
|
+
projection: ["time_altitude", "y"],
|
|
390
|
+
},
|
|
391
|
+
"direction_cosine_x": {
|
|
392
|
+
file_name: "hist_1d_photon_direction_cosine_x_distr",
|
|
393
|
+
title: "Photon direction cosine x distribution",
|
|
394
|
+
projection: ["direction_xy", "x"],
|
|
395
|
+
},
|
|
396
|
+
"direction_cosine_y": {
|
|
397
|
+
file_name: "hist_1d_photon_direction_cosine_y_distr",
|
|
398
|
+
title: "Photon direction cosine y distribution",
|
|
399
|
+
projection: ["direction_xy", "y"],
|
|
1277
400
|
},
|
|
1278
|
-
"
|
|
1279
|
-
fn_key: "get_num_photons_per_event_distr",
|
|
401
|
+
"num_photons": {
|
|
1280
402
|
file_name: "hist_1d_photon_per_event_distr",
|
|
1281
403
|
title: "Photons per event distribution",
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
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,
|
|
404
|
+
"event_type": True,
|
|
405
|
+
x_bins: [100, 0, None, "log"],
|
|
406
|
+
x_axis_title: "Cherenkov photons per event",
|
|
407
|
+
x_axis_unit: u.dimensionless_unscaled,
|
|
408
|
+
log_y: False,
|
|
1291
409
|
},
|
|
1292
410
|
}
|
|
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
411
|
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
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
|
|
412
|
+
for value in hist_1d.values():
|
|
413
|
+
value["is_1d"] = True
|
|
414
|
+
value["log_y"] = value.get("log_y", True)
|
|
415
|
+
value[y_axis_title] = (
|
|
416
|
+
"Counts" if value.get(y_axis_title) is None else value[y_axis_title]
|
|
417
|
+
)
|
|
418
|
+
value[y_axis_unit] = (
|
|
419
|
+
u.dimensionless_unscaled if value.get(y_axis_unit) is None else value[y_axis_unit]
|
|
420
|
+
)
|
|
421
|
+
if value.get("projection") is not None:
|
|
422
|
+
hist_2d_name = value["projection"][0]
|
|
423
|
+
if value["projection"][1] == "x":
|
|
424
|
+
value[x_bins] = self.hist[hist_2d_name]["x_bins"]
|
|
425
|
+
value[x_axis_title] = self.hist[hist_2d_name]["x_axis_title"]
|
|
426
|
+
value[x_axis_unit] = self.hist[hist_2d_name]["x_axis_unit"]
|
|
1338
427
|
else:
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
428
|
+
value[x_bins] = self.hist[hist_2d_name]["y_bins"]
|
|
429
|
+
value[x_axis_title] = self.hist[hist_2d_name]["y_axis_title"]
|
|
430
|
+
value[x_axis_unit] = self.hist[hist_2d_name]["y_axis_unit"]
|
|
431
|
+
elif value.get("event_type", False) is False:
|
|
432
|
+
boost_axes = self._create_regular_axes(value, ["x_bins"])
|
|
433
|
+
value["histogram"] = bh.Histogram(boost_axes[0], storage=bh.storage.Weight())
|
|
434
|
+
return hist_1d
|
|
1343
435
|
|
|
1344
|
-
|
|
1345
|
-
def dict_2d_distributions(self):
|
|
436
|
+
def _set_2d_distributions(self, xy_maximum=1000 * u.m, xy_bin=100):
|
|
1346
437
|
"""
|
|
1347
|
-
|
|
438
|
+
Define 2D histograms.
|
|
1348
439
|
|
|
1349
440
|
Returns
|
|
1350
441
|
-------
|
|
1351
442
|
dict:
|
|
1352
|
-
|
|
443
|
+
Dictionary with 2D histogram information.
|
|
1353
444
|
"""
|
|
1354
|
-
|
|
1355
|
-
file_name = "file name"
|
|
445
|
+
file_name = "file_name"
|
|
1356
446
|
title = "title"
|
|
1357
|
-
|
|
1358
|
-
x_axis_unit = "
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
"
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
"
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
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
|
|
447
|
+
x_bins, y_bins = "x_bins", "y_bins"
|
|
448
|
+
x_axis_title, x_axis_unit = "x_axis_title", "x_axis_unit"
|
|
449
|
+
y_axis_title, y_axis_unit = "y_axis_title", "y_axis_unit"
|
|
450
|
+
z_axis_title, z_axis_unit = "z_axis_title", "z_axis_unit"
|
|
451
|
+
photon_density = "Photon density"
|
|
452
|
+
x_pos = "x position on the ground"
|
|
453
|
+
y_pos = "y position on the ground"
|
|
454
|
+
|
|
455
|
+
hist_2d = {
|
|
456
|
+
"counts_xy": {
|
|
457
|
+
file_name: "hist_2d_photon_count_distr",
|
|
458
|
+
title: "Photon count distribution (ground level)",
|
|
459
|
+
x_bins: [xy_bin, -xy_maximum, xy_maximum, "linear"],
|
|
460
|
+
y_bins: [xy_bin, -xy_maximum, xy_maximum],
|
|
461
|
+
x_axis_title: x_pos,
|
|
462
|
+
x_axis_unit: xy_maximum.unit,
|
|
463
|
+
y_axis_title: y_pos,
|
|
464
|
+
y_axis_unit: xy_maximum.unit,
|
|
465
|
+
},
|
|
466
|
+
"density_xy": {
|
|
467
|
+
file_name: "hist_2d_photon_density_distr",
|
|
468
|
+
title: "Photon density distribution (ground level)",
|
|
469
|
+
x_bins: [xy_bin, -xy_maximum, xy_maximum, "linear"],
|
|
470
|
+
y_bins: [xy_bin, -xy_maximum, xy_maximum, "linear"],
|
|
471
|
+
x_axis_title: x_pos,
|
|
472
|
+
x_axis_unit: xy_maximum.unit,
|
|
473
|
+
y_axis_title: y_pos,
|
|
474
|
+
y_axis_unit: xy_maximum.unit,
|
|
475
|
+
z_axis_title: photon_density,
|
|
476
|
+
z_axis_unit: u.m**-2,
|
|
477
|
+
},
|
|
478
|
+
"density_xy_from_counts": {
|
|
479
|
+
file_name: "hist_2d_photon_density_from_counts_distr",
|
|
480
|
+
title: "Photon density from counts distribution (ground level)",
|
|
481
|
+
x_bins: [xy_bin, -xy_maximum, xy_maximum, "linear"],
|
|
482
|
+
y_bins: [xy_bin, -xy_maximum, xy_maximum, "linear"],
|
|
483
|
+
x_axis_title: x_pos,
|
|
484
|
+
x_axis_unit: xy_maximum.unit,
|
|
485
|
+
y_axis_title: y_pos,
|
|
486
|
+
y_axis_unit: xy_maximum.unit,
|
|
487
|
+
z_axis_title: photon_density,
|
|
488
|
+
z_axis_unit: u.m**-2,
|
|
489
|
+
},
|
|
490
|
+
"direction_xy": {
|
|
491
|
+
file_name: "hist_2d_photon_direction_distr",
|
|
492
|
+
title: "Photon arrival direction",
|
|
493
|
+
x_bins: [100, -1, 1, "linear"],
|
|
494
|
+
y_bins: [100, -1, 1, "linear"],
|
|
495
|
+
x_axis_title: "x direction cosine",
|
|
496
|
+
x_axis_unit: u.dimensionless_unscaled,
|
|
497
|
+
y_axis_title: "y direction cosine",
|
|
498
|
+
y_axis_unit: u.dimensionless_unscaled,
|
|
499
|
+
},
|
|
500
|
+
"time_altitude": {
|
|
501
|
+
file_name: "hist_2d_photon_time_altitude_distr",
|
|
502
|
+
title: "Arrival time vs emission altitude",
|
|
503
|
+
x_bins: [100, -2000 * u.ns, 2000 * u.ns, "linear"],
|
|
504
|
+
y_bins: [100, 120 * u.km, 0 * u.km, "linear"],
|
|
505
|
+
x_axis_title: "Arrival time",
|
|
506
|
+
x_axis_unit: u.ns,
|
|
507
|
+
y_axis_title: "Emission altitude",
|
|
508
|
+
y_axis_unit: u.km,
|
|
509
|
+
},
|
|
510
|
+
"wavelength_altitude": {
|
|
511
|
+
file_name: "hist_2d_photon_wavelength_altitude_distr",
|
|
512
|
+
title: "Wavelength vs emission altitude ",
|
|
513
|
+
x_bins: [100, 100 * u.nm, 1000 * u.nm, "linear"],
|
|
514
|
+
y_bins: [100, 120 * u.km, 0 * u.km, "linear"],
|
|
515
|
+
x_axis_title: "Wavelength",
|
|
516
|
+
x_axis_unit: u.nm,
|
|
517
|
+
y_axis_title: "Emission altitude",
|
|
518
|
+
y_axis_unit: u.km,
|
|
519
|
+
},
|
|
520
|
+
}
|
|
1439
521
|
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
522
|
+
for value in hist_2d.values():
|
|
523
|
+
value["is_1d"] = False
|
|
524
|
+
value["log_z"] = True
|
|
525
|
+
value[z_axis_title] = (
|
|
526
|
+
"Counts" if value.get(z_axis_title) is None else value[z_axis_title]
|
|
527
|
+
)
|
|
528
|
+
value[z_axis_unit] = (
|
|
529
|
+
u.dimensionless_unscaled if value.get(z_axis_unit) is None else value[z_axis_unit]
|
|
530
|
+
)
|
|
531
|
+
boost_axes = self._create_regular_axes(value, ["x_bins", "y_bins"])
|
|
532
|
+
value["histogram"] = bh.Histogram(
|
|
533
|
+
boost_axes[0], boost_axes[1], storage=bh.storage.Weight()
|
|
1444
534
|
)
|
|
1445
535
|
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
536
|
+
return hist_2d
|
|
537
|
+
|
|
538
|
+
def _update_distributions(self):
|
|
539
|
+
"""Update the distributions dictionary with the histogram values and bin edges."""
|
|
540
|
+
self._populate_density_from_probes()
|
|
541
|
+
self._populate_density_from_counts()
|
|
542
|
+
self._filter_density_histograms()
|
|
543
|
+
|
|
544
|
+
for key, value in self.hist.items():
|
|
545
|
+
value["input_file_name"] = str(self.input_file)
|
|
546
|
+
if "hist_values" not in value:
|
|
547
|
+
if value["is_1d"]:
|
|
548
|
+
(
|
|
549
|
+
value["hist_values"],
|
|
550
|
+
value["x_bin_edges"],
|
|
551
|
+
value["uncertainties"],
|
|
552
|
+
) = self.get_hist_1d_projection(key, value)
|
|
1452
553
|
else:
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
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
|
|
554
|
+
(
|
|
555
|
+
value["hist_values"],
|
|
556
|
+
value["x_bin_edges"],
|
|
557
|
+
value["y_bin_edges"],
|
|
558
|
+
value["uncertainties"],
|
|
559
|
+
) = self.get_hist_2d_projection(value["histogram"])
|
|
560
|
+
|
|
561
|
+
def _filter_density_histograms(self):
|
|
562
|
+
"""Filter density histograms based on the normalization method."""
|
|
563
|
+
if self.normalization_method == "per-telescope":
|
|
564
|
+
keys_to_remove = ["density_xy_from_counts", "density_r_from_counts"]
|
|
565
|
+
elif self.normalization_method == "per-bin":
|
|
566
|
+
keys_to_remove = ["density_xy", "density_x", "density_y", "density_r"]
|
|
1512
567
|
else:
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
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.
|
|
568
|
+
raise ValueError(
|
|
569
|
+
f"Unknown normalization_method: {self.normalization_method}. "
|
|
570
|
+
"Must be 'per-telescope' or 'per-bin'."
|
|
571
|
+
)
|
|
1544
572
|
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
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
|
-
)
|
|
573
|
+
for key in keys_to_remove:
|
|
574
|
+
if key in self.hist:
|
|
575
|
+
del self.hist[key]
|
|
1560
576
|
|
|
1561
|
-
|
|
577
|
+
def _fill_projected_density_values(self, value):
|
|
578
|
+
"""Extract 1D density by using projection Counts and normalizing by area."""
|
|
579
|
+
histo_2d = value["projection"][0]
|
|
580
|
+
source_h = self.hist[histo_2d]["histogram"]
|
|
581
|
+
project_axis = value["projection"][1]
|
|
1562
582
|
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
# overwrite takes precedence over append
|
|
1567
|
-
if overwrite is True:
|
|
1568
|
-
append = False
|
|
583
|
+
if project_axis == "x":
|
|
584
|
+
h_1d = source_h[:, sum]
|
|
585
|
+
total_ortho_width = source_h.axes[1].edges[-1] - source_h.axes[1].edges[0]
|
|
1569
586
|
else:
|
|
1570
|
-
|
|
1571
|
-
|
|
587
|
+
h_1d = source_h[sum, :]
|
|
588
|
+
total_ortho_width = source_h.axes[0].edges[-1] - source_h.axes[0].edges[0]
|
|
1572
589
|
|
|
1573
|
-
|
|
1574
|
-
def num_photons_per_telescope(self):
|
|
1575
|
-
"""
|
|
1576
|
-
The number of photons per event, considering the telescopes given by self.telescope_indices.
|
|
590
|
+
areas_1d = h_1d.axes[0].widths * total_ortho_width
|
|
1577
591
|
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
np.
|
|
592
|
+
view = h_1d.view()
|
|
593
|
+
if self._check_for_all_attributes(view):
|
|
594
|
+
vals = view["value"] / areas_1d
|
|
595
|
+
uncs = np.sqrt(view["variance"]) / areas_1d
|
|
596
|
+
else:
|
|
597
|
+
vals = view / areas_1d
|
|
598
|
+
uncs = np.sqrt(vals) # Fallback if no weights
|
|
599
|
+
|
|
600
|
+
value["hist_values"] = np.asarray([vals.T])
|
|
601
|
+
value["x_bin_edges"] = np.asarray([h_1d.axes.edges[0]])
|
|
602
|
+
value["uncertainties"] = np.asarray([uncs.T])
|
|
603
|
+
|
|
604
|
+
def _populate_density_from_probes(self):
|
|
605
|
+
"""Build density distributions from per-telescope sampling."""
|
|
606
|
+
if not self._density_samples:
|
|
607
|
+
return
|
|
608
|
+
|
|
609
|
+
s = self._density_samples
|
|
610
|
+
xs, ys = (np.array([p[k] for p in s]) for k in ("x", "y"))
|
|
611
|
+
dens = np.array([p["density"] for p in s])
|
|
612
|
+
errs = np.array([p["density_error"] for p in s])
|
|
613
|
+
|
|
614
|
+
hxy = self.hist["counts_xy"]["histogram"]
|
|
615
|
+
x_edges, y_edges = hxy.axes[0].edges, hxy.axes[1].edges
|
|
616
|
+
r_edges = self.hist["density_r"]["histogram"].axes[0].edges
|
|
617
|
+
|
|
618
|
+
def avg_unc_nd(coords, edges, values, errors):
|
|
619
|
+
num = np.histogramdd(coords, bins=edges, weights=values)[0]
|
|
620
|
+
den = np.histogramdd(coords, bins=edges)[0]
|
|
621
|
+
var = np.histogramdd(coords, bins=edges, weights=errors**2)[0]
|
|
622
|
+
with np.errstate(divide="ignore", invalid="ignore"):
|
|
623
|
+
avg = np.divide(num, den, out=np.zeros_like(num), where=den > 0)
|
|
624
|
+
unc = np.sqrt(np.divide(var, den**2, out=np.zeros_like(var), where=den > 0))
|
|
625
|
+
return avg, unc
|
|
626
|
+
|
|
627
|
+
# 2D
|
|
628
|
+
avg_xy, unc_xy = avg_unc_nd(np.column_stack((xs, ys)), (x_edges, y_edges), dens, errs)
|
|
629
|
+
|
|
630
|
+
self.hist["density_xy"].update(
|
|
631
|
+
{
|
|
632
|
+
"hist_values": np.asarray([avg_xy.T]),
|
|
633
|
+
"x_bin_edges": np.asarray([x_edges]),
|
|
634
|
+
"y_bin_edges": np.asarray([y_edges]),
|
|
635
|
+
"uncertainties": np.asarray([unc_xy.T]),
|
|
636
|
+
}
|
|
1585
637
|
)
|
|
1586
|
-
return self._num_photons_per_telescope
|
|
1587
638
|
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
The total number of photons.
|
|
639
|
+
# 1D helpers
|
|
640
|
+
def avg_unc_1d(x, e, v, err):
|
|
641
|
+
return avg_unc_nd(x[:, None], (e,), v, err)
|
|
1592
642
|
|
|
1593
|
-
|
|
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.
|
|
643
|
+
ax = self.axis_distance
|
|
1607
644
|
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
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,
|
|
1647
|
-
)
|
|
1648
|
-
return self._event_zenith_angles
|
|
1649
|
-
|
|
1650
|
-
@property
|
|
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,
|
|
1681
|
-
)
|
|
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.
|
|
645
|
+
avg_x, unc_x = (
|
|
646
|
+
avg_unc_1d(xs[np.abs(ys) < ax], x_edges, dens[np.abs(ys) < ax], errs[np.abs(ys) < ax])
|
|
647
|
+
if np.any(np.abs(ys) < ax)
|
|
648
|
+
else (np.zeros(len(x_edges) - 1),) * 2
|
|
649
|
+
)
|
|
1688
650
|
|
|
1689
|
-
|
|
1690
|
-
|
|
651
|
+
avg_y, unc_y = (
|
|
652
|
+
avg_unc_1d(ys[np.abs(xs) < ax], y_edges, dens[np.abs(xs) < ax], errs[np.abs(xs) < ax])
|
|
653
|
+
if np.any(np.abs(xs) < ax)
|
|
654
|
+
else (np.zeros(len(y_edges) - 1),) * 2
|
|
655
|
+
)
|
|
1691
656
|
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
657
|
+
# Radial density
|
|
658
|
+
rs = np.hypot(xs, ys)
|
|
659
|
+
avg_r, unc_r = avg_unc_1d(rs, r_edges, dens, errs)
|
|
660
|
+
|
|
661
|
+
for k, avg, unc, edges in (
|
|
662
|
+
("density_x", avg_x, unc_x, x_edges),
|
|
663
|
+
("density_y", avg_y, unc_y, y_edges),
|
|
664
|
+
("density_r", avg_r, unc_r, r_edges),
|
|
665
|
+
):
|
|
666
|
+
self.hist[k].update(
|
|
667
|
+
{
|
|
668
|
+
"hist_values": np.asarray([avg]),
|
|
669
|
+
"x_bin_edges": np.asarray([edges]),
|
|
670
|
+
"uncertainties": np.asarray([unc]),
|
|
671
|
+
}
|
|
1701
672
|
)
|
|
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.
|
|
1708
|
-
|
|
1709
|
-
A tuple with Earth's magnetic field in the x and z directions are returned.
|
|
1710
673
|
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
The parameter is passed through the key word parameter.
|
|
1729
|
-
Available options are to be found under self.all_event_keys.
|
|
1730
|
-
The unit of the parameter, if any, is given according to the CORSIKA version
|
|
1731
|
-
(please see user guide in this case).
|
|
1732
|
-
|
|
1733
|
-
Parameters
|
|
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,
|
|
674
|
+
def _populate_density_from_counts(self):
|
|
675
|
+
"""Build density distributions by dividing counts histograms by bin area."""
|
|
676
|
+
# --- 2D ---
|
|
677
|
+
hxy = self.hist["counts_xy"]["histogram"]
|
|
678
|
+
xw = np.diff(hxy.axes[0].edges)
|
|
679
|
+
yw = np.diff(hxy.axes[1].edges)
|
|
680
|
+
areas2d = np.outer(xw, yw)
|
|
681
|
+
|
|
682
|
+
dens_xy, unc_xy = self._density_and_unc(hxy.view(), areas2d)
|
|
683
|
+
|
|
684
|
+
self.hist["density_xy_from_counts"].update(
|
|
685
|
+
{
|
|
686
|
+
"hist_values": np.asarray([dens_xy.T]),
|
|
687
|
+
"x_bin_edges": np.asarray([hxy.axes[0].edges]),
|
|
688
|
+
"y_bin_edges": np.asarray([hxy.axes[1].edges]),
|
|
689
|
+
"uncertainties": np.asarray([unc_xy.T]),
|
|
690
|
+
}
|
|
1818
691
|
)
|
|
1819
|
-
return hist, bin_edges
|
|
1820
|
-
|
|
1821
|
-
def event_2d_histogram(self, key_1, key_2, bins=50, hist_range=None):
|
|
1822
|
-
"""
|
|
1823
|
-
Create a 2D histogram for the all events using key_1 and key_2 as parameters.
|
|
1824
692
|
|
|
1825
|
-
|
|
693
|
+
# --- 1D ---
|
|
694
|
+
hr = self.hist["counts_r"]["histogram"]
|
|
695
|
+
r = hr.axes[0].edges
|
|
696
|
+
areas1d = np.pi * (r[1:] ** 2 - r[:-1] ** 2)
|
|
1826
697
|
|
|
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.
|
|
698
|
+
dens_r, unc_r = self._density_and_unc(hr.view(), areas1d)
|
|
1839
699
|
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
y Edges of the histogram.
|
|
700
|
+
self.hist["density_r_from_counts"].update(
|
|
701
|
+
{
|
|
702
|
+
"hist_values": np.asarray([dens_r]),
|
|
703
|
+
"x_bin_edges": np.asarray([r]),
|
|
704
|
+
"uncertainties": np.asarray([unc_r]),
|
|
705
|
+
}
|
|
706
|
+
)
|
|
1848
707
|
|
|
708
|
+
def _density_and_unc(self, view, areas):
|
|
709
|
+
"""Calculate density and uncertainty by dividing histogram values by areas."""
|
|
710
|
+
if self._check_for_all_attributes(view):
|
|
711
|
+
values = view["value"]
|
|
712
|
+
unc = np.sqrt(view["variance"])
|
|
713
|
+
else:
|
|
714
|
+
values = view
|
|
715
|
+
unc = np.sqrt(view)
|
|
716
|
+
return values / areas, unc / areas
|
|
1849
717
|
|
|
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
|
|
718
|
+
def _check_for_all_attributes(self, view):
|
|
719
|
+
"""Check if view has dtype fields ('value', 'variance')."""
|
|
720
|
+
if hasattr(view, "dtype") and view.dtype.names == ("value", "variance"):
|
|
721
|
+
return True
|
|
722
|
+
return False
|