gammasimtools 0.25.0__py3-none-any.whl → 0.26.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. {gammasimtools-0.25.0.dist-info → gammasimtools-0.26.0.dist-info}/METADATA +2 -1
  2. {gammasimtools-0.25.0.dist-info → gammasimtools-0.26.0.dist-info}/RECORD +122 -121
  3. {gammasimtools-0.25.0.dist-info → gammasimtools-0.26.0.dist-info}/entry_points.txt +2 -1
  4. {gammasimtools-0.25.0.dist-info → gammasimtools-0.26.0.dist-info}/licenses/LICENSE +1 -1
  5. simtools/_version.py +2 -2
  6. simtools/application_control.py +35 -7
  7. simtools/applications/calculate_incident_angles.py +0 -2
  8. simtools/applications/convert_geo_coordinates_of_array_elements.py +1 -2
  9. simtools/applications/db_add_file_to_db.py +1 -1
  10. simtools/applications/db_add_simulation_model_from_repository_to_db.py +1 -1
  11. simtools/applications/db_add_value_from_json_to_db.py +1 -1
  12. simtools/applications/db_generate_compound_indexes.py +1 -1
  13. simtools/applications/db_get_array_layouts_from_db.py +2 -6
  14. simtools/applications/db_get_file_from_db.py +1 -1
  15. simtools/applications/db_get_parameter_from_db.py +1 -1
  16. simtools/applications/db_inspect_databases.py +1 -1
  17. simtools/applications/db_upload_model_repository.py +1 -1
  18. simtools/applications/derive_ctao_array_layouts.py +1 -2
  19. simtools/applications/derive_mirror_rnda.py +1 -3
  20. simtools/applications/derive_psf_parameters.py +0 -1
  21. simtools/applications/derive_pulse_shape_parameters.py +0 -1
  22. simtools/applications/derive_trigger_rates.py +1 -1
  23. simtools/applications/docs_produce_array_element_report.py +2 -8
  24. simtools/applications/docs_produce_calibration_reports.py +1 -3
  25. simtools/applications/docs_produce_model_parameter_reports.py +0 -2
  26. simtools/applications/docs_produce_simulation_configuration_report.py +1 -3
  27. simtools/applications/generate_array_config.py +0 -1
  28. simtools/applications/generate_corsika_histograms.py +48 -235
  29. simtools/applications/generate_regular_arrays.py +5 -35
  30. simtools/applications/generate_simtel_event_data.py +2 -2
  31. simtools/applications/maintain_simulation_model_add_production.py +2 -2
  32. simtools/applications/maintain_simulation_model_write_array_element_positions.py +87 -0
  33. simtools/applications/plot_array_layout.py +5 -111
  34. simtools/applications/plot_simulated_event_distributions.py +57 -0
  35. simtools/applications/plot_tabular_data.py +0 -1
  36. simtools/applications/plot_tabular_data_for_model_parameter.py +1 -6
  37. simtools/applications/production_derive_corsika_limits.py +1 -1
  38. simtools/applications/production_generate_grid.py +0 -1
  39. simtools/applications/run_application.py +1 -1
  40. simtools/applications/simulate_flasher.py +0 -2
  41. simtools/applications/simulate_illuminator.py +0 -1
  42. simtools/applications/simulate_pedestals.py +1 -5
  43. simtools/applications/simulate_prod.py +1 -5
  44. simtools/applications/simulate_prod_htcondor_generator.py +1 -1
  45. simtools/applications/submit_array_layouts.py +2 -4
  46. simtools/applications/submit_model_parameter_from_external.py +1 -3
  47. simtools/applications/validate_camera_efficiency.py +0 -1
  48. simtools/applications/validate_camera_fov.py +0 -1
  49. simtools/applications/validate_cumulative_psf.py +0 -2
  50. simtools/applications/validate_optics.py +0 -13
  51. simtools/camera/camera_efficiency.py +1 -6
  52. simtools/camera/single_photon_electron_spectrum.py +2 -1
  53. simtools/configuration/commandline_parser.py +35 -2
  54. simtools/configuration/configurator.py +6 -11
  55. simtools/corsika/corsika_config.py +16 -21
  56. simtools/corsika/corsika_histograms.py +411 -1735
  57. simtools/corsika/primary_particle.py +1 -1
  58. simtools/data_model/metadata_collector.py +5 -2
  59. simtools/data_model/metadata_model.py +0 -4
  60. simtools/data_model/model_data_writer.py +13 -15
  61. simtools/data_model/validate_data.py +1 -3
  62. simtools/db/db_handler.py +19 -8
  63. simtools/dependencies.py +81 -38
  64. simtools/io/ascii_handler.py +4 -2
  65. simtools/io/table_handler.py +1 -1
  66. simtools/layout/array_layout.py +4 -12
  67. simtools/layout/array_layout_utils.py +226 -57
  68. simtools/model/array_model.py +1 -13
  69. simtools/model/calibration_model.py +0 -4
  70. simtools/model/legacy_model_parameter.py +134 -0
  71. simtools/model/model_parameter.py +24 -13
  72. simtools/model/model_utils.py +1 -6
  73. simtools/model/site_model.py +0 -4
  74. simtools/model/telescope_model.py +6 -11
  75. simtools/production_configuration/derive_corsika_limits.py +6 -11
  76. simtools/production_configuration/interpolation_handler.py +16 -16
  77. simtools/ray_tracing/incident_angles.py +5 -11
  78. simtools/ray_tracing/mirror_panel_psf.py +3 -7
  79. simtools/ray_tracing/psf_analysis.py +18 -19
  80. simtools/ray_tracing/psf_parameter_optimisation.py +0 -1
  81. simtools/ray_tracing/ray_tracing.py +6 -15
  82. simtools/reporting/docs_auto_report_generator.py +8 -13
  83. simtools/reporting/docs_read_parameters.py +2 -8
  84. simtools/runners/corsika_runner.py +5 -9
  85. simtools/runners/corsika_simtel_runner.py +3 -8
  86. simtools/runners/simtel_runner.py +0 -5
  87. simtools/runners/simtools_runner.py +2 -4
  88. simtools/settings.py +154 -0
  89. simtools/{io/eventio_handler.py → sim_events/file_info.py} +3 -3
  90. simtools/{simtel/simtel_io_event_histograms.py → sim_events/histograms.py} +25 -15
  91. simtools/{simtel/simtel_io_event_reader.py → sim_events/reader.py} +20 -17
  92. simtools/{simtel/simtel_io_event_writer.py → sim_events/writer.py} +84 -25
  93. simtools/simtel/pulse_shapes.py +7 -2
  94. simtools/simtel/simtel_config_writer.py +79 -36
  95. simtools/simtel/simtel_table_reader.py +6 -4
  96. simtools/simtel/simulator_array.py +4 -11
  97. simtools/simtel/simulator_camera_efficiency.py +4 -6
  98. simtools/simtel/simulator_light_emission.py +69 -24
  99. simtools/simtel/simulator_ray_tracing.py +4 -10
  100. simtools/simulator.py +7 -14
  101. simtools/telescope_trigger_rates.py +3 -4
  102. simtools/testing/assertions.py +84 -33
  103. simtools/testing/configuration.py +1 -2
  104. simtools/testing/helpers.py +2 -3
  105. simtools/testing/log_inspector.py +1 -0
  106. simtools/testing/sim_telarray_metadata.py +1 -1
  107. simtools/testing/validate_output.py +34 -23
  108. simtools/utils/general.py +37 -0
  109. simtools/utils/geometry.py +0 -77
  110. simtools/utils/names.py +5 -5
  111. simtools/visualization/legend_handlers.py +7 -6
  112. simtools/visualization/plot_array_layout.py +91 -16
  113. simtools/visualization/plot_corsika_histograms.py +143 -605
  114. simtools/visualization/plot_mirrors.py +1 -4
  115. simtools/visualization/plot_pixels.py +2 -4
  116. simtools/visualization/plot_psf.py +0 -1
  117. simtools/visualization/plot_simtel_event_histograms.py +4 -4
  118. simtools/visualization/plot_simtel_events.py +6 -11
  119. simtools/visualization/plot_tables.py +8 -19
  120. simtools/visualization/visualize.py +22 -2
  121. simtools/applications/db_development_tools/write_array_elements_positions_to_repository.py +0 -160
  122. simtools/applications/print_version.py +0 -53
  123. simtools/io/hdf5_handler.py +0 -139
  124. {gammasimtools-0.25.0.dist-info → gammasimtools-0.26.0.dist-info}/WHEEL +0 -0
  125. {gammasimtools-0.25.0.dist-info → gammasimtools-0.26.0.dist-info}/top_level.txt +0 -0
@@ -26,11 +26,10 @@ MARKDOWN_LINK_RE = re.compile(r"\[([^\]]+)\]\(([^)]+)\)")
26
26
  class ReadParameters:
27
27
  """Read and manage model parameter data for report generation."""
28
28
 
29
- def __init__(self, db_config, args, output_path):
29
+ def __init__(self, args, output_path):
30
30
  """Initialise class."""
31
31
  self._logger = logging.getLogger(__name__)
32
- self.db = db_handler.DatabaseHandler(db_config=db_config)
33
- self.db_config = db_config
32
+ self.db = db_handler.DatabaseHandler()
34
33
  self.array_element = args.get("telescope", None)
35
34
  self.site = args.get("site", None)
36
35
  self.model_version = args.get("model_version", None)
@@ -109,7 +108,6 @@ class ReadParameters:
109
108
  plot_pixels.plot(
110
109
  config=plot_config,
111
110
  output_file=Path(f"{outpath}/{plot_name}"),
112
- db_config=self.db_config,
113
111
  )
114
112
  plot_names.append(plot_name)
115
113
  else:
@@ -142,7 +140,6 @@ class ReadParameters:
142
140
  plot_mirrors.plot(
143
141
  config=plot_config,
144
142
  output_file=Path(f"{outpath}/{plot_name}"),
145
- db_config=self.db_config,
146
143
  )
147
144
  plot_names.append(plot_name)
148
145
  else:
@@ -165,7 +162,6 @@ class ReadParameters:
165
162
  telescope=tel,
166
163
  output_path=outpath,
167
164
  plot_type="all",
168
- db_config=self.db_config,
169
165
  )
170
166
 
171
167
  if not config_data:
@@ -180,7 +176,6 @@ class ReadParameters:
180
176
  plot_tables.plot(
181
177
  config=plot_config,
182
178
  output_file=image_output_file,
183
- db_config=self.db_config,
184
179
  )
185
180
  plt.close("all")
186
181
 
@@ -582,7 +577,6 @@ class ReadParameters:
582
577
  telescope_name=self.array_element,
583
578
  model_version=self.model_version,
584
579
  label="reports",
585
- db_config=self.db_config,
586
580
  ignore_software_version=True,
587
581
  )
588
582
 
@@ -2,8 +2,8 @@
2
2
 
3
3
  import logging
4
4
  import stat
5
- from pathlib import Path
6
5
 
6
+ from simtools import settings
7
7
  from simtools.io import io_handler
8
8
  from simtools.runners.runner_services import RunnerServices
9
9
 
@@ -26,8 +26,6 @@ class CorsikaRunner:
26
26
  ----------
27
27
  corsika_config_data: CorsikaConfig
28
28
  CORSIKA configuration.
29
- simtel_path: str or Path
30
- Location of source of the sim_telarray/CORSIKA package.
31
29
  label: str
32
30
  Instance label.
33
31
  keep_seeds: bool
@@ -41,7 +39,6 @@ class CorsikaRunner:
41
39
  def __init__(
42
40
  self,
43
41
  corsika_config,
44
- simtel_path,
45
42
  label=None,
46
43
  keep_seeds=False,
47
44
  use_multipipe=False,
@@ -57,7 +54,6 @@ class CorsikaRunner:
57
54
  self._use_multipipe = use_multipipe
58
55
  self.curved_atmosphere_min_zenith_angle = curved_atmosphere_min_zenith_angle
59
56
 
60
- self._simtel_path = Path(simtel_path)
61
57
  self.io_handler = io_handler.IOHandler()
62
58
 
63
59
  self.runner_service = RunnerServices(corsika_config, label)
@@ -178,7 +174,7 @@ class CorsikaRunner:
178
174
  str
179
175
  pfp command.
180
176
  """
181
- cmd = self._simtel_path.joinpath("sim_telarray/bin/pfp")
177
+ cmd = settings.config.sim_telarray_path / "bin/pfp"
182
178
  cmd = str(cmd) + f" -V -DWITHOUT_MULTIPIPE - < {corsika_input_file}"
183
179
  cmd += f" > {input_tmp_file} || exit\n"
184
180
  return cmd
@@ -203,17 +199,17 @@ class CorsikaRunner:
203
199
  autoinputs command.
204
200
  """
205
201
  if self.corsika_config.use_curved_atmosphere:
206
- corsika_bin_path = self._simtel_path.joinpath("corsika-run/corsika-curved")
202
+ corsika_bin_path = settings.config.corsika_exe_curved
207
203
  self._logger.debug("Using curved-atmosphere CORSIKA binary.")
208
204
  else:
209
- corsika_bin_path = self._simtel_path.joinpath("corsika-run/corsika")
205
+ corsika_bin_path = settings.config.corsika_exe
210
206
  self._logger.debug("Using flat-atmosphere CORSIKA binary.")
211
207
 
212
208
  log_file = self.get_file_name(file_type="log", run_number=run_number)
213
209
  if self._use_multipipe:
214
210
  log_file = log_file.with_name(f"multipipe_{log_file.name}")
215
211
 
216
- cmd = self._simtel_path.joinpath("sim_telarray/bin/corsika_autoinputs")
212
+ cmd = settings.config.sim_telarray_path.joinpath("bin/corsika_autoinputs")
217
213
  cmd = str(cmd) + f" --run {corsika_bin_path}"
218
214
  cmd += f" -R {run_number}"
219
215
  cmd += ' -p "$CORSIKA_DATA"'
@@ -5,6 +5,7 @@ import stat
5
5
  from pathlib import Path
6
6
 
7
7
  import simtools.utils.general as gen
8
+ from simtools import settings
8
9
  from simtools.runners.corsika_runner import CorsikaRunner
9
10
  from simtools.simtel.simulator_array import SimulatorArray
10
11
 
@@ -21,8 +22,6 @@ class CorsikaSimtelRunner:
21
22
  corsika_config : CorsikaConfig or list of CorsikaConfig
22
23
  A list of "CorsikaConfig" instances which
23
24
  contain the CORSIKA configuration parameters.
24
- simtel_path : str or Path
25
- Location of the sim_telarray package.
26
25
  label : str
27
26
  Label.
28
27
  keep_seeds : bool
@@ -38,7 +37,6 @@ class CorsikaSimtelRunner:
38
37
  def __init__(
39
38
  self,
40
39
  corsika_config,
41
- simtel_path,
42
40
  label=None,
43
41
  keep_seeds=False,
44
42
  use_multipipe=False,
@@ -52,7 +50,6 @@ class CorsikaSimtelRunner:
52
50
  # the base corsika config is the one used to define the CORSIKA specific parameters.
53
51
  # The others are used for the array configurations.
54
52
  self.base_corsika_config = self.corsika_config[0]
55
- self._simtel_path = simtel_path
56
53
  self.sim_telarray_seeds = sim_telarray_seeds
57
54
  self.label = label
58
55
  self.sequential = "--sequential" if sequential else ""
@@ -60,7 +57,6 @@ class CorsikaSimtelRunner:
60
57
  self.base_corsika_config.set_output_file_and_directory(use_multipipe)
61
58
  self.corsika_runner = CorsikaRunner(
62
59
  corsika_config=self.base_corsika_config,
63
- simtel_path=simtel_path,
64
60
  label=label,
65
61
  keep_seeds=keep_seeds,
66
62
  use_multipipe=use_multipipe,
@@ -73,7 +69,6 @@ class CorsikaSimtelRunner:
73
69
  self.simulator_array.append(
74
70
  SimulatorArray(
75
71
  corsika_config=_corsika_config,
76
- simtel_path=simtel_path,
77
72
  label=label,
78
73
  use_multipipe=use_multipipe,
79
74
  sim_telarray_seeds=sim_telarray_seeds,
@@ -171,8 +166,8 @@ class CorsikaSimtelRunner:
171
166
  "run_cta_multipipe"
172
167
  )
173
168
  with open(multipipe_script, "w", encoding="utf-8") as file:
174
- multipipe_command = Path(self._simtel_path).joinpath(
175
- f"sim_telarray/bin/multipipe_corsika -c {multipipe_file} {self.sequential} "
169
+ multipipe_command = settings.config.sim_telarray_path.joinpath(
170
+ f"bin/multipipe_corsika -c {multipipe_file} {self.sequential} "
176
171
  "|| echo 'Fan-out failed'"
177
172
  )
178
173
  file.write(f"{multipipe_command}")
@@ -2,7 +2,6 @@
2
2
 
3
3
  import logging
4
4
  import subprocess
5
- from pathlib import Path
6
5
 
7
6
  import simtools.utils.general as gen
8
7
  from simtools.runners.runner_services import RunnerServices
@@ -25,8 +24,6 @@ class SimtelRunner:
25
24
 
26
25
  Parameters
27
26
  ----------
28
- simtel_path: str or Path
29
- Location of sim_telarray installation.
30
27
  label: str
31
28
  Instance label. Important for output file naming.
32
29
  corsika_config: CorsikaConfig
@@ -39,7 +36,6 @@ class SimtelRunner:
39
36
 
40
37
  def __init__(
41
38
  self,
42
- simtel_path,
43
39
  label=None,
44
40
  corsika_config=None,
45
41
  use_multipipe=False,
@@ -48,7 +44,6 @@ class SimtelRunner:
48
44
  """Initialize SimtelRunner."""
49
45
  self._logger = logging.getLogger(__name__)
50
46
 
51
- self._simtel_path = Path(simtel_path)
52
47
  self.label = label
53
48
  self._base_directory = None
54
49
  self.calibration_run_mode = calibration_run_mode
@@ -9,7 +9,7 @@ from simtools import dependencies
9
9
  from simtools.io import ascii_handler
10
10
 
11
11
 
12
- def run_applications(args_dict, db_config, logger):
12
+ def run_applications(args_dict, logger):
13
13
  """
14
14
  Run simtools applications step-by-step as defined in a configuration file.
15
15
 
@@ -17,8 +17,6 @@ def run_applications(args_dict, db_config, logger):
17
17
  ----------
18
18
  args_dict : dict
19
19
  Dictionary containing command line arguments.
20
- db_config : dict
21
- Database configuration
22
20
  logger : logging.Logger
23
21
  Logger for logging application output.
24
22
  """
@@ -33,7 +31,7 @@ def run_applications(args_dict, db_config, logger):
33
31
 
34
32
  with log_file.open("w", encoding="utf-8") as file:
35
33
  file.write("Running simtools applications\n")
36
- file.write(dependencies.get_version_string(db_config, run_time))
34
+ file.write(dependencies.get_version_string(run_time))
37
35
 
38
36
  for config in configurations:
39
37
  app = config.get("application")
simtools/settings.py ADDED
@@ -0,0 +1,154 @@
1
+ """Centralized settings object with command line and environment variables."""
2
+
3
+ import os
4
+ from pathlib import Path
5
+ from types import MappingProxyType
6
+
7
+
8
+ class _Config:
9
+ """Centralized settings object with command line and environment variables."""
10
+
11
+ def __init__(self):
12
+ """Initialize empty config."""
13
+ self._args = {}
14
+ self._db_config = {}
15
+ self._sim_telarray_path = None
16
+ self._sim_telarray_exe = None
17
+ self._corsika_path = None
18
+ self._corsika_exe = None
19
+
20
+ def load(self, args=None, db_config=None):
21
+ """
22
+ Load configuration from command line arguments and environment variables.
23
+
24
+ For paths, first check for environment variables, then command line arguments.
25
+
26
+ Parameters
27
+ ----------
28
+ args : dict, optional
29
+ Command line arguments.
30
+ db_config : dict, optional
31
+ Database configuration.
32
+
33
+ """
34
+ self._args = MappingProxyType(args) if args is not None else {}
35
+ self._db_config = MappingProxyType(db_config) if db_config is not None else {}
36
+ self._sim_telarray_path = (
37
+ args.get("sim_telarray_path")
38
+ if args is not None and "sim_telarray_path" in args
39
+ else os.getenv("SIMTOOLS_SIM_TELARRAY_PATH")
40
+ )
41
+
42
+ self._sim_telarray_exe = (
43
+ args.get("sim_telarray_executable")
44
+ if args is not None and "sim_telarray_executable" in args
45
+ else os.getenv("SIMTOOLS_SIM_TELARRAY_EXECUTABLE", "sim_telarray")
46
+ )
47
+
48
+ self._corsika_path = (
49
+ args.get("corsika_path")
50
+ if args is not None and "corsika_path" in args
51
+ else os.getenv("SIMTOOLS_CORSIKA_PATH")
52
+ )
53
+
54
+ self._corsika_exe = self._get_corsika_exec() if self._corsika_path is not None else None
55
+
56
+ def _get_corsika_exec(self):
57
+ """
58
+ Get the CORSIKA executable from environment variable or command line argument.
59
+
60
+ Build the executable name based on configured interaction models. Fall back to
61
+ legacy naming (simply "corsika") if models are not specified.
62
+ """
63
+ he_model = (
64
+ self._args.get("corsika_he_interaction")
65
+ if self._args is not None and "corsika_he_interaction" in self._args
66
+ else os.getenv("SIMTOOLS_CORSIKA_HE_INTERACTION")
67
+ )
68
+
69
+ le_model = (
70
+ self._args.get("corsika_le_interaction")
71
+ if self._args is not None and "corsika_le_interaction" in self._args
72
+ else os.getenv("SIMTOOLS_CORSIKA_LE_INTERACTION")
73
+ )
74
+
75
+ if he_model and le_model:
76
+ corsika_exe = self.corsika_path / f"corsika_{he_model}_{le_model}_flat"
77
+ if corsika_exe.exists():
78
+ return corsika_exe
79
+
80
+ # legacy naming
81
+ return self.corsika_path / "corsika"
82
+
83
+ @property
84
+ def args(self):
85
+ """Command line arguments."""
86
+ return self._args
87
+
88
+ @property
89
+ def db_config(self):
90
+ """Database configuration."""
91
+ return self._db_config
92
+
93
+ @property
94
+ def sim_telarray_path(self):
95
+ """Path to the sim_telarray installation directory."""
96
+ return Path(self._sim_telarray_path) if self._sim_telarray_path is not None else None
97
+
98
+ @property
99
+ def sim_telarray_exe(self):
100
+ """Path to the sim_telarray executable."""
101
+ return (
102
+ Path(self._sim_telarray_path) / "bin" / self._sim_telarray_exe
103
+ if self._sim_telarray_path is not None
104
+ else None
105
+ )
106
+
107
+ @property
108
+ def sim_telarray_exe_debug_trace(self):
109
+ """Path to the debug trace version of the sim_telarray executable."""
110
+ return (
111
+ Path(self._sim_telarray_path) / "bin" / (self._sim_telarray_exe + "_debug_trace")
112
+ if self._sim_telarray_path is not None
113
+ else None
114
+ )
115
+
116
+ @property
117
+ def corsika_path(self):
118
+ """Path to the CORSIKA installation directory."""
119
+ return Path(self._corsika_path) if self._corsika_path is not None else None
120
+
121
+ @property
122
+ def corsika_exe(self):
123
+ """Path to the CORSIKA executable."""
124
+ return (
125
+ Path(self._corsika_path) / self._corsika_exe if self._corsika_path is not None else None
126
+ )
127
+
128
+ @property
129
+ def corsika_exe_curved(self):
130
+ """Path to the curved version of the CORSIKA executable."""
131
+ if self._corsika_exe is None:
132
+ return None
133
+ corsika_curved = (
134
+ self._corsika_exe.name.replace("_flat", "_curved")
135
+ if "_flat" in self._corsika_exe.name
136
+ else self._corsika_exe.name + "-curved" # legacy naming convention
137
+ )
138
+ return Path(self._corsika_path) / corsika_curved if self._corsika_path is not None else None
139
+
140
+ @property
141
+ def corsika_dummy_file(self):
142
+ """
143
+ Path to a dummy CORSIKA file required by sim_telarray for ray-tracing simulations.
144
+
145
+ This file does not need to exist; sim_telarray only requires a file path.
146
+ """
147
+ return (
148
+ self.sim_telarray_path / "run9991.corsika.gz"
149
+ if self._sim_telarray_path is not None
150
+ else None
151
+ )
152
+
153
+
154
+ config = _Config()
@@ -24,7 +24,7 @@ def get_corsika_run_number(file):
24
24
  int, None
25
25
  CORSIKA run number. Returns None if not found.
26
26
  """
27
- run_header = get_combined_corsika_run_header(file)
27
+ run_header = get_combined_eventio_run_header(file)
28
28
  if run_header and "run" in run_header:
29
29
  return run_header["run"]
30
30
  run_header, _ = get_corsika_run_and_event_headers(file)
@@ -34,9 +34,9 @@ def get_corsika_run_number(file):
34
34
  return None
35
35
 
36
36
 
37
- def get_combined_corsika_run_header(sim_telarray_file):
37
+ def get_combined_eventio_run_header(sim_telarray_file):
38
38
  """
39
- Return the CORSIKA run header information from an sim_telarray file.
39
+ Return the CORSIKA run header information from an eventio (sim_telarray) file.
40
40
 
41
41
  Reads both RunHeader and MCRunHeader object from file and returns a merged dictionary.
42
42
  Adds primary id from the first event.
@@ -1,4 +1,4 @@
1
- """Histograms for shower and triggered events."""
1
+ """Histograms for shower and (if available) triggered events."""
2
2
 
3
3
  import copy
4
4
  import logging
@@ -6,12 +6,12 @@ import logging
6
6
  import astropy.units as u
7
7
  import numpy as np
8
8
 
9
- from simtools.simtel.simtel_io_event_reader import SimtelIOEventDataReader
9
+ from simtools.sim_events.reader import EventDataReader
10
10
 
11
11
 
12
- class SimtelIOEventHistograms:
12
+ class EventDataHistograms:
13
13
  """
14
- Generate and fill histograms for shower and triggered events.
14
+ Generate and fill histograms for shower and (if available) triggered events.
15
15
 
16
16
  Event data is read from the reduced MC event data file.
17
17
  Calculate cumulative and relative (efficiency) distributions.
@@ -35,7 +35,7 @@ class SimtelIOEventHistograms:
35
35
  self.histograms = {}
36
36
  self.file_info = {}
37
37
 
38
- self.reader = SimtelIOEventDataReader(event_data_file, telescope_list=telescope_list)
38
+ self.reader = EventDataReader(event_data_file, telescope_list=telescope_list)
39
39
 
40
40
  def fill(self):
41
41
  """
@@ -63,12 +63,10 @@ class SimtelIOEventHistograms:
63
63
 
64
64
  self.histograms = self._define_histograms(event_data, triggered_data, shower_data)
65
65
 
66
- for name, data in self.histograms.items():
67
- self._logger.debug(f"Filling histogram {name}")
66
+ for data in self.histograms.values():
68
67
  self._fill_histogram_and_bin_edges(data)
69
68
 
70
69
  self.print_summary()
71
-
72
70
  self.calculate_efficiency_data()
73
71
  self.calculate_cumulative_data()
74
72
 
@@ -202,11 +200,14 @@ class SimtelIOEventHistograms:
202
200
  Adds to existing histogram if present, otherwise initializes it.
203
201
  """
204
202
  if data["1d"]:
203
+ if data["event_data"] is None:
204
+ return
205
205
  hist, _ = np.histogram(
206
- getattr(data["event_data"], data["event_data_column"]),
207
- bins=data["bin_edges"],
206
+ getattr(data["event_data"], data["event_data_column"]), bins=data["bin_edges"]
208
207
  )
209
208
  else:
209
+ if data["event_data"][0] is None or data["event_data"][1] is None:
210
+ return
210
211
  hist, _, _ = np.histogram2d(
211
212
  getattr(data["event_data"][0], data["event_data_column"][0]),
212
213
  getattr(data["event_data"][1], data["event_data_column"][1]),
@@ -227,6 +228,8 @@ class SimtelIOEventHistograms:
227
228
  dict
228
229
  Dictionary containing the efficiency histograms.
229
230
  """
231
+ if not any(isinstance(ds, dict) and "TRIGGERS" in ds for ds in self.reader.data_sets):
232
+ return None
230
233
 
231
234
  def calculate_efficiency(trig_hist, mc_hist):
232
235
  with np.errstate(divide="ignore", invalid="ignore"):
@@ -295,11 +298,15 @@ class SimtelIOEventHistograms:
295
298
  """Return bins for the viewcone histogram."""
296
299
  if "viewcone_bin_edges" in self.histograms:
297
300
  return self.histograms["viewcone_bin_edges"]
298
- return np.linspace(
299
- self.file_info.get("viewcone_min", 0.0 * u.deg).to("deg").value,
300
- self.file_info.get("viewcone_max", 20.0 * u.deg).to("deg").value,
301
- 100,
302
- )
301
+
302
+ viewcone_min = self.file_info.get("viewcone_min", 0.0 * u.deg).to("deg").value
303
+ viewcone_max = self.file_info.get("viewcone_max", 20.0 * u.deg).to("deg").value
304
+
305
+ # avoid zero-width bins
306
+ if viewcone_min == viewcone_max:
307
+ viewcone_max = viewcone_min + 0.5
308
+
309
+ return np.linspace(viewcone_min, viewcone_max, 100)
303
310
 
304
311
  def calculate_cumulative_data(self):
305
312
  """
@@ -310,6 +317,9 @@ class SimtelIOEventHistograms:
310
317
  dict
311
318
  Dictionary containing the cumulative histograms.
312
319
  """
320
+ if not any(isinstance(ds, dict) and "TRIGGERS" in ds for ds in self.reader.data_sets):
321
+ return None
322
+
313
323
  cumulative_data = {}
314
324
  suffix = "_cumulative"
315
325
 
@@ -44,11 +44,11 @@ class TriggeredEventData:
44
44
  angular_distance: list[float] = field(default_factory=list)
45
45
 
46
46
 
47
- class SimtelIOEventDataReader:
47
+ class EventDataReader:
48
48
  """Read reduced MC data set stored in astropy tables."""
49
49
 
50
50
  def __init__(self, event_data_file, telescope_list=None):
51
- """Initialize SimtelIOEventDataReader."""
51
+ """Initialize EventDataReader."""
52
52
  self._logger = logging.getLogger(__name__)
53
53
  self.telescope_list = telescope_list
54
54
 
@@ -61,7 +61,7 @@ class SimtelIOEventDataReader:
61
61
 
62
62
  Rearrange dictionary with tables names into a list of dictionaries
63
63
  under the assumption that the file contains the tables "SHOWERS",
64
- "TRIGGERS", and "FILE_INFO".
64
+ "TRIGGERS", and "FILE_INFO". Note that not all tables need to be present.
65
65
 
66
66
  Parameters
67
67
  ----------
@@ -88,13 +88,13 @@ class SimtelIOEventDataReader:
88
88
  except (ValueError, AttributeError):
89
89
  sorted_indices = [0] # Handle the case where the key is only "SHOWERS"
90
90
  for i in sorted_indices:
91
- data_sets.append(
92
- {
93
- "SHOWERS": dataset_dict["SHOWERS"][i],
94
- "TRIGGERS": dataset_dict["TRIGGERS"][i],
95
- "FILE_INFO": dataset_dict["FILE_INFO"][i],
96
- }
97
- )
91
+ entry = {
92
+ "SHOWERS": dataset_dict["SHOWERS"][i],
93
+ "FILE_INFO": dataset_dict["FILE_INFO"][i],
94
+ }
95
+ if i < len(dataset_dict["TRIGGERS"]) and dataset_dict["TRIGGERS"][i]:
96
+ entry["TRIGGERS"] = dataset_dict["TRIGGERS"][i]
97
+ data_sets.append(entry)
98
98
 
99
99
  return data_sets
100
100
 
@@ -248,20 +248,23 @@ class SimtelIOEventDataReader:
248
248
  tuple
249
249
  A tuple with file info table, shower, triggered shower, and triggered event data.
250
250
  """
251
- table_name_map = table_name_map or {}
252
251
 
253
- def get_name(key):
254
- return table_name_map.get(key, key)
252
+ def get_name(k):
253
+ return k if table_name_map is None else table_name_map.get(k)
255
254
 
256
- tables = table_handler.read_tables(
257
- event_data_file,
258
- table_names=[get_name(k) for k in ("SHOWERS", "TRIGGERS", "FILE_INFO")],
259
- )
255
+ table_names = [
256
+ name for k in ("SHOWERS", "TRIGGERS", "FILE_INFO") if (name := get_name(k)) is not None
257
+ ]
258
+ tables = table_handler.read_tables(event_data_file, table_names=table_names)
260
259
  self.reduced_file_info = self.get_reduced_simulation_file_info(
261
260
  tables[get_name("FILE_INFO")]
262
261
  )
263
262
 
264
263
  shower_data = self._table_to_shower_data(tables[get_name("SHOWERS")])
264
+ if tables.get(get_name("TRIGGERS")) is None:
265
+ self._logger.info("No triggered event data found in the file.")
266
+ return tables[get_name("FILE_INFO")], shower_data, None, None
267
+
265
268
  triggered_data = self._table_to_triggered_data(tables[get_name("TRIGGERS")])
266
269
  triggered_shower = self._get_triggered_shower_data(
267
270
  shower_data,