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.
Files changed (138) hide show
  1. {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/METADATA +6 -1
  2. {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/RECORD +135 -130
  3. {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/WHEEL +1 -1
  4. {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/entry_points.txt +3 -2
  5. {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/licenses/LICENSE +1 -1
  6. simtools/_version.py +2 -2
  7. simtools/application_control.py +35 -7
  8. simtools/applications/convert_geo_coordinates_of_array_elements.py +3 -3
  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 +3 -7
  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/{calculate_incident_angles.py → derive_incident_angle.py} +16 -18
  20. simtools/applications/derive_mirror_rnda.py +112 -180
  21. simtools/applications/derive_psf_parameters.py +0 -1
  22. simtools/applications/derive_pulse_shape_parameters.py +0 -1
  23. simtools/applications/derive_trigger_rates.py +1 -1
  24. simtools/applications/docs_produce_array_element_report.py +2 -8
  25. simtools/applications/docs_produce_calibration_reports.py +1 -3
  26. simtools/applications/docs_produce_model_parameter_reports.py +0 -2
  27. simtools/applications/docs_produce_simulation_configuration_report.py +1 -3
  28. simtools/applications/generate_array_config.py +0 -1
  29. simtools/applications/generate_corsika_histograms.py +79 -229
  30. simtools/applications/generate_regular_arrays.py +76 -69
  31. simtools/applications/generate_simtel_event_data.py +2 -2
  32. simtools/applications/maintain_simulation_model_add_production.py +2 -2
  33. simtools/applications/maintain_simulation_model_write_array_element_positions.py +87 -0
  34. simtools/applications/plot_array_layout.py +5 -111
  35. simtools/applications/plot_simulated_event_distributions.py +57 -0
  36. simtools/applications/plot_tabular_data.py +0 -1
  37. simtools/applications/plot_tabular_data_for_model_parameter.py +1 -6
  38. simtools/applications/production_derive_corsika_limits.py +1 -1
  39. simtools/applications/production_generate_grid.py +0 -1
  40. simtools/applications/run_application.py +1 -1
  41. simtools/applications/simulate_flasher.py +3 -15
  42. simtools/applications/simulate_illuminator.py +2 -11
  43. simtools/applications/simulate_pedestals.py +1 -5
  44. simtools/applications/simulate_prod.py +8 -11
  45. simtools/applications/simulate_prod_htcondor_generator.py +1 -1
  46. simtools/applications/submit_array_layouts.py +2 -4
  47. simtools/applications/submit_data_from_external.py +2 -1
  48. simtools/applications/submit_model_parameter_from_external.py +1 -3
  49. simtools/applications/validate_camera_efficiency.py +28 -28
  50. simtools/applications/validate_camera_fov.py +0 -1
  51. simtools/applications/validate_cumulative_psf.py +1 -5
  52. simtools/applications/validate_optics.py +2 -14
  53. simtools/atmosphere.py +83 -0
  54. simtools/camera/camera_efficiency.py +171 -53
  55. simtools/camera/single_photon_electron_spectrum.py +8 -7
  56. simtools/configuration/commandline_parser.py +82 -11
  57. simtools/configuration/configurator.py +6 -11
  58. simtools/constants.py +5 -0
  59. simtools/corsika/corsika_config.py +100 -202
  60. simtools/corsika/corsika_histograms.py +561 -1708
  61. simtools/corsika/primary_particle.py +1 -1
  62. simtools/data_model/metadata_collector.py +5 -2
  63. simtools/data_model/metadata_model.py +0 -4
  64. simtools/data_model/model_data_writer.py +59 -64
  65. simtools/data_model/schema.py +2 -0
  66. simtools/data_model/validate_data.py +1 -3
  67. simtools/db/db_handler.py +23 -10
  68. simtools/db/mongo_db.py +2 -2
  69. simtools/dependencies.py +81 -38
  70. simtools/io/ascii_handler.py +55 -5
  71. simtools/io/io_handler.py +23 -12
  72. simtools/io/table_handler.py +1 -1
  73. simtools/job_execution/job_manager.py +154 -79
  74. simtools/job_execution/process_pool.py +137 -0
  75. simtools/layout/array_layout.py +4 -13
  76. simtools/layout/array_layout_utils.py +348 -57
  77. simtools/model/array_model.py +23 -63
  78. simtools/model/calibration_model.py +4 -8
  79. simtools/model/legacy_model_parameter.py +134 -0
  80. simtools/model/model_parameter.py +147 -86
  81. simtools/model/model_utils.py +40 -6
  82. simtools/model/site_model.py +4 -8
  83. simtools/model/telescope_model.py +10 -16
  84. simtools/production_configuration/derive_corsika_limits.py +6 -11
  85. simtools/production_configuration/interpolation_handler.py +16 -16
  86. simtools/ray_tracing/incident_angles.py +92 -17
  87. simtools/ray_tracing/mirror_panel_psf.py +338 -222
  88. simtools/ray_tracing/psf_analysis.py +62 -48
  89. simtools/ray_tracing/psf_parameter_optimisation.py +3 -3
  90. simtools/ray_tracing/ray_tracing.py +43 -25
  91. simtools/reporting/docs_auto_report_generator.py +8 -13
  92. simtools/reporting/docs_read_parameters.py +2 -8
  93. simtools/runners/corsika_runner.py +52 -195
  94. simtools/runners/corsika_simtel_runner.py +77 -108
  95. simtools/runners/runner_services.py +214 -213
  96. simtools/runners/simtel_runner.py +27 -160
  97. simtools/runners/simtools_runner.py +11 -73
  98. simtools/schemas/application_workflow.metaschema.yml +8 -0
  99. simtools/settings.py +173 -0
  100. simtools/{io/eventio_handler.py → sim_events/file_info.py} +3 -3
  101. simtools/{simtel/simtel_io_event_histograms.py → sim_events/histograms.py} +25 -15
  102. simtools/{simtel/simtel_io_event_reader.py → sim_events/reader.py} +20 -17
  103. simtools/{simtel/simtel_io_event_writer.py → sim_events/writer.py} +84 -25
  104. simtools/simtel/pulse_shapes.py +7 -2
  105. simtools/simtel/simtel_config_writer.py +79 -91
  106. simtools/simtel/simtel_seeds.py +184 -0
  107. simtools/simtel/simtel_table_reader.py +6 -4
  108. simtools/simtel/simulator_array.py +114 -109
  109. simtools/simtel/simulator_camera_efficiency.py +68 -46
  110. simtools/simtel/simulator_light_emission.py +164 -132
  111. simtools/simtel/simulator_ray_tracing.py +80 -71
  112. simtools/simulator.py +137 -355
  113. simtools/telescope_trigger_rates.py +3 -4
  114. simtools/testing/assertions.py +84 -33
  115. simtools/testing/configuration.py +1 -2
  116. simtools/testing/helpers.py +2 -3
  117. simtools/testing/log_inspector.py +1 -0
  118. simtools/testing/sim_telarray_metadata.py +14 -12
  119. simtools/testing/validate_output.py +121 -42
  120. simtools/utils/general.py +43 -17
  121. simtools/utils/geometry.py +0 -77
  122. simtools/utils/names.py +5 -5
  123. simtools/utils/random.py +36 -0
  124. simtools/visualization/legend_handlers.py +7 -6
  125. simtools/visualization/plot_array_layout.py +91 -16
  126. simtools/visualization/plot_corsika_histograms.py +145 -605
  127. simtools/visualization/plot_incident_angles.py +48 -1
  128. simtools/visualization/plot_mirrors.py +1 -4
  129. simtools/visualization/plot_pixels.py +2 -4
  130. simtools/visualization/plot_psf.py +160 -19
  131. simtools/visualization/plot_simtel_event_histograms.py +4 -4
  132. simtools/visualization/plot_simtel_events.py +6 -11
  133. simtools/visualization/plot_tables.py +8 -19
  134. simtools/visualization/visualize.py +22 -2
  135. simtools/applications/db_development_tools/write_array_elements_positions_to_repository.py +0 -160
  136. simtools/applications/print_version.py +0 -53
  137. simtools/io/hdf5_handler.py +0 -139
  138. {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/top_level.txt +0 -0
@@ -1,19 +1,18 @@
1
1
  """Base class for running sim_telarray simulations."""
2
2
 
3
3
  import logging
4
- import subprocess
5
- from pathlib import Path
6
4
 
7
- import simtools.utils.general as gen
5
+ from simtools.job_execution import job_manager
8
6
  from simtools.runners.runner_services import RunnerServices
9
7
 
8
+ SIM_TELARRAY_ENV = {
9
+ "SIM_TELARRAY_CONFIG_PATH": "",
10
+ }
10
11
 
11
- class SimtelExecutionError(Exception):
12
- """Exception for sim_telarray execution error."""
13
12
 
14
-
15
- class InvalidOutputFileError(Exception):
16
- """Exception for invalid output file."""
13
+ def sim_telarray_env_as_string():
14
+ """Return the sim_telarray environment variables as a string."""
15
+ return " ".join(f'{key}="{value}" ' for key, value in SIM_TELARRAY_ENV.items())
17
16
 
18
17
 
19
18
  class SimtelRunner:
@@ -25,40 +24,26 @@ 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
- corsika_config: CorsikaConfig
33
- CORSIKA configuration.
34
- use_multipipe: bool
35
- Use multipipe to run CORSIKA and sim_telarray.
36
- calibration_run_mode: str
37
- Calibration run mode.
29
+ config: CorsikaConfig or dict
30
+ Configuration parameters.
31
+ is_calibration_run: bool
32
+ Flag to indicate if this is a calibration run.
38
33
  """
39
34
 
40
- def __init__(
41
- self,
42
- simtel_path,
43
- label=None,
44
- corsika_config=None,
45
- use_multipipe=False,
46
- calibration_run_mode=None,
47
- ):
35
+ def __init__(self, label=None, config=None, is_calibration_run=False):
48
36
  """Initialize SimtelRunner."""
49
37
  self._logger = logging.getLogger(__name__)
50
38
 
51
- self._simtel_path = Path(simtel_path)
52
39
  self.label = label
53
40
  self._base_directory = None
54
- self.calibration_run_mode = calibration_run_mode
41
+ self.is_calibration_run = is_calibration_run
55
42
 
56
43
  self.runs_per_set = 1
57
44
 
58
- self.runner_service = RunnerServices(corsika_config, label)
59
- self._directory = self.runner_service.load_data_directories(
60
- "corsika_sim_telarray" if use_multipipe else "sim_telarray"
61
- )
45
+ self.runner_service = RunnerServices(config, run_type="sim_telarray", label=label)
46
+ self.file_list = None
62
47
 
63
48
  def run(self, test=False, input_file=None, run_number=None):
64
49
  """
@@ -75,142 +60,24 @@ class SimtelRunner:
75
60
  """
76
61
  self._logger.debug("Running sim_telarray")
77
62
 
78
- command, stdout_file, stderr_file = self._make_run_command(
63
+ command, stdout_file, stderr_file = self.make_run_command(
79
64
  run_number=run_number, input_file=input_file
80
65
  )
81
-
82
- if test:
83
- self._logger.info(f"Running (test) with command: {command}")
84
- self._run_simtel_and_check_output(command, stdout_file, stderr_file)
85
- else:
86
- self._logger.debug(f"Running ({self.runs_per_set}x) with command: {command}")
87
- for _ in range(self.runs_per_set):
88
- self._run_simtel_and_check_output(command, stdout_file, stderr_file)
89
-
90
- self._check_run_result(run_number=run_number)
91
-
92
- def _check_run_result(self, run_number=None): # pylint: disable=all
93
- """Check if simtel output file exists."""
94
- pass
95
-
96
- def _raise_simtel_error(self):
97
- """
98
- Raise sim_telarray execution error.
99
-
100
- Final 30 lines from the log file are collected and printed.
101
-
102
- Raises
103
- ------
104
- SimtelExecutionError
105
- """
106
- if hasattr(self, "_log_file"):
107
- msg = gen.get_log_excerpt(self._log_file)
108
- else:
109
- msg = "Simtel log file does not exist."
110
- raise SimtelExecutionError(msg)
111
-
112
- def _run_simtel_and_check_output(self, command, stdout_file, stderr_file):
113
- """
114
- Run the sim_telarray command and check the exit code.
115
-
116
- Raises
117
- ------
118
- SimtelExecutionError
119
- if run was not successful.
120
- """
121
- stdout_file = stdout_file if stdout_file else "/dev/null"
122
- stderr_file = stderr_file if stderr_file else "/dev/null"
123
- with (
124
- open(f"{stdout_file}", "w", encoding="utf-8") as stdout,
125
- open(f"{stderr_file}", "w", encoding="utf-8") as stderr,
126
- ):
127
- result = subprocess.run(
66
+ runs = 1 if test else self.runs_per_set
67
+ label = "test" if test else f"{self.runs_per_set}x"
68
+ self._logger.info(f"Running ({label}) with command: {command}")
69
+ for _ in range(runs):
70
+ job_manager.submit(
128
71
  command,
129
- shell=True,
130
- text=True,
131
- stdout=stdout,
132
- stderr=stderr,
72
+ out_file=stdout_file,
73
+ err_file=stderr_file,
74
+ env=SIM_TELARRAY_ENV,
133
75
  )
134
76
 
135
- if result.returncode != 0:
136
- self._logger.error(result.stderr)
137
- self._raise_simtel_error()
138
- return result.returncode
139
-
140
- def _make_run_command(self, run_number=None, input_file=None):
141
- self._logger.debug(
142
- "make_run_command is being called from the base class - "
143
- "it should be implemented in the sub class"
144
- )
145
- input_file = input_file if input_file else "nofile"
146
- run_number = run_number if run_number else 1
147
- return f"{input_file}-{run_number}", None, None
148
-
149
- @staticmethod
150
- def get_config_option(par, value=None, weak_option=False):
151
- """
152
- Build sim_telarray command.
153
-
154
- Parameters
155
- ----------
156
- par: str
157
- Parameter name.
158
- value: str
159
- Parameter value.
160
- weak_option: bool
161
- If True, use -W option instead of -C.
162
-
163
- Returns
164
- -------
165
- str
166
- Command for sim_telarray.
167
- """
168
- option_syntax = "-W" if weak_option else "-C"
169
- c = f" {option_syntax} {par}"
170
- c += f"={value}" if value is not None else ""
171
- return c
77
+ def make_run_command(self, run_number=None, input_file=None):
78
+ """Make the sim_telarray run command (to implemented in subclasses)."""
79
+ raise NotImplementedError("Must be implemented in concrete subclass")
172
80
 
173
81
  def get_resources(self, run_number=None):
174
82
  """Return computing resources used."""
175
83
  return self.runner_service.get_resources(run_number)
176
-
177
- def get_file_name(
178
- self,
179
- simulation_software="sim_telarray",
180
- file_type=None,
181
- run_number=None,
182
- mode="",
183
- model_version_index=0,
184
- ):
185
- """
186
- Get the full path of a file for a given run number.
187
-
188
- Parameters
189
- ----------
190
- simulation_software: str
191
- Simulation software.
192
- file_type: str
193
- File type.
194
- run_number: int
195
- Run number.
196
- model_version_index: int
197
- Index of the model version.
198
- This is used to select the correct simulator_array instance in case
199
- multiple array models are simulated.
200
-
201
- Returns
202
- -------
203
- str
204
- File name with full path.
205
- """
206
- if simulation_software.lower() != "sim_telarray":
207
- raise ValueError(
208
- f"simulation_software ({simulation_software}) is not supported in SimulatorArray"
209
- )
210
- return self.runner_service.get_file_name(
211
- file_type=file_type,
212
- run_number=run_number,
213
- mode=mode,
214
- _model_version_index=model_version_index,
215
- calibration_run_mode=self.calibration_run_mode,
216
- )
@@ -1,15 +1,15 @@
1
1
  """Tools for running applications in the simtools framework."""
2
2
 
3
3
  import shutil
4
- import subprocess
5
4
  from pathlib import Path
6
5
 
7
6
  import simtools.utils.general as gen
8
7
  from simtools import dependencies
9
8
  from simtools.io import ascii_handler
9
+ from simtools.job_execution import job_manager
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")
@@ -41,75 +39,15 @@ def run_applications(args_dict, db_config, logger):
41
39
  logger.info(f"Skipping application: {app}")
42
40
  continue
43
41
  logger.info(f"Running application: {app}")
44
- stdout, stderr = run_application(run_time, app, config.get("configuration"), logger)
42
+ result = job_manager.submit(
43
+ app,
44
+ out_file=None,
45
+ err_file=None,
46
+ configuration=config.get("configuration"),
47
+ runtime_environment=run_time,
48
+ )
45
49
  file.write("=" * 80 + "\n")
46
- file.write(f"Application: {app}\nSTDOUT:\n{stdout}\nSTDERR:\n{stderr}\n")
47
-
48
-
49
- def run_application(runtime_environment, application, configuration, logger):
50
- """
51
- Run a simtools application and return stdout and stderr.
52
-
53
- Allow to specify a runtime environment (e.g., Docker) and a working directory.
54
-
55
- Parameters
56
- ----------
57
- runtime_environment : list
58
- Command to run the application in the specified runtime environment.
59
- application : str
60
- Name of the application to run.
61
- configuration : dict
62
- Configuration for the application.
63
- logger : logging.Logger
64
- Logger for logging application output.
65
-
66
- Returns
67
- -------
68
- tuple
69
- stdout and stderr from the application run.
70
-
71
- """
72
- command = [application, *_convert_dict_to_args(configuration)]
73
- if runtime_environment:
74
- command = runtime_environment + command
75
- try:
76
- result = subprocess.run(
77
- command,
78
- check=True,
79
- capture_output=True,
80
- text=True,
81
- )
82
- except subprocess.CalledProcessError as exc:
83
- logger.error(f"Error running application {application}: {exc.stderr}")
84
- raise exc
85
-
86
- return result.stdout, result.stderr
87
-
88
-
89
- def _convert_dict_to_args(parameters):
90
- """
91
- Convert a dictionary of parameters to a list of command line arguments.
92
-
93
- Parameters
94
- ----------
95
- parameters : dict
96
- Dictionary containing parameters to convert.
97
-
98
- Returns
99
- -------
100
- list
101
- List of command line arguments.
102
- """
103
- args = []
104
- for key, value in parameters.items():
105
- if isinstance(value, bool):
106
- if value:
107
- args.append(f"--{key}")
108
- elif isinstance(value, list):
109
- args.extend([f"--{key}", *(str(item) for item in value)])
110
- else:
111
- args.extend([f"--{key}", str(value)])
112
- return args
50
+ file.write(f"Application: {app}\nSTDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}\n")
113
51
 
114
52
 
115
53
  def _read_application_configuration(configuration_file, steps, logger):
@@ -142,6 +142,10 @@ definitions:
142
142
  description: |
143
143
  "Allowed tolerance for floating point comparison."
144
144
  type: number
145
+ scaling:
146
+ description: |
147
+ "Scaling factor to apply to the model parameter value before comparison."
148
+ type: number
145
149
  test_simtel_cfg_files:
146
150
  description: |
147
151
  "Reference file used for comparison of sim_telarray configuration files."
@@ -152,6 +156,10 @@ definitions:
152
156
  type: string
153
157
  description: Path to the configuration file for the given version.
154
158
  minProperties: 1
159
+ test_output_file:
160
+ description: |
161
+ "Output file generated by the integration test for comparison with the reference file."
162
+ type: string
155
163
  tolerance:
156
164
  description: "Allowed tolerance for floating point comparison."
157
165
  type: number
simtools/settings.py ADDED
@@ -0,0 +1,173 @@
1
+ """Centralized settings object with command line and environment variables."""
2
+
3
+ import os
4
+ import socket
5
+ from pathlib import Path
6
+ from types import MappingProxyType
7
+
8
+
9
+ class _Config:
10
+ """Centralized settings object with command line and environment variables."""
11
+
12
+ def __init__(self):
13
+ """Initialize empty config."""
14
+ self._args = {}
15
+ self._db_config = {}
16
+ self._sim_telarray_path = None
17
+ self._sim_telarray_exe = None
18
+ self._corsika_path = None
19
+ self._corsika_interaction_table_path = None
20
+ self._corsika_exe = None
21
+ self.user = os.getenv("USER", "unknown")
22
+ self.hostname = socket.gethostname()
23
+
24
+ def load(self, args=None, db_config=None):
25
+ """
26
+ Load configuration from command line arguments and environment variables.
27
+
28
+ For paths, first check for environment variables, then command line arguments.
29
+
30
+ Parameters
31
+ ----------
32
+ args : dict, optional
33
+ Command line arguments.
34
+ db_config : dict, optional
35
+ Database configuration.
36
+
37
+ """
38
+ self._args = MappingProxyType(args) if args is not None else {}
39
+ self._db_config = MappingProxyType(db_config) if db_config is not None else {}
40
+ self._sim_telarray_path = (
41
+ args.get("sim_telarray_path")
42
+ if args is not None and "sim_telarray_path" in args
43
+ else os.getenv("SIMTOOLS_SIM_TELARRAY_PATH")
44
+ )
45
+
46
+ self._sim_telarray_exe = (
47
+ args.get("sim_telarray_executable")
48
+ if args is not None and "sim_telarray_executable" in args
49
+ else os.getenv("SIMTOOLS_SIM_TELARRAY_EXECUTABLE", "sim_telarray")
50
+ )
51
+
52
+ self._corsika_path = (
53
+ args.get("corsika_path")
54
+ if args is not None and "corsika_path" in args
55
+ else os.getenv("SIMTOOLS_CORSIKA_PATH")
56
+ )
57
+
58
+ self._corsika_interaction_table_path = (
59
+ args.get("corsika_interaction_table_path")
60
+ if args is not None and "corsika_interaction_table_path" in args
61
+ else os.getenv("SIMTOOLS_CORSIKA_INTERACTION_TABLE_PATH")
62
+ )
63
+
64
+ self._corsika_exe = self._get_corsika_exec() if self._corsika_path is not None else None
65
+
66
+ def _get_corsika_exec(self):
67
+ """
68
+ Get the CORSIKA executable from environment variable or command line argument.
69
+
70
+ Build the executable name based on configured interaction models. Fall back to
71
+ legacy naming (simply "corsika") if models are not specified.
72
+ """
73
+ he_model = (
74
+ self._args.get("corsika_he_interaction")
75
+ if self._args is not None and "corsika_he_interaction" in self._args
76
+ else os.getenv("SIMTOOLS_CORSIKA_HE_INTERACTION")
77
+ )
78
+
79
+ le_model = (
80
+ self._args.get("corsika_le_interaction")
81
+ if self._args is not None and "corsika_le_interaction" in self._args
82
+ else os.getenv("SIMTOOLS_CORSIKA_LE_INTERACTION")
83
+ )
84
+
85
+ if he_model and le_model:
86
+ corsika_exe = self.corsika_path / f"corsika_{he_model}_{le_model}_flat"
87
+ if corsika_exe.exists():
88
+ return corsika_exe
89
+
90
+ # legacy naming
91
+ return self.corsika_path / "corsika"
92
+
93
+ @property
94
+ def args(self):
95
+ """Command line arguments."""
96
+ return self._args
97
+
98
+ @property
99
+ def db_config(self):
100
+ """Database configuration."""
101
+ return self._db_config
102
+
103
+ @property
104
+ def sim_telarray_path(self):
105
+ """Path to the sim_telarray installation directory."""
106
+ return Path(self._sim_telarray_path) if self._sim_telarray_path is not None else None
107
+
108
+ @property
109
+ def sim_telarray_exe(self):
110
+ """Path to the sim_telarray executable."""
111
+ return (
112
+ Path(self._sim_telarray_path) / "bin" / self._sim_telarray_exe
113
+ if self._sim_telarray_path is not None
114
+ else None
115
+ )
116
+
117
+ @property
118
+ def sim_telarray_exe_debug_trace(self):
119
+ """Path to the debug trace version of the sim_telarray executable."""
120
+ return (
121
+ Path(self._sim_telarray_path) / "bin" / (self._sim_telarray_exe + "_debug_trace")
122
+ if self._sim_telarray_path is not None
123
+ else None
124
+ )
125
+
126
+ @property
127
+ def corsika_path(self):
128
+ """Path to the CORSIKA installation directory."""
129
+ return Path(self._corsika_path) if self._corsika_path is not None else None
130
+
131
+ @property
132
+ def corsika_interaction_table_path(self):
133
+ """Path to the CORSIKA interaction table directory."""
134
+ return (
135
+ Path(self._corsika_interaction_table_path)
136
+ if self._corsika_interaction_table_path is not None
137
+ else self.corsika_path
138
+ )
139
+
140
+ @property
141
+ def corsika_exe(self):
142
+ """Path to the CORSIKA executable."""
143
+ return (
144
+ Path(self._corsika_path) / self._corsika_exe if self._corsika_path is not None else None
145
+ )
146
+
147
+ @property
148
+ def corsika_exe_curved(self):
149
+ """Path to the curved version of the CORSIKA executable."""
150
+ if self._corsika_exe is None:
151
+ return None
152
+ corsika_curved = (
153
+ self._corsika_exe.name.replace("_flat", "_curved")
154
+ if "_flat" in self._corsika_exe.name
155
+ else self._corsika_exe.name + "-curved" # legacy naming convention
156
+ )
157
+ return Path(self._corsika_path) / corsika_curved if self._corsika_path is not None else None
158
+
159
+ @property
160
+ def corsika_dummy_file(self):
161
+ """
162
+ Path to a dummy CORSIKA file required by sim_telarray for ray-tracing simulations.
163
+
164
+ This file does not need to exist; sim_telarray only requires a file path.
165
+ """
166
+ return (
167
+ self.sim_telarray_path / "run9991.corsika.gz"
168
+ if self._sim_telarray_path is not None
169
+ else None
170
+ )
171
+
172
+
173
+ 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