gammasimtools 0.24.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 (138) hide show
  1. {gammasimtools-0.24.0.dist-info → gammasimtools-0.26.0.dist-info}/METADATA +2 -1
  2. {gammasimtools-0.24.0.dist-info → gammasimtools-0.26.0.dist-info}/RECORD +134 -130
  3. {gammasimtools-0.24.0.dist-info → gammasimtools-0.26.0.dist-info}/entry_points.txt +3 -1
  4. {gammasimtools-0.24.0.dist-info → gammasimtools-0.26.0.dist-info}/licenses/LICENSE +1 -1
  5. simtools/_version.py +2 -2
  6. simtools/application_control.py +78 -0
  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 +5 -1
  21. simtools/applications/derive_pulse_shape_parameters.py +194 -0
  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 +64 -108
  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 +3 -4
  41. simtools/applications/simulate_illuminator.py +0 -1
  42. simtools/applications/simulate_pedestals.py +2 -6
  43. simtools/applications/simulate_prod.py +9 -28
  44. simtools/applications/simulate_prod_htcondor_generator.py +8 -1
  45. simtools/applications/submit_array_layouts.py +7 -7
  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_file_using_schema.py +49 -123
  51. simtools/applications/validate_optics.py +0 -13
  52. simtools/camera/camera_efficiency.py +1 -6
  53. simtools/camera/single_photon_electron_spectrum.py +2 -1
  54. simtools/configuration/commandline_parser.py +43 -8
  55. simtools/configuration/configurator.py +6 -11
  56. simtools/corsika/corsika_config.py +204 -99
  57. simtools/corsika/corsika_histograms.py +411 -1735
  58. simtools/corsika/primary_particle.py +1 -1
  59. simtools/data_model/metadata_collector.py +5 -2
  60. simtools/data_model/metadata_model.py +0 -4
  61. simtools/data_model/model_data_writer.py +27 -17
  62. simtools/data_model/schema.py +112 -5
  63. simtools/data_model/validate_data.py +80 -48
  64. simtools/db/db_handler.py +19 -8
  65. simtools/db/db_model_upload.py +2 -1
  66. simtools/db/mongo_db.py +133 -42
  67. simtools/dependencies.py +83 -44
  68. simtools/io/ascii_handler.py +4 -2
  69. simtools/io/table_handler.py +1 -1
  70. simtools/job_execution/htcondor_script_generator.py +0 -2
  71. simtools/layout/array_layout.py +4 -12
  72. simtools/layout/array_layout_utils.py +227 -58
  73. simtools/model/array_model.py +37 -18
  74. simtools/model/calibration_model.py +0 -4
  75. simtools/model/legacy_model_parameter.py +134 -0
  76. simtools/model/model_parameter.py +24 -14
  77. simtools/model/model_repository.py +18 -5
  78. simtools/model/model_utils.py +1 -6
  79. simtools/model/site_model.py +0 -4
  80. simtools/model/telescope_model.py +6 -11
  81. simtools/production_configuration/derive_corsika_limits.py +6 -11
  82. simtools/production_configuration/interpolation_handler.py +16 -16
  83. simtools/ray_tracing/incident_angles.py +5 -11
  84. simtools/ray_tracing/mirror_panel_psf.py +3 -7
  85. simtools/ray_tracing/psf_analysis.py +29 -27
  86. simtools/ray_tracing/psf_parameter_optimisation.py +822 -680
  87. simtools/ray_tracing/ray_tracing.py +6 -15
  88. simtools/reporting/docs_auto_report_generator.py +8 -13
  89. simtools/reporting/docs_read_parameters.py +70 -16
  90. simtools/runners/corsika_runner.py +15 -10
  91. simtools/runners/corsika_simtel_runner.py +9 -8
  92. simtools/runners/runner_services.py +17 -7
  93. simtools/runners/simtel_runner.py +11 -58
  94. simtools/runners/simtools_runner.py +2 -4
  95. simtools/schemas/model_parameters/flasher_pulse_exp_decay.schema.yml +2 -0
  96. simtools/schemas/model_parameters/flasher_pulse_shape.schema.yml +50 -0
  97. simtools/schemas/model_parameters/flasher_pulse_width.schema.yml +2 -0
  98. simtools/schemas/simulation_models_info.schema.yml +2 -0
  99. simtools/settings.py +154 -0
  100. simtools/sim_events/file_info.py +128 -0
  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 +273 -0
  105. simtools/simtel/simtel_config_writer.py +146 -22
  106. simtools/simtel/simtel_table_reader.py +6 -4
  107. simtools/simtel/simulator_array.py +62 -23
  108. simtools/simtel/simulator_camera_efficiency.py +4 -6
  109. simtools/simtel/simulator_light_emission.py +101 -19
  110. simtools/simtel/simulator_ray_tracing.py +4 -10
  111. simtools/simulator.py +360 -353
  112. simtools/telescope_trigger_rates.py +3 -4
  113. simtools/testing/assertions.py +115 -8
  114. simtools/testing/configuration.py +2 -3
  115. simtools/testing/helpers.py +2 -3
  116. simtools/testing/log_inspector.py +5 -1
  117. simtools/testing/sim_telarray_metadata.py +1 -1
  118. simtools/testing/validate_output.py +69 -23
  119. simtools/utils/general.py +37 -0
  120. simtools/utils/geometry.py +0 -77
  121. simtools/utils/names.py +7 -9
  122. simtools/version.py +37 -0
  123. simtools/visualization/legend_handlers.py +21 -10
  124. simtools/visualization/plot_array_layout.py +312 -41
  125. simtools/visualization/plot_corsika_histograms.py +143 -605
  126. simtools/visualization/plot_mirrors.py +834 -0
  127. simtools/visualization/plot_pixels.py +2 -4
  128. simtools/visualization/plot_psf.py +0 -1
  129. simtools/visualization/plot_simtel_event_histograms.py +4 -4
  130. simtools/visualization/plot_simtel_events.py +6 -11
  131. simtools/visualization/plot_tables.py +8 -19
  132. simtools/visualization/visualize.py +22 -2
  133. simtools/applications/db_development_tools/write_array_elements_positions_to_repository.py +0 -160
  134. simtools/applications/print_version.py +0 -53
  135. simtools/io/hdf5_handler.py +0 -139
  136. simtools/simtel/simtel_io_file_info.py +0 -62
  137. {gammasimtools-0.24.0.dist-info → gammasimtools-0.26.0.dist-info}/WHEEL +0 -0
  138. {gammasimtools-0.24.0.dist-info → gammasimtools-0.26.0.dist-info}/top_level.txt +0 -0
@@ -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")
@@ -1,6 +1,8 @@
1
1
  %YAML 1.2
2
2
  ---
3
3
  title: Schema for flasher_pulse_exp_decay model parameter
4
+ deprecated: true
5
+ deprecation_note: "Replaced by updated definition of flasher_pulse_shape"
4
6
  schema_version: 0.1.0
5
7
  meta_schema: simpipe-schema
6
8
  meta_schema_url: https://raw.githubusercontent.com/gammasim/simtools/main/src/simtools/schemas/model_parameter_and_data_schema.metaschema.yml
@@ -1,6 +1,56 @@
1
1
  %YAML 1.2
2
2
  ---
3
3
  title: Schema for flasher_pulse_shape model parameter
4
+ schema_version: 0.2.0
5
+ meta_schema: simpipe-schema
6
+ meta_schema_url: https://raw.githubusercontent.com/gammasim/simtools/main/src/simtools/schemas/model_parameter_and_data_schema.metaschema.yml
7
+ meta_schema_version: 0.1.0
8
+ name: flasher_pulse_shape
9
+ short_description: |-
10
+ Definition of flasher pulse shape.
11
+ description: |-
12
+ Definition of flasher pulse shape both shape and relevant
13
+ timing parameters.
14
+ data:
15
+ - type: string
16
+ description: Shape of the flasher pulse.
17
+ default: Gauss
18
+ allowed_values:
19
+ - Gauss
20
+ - TopHat
21
+ - Exponential
22
+ - Gauss-Exponential
23
+ - type: float64
24
+ description: |-
25
+ Characteristic width of the flasher pulse shape.
26
+ For 'TopHat', this is the pulse width, for 'Gauss' and 'Gauss-Exponential'
27
+ the rms width. Ignored if shape is 'Exponential'.
28
+ unit: ns
29
+ default: 0
30
+ allowed_range:
31
+ min: 0
32
+ - type: float64
33
+ description: |-
34
+ Exponential decay time of the flasher pulse shape.
35
+ Only relevant if shape is 'Exponential' or 'Gauss-Exponential'.
36
+ unit: ns
37
+ default: 0
38
+ allowed_range:
39
+ min: 0
40
+ instrument:
41
+ class: Calibration
42
+ activity:
43
+ setting:
44
+ - SetParameterFromExternal
45
+ validation:
46
+ - ValidateParameterByExpert
47
+ source:
48
+ - Initial instrument setup
49
+ simulation_software:
50
+ - name: simtools
51
+ version: ">0.24.0"
52
+ ---
53
+ title: Schema for flasher_pulse_shape model parameter
4
54
  schema_version: 0.1.0
5
55
  meta_schema: simpipe-schema
6
56
  meta_schema_url: https://raw.githubusercontent.com/gammasim/simtools/main/src/simtools/schemas/model_parameter_and_data_schema.metaschema.yml
@@ -1,6 +1,8 @@
1
1
  %YAML 1.2
2
2
  ---
3
3
  title: Schema for flasher_pulse_width model parameter
4
+ deprecated: true
5
+ deprecation_note: "Replaced by updated definition of flasher_pulse_shape"
4
6
  schema_version: 0.1.0
5
7
  meta_schema: simpipe-schema
6
8
  meta_schema_url: https://raw.githubusercontent.com/gammasim/simtools/main/src/simtools/schemas/model_parameter_and_data_schema.metaschema.yml
@@ -74,6 +74,8 @@ $defs:
74
74
  - type: string
75
75
  - type: array
76
76
  - type: "null"
77
+ model_parameter_schema_version:
78
+ $ref: 'common_definitions.schema.yml#/$defs/semantic_version_pattern'
77
79
  required:
78
80
  - version
79
81
  additionalProperties: false
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()
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/python3
2
+ """Read file info and run headers from eventio (CORSIKA IACT, sim_telarray) files."""
3
+
4
+ import warnings
5
+
6
+ from eventio import EventIOFile, iact
7
+ from eventio.simtel import MCRunHeader, MCShower, RunHeader
8
+
9
+ # Suppress all UserWarnings from corsikaio - no CORSIKA versions <7.7 are supported anyway
10
+ warnings.filterwarnings("ignore", category=UserWarning, module=r"corsikaio\.subblocks\..*")
11
+
12
+
13
+ def get_corsika_run_number(file):
14
+ """
15
+ Return the CORSIKA run number from an eventio (CORSIKA IACT or sim_telarray) file.
16
+
17
+ Parameters
18
+ ----------
19
+ file: str
20
+ Path to the eventio file.
21
+
22
+ Returns
23
+ -------
24
+ int, None
25
+ CORSIKA run number. Returns None if not found.
26
+ """
27
+ run_header = get_combined_eventio_run_header(file)
28
+ if run_header and "run" in run_header:
29
+ return run_header["run"]
30
+ run_header, _ = get_corsika_run_and_event_headers(file)
31
+ try:
32
+ return int(run_header["run_number"])
33
+ except (TypeError, KeyError, ValueError):
34
+ return None
35
+
36
+
37
+ def get_combined_eventio_run_header(sim_telarray_file):
38
+ """
39
+ Return the CORSIKA run header information from an eventio (sim_telarray) file.
40
+
41
+ Reads both RunHeader and MCRunHeader object from file and returns a merged dictionary.
42
+ Adds primary id from the first event.
43
+
44
+ Parameters
45
+ ----------
46
+ sim_telarray_file: str
47
+ Path to the sim_telarray file.
48
+
49
+ Returns
50
+ -------
51
+ dict, None
52
+ CORSIKA run header. Returns None if not found.
53
+ """
54
+ run_header = mc_run_header = None
55
+ primary_id = None
56
+
57
+ with EventIOFile(sim_telarray_file) as f:
58
+ for o in f:
59
+ if isinstance(o, RunHeader) and run_header is None:
60
+ run_header = o.parse()
61
+ elif isinstance(o, MCRunHeader) and mc_run_header is None:
62
+ mc_run_header = o.parse()
63
+ elif isinstance(o, MCShower): # get primary_id from first MCShower
64
+ primary_id = o.parse().get("primary_id")
65
+ if run_header and mc_run_header and primary_id is not None:
66
+ break
67
+
68
+ run_header = run_header or {}
69
+ mc_run_header = mc_run_header or {}
70
+ if primary_id is not None:
71
+ mc_run_header["primary_id"] = primary_id
72
+ return run_header | mc_run_header or None
73
+
74
+
75
+ def get_corsika_run_and_event_headers(corsika_iact_file):
76
+ """
77
+ Return the CORSIKA run and event headers from a CORSIKA IACT eventio file.
78
+
79
+ Parameters
80
+ ----------
81
+ corsika_iact_file: str, Path
82
+ Path to the CORSIKA IACT eventio file.
83
+
84
+ Returns
85
+ -------
86
+ tuple
87
+ CORSIKA run header and event header as dictionaries.
88
+ """
89
+ run_header = event_header = None
90
+
91
+ with EventIOFile(corsika_iact_file) as f:
92
+ for o in f:
93
+ if isinstance(o, iact.RunHeader) and run_header is None:
94
+ run_header = o.parse()
95
+ elif isinstance(o, iact.EventHeader) and event_header is None:
96
+ event_header = o.parse()
97
+ if run_header and event_header:
98
+ break
99
+
100
+ return run_header, event_header
101
+
102
+
103
+ def get_simulated_events(event_io_file):
104
+ """
105
+ Return the number of shower and MC events from a simulation (eventio) file.
106
+
107
+ For a sim_telarray file, the number of simulated showers and MC events is
108
+ determined by counting the number of MCShower (type id 2020) and MCEvent
109
+ objects (type id 2021). For a CORSIKA IACT file, the number of simulated
110
+ showers is determined by counting the number of IACTShower (type id 1202).
111
+
112
+ Parameters
113
+ ----------
114
+ event_io_file: str, Path
115
+ Path to the eventio file.
116
+
117
+ Returns
118
+ -------
119
+ tuple
120
+ Number of showers and number of MC events (MC events for sim_telarray files only).
121
+ """
122
+ counts = {1202: 0, 2020: 0, 2021: 0}
123
+ with EventIOFile(event_io_file) as f:
124
+ for o in f:
125
+ t = o.header.type
126
+ if t in counts:
127
+ counts[t] += 1
128
+ return counts[2020] if counts[2020] else counts[1202], counts[2021]
@@ -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,