gammasimtools 0.10.0__py3-none-any.whl → 0.12.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.
Files changed (84) hide show
  1. {gammasimtools-0.10.0.dist-info → gammasimtools-0.12.0.dist-info}/METADATA +3 -1
  2. {gammasimtools-0.10.0.dist-info → gammasimtools-0.12.0.dist-info}/RECORD +84 -77
  3. {gammasimtools-0.10.0.dist-info → gammasimtools-0.12.0.dist-info}/WHEEL +1 -1
  4. {gammasimtools-0.10.0.dist-info → gammasimtools-0.12.0.dist-info}/entry_points.txt +4 -0
  5. simtools/_version.py +9 -4
  6. simtools/applications/convert_all_model_parameters_from_simtel.py +0 -1
  7. simtools/applications/convert_model_parameter_from_simtel.py +0 -1
  8. simtools/applications/db_add_file_to_db.py +0 -1
  9. simtools/applications/db_get_parameter_from_db.py +7 -28
  10. simtools/applications/derive_mirror_rnda.py +1 -2
  11. simtools/applications/derive_psf_parameters.py +1 -0
  12. simtools/applications/docs_produce_array_element_report.py +71 -0
  13. simtools/applications/docs_produce_model_parameter_reports.py +63 -0
  14. simtools/applications/generate_corsika_histograms.py +2 -2
  15. simtools/applications/generate_regular_arrays.py +4 -2
  16. simtools/applications/production_derive_limits.py +95 -0
  17. simtools/applications/production_generate_simulation_config.py +15 -29
  18. simtools/applications/production_scale_events.py +2 -7
  19. simtools/applications/run_application.py +165 -0
  20. simtools/applications/simulate_light_emission.py +0 -4
  21. simtools/applications/submit_model_parameter_from_external.py +11 -6
  22. simtools/applications/validate_file_using_schema.py +3 -3
  23. simtools/configuration/commandline_parser.py +30 -1
  24. simtools/configuration/configurator.py +8 -10
  25. simtools/corsika/corsika_config.py +11 -10
  26. simtools/corsika/corsika_histograms.py +4 -6
  27. simtools/corsika/corsika_histograms_visualize.py +2 -4
  28. simtools/data_model/metadata_collector.py +18 -9
  29. simtools/data_model/model_data_writer.py +67 -15
  30. simtools/data_model/schema.py +10 -3
  31. simtools/data_model/validate_data.py +70 -24
  32. simtools/db/db_handler.py +46 -14
  33. simtools/dependencies.py +112 -0
  34. simtools/layout/array_layout.py +5 -4
  35. simtools/model/model_parameter.py +35 -2
  36. simtools/production_configuration/calculate_statistical_errors_grid_point.py +5 -6
  37. simtools/production_configuration/event_scaler.py +3 -19
  38. simtools/production_configuration/generate_simulation_config.py +4 -12
  39. simtools/production_configuration/interpolation_handler.py +2 -5
  40. simtools/production_configuration/limits_calculation.py +202 -0
  41. simtools/reporting/docs_read_parameters.py +310 -0
  42. simtools/runners/corsika_simtel_runner.py +1 -3
  43. simtools/schemas/{integration_tests_config.metaschema.yml → application_workflow.metaschema.yml} +51 -27
  44. simtools/schemas/array_elements.yml +8 -0
  45. simtools/schemas/model_parameter.metaschema.yml +96 -0
  46. simtools/schemas/model_parameter_and_data_schema.metaschema.yml +2 -1
  47. simtools/schemas/model_parameters/correct_nsb_spectrum_to_telescope_altitude.schema.yml +1 -1
  48. simtools/schemas/model_parameters/corsika_cherenkov_photon_bunch_size.schema.yml +2 -0
  49. simtools/schemas/model_parameters/corsika_cherenkov_photon_wavelength_range.schema.yml +2 -0
  50. simtools/schemas/model_parameters/corsika_first_interaction_height.schema.yml +2 -0
  51. simtools/schemas/model_parameters/corsika_iact_io_buffer.schema.yml +2 -0
  52. simtools/schemas/model_parameters/corsika_iact_max_bunches.schema.yml +2 -0
  53. simtools/schemas/model_parameters/corsika_iact_split_auto.schema.yml +2 -0
  54. simtools/schemas/model_parameters/corsika_longitudinal_shower_development.schema.yml +2 -0
  55. simtools/schemas/model_parameters/corsika_particle_kinetic_energy_cutoff.schema.yml +2 -0
  56. simtools/schemas/model_parameters/corsika_starting_grammage.schema.yml +2 -0
  57. simtools/schemas/model_parameters/iobuf_maximum.schema.yml +1 -1
  58. simtools/schemas/model_parameters/iobuf_output_maximum.schema.yml +1 -1
  59. simtools/schemas/model_parameters/lightguide_efficiency_vs_incidence_angle.schema.yml +1 -1
  60. simtools/schemas/model_parameters/lightguide_efficiency_vs_wavelength.schema.yml +1 -1
  61. simtools/schemas/model_parameters/min_photoelectrons.schema.yml +1 -1
  62. simtools/schemas/model_parameters/min_photons.schema.yml +1 -1
  63. simtools/schemas/model_parameters/random_generator.schema.yml +1 -1
  64. simtools/schemas/model_parameters/sampled_output.schema.yml +1 -1
  65. simtools/schemas/model_parameters/save_pe_with_amplitude.schema.yml +1 -1
  66. simtools/schemas/model_parameters/store_photoelectrons.schema.yml +1 -1
  67. simtools/schemas/model_parameters/tailcut_scale.schema.yml +1 -1
  68. simtools/schemas/production_tables.schema.yml +1 -1
  69. simtools/simtel/simtel_config_reader.py +1 -2
  70. simtools/simtel/simtel_config_writer.py +1 -2
  71. simtools/simtel/simtel_io_histogram.py +0 -1
  72. simtools/simtel/simtel_io_histograms.py +2 -4
  73. simtools/simtel/simulator_camera_efficiency.py +1 -3
  74. simtools/simtel/simulator_light_emission.py +2 -5
  75. simtools/simtel/simulator_ray_tracing.py +1 -3
  76. simtools/testing/configuration.py +2 -1
  77. simtools/testing/validate_output.py +23 -13
  78. simtools/utils/general.py +12 -2
  79. simtools/utils/names.py +290 -152
  80. simtools/utils/value_conversion.py +20 -14
  81. simtools/version.py +2 -2
  82. simtools/visualization/legend_handlers.py +2 -0
  83. {gammasimtools-0.10.0.dist-info → gammasimtools-0.12.0.dist-info}/LICENSE +0 -0
  84. {gammasimtools-0.10.0.dist-info → gammasimtools-0.12.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,112 @@
1
+ """
2
+ Simtools dependencies version management.
3
+
4
+ This modules provides two main functionalities:
5
+
6
+ - retrieve the versions of simtools dependencies (e.g., databases, sim_telarray, CORSIKA)
7
+ - provide space for future implementations of version management
8
+
9
+ """
10
+
11
+ import logging
12
+ import os
13
+ import re
14
+ import subprocess
15
+ from pathlib import Path
16
+
17
+ import simtools.utils.general as gen
18
+ from simtools.db.db_handler import DatabaseHandler
19
+
20
+ _logger = logging.getLogger(__name__)
21
+
22
+
23
+ def get_version_string(db_config=None):
24
+ """Print the versions of the dependencies."""
25
+ return (
26
+ f"Database version: {get_database_version(db_config)}\n"
27
+ f"sim_telarray version: {get_sim_telarray_version()}\n"
28
+ f"CORSIKA version: {get_corsika_version()}\n"
29
+ )
30
+
31
+
32
+ def get_database_version(db_config):
33
+ """
34
+ Get the version of the simulation model data base used.
35
+
36
+ Parameters
37
+ ----------
38
+ db_config : dict
39
+ Dictionary containing the database configuration.
40
+
41
+ Returns
42
+ -------
43
+ str
44
+ Version of the simulation model data base used.
45
+
46
+ """
47
+ if db_config is None:
48
+ return None
49
+ db = DatabaseHandler(db_config)
50
+ return db.mongo_db_config.get("db_simulation_model")
51
+
52
+
53
+ def get_sim_telarray_version():
54
+ """
55
+ Get the version of the sim_telarray package using 'sim_telarray --version'.
56
+
57
+ Returns
58
+ -------
59
+ str
60
+ Version of the sim_telarray package.
61
+ """
62
+ sim_telarray_path = os.getenv("SIMTOOLS_SIMTEL_PATH")
63
+ if sim_telarray_path is None:
64
+ _logger.warning("Environment variable SIMTOOLS_SIMTEL_PATH is not set.")
65
+ return None
66
+ sim_telarray_path = Path(sim_telarray_path) / "sim_telarray" / "bin" / "sim_telarray"
67
+
68
+ # expect stdout with e.g. a line 'Release: 2024.271.0 from 2024-09-27'
69
+ result = subprocess.run(
70
+ [sim_telarray_path, "--version"],
71
+ capture_output=True,
72
+ text=True,
73
+ check=False,
74
+ )
75
+ match = re.search(r"^Release:\s+(.+)", result.stdout, re.MULTILINE)
76
+
77
+ if match:
78
+ return match.group(1).split()[0]
79
+ raise ValueError(f"sim_telarray release not found in {result.stdout}")
80
+
81
+
82
+ def get_corsika_version():
83
+ """
84
+ Get the version of the corsika package.
85
+
86
+ Returns
87
+ -------
88
+ str
89
+ Version of the corsika package.
90
+ """
91
+ try:
92
+ build_opts = get_build_options()
93
+ except (FileNotFoundError, TypeError):
94
+ _logger.warning("CORSIKA version not implemented yet.")
95
+ return None
96
+ return build_opts.get("corsika_version")
97
+
98
+
99
+ def get_build_options():
100
+ """
101
+ Return CORSIKA / sim_telarray build options.
102
+
103
+ Expects a build_opts.yml file in the sim_telarray directory.
104
+ """
105
+ try:
106
+ return gen.collect_data_from_file(
107
+ Path(os.getenv("SIMTOOLS_SIMTEL_PATH")) / "build_opts.yml"
108
+ )
109
+ except FileNotFoundError as exc:
110
+ raise FileNotFoundError("No build_opts.yml file found.") from exc
111
+ except TypeError as exc:
112
+ raise TypeError("SIMTOOLS_SIMTEL_PATH not defined.") from exc
@@ -445,9 +445,11 @@ class ArrayLayout:
445
445
  )
446
446
  try:
447
447
  tel_model = self._get_telescope_model(telescope_name)
448
- except ValueError:
448
+ except ValueError: # telescope not found in the database revert to design model
449
449
  tel_model = self._get_telescope_model(
450
- names.get_array_element_type_from_name(telescope_name) + "-design",
450
+ names.array_element_design_types(
451
+ names.get_array_element_type_from_name(telescope_name)
452
+ )[0]
451
453
  )
452
454
 
453
455
  for para in ("telescope_axis_height", "telescope_sphere_radius"):
@@ -716,8 +718,7 @@ class ArrayLayout:
716
718
  set(_telescope_list_from_name + _telescope_list_from_type)
717
719
  )
718
720
  self._logger.info(
719
- f"Selected {len(self._telescope_list)} telescopes"
720
- f" (from originally {_n_telescopes})"
721
+ f"Selected {len(self._telescope_list)} telescopes (from originally {_n_telescopes})"
721
722
  )
722
723
  except TypeError:
723
724
  self._logger.info("No asset list provided, keeping all telescopes")
@@ -76,6 +76,9 @@ class ModelParameter:
76
76
  if array_element_name is not None
77
77
  else None
78
78
  )
79
+ self.design_model = self.db.get_design_model(
80
+ self.model_version, self.name, collection="telescopes"
81
+ )
79
82
  self._config_file_directory = None
80
83
  self._config_file_path = None
81
84
  self._load_parameters_from_db()
@@ -239,6 +242,22 @@ class ModelParameter:
239
242
  self._logger.debug(f"Parameter {par_name} does not have a file associated with it.")
240
243
  return False
241
244
 
245
+ def get_parameter_version(self, par_name):
246
+ """
247
+ Get version for a given parameter used in the model.
248
+
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"]
260
+
242
261
  def print_parameters(self):
243
262
  """Print parameters and their values for debugging purposes."""
244
263
  for par in self._parameters:
@@ -281,6 +300,21 @@ class ModelParameter:
281
300
  """
282
301
  return self._simulation_config_parameters.get(simulation_software)
283
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
+
284
318
  def _load_simulation_software_parameter(self):
285
319
  """Read simulation software parameters from DB."""
286
320
  for simulation_software in self._simulation_config_parameters:
@@ -409,8 +443,7 @@ class ModelParameter:
409
443
  )
410
444
 
411
445
  self._logger.debug(
412
- f"Changing parameter {par_name} "
413
- 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}"
414
447
  )
415
448
  self._parameters[par_name]["value"] = value
416
449
 
@@ -81,8 +81,7 @@ class StatisticalErrorEvaluator:
81
81
  unique_zeniths = 90 * u.deg - np.unique(events_data["PNT_ALT"]) * u.deg
82
82
  if len(unique_azimuths) > 1 or len(unique_zeniths) > 1:
83
83
  msg = (
84
- f"Multiple values found for azimuth ({unique_azimuths}) "
85
- f"zenith ({unique_zeniths})."
84
+ f"Multiple values found for azimuth ({unique_azimuths}) zenith ({unique_zeniths})."
86
85
  )
87
86
  self._logger.error(msg)
88
87
  raise ValueError(msg)
@@ -326,15 +325,15 @@ class StatisticalErrorEvaluator:
326
325
  def calculate_metrics(self):
327
326
  """Calculate all defined metrics as specified in self.metrics and store results."""
328
327
  if "uncertainty_effective_area" in self.metrics:
329
-
330
328
  self.uncertainty_effective_area = self.calculate_uncertainty_effective_area()
331
329
  if self.uncertainty_effective_area:
332
330
  energy_range = self.metrics.get("uncertainty_effective_area", {}).get(
333
331
  "energy_range"
334
332
  )
335
- min_energy, max_energy = energy_range["value"][0] * u.Unit(
336
- energy_range["unit"]
337
- ), energy_range["value"][1] * u.Unit(energy_range["unit"])
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"]),
336
+ )
338
337
 
339
338
  valid_errors = [
340
339
  error
@@ -19,21 +19,18 @@ class EventScaler:
19
19
  Supports scaling both the entire dataset and specific grid points like energy values.
20
20
  """
21
21
 
22
- def __init__(self, evaluator, science_case: str, metrics: dict):
22
+ def __init__(self, evaluator, metrics: dict):
23
23
  """
24
- Initialize the EventScaler with the evaluator, science case, and metrics.
24
+ Initialize the EventScaler with the evaluator and metrics.
25
25
 
26
26
  Parameters
27
27
  ----------
28
28
  evaluator : StatisticalErrorEvaluator
29
29
  The evaluator responsible for calculating metrics and handling event data.
30
- science_case : str
31
- The science case used to adjust the uncertainty factor.
32
30
  metrics : dict
33
31
  Dictionary containing metrics, including target error for effective area.
34
32
  """
35
33
  self.evaluator = evaluator
36
- self.science_case = science_case
37
34
  self.metrics = metrics
38
35
 
39
36
  def scale_events(self, return_sum: bool = True) -> u.Quantity:
@@ -77,20 +74,7 @@ class EventScaler:
77
74
  "value"
78
75
  ]
79
76
 
80
- return (
81
- current_max_error / target_max_error
82
- ) ** 2 * self._apply_science_case_scaling_factor()
83
-
84
- def _apply_science_case_scaling_factor(self) -> float:
85
- """
86
- Apply the uncertainty factor based on the science case.
87
-
88
- Returns
89
- -------
90
- float
91
- The final scaling factor after applying uncertainty.
92
- """
93
- return 1 if self.science_case == "science case 1" else 1.0
77
+ return (current_max_error / target_max_error) ** 2
94
78
 
95
79
  def _number_of_simulated_events(self) -> u.Quantity:
96
80
  """
@@ -16,10 +16,6 @@ class SimulationConfig:
16
16
  ----------
17
17
  grid_point : dict
18
18
  Dictionary representing a grid point with azimuth, elevation, and night sky background.
19
- ctao_data_level : str
20
- The data level (e.g., 'A', 'B', 'C') for the simulation configuration.
21
- science_case : str
22
- The science case for the simulation configuration.
23
19
  file_path : str
24
20
  Path to the DL2 MC event file for statistical uncertainty evaluation.
25
21
  file_type : str
@@ -31,26 +27,22 @@ class SimulationConfig:
31
27
  def __init__(
32
28
  self,
33
29
  grid_point: dict[str, float],
34
- ctao_data_level: str,
35
- science_case: str,
36
30
  file_path: str,
37
31
  file_type: str,
38
32
  metrics: dict[str, float] | None = None,
39
33
  ):
40
34
  """Initialize the simulation configuration for a grid point."""
41
35
  self.grid_point = grid_point
42
- self.ctao_data_level = ctao_data_level
43
- self.science_case = science_case
44
36
  self.file_path = file_path
45
37
  self.file_type = file_type
46
38
  self.metrics = metrics or {}
47
39
  self.evaluator = StatisticalErrorEvaluator(file_path, file_type, metrics)
48
- self.event_scaler = EventScaler(self.evaluator, science_case, self.metrics)
40
+ self.event_scaler = EventScaler(self.evaluator, self.metrics)
49
41
  self.simulation_params = {}
50
42
 
51
43
  def configure_simulation(self) -> dict[str, float]:
52
44
  """
53
- Configure the simulation parameters for the grid point, data level, and science case.
45
+ Configure the simulation parameters for the grid point.
54
46
 
55
47
  Returns
56
48
  -------
@@ -80,7 +72,7 @@ class SimulationConfig:
80
72
 
81
73
  def _calculate_core_scatter_area(self) -> float:
82
74
  """
83
- Calculate the core scatter area based on the grid point and data level.
75
+ Calculate the core scatter area based on the grid point.
84
76
 
85
77
  Returns
86
78
  -------
@@ -93,7 +85,7 @@ class SimulationConfig:
93
85
 
94
86
  def _calculate_viewcone(self) -> float:
95
87
  """
96
- Calculate the viewcone based on the grid point conditions and data level.
88
+ Calculate the viewcone based on the grid point conditions.
97
89
 
98
90
  Returns
99
91
  -------
@@ -12,13 +12,10 @@ __all__ = ["InterpolationHandler"]
12
12
  class InterpolationHandler:
13
13
  """Handle interpolation between multiple StatisticalErrorEvaluator instances."""
14
14
 
15
- def __init__(self, evaluators, science_case: str, metrics: dict):
15
+ def __init__(self, evaluators, metrics: dict):
16
16
  self.evaluators = evaluators
17
- self.science_case = science_case
18
17
  self.metrics = metrics
19
- self.event_scalers = [
20
- EventScaler(e, self.science_case, self.metrics) for e in self.evaluators
21
- ]
18
+ self.event_scalers = [EventScaler(e, self.metrics) for e in self.evaluators]
22
19
 
23
20
  self.azimuths = [e.grid_point[1].to(u.deg).value for e in self.evaluators]
24
21
  self.zeniths = [e.grid_point[2].to(u.deg).value for e in self.evaluators]
@@ -0,0 +1,202 @@
1
+ """Calculate the thresholds for energy, radial distance, and viewcone."""
2
+
3
+ import astropy.units as u
4
+ import numpy as np
5
+
6
+
7
+ class LimitCalculator:
8
+ """
9
+ Compute thresholds/limits for energy, radial distance, and viewcone.
10
+
11
+ Histograms are generated with simtools-generate-simtel-array-histograms with --hdf5 flag.
12
+
13
+ Event data is read from the generated HDF5 file from the following tables:
14
+ - angle_to_observing_position__triggered_showers_ for the viewcone limit.
15
+ - event_weight__ra3d__log10_e__ for the energy and radial distance limit.
16
+
17
+
18
+ Parameters
19
+ ----------
20
+ event_data_file : list of astropy.table.Table
21
+ The list of tables containing the event data.
22
+ """
23
+
24
+ def __init__(self, event_data_file_tables):
25
+ """
26
+ Initialize the LimitCalculator with the given event data file.
27
+
28
+ Parameters
29
+ ----------
30
+ event_data_file : list of astropy.table.Table
31
+ The list of tables containing the event data.
32
+ """
33
+ self.angle_to_observing_position__triggered_showers_ = None
34
+ self.event_weight__ra3d__log10_e__ = None
35
+
36
+ for table in event_data_file_tables:
37
+ if (
38
+ "Title" in table.meta
39
+ and table.meta["Title"] == "angle_to_observing_position__triggered_showers_"
40
+ ):
41
+ self.angle_to_observing_position__triggered_showers_ = table
42
+ elif "Title" in table.meta and table.meta["Title"] == "event_weight__ra3d__log10_e__":
43
+ self.event_weight__ra3d__log10_e__ = table
44
+
45
+ def _compute_limits(
46
+ self, event_weight_array, bin_edges, loss_fraction, axis=0, limit_type="lower"
47
+ ):
48
+ """
49
+ Compute the limits based on the loss fraction.
50
+
51
+ Parameters
52
+ ----------
53
+ event_weight_array : np.ndarray
54
+ Array of event weights.
55
+ bin_edges : np.ndarray
56
+ Array of bin edges.
57
+ loss_fraction : float
58
+ Fraction of events to be lost.
59
+ axis : int, optional
60
+ Axis along which to sum the event weights. Default is 0.
61
+ limit_type : str, optional
62
+ Type of limit ('lower' or 'upper'). Default is 'lower'.
63
+
64
+ Returns
65
+ -------
66
+ int
67
+ Bin index where the threshold is reached.
68
+ float
69
+ Bin edge value corresponding to the threshold.
70
+ """
71
+ projection = np.sum(event_weight_array, axis=axis)
72
+ bin_edge_value = None
73
+ cumulative_sum = None
74
+ if limit_type == "upper":
75
+ cumulative_sum = np.cumsum(projection)
76
+
77
+ elif limit_type == "lower":
78
+ cumulative_sum = np.cumsum(projection[::-1])
79
+
80
+ total_events = np.sum(projection)
81
+ threshold = (1 - loss_fraction) * total_events
82
+ bin_index = np.searchsorted(cumulative_sum, threshold)
83
+ if limit_type == "upper":
84
+ bin_edge_value = bin_edges[bin_index]
85
+ elif limit_type == "lower":
86
+ bin_edge_value = bin_edges[-bin_index]
87
+ return bin_index, bin_edge_value
88
+
89
+ def get_bin_edges_and_units(self, table, axis="x"):
90
+ """
91
+ Extract bin edges and units from the table metadata.
92
+
93
+ Parameters
94
+ ----------
95
+ table : astropy.table.Table
96
+ Table containing the event data.
97
+
98
+ Returns
99
+ -------
100
+ tuple
101
+ Tuple containing the bin edges and their units.
102
+ """
103
+ bin_edges = table.meta[f"{axis}_bin_edges"]
104
+ try:
105
+ bin_edges_unit = table.meta[f"{axis}_bin_edges_unit"]
106
+ except KeyError:
107
+ bin_edges_unit = ""
108
+ return bin_edges, bin_edges_unit
109
+
110
+ def compute_lower_energy_limit(self, loss_fraction):
111
+ """
112
+ Compute the lower energy limit in TeV based on the event loss fraction.
113
+
114
+ Parameters
115
+ ----------
116
+ loss_fraction : float
117
+ Fraction of events to be lost.
118
+
119
+ Returns
120
+ -------
121
+ astropy.units.Quantity
122
+ Lower energy limit.
123
+ """
124
+ event_weight_array = np.column_stack(
125
+ [
126
+ self.event_weight__ra3d__log10_e__[name]
127
+ for name in self.event_weight__ra3d__log10_e__.dtype.names
128
+ ]
129
+ )
130
+ bin_edges, bin_edges_unit = self.get_bin_edges_and_units(
131
+ self.event_weight__ra3d__log10_e__, axis="y"
132
+ )
133
+ if bin_edges_unit == "":
134
+ bin_edges_unit = "TeV"
135
+ _, lower_bin_edge_value = self._compute_limits(
136
+ event_weight_array, bin_edges, loss_fraction, axis=0, limit_type="lower"
137
+ )
138
+ return (10**lower_bin_edge_value) * u.Unit(bin_edges_unit)
139
+
140
+ def compute_upper_radial_distance(self, loss_fraction):
141
+ """
142
+ Compute the upper radial distance based on the event loss fraction.
143
+
144
+ Parameters
145
+ ----------
146
+ loss_fraction : float
147
+ Fraction of events to be lost.
148
+
149
+ Returns
150
+ -------
151
+ astropy.units.Quantity
152
+ Upper radial distance in m.
153
+ """
154
+ event_weight_array = np.column_stack(
155
+ [
156
+ self.event_weight__ra3d__log10_e__[name]
157
+ for name in self.event_weight__ra3d__log10_e__.dtype.names
158
+ ]
159
+ )
160
+ bin_edges, bin_edges_unit = self.get_bin_edges_and_units(
161
+ self.event_weight__ra3d__log10_e__, axis="x"
162
+ )
163
+ if bin_edges_unit == "":
164
+ bin_edges_unit = "m"
165
+ _, upper_bin_edge_value = self._compute_limits(
166
+ event_weight_array, bin_edges, loss_fraction, axis=1, limit_type="upper"
167
+ )
168
+ return upper_bin_edge_value * u.Unit(bin_edges_unit)
169
+
170
+ def compute_viewcone(self, loss_fraction):
171
+ """
172
+ Compute the viewcone based on the event loss fraction.
173
+
174
+ Parameters
175
+ ----------
176
+ loss_fraction : float
177
+ Fraction of events to be lost.
178
+
179
+ Returns
180
+ -------
181
+ astropy.units.Quantity
182
+ Viewcone radius in degrees.
183
+ """
184
+ angle_to_observing_position__triggered_showers = np.column_stack(
185
+ [
186
+ self.angle_to_observing_position__triggered_showers_[name]
187
+ for name in self.angle_to_observing_position__triggered_showers_.dtype.names
188
+ ]
189
+ )
190
+ bin_edges, bin_edges_unit = self.get_bin_edges_and_units(
191
+ self.angle_to_observing_position__triggered_showers_, axis="x"
192
+ )
193
+ if bin_edges_unit == "":
194
+ bin_edges_unit = "deg"
195
+ _, upper_bin_edge_value = self._compute_limits(
196
+ angle_to_observing_position__triggered_showers,
197
+ bin_edges,
198
+ loss_fraction,
199
+ axis=0,
200
+ limit_type="upper",
201
+ )
202
+ return upper_bin_edge_value * u.Unit(bin_edges_unit)