gammasimtools 0.9.0__py3-none-any.whl → 0.11.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.9.0.dist-info → gammasimtools-0.11.0.dist-info}/METADATA +4 -2
- {gammasimtools-0.9.0.dist-info → gammasimtools-0.11.0.dist-info}/RECORD +133 -117
- {gammasimtools-0.9.0.dist-info → gammasimtools-0.11.0.dist-info}/WHEEL +1 -1
- {gammasimtools-0.9.0.dist-info → gammasimtools-0.11.0.dist-info}/entry_points.txt +6 -1
- simtools/_version.py +9 -4
- simtools/applications/calculate_trigger_rate.py +15 -38
- simtools/applications/convert_all_model_parameters_from_simtel.py +9 -29
- simtools/applications/convert_geo_coordinates_of_array_elements.py +47 -45
- simtools/applications/convert_model_parameter_from_simtel.py +2 -3
- simtools/applications/db_add_file_to_db.py +1 -3
- simtools/applications/db_add_simulation_model_from_repository_to_db.py +110 -0
- simtools/applications/db_add_value_from_json_to_db.py +1 -2
- simtools/applications/db_development_tools/write_array_elements_positions_to_repository.py +6 -6
- simtools/applications/db_get_file_from_db.py +11 -12
- simtools/applications/db_get_parameter_from_db.py +26 -35
- simtools/applications/derive_mirror_rnda.py +1 -2
- simtools/applications/derive_photon_electron_spectrum.py +99 -0
- simtools/applications/derive_psf_parameters.py +1 -0
- simtools/applications/docs_produce_array_element_report.py +71 -0
- simtools/applications/docs_produce_model_parameter_reports.py +63 -0
- simtools/applications/generate_array_config.py +17 -17
- simtools/applications/generate_corsika_histograms.py +2 -2
- simtools/applications/generate_regular_arrays.py +19 -17
- simtools/applications/generate_simtel_array_histograms.py +11 -48
- simtools/applications/production_derive_limits.py +95 -0
- simtools/applications/production_generate_simulation_config.py +37 -33
- simtools/applications/production_scale_events.py +4 -9
- simtools/applications/run_application.py +165 -0
- simtools/applications/simulate_light_emission.py +0 -4
- simtools/applications/simulate_prod.py +1 -1
- simtools/applications/simulate_prod_htcondor_generator.py +26 -26
- simtools/applications/submit_data_from_external.py +12 -4
- simtools/applications/submit_model_parameter_from_external.py +18 -11
- simtools/applications/validate_camera_efficiency.py +2 -2
- simtools/applications/validate_file_using_schema.py +26 -22
- simtools/camera/single_photon_electron_spectrum.py +168 -0
- simtools/configuration/commandline_parser.py +37 -1
- simtools/configuration/configurator.py +8 -10
- simtools/constants.py +10 -3
- simtools/corsika/corsika_config.py +19 -17
- simtools/corsika/corsika_histograms.py +5 -7
- simtools/corsika/corsika_histograms_visualize.py +2 -4
- simtools/data_model/data_reader.py +0 -3
- simtools/data_model/metadata_collector.py +20 -12
- simtools/data_model/metadata_model.py +8 -124
- simtools/data_model/model_data_writer.py +81 -75
- simtools/data_model/schema.py +220 -0
- simtools/data_model/validate_data.py +79 -68
- simtools/db/db_handler.py +350 -492
- simtools/db/db_model_upload.py +139 -0
- simtools/dependencies.py +112 -0
- simtools/io_operations/hdf5_handler.py +54 -24
- simtools/layout/array_layout.py +38 -32
- simtools/model/array_model.py +13 -7
- simtools/model/model_parameter.py +55 -54
- simtools/model/site_model.py +2 -2
- simtools/production_configuration/calculate_statistical_errors_grid_point.py +119 -145
- simtools/production_configuration/event_scaler.py +9 -35
- simtools/production_configuration/generate_simulation_config.py +9 -44
- simtools/production_configuration/interpolation_handler.py +9 -15
- simtools/production_configuration/limits_calculation.py +202 -0
- simtools/reporting/docs_read_parameters.py +310 -0
- simtools/runners/corsika_simtel_runner.py +4 -4
- simtools/schemas/{integration_tests_config.metaschema.yml → application_workflow.metaschema.yml} +61 -27
- simtools/schemas/array_elements.yml +8 -0
- simtools/schemas/input/MST_mirror_2f_measurements.schema.yml +39 -0
- simtools/schemas/input/single_pe_spectrum.schema.yml +38 -0
- simtools/schemas/model_parameter.metaschema.yml +103 -2
- simtools/schemas/model_parameter_and_data_schema.metaschema.yml +4 -1
- simtools/schemas/model_parameters/array_element_position_utm.schema.yml +1 -1
- simtools/schemas/model_parameters/array_window.schema.yml +37 -0
- simtools/schemas/model_parameters/asum_clipping.schema.yml +0 -4
- simtools/schemas/model_parameters/channels_per_chip.schema.yml +1 -1
- simtools/schemas/model_parameters/correct_nsb_spectrum_to_telescope_altitude.schema.yml +1 -1
- simtools/schemas/model_parameters/corsika_cherenkov_photon_bunch_size.schema.yml +2 -0
- simtools/schemas/model_parameters/corsika_cherenkov_photon_wavelength_range.schema.yml +2 -0
- simtools/schemas/model_parameters/corsika_first_interaction_height.schema.yml +2 -0
- simtools/schemas/model_parameters/corsika_iact_io_buffer.schema.yml +4 -2
- simtools/schemas/model_parameters/corsika_iact_max_bunches.schema.yml +2 -0
- simtools/schemas/model_parameters/corsika_iact_split_auto.schema.yml +2 -0
- simtools/schemas/model_parameters/corsika_longitudinal_shower_development.schema.yml +2 -0
- simtools/schemas/model_parameters/corsika_particle_kinetic_energy_cutoff.schema.yml +2 -0
- simtools/schemas/model_parameters/corsika_starting_grammage.schema.yml +2 -0
- simtools/schemas/model_parameters/dsum_clipping.schema.yml +0 -2
- simtools/schemas/model_parameters/dsum_ignore_below.schema.yml +0 -2
- simtools/schemas/model_parameters/dsum_offset.schema.yml +0 -2
- simtools/schemas/model_parameters/dsum_pedsub.schema.yml +0 -2
- simtools/schemas/model_parameters/dsum_pre_clipping.schema.yml +0 -2
- simtools/schemas/model_parameters/dsum_prescale.schema.yml +0 -2
- simtools/schemas/model_parameters/dsum_presum_max.schema.yml +0 -2
- simtools/schemas/model_parameters/dsum_presum_shift.schema.yml +0 -2
- simtools/schemas/model_parameters/dsum_shaping.schema.yml +0 -2
- simtools/schemas/model_parameters/dsum_shaping_renormalize.schema.yml +0 -2
- simtools/schemas/model_parameters/dsum_threshold.schema.yml +0 -2
- simtools/schemas/model_parameters/dsum_zero_clip.schema.yml +0 -2
- simtools/schemas/model_parameters/fadc_compensate_pedestal.schema.yml +1 -1
- simtools/schemas/model_parameters/fadc_lg_compensate_pedestal.schema.yml +1 -1
- simtools/schemas/model_parameters/fadc_noise.schema.yml +3 -3
- simtools/schemas/model_parameters/fake_mirror_list.schema.yml +33 -0
- simtools/schemas/model_parameters/iobuf_maximum.schema.yml +1 -1
- simtools/schemas/model_parameters/iobuf_output_maximum.schema.yml +1 -1
- simtools/schemas/model_parameters/laser_photons.schema.yml +2 -2
- simtools/schemas/model_parameters/lightguide_efficiency_vs_incidence_angle.schema.yml +1 -1
- simtools/schemas/model_parameters/lightguide_efficiency_vs_wavelength.schema.yml +1 -1
- simtools/schemas/model_parameters/min_photoelectrons.schema.yml +1 -1
- simtools/schemas/model_parameters/min_photons.schema.yml +1 -1
- simtools/schemas/model_parameters/random_generator.schema.yml +1 -1
- simtools/schemas/model_parameters/sampled_output.schema.yml +1 -1
- simtools/schemas/model_parameters/save_pe_with_amplitude.schema.yml +1 -1
- simtools/schemas/model_parameters/secondary_mirror_degraded_reflection.schema.yml +1 -1
- simtools/schemas/model_parameters/store_photoelectrons.schema.yml +1 -1
- simtools/schemas/model_parameters/tailcut_scale.schema.yml +1 -1
- simtools/schemas/production_configuration_metrics.schema.yml +68 -0
- simtools/schemas/production_tables.schema.yml +41 -0
- simtools/simtel/simtel_config_reader.py +1 -2
- simtools/simtel/simtel_config_writer.py +6 -8
- simtools/simtel/simtel_io_histogram.py +32 -68
- simtools/simtel/simtel_io_histograms.py +17 -34
- simtools/simtel/simulator_array.py +2 -1
- simtools/simtel/simulator_camera_efficiency.py +6 -3
- simtools/simtel/simulator_light_emission.py +5 -6
- simtools/simtel/simulator_ray_tracing.py +3 -4
- simtools/testing/configuration.py +2 -1
- simtools/testing/helpers.py +6 -13
- simtools/testing/validate_output.py +141 -47
- simtools/utils/general.py +114 -14
- simtools/utils/names.py +299 -157
- simtools/utils/value_conversion.py +17 -13
- simtools/version.py +2 -2
- simtools/visualization/legend_handlers.py +2 -0
- simtools/applications/db_add_model_parameters_from_repository_to_db.py +0 -176
- simtools/db/db_array_elements.py +0 -130
- {gammasimtools-0.9.0.dist-info → gammasimtools-0.11.0.dist-info}/LICENSE +0 -0
- {gammasimtools-0.9.0.dist-info → gammasimtools-0.11.0.dist-info}/top_level.txt +0 -0
- /simtools/{camera_efficiency.py → camera/camera_efficiency.py} +0 -0
|
@@ -67,7 +67,6 @@ class ModelParameter:
|
|
|
67
67
|
|
|
68
68
|
self._parameters = {}
|
|
69
69
|
self._simulation_config_parameters = {"corsika": {}, "simtel": {}}
|
|
70
|
-
self._derived = None
|
|
71
70
|
self.collection = collection
|
|
72
71
|
self.label = label
|
|
73
72
|
self.model_version = model_version
|
|
@@ -77,6 +76,9 @@ class ModelParameter:
|
|
|
77
76
|
if array_element_name is not None
|
|
78
77
|
else None
|
|
79
78
|
)
|
|
79
|
+
self.design_model = self.db.get_design_model(
|
|
80
|
+
self.model_version, self.name, collection="telescopes"
|
|
81
|
+
)
|
|
80
82
|
self._config_file_directory = None
|
|
81
83
|
self._config_file_path = None
|
|
82
84
|
self._load_parameters_from_db()
|
|
@@ -111,12 +113,8 @@ class ModelParameter:
|
|
|
111
113
|
"""
|
|
112
114
|
try:
|
|
113
115
|
return self._parameters[par_name]
|
|
114
|
-
except KeyError:
|
|
115
|
-
pass
|
|
116
|
-
try:
|
|
117
|
-
return self.derived[par_name]
|
|
118
116
|
except (KeyError, ValueError) as e:
|
|
119
|
-
msg = f"Parameter {par_name} was not found in the model"
|
|
117
|
+
msg = f"Parameter {par_name} was not found in the model {self.name}, {self.site}."
|
|
120
118
|
self._logger.error(msg)
|
|
121
119
|
raise InvalidModelParameterError(msg) from e
|
|
122
120
|
|
|
@@ -180,7 +178,10 @@ class ModelParameter:
|
|
|
180
178
|
_value = self.get_parameter_value(par_name, _parameter)
|
|
181
179
|
|
|
182
180
|
try:
|
|
183
|
-
|
|
181
|
+
if isinstance(_parameter.get("unit"), str):
|
|
182
|
+
_unit = [item.strip() for item in _parameter.get("unit").split(",")]
|
|
183
|
+
else:
|
|
184
|
+
_unit = _parameter.get("unit")
|
|
184
185
|
|
|
185
186
|
# if there is only one value or the values share one unit
|
|
186
187
|
if (isinstance(_value, (int | float))) or (len(_value) > len(_unit)):
|
|
@@ -241,32 +242,21 @@ class ModelParameter:
|
|
|
241
242
|
self._logger.debug(f"Parameter {par_name} does not have a file associated with it.")
|
|
242
243
|
return False
|
|
243
244
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
if self._derived is None:
|
|
248
|
-
self._load_derived_values()
|
|
249
|
-
self._export_derived_files()
|
|
250
|
-
return self._derived
|
|
251
|
-
|
|
252
|
-
def _load_derived_values(self):
|
|
253
|
-
"""Load derived values from the DB."""
|
|
254
|
-
self._logger.debug("Reading derived values from DB")
|
|
255
|
-
self._derived = self.db.get_derived_values(
|
|
256
|
-
self.site,
|
|
257
|
-
self.name,
|
|
258
|
-
self.model_version,
|
|
259
|
-
)
|
|
245
|
+
def get_parameter_version(self, par_name):
|
|
246
|
+
"""
|
|
247
|
+
Get version for a given parameter used in the model.
|
|
260
248
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
249
|
+
Parameters
|
|
250
|
+
----------
|
|
251
|
+
par_name: str
|
|
252
|
+
Name of the parameter.
|
|
253
|
+
|
|
254
|
+
Returns
|
|
255
|
+
-------
|
|
256
|
+
str
|
|
257
|
+
parameter version used in the model (eg. '1.0.0')
|
|
258
|
+
"""
|
|
259
|
+
return self._get_parameter_dict(par_name)["parameter_version"]
|
|
270
260
|
|
|
271
261
|
def print_parameters(self):
|
|
272
262
|
"""Print parameters and their values for debugging purposes."""
|
|
@@ -310,6 +300,21 @@ class ModelParameter:
|
|
|
310
300
|
"""
|
|
311
301
|
return self._simulation_config_parameters.get(simulation_software)
|
|
312
302
|
|
|
303
|
+
def has_parameter(self, par_name):
|
|
304
|
+
"""Check if a parameter exists in the model.
|
|
305
|
+
|
|
306
|
+
Parameters
|
|
307
|
+
----------
|
|
308
|
+
par_name : str
|
|
309
|
+
Name of the parameter.
|
|
310
|
+
|
|
311
|
+
Returns
|
|
312
|
+
-------
|
|
313
|
+
bool
|
|
314
|
+
True if parameter exists in the model.
|
|
315
|
+
"""
|
|
316
|
+
return par_name in self._parameters
|
|
317
|
+
|
|
313
318
|
def _load_simulation_software_parameter(self):
|
|
314
319
|
"""Read simulation software parameters from DB."""
|
|
315
320
|
for simulation_software in self._simulation_config_parameters:
|
|
@@ -322,10 +327,11 @@ class ModelParameter:
|
|
|
322
327
|
simulation_software=simulation_software,
|
|
323
328
|
)
|
|
324
329
|
)
|
|
325
|
-
except ValueError:
|
|
330
|
+
except ValueError as exc:
|
|
326
331
|
self._logger.warning(
|
|
327
332
|
f"No {simulation_software} parameters found for "
|
|
328
|
-
f"{self.site}, {self.name} (model version {self.model_version})."
|
|
333
|
+
f"{self.site}, {self.name} (model version {self.model_version}). "
|
|
334
|
+
f" (Query {exc})"
|
|
329
335
|
)
|
|
330
336
|
|
|
331
337
|
def _load_parameters_from_db(self):
|
|
@@ -335,15 +341,18 @@ class ModelParameter:
|
|
|
335
341
|
|
|
336
342
|
if self.name is not None:
|
|
337
343
|
self._parameters = self.db.get_model_parameters(
|
|
338
|
-
self.site, self.name, self.
|
|
344
|
+
self.site, self.name, self.collection, self.model_version
|
|
339
345
|
)
|
|
340
346
|
|
|
341
347
|
if self.site is not None:
|
|
342
|
-
|
|
343
|
-
self.
|
|
348
|
+
self._parameters.update(
|
|
349
|
+
self.db.get_model_parameters(
|
|
350
|
+
self.site,
|
|
351
|
+
None,
|
|
352
|
+
"sites",
|
|
353
|
+
self.model_version,
|
|
354
|
+
)
|
|
344
355
|
)
|
|
345
|
-
self._parameters.update(_site_pars)
|
|
346
|
-
|
|
347
356
|
self._load_simulation_software_parameter()
|
|
348
357
|
|
|
349
358
|
def set_extra_label(self, extra_label):
|
|
@@ -369,7 +378,7 @@ class ModelParameter:
|
|
|
369
378
|
"""Return the extra label if defined, if not return ''."""
|
|
370
379
|
return self._extra_label if self._extra_label is not None else ""
|
|
371
380
|
|
|
372
|
-
def get_simtel_parameters(self, parameters=None
|
|
381
|
+
def get_simtel_parameters(self, parameters=None):
|
|
373
382
|
"""
|
|
374
383
|
Get simtel parameters as name and value pairs.
|
|
375
384
|
|
|
@@ -377,10 +386,6 @@ class ModelParameter:
|
|
|
377
386
|
----------
|
|
378
387
|
parameters: dict
|
|
379
388
|
Parameters (simtools) to be renamed (if necessary)
|
|
380
|
-
telescope_model: bool
|
|
381
|
-
If True, telescope model parameters are included.
|
|
382
|
-
site_model: bool
|
|
383
|
-
If True, site model parameters are included.
|
|
384
389
|
|
|
385
390
|
Returns
|
|
386
391
|
-------
|
|
@@ -394,10 +399,7 @@ class ModelParameter:
|
|
|
394
399
|
_simtel_parameter_value = {}
|
|
395
400
|
for key in parameters:
|
|
396
401
|
_par_name = names.get_simulation_software_name_from_parameter_name(
|
|
397
|
-
key,
|
|
398
|
-
simulation_software="sim_telarray",
|
|
399
|
-
search_telescope_parameters=telescope_model,
|
|
400
|
-
search_site_parameters=site_model,
|
|
402
|
+
key, simulation_software="sim_telarray"
|
|
401
403
|
)
|
|
402
404
|
if _par_name is not None:
|
|
403
405
|
_simtel_parameter_value[_par_name] = parameters[key].get("value")
|
|
@@ -441,8 +443,7 @@ class ModelParameter:
|
|
|
441
443
|
)
|
|
442
444
|
|
|
443
445
|
self._logger.debug(
|
|
444
|
-
f"Changing parameter {par_name} "
|
|
445
|
-
f"from {self.get_parameter_value(par_name)} to {value}"
|
|
446
|
+
f"Changing parameter {par_name} from {self.get_parameter_value(par_name)} to {value}"
|
|
446
447
|
)
|
|
447
448
|
self._parameters[par_name]["value"] = value
|
|
448
449
|
|
|
@@ -513,7 +514,7 @@ class ModelParameter:
|
|
|
513
514
|
for par in self._added_parameter_files:
|
|
514
515
|
pars_from_db.pop(par)
|
|
515
516
|
|
|
516
|
-
self.db.export_model_files(pars_from_db, self.config_file_directory)
|
|
517
|
+
self.db.export_model_files(parameters=pars_from_db, dest=self.config_file_directory)
|
|
517
518
|
self._is_exported_model_files_up_to_date = True
|
|
518
519
|
|
|
519
520
|
def get_model_file_as_table(self, par_name):
|
|
@@ -535,7 +536,7 @@ class ModelParameter:
|
|
|
535
536
|
_par_entry[par_name] = self._parameters[par_name]
|
|
536
537
|
except KeyError as exc:
|
|
537
538
|
raise ValueError(f"Parameter {par_name} not found in the model.") from exc
|
|
538
|
-
self.db.export_model_files(_par_entry, self.config_file_directory)
|
|
539
|
+
self.db.export_model_files(parameters=_par_entry, dest=self.config_file_directory)
|
|
539
540
|
if _par_entry[par_name]["value"].endswith("ecsv"):
|
|
540
541
|
return Table.read(
|
|
541
542
|
self.config_file_directory.joinpath(_par_entry[par_name]["value"]),
|
|
@@ -620,7 +621,7 @@ class ModelParameter:
|
|
|
620
621
|
Model directory to export the file to.
|
|
621
622
|
"""
|
|
622
623
|
self.db.export_model_files(
|
|
623
|
-
{
|
|
624
|
+
parameters={
|
|
624
625
|
"nsb_spectrum_at_2200m": {
|
|
625
626
|
"value": self._simulation_config_parameters["simtel"][
|
|
626
627
|
"correct_nsb_spectrum_to_telescope_altitude"
|
|
@@ -628,5 +629,5 @@ class ModelParameter:
|
|
|
628
629
|
"file": True,
|
|
629
630
|
}
|
|
630
631
|
},
|
|
631
|
-
model_directory,
|
|
632
|
+
dest=model_directory,
|
|
632
633
|
)
|
simtools/model/site_model.py
CHANGED
|
@@ -151,11 +151,11 @@ class SiteModel(ModelParameter):
|
|
|
151
151
|
Model directory to export the file to.
|
|
152
152
|
"""
|
|
153
153
|
self.db.export_model_files(
|
|
154
|
-
{
|
|
154
|
+
parameters={
|
|
155
155
|
"atmospheric_transmission_file": {
|
|
156
156
|
"value": self.get_parameter_value("atmospheric_profile"),
|
|
157
157
|
"file": True,
|
|
158
158
|
}
|
|
159
159
|
},
|
|
160
|
-
model_directory,
|
|
160
|
+
dest=model_directory,
|
|
161
161
|
)
|
|
@@ -1,13 +1,4 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Provides functionality to evaluate statistical uncertainties from DL2 MC event files.
|
|
3
|
-
|
|
4
|
-
Classes
|
|
5
|
-
-------
|
|
6
|
-
StatisticalErrorEvaluator
|
|
7
|
-
Handles error calculation for given DL2 MC event files and specified metrics.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
"""
|
|
1
|
+
"""Evaluate statistical uncertainties from DL2 MC event files."""
|
|
11
2
|
|
|
12
3
|
import logging
|
|
13
4
|
|
|
@@ -15,7 +6,7 @@ import numpy as np
|
|
|
15
6
|
from astropy import units as u
|
|
16
7
|
from astropy.io import fits
|
|
17
8
|
|
|
18
|
-
|
|
9
|
+
__all__ = ["StatisticalErrorEvaluator"]
|
|
19
10
|
|
|
20
11
|
|
|
21
12
|
class StatisticalErrorEvaluator:
|
|
@@ -41,26 +32,13 @@ class StatisticalErrorEvaluator:
|
|
|
41
32
|
metrics: dict[str, float],
|
|
42
33
|
grid_point: tuple[float, float, float, float, float] | None = None,
|
|
43
34
|
):
|
|
44
|
-
"""
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
Parameters
|
|
48
|
-
----------
|
|
49
|
-
file_path : str
|
|
50
|
-
The path to the DL2 MC event file.
|
|
51
|
-
file_type : str
|
|
52
|
-
The type of the file ('point-like' or 'cone').
|
|
53
|
-
metrics : dict, optional
|
|
54
|
-
Dictionary specifying which metrics to compute and their reference values.
|
|
55
|
-
grid_point : tuple, optional
|
|
56
|
-
Tuple specifying the grid point (energy, azimuth, zenith, NSB, offset).
|
|
57
|
-
"""
|
|
58
|
-
self.file_path = file_path
|
|
35
|
+
"""Init the evaluator with a DL2 MC event file, its type, and metrics to calculate."""
|
|
36
|
+
self._logger = logging.getLogger(__name__)
|
|
59
37
|
self.file_type = file_type
|
|
60
38
|
self.metrics = metrics
|
|
61
39
|
self.grid_point = grid_point
|
|
62
40
|
|
|
63
|
-
self.data = self.load_data_from_file()
|
|
41
|
+
self.data = self.load_data_from_file(file_path)
|
|
64
42
|
|
|
65
43
|
self.uncertainty_effective_area = None
|
|
66
44
|
self.energy_estimate = None
|
|
@@ -70,7 +48,58 @@ class StatisticalErrorEvaluator:
|
|
|
70
48
|
self.metric_results = None
|
|
71
49
|
self.energy_threshold = None
|
|
72
50
|
|
|
73
|
-
def
|
|
51
|
+
def _load_event_data(self, hdul, data_type):
|
|
52
|
+
"""
|
|
53
|
+
Load data and units for the event and simulated data data.
|
|
54
|
+
|
|
55
|
+
Parameters
|
|
56
|
+
----------
|
|
57
|
+
hdul : HDUList
|
|
58
|
+
The HDUList object.
|
|
59
|
+
data_type: str
|
|
60
|
+
The type of data to load ('EVENTS' or 'SIMULATED EVENTS').
|
|
61
|
+
|
|
62
|
+
Returns
|
|
63
|
+
-------
|
|
64
|
+
dict
|
|
65
|
+
Dictionary containing units for the event data.
|
|
66
|
+
"""
|
|
67
|
+
_data = hdul[data_type].data # pylint: disable=E1101
|
|
68
|
+
_header = hdul[data_type].header # pylint: disable=E1101
|
|
69
|
+
_units = {}
|
|
70
|
+
for idx, col_name in enumerate(_data.columns.names, start=1):
|
|
71
|
+
unit_key = f"TUNIT{idx}"
|
|
72
|
+
if unit_key in _header:
|
|
73
|
+
_units[col_name] = u.Unit(_header[unit_key])
|
|
74
|
+
else:
|
|
75
|
+
_units[col_name] = None
|
|
76
|
+
return _data, _units
|
|
77
|
+
|
|
78
|
+
def _set_grid_point(self, events_data):
|
|
79
|
+
"""Set azimuth/zenith angle of grid point."""
|
|
80
|
+
unique_azimuths = np.unique(events_data["PNT_AZ"]) * u.deg
|
|
81
|
+
unique_zeniths = 90 * u.deg - np.unique(events_data["PNT_ALT"]) * u.deg
|
|
82
|
+
if len(unique_azimuths) > 1 or len(unique_zeniths) > 1:
|
|
83
|
+
msg = (
|
|
84
|
+
f"Multiple values found for azimuth ({unique_azimuths}) zenith ({unique_zeniths})."
|
|
85
|
+
)
|
|
86
|
+
self._logger.error(msg)
|
|
87
|
+
raise ValueError(msg)
|
|
88
|
+
if self.grid_point is not None:
|
|
89
|
+
self._logger.warning(
|
|
90
|
+
f"Grid point already set to: {self.grid_point}. "
|
|
91
|
+
"Overwriting with new values from file."
|
|
92
|
+
)
|
|
93
|
+
self.grid_point = (
|
|
94
|
+
1 * u.TeV,
|
|
95
|
+
unique_azimuths[0],
|
|
96
|
+
unique_zeniths[0],
|
|
97
|
+
0,
|
|
98
|
+
0 * u.deg,
|
|
99
|
+
)
|
|
100
|
+
self._logger.info(f"Grid point values: {self.grid_point}")
|
|
101
|
+
|
|
102
|
+
def load_data_from_file(self, file_path):
|
|
74
103
|
"""
|
|
75
104
|
Load data from the DL2 MC event file and return dictionaries with units.
|
|
76
105
|
|
|
@@ -81,91 +110,24 @@ class StatisticalErrorEvaluator:
|
|
|
81
110
|
"""
|
|
82
111
|
data = {}
|
|
83
112
|
try:
|
|
84
|
-
with fits.open(
|
|
85
|
-
events_data = hdul
|
|
86
|
-
sim_events_data = hdul
|
|
87
|
-
event_units = {}
|
|
88
|
-
for idx, col_name in enumerate(events_data.columns.names, start=1):
|
|
89
|
-
unit_key = f"TUNIT{idx}"
|
|
90
|
-
if unit_key in hdul["EVENTS"].header: # pylint: disable=E1101
|
|
91
|
-
event_units[col_name] = u.Unit(
|
|
92
|
-
hdul["EVENTS"].header[unit_key] # pylint: disable=E1101
|
|
93
|
-
)
|
|
94
|
-
else:
|
|
95
|
-
event_units[col_name] = None
|
|
96
|
-
|
|
97
|
-
sim_units = {}
|
|
98
|
-
for idx, col_name in enumerate(sim_events_data.columns.names, start=1):
|
|
99
|
-
unit_key = f"TUNIT{idx}"
|
|
100
|
-
if unit_key in hdul["SIMULATED EVENTS"].header: # pylint: disable=E1101
|
|
101
|
-
sim_units[col_name] = u.Unit(
|
|
102
|
-
hdul["SIMULATED EVENTS"].header[unit_key] # pylint: disable=E1101
|
|
103
|
-
)
|
|
104
|
-
else:
|
|
105
|
-
sim_units[col_name] = None
|
|
106
|
-
# dl2 files are required to have units for these entries
|
|
107
|
-
event_energies_reco = events_data["ENERGY"] * event_units["ENERGY"]
|
|
108
|
-
|
|
109
|
-
event_energies_mc = events_data["MC_ENERGY"] * event_units["MC_ENERGY"]
|
|
110
|
-
|
|
111
|
-
bin_edges_low = sim_events_data["MC_ENERG_LO"] * sim_units["MC_ENERG_LO"]
|
|
112
|
-
|
|
113
|
-
bin_edges_high = sim_events_data["MC_ENERG_HI"] * sim_units["MC_ENERG_HI"]
|
|
114
|
-
|
|
115
|
-
simulated_event_histogram = sim_events_data["EVENTS"] * u.count
|
|
116
|
-
|
|
117
|
-
viewcone = hdul[3].data["viewcone"][0][1] # pylint: disable=E1101
|
|
118
|
-
core_range = hdul[3].data["core_range"][0][1] # pylint: disable=E1101
|
|
113
|
+
with fits.open(file_path) as hdul:
|
|
114
|
+
events_data, event_units = self._load_event_data(hdul, "EVENTS")
|
|
115
|
+
sim_events_data, sim_units = self._load_event_data(hdul, "SIMULATED EVENTS")
|
|
119
116
|
|
|
120
117
|
data = {
|
|
121
|
-
"event_energies_reco":
|
|
122
|
-
"event_energies_mc":
|
|
123
|
-
"bin_edges_low":
|
|
124
|
-
"bin_edges_high":
|
|
125
|
-
"simulated_event_histogram":
|
|
126
|
-
"viewcone": viewcone,
|
|
127
|
-
"core_range": core_range,
|
|
118
|
+
"event_energies_reco": events_data["ENERGY"] * event_units["ENERGY"],
|
|
119
|
+
"event_energies_mc": events_data["MC_ENERGY"] * event_units["MC_ENERGY"],
|
|
120
|
+
"bin_edges_low": sim_events_data["MC_ENERG_LO"] * sim_units["MC_ENERG_LO"],
|
|
121
|
+
"bin_edges_high": sim_events_data["MC_ENERG_HI"] * sim_units["MC_ENERG_HI"],
|
|
122
|
+
"simulated_event_histogram": sim_events_data["EVENTS"] * u.count,
|
|
123
|
+
"viewcone": hdul[3].data["viewcone"][0][1], # pylint: disable=E1101
|
|
124
|
+
"core_range": hdul[3].data["core_range"][0][1], # pylint: disable=E1101
|
|
128
125
|
}
|
|
129
|
-
|
|
130
|
-
unique_zeniths = 90 * u.deg - np.unique(events_data["PNT_ALT"]) * u.deg
|
|
131
|
-
if self.grid_point is None:
|
|
132
|
-
_logger.info(f"Unique azimuths: {unique_azimuths}")
|
|
133
|
-
_logger.info(f"Unique zeniths: {unique_zeniths}")
|
|
134
|
-
|
|
135
|
-
if len(unique_azimuths) == 1 and len(unique_zeniths) == 1:
|
|
136
|
-
_logger.info(
|
|
137
|
-
f"Setting initial grid point with azimuth: {unique_azimuths[0]}"
|
|
138
|
-
f" zenith: {unique_zeniths[0]}"
|
|
139
|
-
)
|
|
140
|
-
self.grid_point = (
|
|
141
|
-
1 * u.TeV,
|
|
142
|
-
unique_azimuths[0],
|
|
143
|
-
unique_zeniths[0],
|
|
144
|
-
0,
|
|
145
|
-
0 * u.deg,
|
|
146
|
-
) # Initialize grid point with azimuth and zenith
|
|
147
|
-
else:
|
|
148
|
-
msg = "Multiple unique values found for azimuth or zenith."
|
|
149
|
-
_logger.error(msg)
|
|
150
|
-
raise ValueError(msg)
|
|
151
|
-
else:
|
|
152
|
-
_logger.warning(
|
|
153
|
-
f"Grid point already set to: {self.grid_point}. "
|
|
154
|
-
"Overwriting with new values from file."
|
|
155
|
-
)
|
|
156
|
-
|
|
157
|
-
self.grid_point = (
|
|
158
|
-
1 * u.TeV,
|
|
159
|
-
unique_azimuths[0],
|
|
160
|
-
unique_zeniths[0],
|
|
161
|
-
0,
|
|
162
|
-
0 * u.deg,
|
|
163
|
-
)
|
|
164
|
-
_logger.info(f"New grid point values: {self.grid_point}")
|
|
126
|
+
self._set_grid_point(events_data)
|
|
165
127
|
|
|
166
128
|
except FileNotFoundError as e:
|
|
167
|
-
error_message = f"Error loading file {
|
|
168
|
-
_logger.error(error_message)
|
|
129
|
+
error_message = f"Error loading file {file_path}: {e}"
|
|
130
|
+
self._logger.error(error_message)
|
|
169
131
|
raise FileNotFoundError(error_message) from e
|
|
170
132
|
return data
|
|
171
133
|
|
|
@@ -183,9 +145,9 @@ class StatisticalErrorEvaluator:
|
|
|
183
145
|
bin_edges = np.concatenate([bin_edges_low, [bin_edges_high[-1]]])
|
|
184
146
|
return np.unique(bin_edges)
|
|
185
147
|
|
|
186
|
-
def
|
|
148
|
+
def compute_reconstructed_event_histogram(self, event_energies_reco, bin_edges):
|
|
187
149
|
"""
|
|
188
|
-
Compute histogram
|
|
150
|
+
Compute histogram of events as function of reconstructed energy.
|
|
189
151
|
|
|
190
152
|
Parameters
|
|
191
153
|
----------
|
|
@@ -196,24 +158,26 @@ class StatisticalErrorEvaluator:
|
|
|
196
158
|
|
|
197
159
|
Returns
|
|
198
160
|
-------
|
|
199
|
-
|
|
200
|
-
Histogram of
|
|
161
|
+
reconstructed_event_histogram : array
|
|
162
|
+
Histogram of reconstructed events.
|
|
201
163
|
"""
|
|
202
164
|
event_energies_reco = event_energies_reco.to(bin_edges.unit)
|
|
203
165
|
|
|
204
|
-
|
|
205
|
-
|
|
166
|
+
reconstructed_event_histogram, _ = np.histogram(
|
|
167
|
+
event_energies_reco.value, bins=bin_edges.value
|
|
168
|
+
)
|
|
169
|
+
return reconstructed_event_histogram * u.count
|
|
206
170
|
|
|
207
|
-
def compute_efficiency_and_errors(self,
|
|
171
|
+
def compute_efficiency_and_errors(self, reconstructed_event_counts, simulated_event_counts):
|
|
208
172
|
"""
|
|
209
|
-
Compute
|
|
173
|
+
Compute reconstructed event efficiency and its uncertainty assuming binomial distribution.
|
|
210
174
|
|
|
211
175
|
Parameters
|
|
212
176
|
----------
|
|
213
|
-
|
|
214
|
-
Histogram counts of
|
|
177
|
+
reconstructed_event_counts : array with units
|
|
178
|
+
Histogram counts of reconstructed events.
|
|
215
179
|
simulated_event_counts : array with units
|
|
216
|
-
Histogram counts of
|
|
180
|
+
Histogram counts of simulated events.
|
|
217
181
|
|
|
218
182
|
Returns
|
|
219
183
|
-------
|
|
@@ -223,32 +187,39 @@ class StatisticalErrorEvaluator:
|
|
|
223
187
|
Array of relative uncertainties.
|
|
224
188
|
"""
|
|
225
189
|
# Ensure the inputs have compatible units
|
|
226
|
-
|
|
227
|
-
|
|
190
|
+
reconstructed_event_counts = (
|
|
191
|
+
reconstructed_event_counts.to(u.ct)
|
|
192
|
+
if isinstance(reconstructed_event_counts, u.Quantity)
|
|
193
|
+
else reconstructed_event_counts * u.ct
|
|
194
|
+
)
|
|
195
|
+
simulated_event_counts = (
|
|
196
|
+
simulated_event_counts.to(u.ct)
|
|
197
|
+
if isinstance(simulated_event_counts, u.Quantity)
|
|
198
|
+
else simulated_event_counts * u.ct
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
if np.any(reconstructed_event_counts > simulated_event_counts):
|
|
202
|
+
raise ValueError("Reconstructed event counts exceed simulated event counts.")
|
|
228
203
|
|
|
229
204
|
# Compute efficiencies, ensuring the output is dimensionless
|
|
230
205
|
efficiencies = np.divide(
|
|
231
|
-
|
|
206
|
+
reconstructed_event_counts,
|
|
232
207
|
simulated_event_counts,
|
|
233
|
-
out=np.zeros_like(
|
|
208
|
+
out=np.zeros_like(reconstructed_event_counts),
|
|
234
209
|
where=simulated_event_counts > 0,
|
|
235
210
|
).to(u.dimensionless_unscaled)
|
|
236
211
|
|
|
237
212
|
# Set up a mask for valid data with a unit-consistent threshold
|
|
238
|
-
|
|
239
|
-
raise ValueError(
|
|
240
|
-
"Triggered event counts exceed simulated event counts. Please check input data."
|
|
241
|
-
)
|
|
242
|
-
valid = (simulated_event_counts > 0 * u.ct) & (triggered_event_counts > 0 * u.ct)
|
|
213
|
+
valid = (simulated_event_counts > 0) & (reconstructed_event_counts > 0)
|
|
243
214
|
|
|
244
|
-
uncertainties = np.zeros_like(
|
|
215
|
+
uncertainties = np.zeros_like(reconstructed_event_counts.value) * u.dimensionless_unscaled
|
|
245
216
|
|
|
246
217
|
if np.any(valid):
|
|
247
218
|
uncertainties[valid] = np.sqrt(
|
|
248
219
|
np.maximum(
|
|
249
220
|
simulated_event_counts[valid]
|
|
250
|
-
/
|
|
251
|
-
* (1 -
|
|
221
|
+
/ reconstructed_event_counts[valid]
|
|
222
|
+
* (1 - reconstructed_event_counts[valid] / simulated_event_counts[valid]),
|
|
252
223
|
0,
|
|
253
224
|
)
|
|
254
225
|
)
|
|
@@ -273,13 +244,13 @@ class StatisticalErrorEvaluator:
|
|
|
273
244
|
Energy threshold value.
|
|
274
245
|
"""
|
|
275
246
|
bin_edges = self.create_bin_edges()
|
|
276
|
-
|
|
247
|
+
reconstructed_event_histogram = self.compute_reconstructed_event_histogram(
|
|
277
248
|
self.data["event_energies_mc"], bin_edges
|
|
278
249
|
)
|
|
279
250
|
simulated_event_histogram = self.data["simulated_event_histogram"]
|
|
280
251
|
|
|
281
252
|
efficiencies, _ = self.compute_efficiency_and_errors(
|
|
282
|
-
|
|
253
|
+
reconstructed_event_histogram, simulated_event_histogram
|
|
283
254
|
)
|
|
284
255
|
|
|
285
256
|
# Determine the effective area threshold (10% of max effective area)
|
|
@@ -302,12 +273,12 @@ class StatisticalErrorEvaluator:
|
|
|
302
273
|
Dictionary with uncertainties for the file.
|
|
303
274
|
"""
|
|
304
275
|
bin_edges = self.create_bin_edges()
|
|
305
|
-
|
|
276
|
+
reconstructed_event_histogram = self.compute_reconstructed_event_histogram(
|
|
306
277
|
self.data["event_energies_mc"], bin_edges
|
|
307
278
|
)
|
|
308
279
|
simulated_event_histogram = self.data["simulated_event_histogram"]
|
|
309
280
|
_, relative_errors = self.compute_efficiency_and_errors(
|
|
310
|
-
|
|
281
|
+
reconstructed_event_histogram, simulated_event_histogram
|
|
311
282
|
)
|
|
312
283
|
return {"relative_errors": relative_errors}
|
|
313
284
|
|
|
@@ -326,7 +297,10 @@ class StatisticalErrorEvaluator:
|
|
|
326
297
|
event_energies_mc = self.data["event_energies_mc"]
|
|
327
298
|
|
|
328
299
|
if len(event_energies_reco) != len(event_energies_mc):
|
|
329
|
-
raise ValueError(
|
|
300
|
+
raise ValueError(
|
|
301
|
+
f"Mismatch in the number of energies: {len(event_energies_reco)} vs "
|
|
302
|
+
f"{len(event_energies_mc)}"
|
|
303
|
+
)
|
|
330
304
|
|
|
331
305
|
energy_deviation = (event_energies_reco - event_energies_mc) / event_energies_mc
|
|
332
306
|
|
|
@@ -351,15 +325,15 @@ class StatisticalErrorEvaluator:
|
|
|
351
325
|
def calculate_metrics(self):
|
|
352
326
|
"""Calculate all defined metrics as specified in self.metrics and store results."""
|
|
353
327
|
if "uncertainty_effective_area" in self.metrics:
|
|
354
|
-
|
|
355
328
|
self.uncertainty_effective_area = self.calculate_uncertainty_effective_area()
|
|
356
329
|
if self.uncertainty_effective_area:
|
|
357
|
-
|
|
358
|
-
"
|
|
330
|
+
energy_range = self.metrics.get("uncertainty_effective_area", {}).get(
|
|
331
|
+
"energy_range"
|
|
332
|
+
)
|
|
333
|
+
min_energy, max_energy = (
|
|
334
|
+
energy_range["value"][0] * u.Unit(energy_range["unit"]),
|
|
335
|
+
energy_range["value"][1] * u.Unit(energy_range["unit"]),
|
|
359
336
|
)
|
|
360
|
-
min_energy, max_energy = validity_range["value"][0] * u.Unit(
|
|
361
|
-
validity_range["unit"]
|
|
362
|
-
), validity_range["value"][1] * u.Unit(validity_range["unit"])
|
|
363
337
|
|
|
364
338
|
valid_errors = [
|
|
365
339
|
error
|
|
@@ -375,7 +349,7 @@ class StatisticalErrorEvaluator:
|
|
|
375
349
|
ref_value = self.metrics.get("uncertainty_effective_area", {}).get("target_error")[
|
|
376
350
|
"value"
|
|
377
351
|
]
|
|
378
|
-
_logger.info(
|
|
352
|
+
self._logger.info(
|
|
379
353
|
f"Effective Area Error (max in validity range): "
|
|
380
354
|
f"{self.uncertainty_effective_area['max_error'].value:.6f}, "
|
|
381
355
|
f"Reference: {ref_value:.3f}"
|
|
@@ -386,7 +360,7 @@ class StatisticalErrorEvaluator:
|
|
|
386
360
|
self.calculate_energy_estimate()
|
|
387
361
|
)
|
|
388
362
|
ref_value = self.metrics.get("energy_estimate", {}).get("target_error")["value"]
|
|
389
|
-
_logger.info(
|
|
363
|
+
self._logger.info(
|
|
390
364
|
f"Energy Estimate Error: {self.energy_estimate:.3f}, Reference: {ref_value:.3f}"
|
|
391
365
|
)
|
|
392
366
|
else:
|
|
@@ -442,7 +416,7 @@ class StatisticalErrorEvaluator:
|
|
|
442
416
|
overall_max_errors[metric_name] = result
|
|
443
417
|
else:
|
|
444
418
|
raise ValueError(f"Unsupported result type for {metric_name}: {type(result)}")
|
|
445
|
-
_logger.info(f"overall_max_errors {overall_max_errors}")
|
|
419
|
+
self._logger.info(f"overall_max_errors {overall_max_errors}")
|
|
446
420
|
all_max_errors = list(overall_max_errors.values())
|
|
447
421
|
if metric == "average":
|
|
448
422
|
overall_metric = np.mean(all_max_errors)
|