gammasimtools 0.16.0__py3-none-any.whl → 0.18.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 (85) hide show
  1. {gammasimtools-0.16.0.dist-info → gammasimtools-0.18.0.dist-info}/METADATA +5 -2
  2. {gammasimtools-0.16.0.dist-info → gammasimtools-0.18.0.dist-info}/RECORD +82 -74
  3. {gammasimtools-0.16.0.dist-info → gammasimtools-0.18.0.dist-info}/WHEEL +1 -1
  4. {gammasimtools-0.16.0.dist-info → gammasimtools-0.18.0.dist-info}/entry_points.txt +4 -1
  5. simtools/_version.py +2 -2
  6. simtools/applications/db_add_simulation_model_from_repository_to_db.py +10 -1
  7. simtools/applications/derive_ctao_array_layouts.py +5 -5
  8. simtools/applications/derive_mirror_rnda.py +1 -1
  9. simtools/applications/generate_simtel_event_data.py +128 -46
  10. simtools/applications/merge_tables.py +102 -0
  11. simtools/applications/plot_array_layout.py +145 -258
  12. simtools/applications/plot_tabular_data.py +12 -1
  13. simtools/applications/plot_tabular_data_for_model_parameter.py +103 -0
  14. simtools/applications/production_derive_corsika_limits.py +78 -225
  15. simtools/applications/production_derive_statistics.py +77 -43
  16. simtools/applications/simulate_light_emission.py +1 -0
  17. simtools/applications/simulate_prod.py +30 -18
  18. simtools/applications/simulate_prod_htcondor_generator.py +0 -1
  19. simtools/applications/submit_array_layouts.py +93 -0
  20. simtools/applications/verify_simulation_model_production_tables.py +52 -0
  21. simtools/camera/camera_efficiency.py +3 -3
  22. simtools/configuration/commandline_parser.py +30 -35
  23. simtools/configuration/configurator.py +0 -4
  24. simtools/constants.py +2 -0
  25. simtools/corsika/corsika_config.py +17 -12
  26. simtools/corsika/primary_particle.py +46 -13
  27. simtools/data_model/metadata_collector.py +7 -3
  28. simtools/data_model/schema.py +15 -1
  29. simtools/db/db_handler.py +16 -11
  30. simtools/db/db_model_upload.py +2 -2
  31. simtools/io_operations/io_handler.py +2 -2
  32. simtools/io_operations/io_table_handler.py +345 -0
  33. simtools/job_execution/htcondor_script_generator.py +2 -2
  34. simtools/job_execution/job_manager.py +7 -121
  35. simtools/layout/array_layout_utils.py +389 -0
  36. simtools/model/array_model.py +10 -1
  37. simtools/model/model_repository.py +134 -0
  38. simtools/production_configuration/{calculate_statistical_errors_grid_point.py → calculate_statistical_uncertainties_grid_point.py} +101 -112
  39. simtools/production_configuration/derive_corsika_limits.py +239 -111
  40. simtools/production_configuration/derive_corsika_limits_grid.py +232 -0
  41. simtools/production_configuration/derive_production_statistics.py +57 -26
  42. simtools/production_configuration/derive_production_statistics_handler.py +70 -37
  43. simtools/production_configuration/interpolation_handler.py +296 -94
  44. simtools/ray_tracing/ray_tracing.py +7 -6
  45. simtools/reporting/docs_read_parameters.py +104 -62
  46. simtools/resources/array-element-ids.json +126 -0
  47. simtools/runners/corsika_simtel_runner.py +4 -1
  48. simtools/runners/runner_services.py +5 -4
  49. simtools/schemas/model_parameter_and_data_schema.metaschema.yml +5 -1
  50. simtools/schemas/model_parameters/atmospheric_profile.schema.yml +41 -0
  51. simtools/schemas/model_parameters/atmospheric_transmission.schema.yml +43 -0
  52. simtools/schemas/model_parameters/camera_filter.schema.yml +10 -0
  53. simtools/schemas/model_parameters/camera_filter_incidence_angle.schema.yml +10 -0
  54. simtools/schemas/model_parameters/discriminator_pulse_shape.schema.yml +31 -0
  55. simtools/schemas/model_parameters/dsum_threshold.schema.yml +41 -0
  56. simtools/schemas/model_parameters/fadc_pulse_shape.schema.yml +12 -0
  57. simtools/schemas/model_parameters/lightguide_efficiency_vs_incidence_angle.schema.yml +10 -0
  58. simtools/schemas/model_parameters/mirror_reflectivity.schema.yml +10 -0
  59. simtools/schemas/model_parameters/nsb_reference_spectrum.schema.yml +12 -0
  60. simtools/schemas/model_parameters/pm_photoelectron_spectrum.schema.yml +19 -0
  61. simtools/schemas/model_parameters/quantum_efficiency.schema.yml +10 -0
  62. simtools/schemas/plot_configuration.metaschema.yml +46 -57
  63. simtools/schemas/production_configuration_metrics.schema.yml +2 -2
  64. simtools/simtel/simtel_config_writer.py +34 -14
  65. simtools/simtel/simtel_io_event_reader.py +301 -194
  66. simtools/simtel/simtel_io_event_writer.py +237 -221
  67. simtools/simtel/simtel_io_file_info.py +9 -4
  68. simtools/simtel/simtel_io_metadata.py +119 -8
  69. simtools/simtel/simulator_array.py +2 -2
  70. simtools/simtel/simulator_light_emission.py +79 -34
  71. simtools/simtel/simulator_ray_tracing.py +2 -2
  72. simtools/simulator.py +101 -68
  73. simtools/testing/validate_output.py +4 -1
  74. simtools/utils/general.py +1 -3
  75. simtools/utils/names.py +76 -7
  76. simtools/visualization/plot_array_layout.py +242 -0
  77. simtools/visualization/plot_pixels.py +680 -0
  78. simtools/visualization/plot_tables.py +81 -2
  79. simtools/visualization/visualize.py +3 -219
  80. simtools/applications/production_generate_simulation_config.py +0 -152
  81. simtools/layout/ctao_array_layouts.py +0 -172
  82. simtools/production_configuration/generate_simulation_config.py +0 -158
  83. {gammasimtools-0.16.0.dist-info → gammasimtools-0.18.0.dist-info}/licenses/LICENSE +0 -0
  84. {gammasimtools-0.16.0.dist-info → gammasimtools-0.18.0.dist-info}/top_level.txt +0 -0
  85. /simtools/{schemas → resources}/array_elements.yml +0 -0
@@ -1,10 +1,11 @@
1
- """Generate a reduced dataset from given simulation event list and save the output to file."""
1
+ """Generate a reduced dataset from sim_telarray output files using astropy tables."""
2
2
 
3
3
  import logging
4
- from dataclasses import dataclass, field
4
+ from dataclasses import dataclass
5
5
 
6
+ import astropy.units as u
6
7
  import numpy as np
7
- import tables
8
+ from astropy.table import Table
8
9
  from eventio import EventIOFile
9
10
  from eventio.simtel import (
10
11
  ArrayEvent,
@@ -15,107 +16,138 @@ from eventio.simtel import (
15
16
  TriggerInformation,
16
17
  )
17
18
 
19
+ from simtools.corsika.primary_particle import PrimaryParticle
20
+ from simtools.simtel.simtel_io_file_info import get_corsika_run_header
21
+ from simtools.simtel.simtel_io_metadata import (
22
+ get_sim_telarray_telescope_id_to_telescope_name_mapping,
23
+ )
18
24
  from simtools.utils.geometry import calculate_circular_mean
19
-
20
- DEFAULT_FILTERS = tables.Filters(complevel=5, complib="zlib", shuffle=True, bitshuffle=False)
21
-
22
-
23
- @dataclass
24
- class ShowerEventData:
25
- """Shower event data."""
26
-
27
- simulated_energy: list = field(default_factory=list)
28
- x_core: list = field(default_factory=list)
29
- y_core: list = field(default_factory=list)
30
- shower_azimuth: list = field(default_factory=list)
31
- shower_altitude: list = field(default_factory=list)
32
- shower_id: list = field(default_factory=list)
33
- area_weight: list = field(default_factory=list)
34
-
35
- x_core_shower: list = field(default_factory=list)
36
- y_core_shower: list = field(default_factory=list)
37
- core_distance_shower: list = field(default_factory=list)
25
+ from simtools.utils.names import get_common_identifier_from_array_element_name
38
26
 
39
27
 
40
28
  @dataclass
41
- class TriggeredEventData:
42
- """Triggered event data."""
43
-
44
- triggered_id: list = field(default_factory=list)
45
- array_altitudes: list = field(default_factory=list)
46
- array_azimuths: list = field(default_factory=list)
47
- trigger_telescope_list_list: list = field(default_factory=list)
48
- angular_distance: list = field(default_factory=list)
29
+ class TableSchemas:
30
+ """Define schemas for output tables with units."""
31
+
32
+ shower_schema = {
33
+ "shower_id": (np.uint32, None),
34
+ "event_id": (np.uint32, None),
35
+ "file_id": (np.uint32, None),
36
+ "simulated_energy": (np.float64, u.TeV),
37
+ "x_core": (np.float64, u.m),
38
+ "y_core": (np.float64, u.m),
39
+ "shower_azimuth": (np.float64, u.rad),
40
+ "shower_altitude": (np.float64, u.rad),
41
+ "area_weight": (np.float64, None),
42
+ }
43
+
44
+ trigger_schema = {
45
+ "shower_id": (np.uint32, None),
46
+ "event_id": (np.uint32, None),
47
+ "file_id": (np.uint32, None),
48
+ "array_altitude": (np.float64, u.rad),
49
+ "array_azimuth": (np.float64, u.rad),
50
+ "telescope_list": (str, None), # Store as comma-separated string
51
+ "telescope_list_common_id": (str, None), # Store as comma-separated string
52
+ }
53
+
54
+ file_info_schema = {
55
+ "file_name": (str, None),
56
+ "file_id": (np.uint32, None),
57
+ "particle_id": (np.uint32, None),
58
+ "energy_min": (np.float64, u.TeV),
59
+ "energy_max": (np.float64, u.TeV),
60
+ "viewcone_min": (np.float64, u.deg),
61
+ "viewcone_max": (np.float64, u.deg),
62
+ "core_scatter_min": (np.float64, u.m),
63
+ "core_scatter_max": (np.float64, u.m),
64
+ "zenith": (np.float64, u.deg),
65
+ "azimuth": (np.float64, u.deg),
66
+ "nsb_level": (np.float64, None),
67
+ }
49
68
 
50
69
 
51
70
  class SimtelIOEventDataWriter:
52
71
  """
53
- Generate a reduced dataset from given simulation event list and save the output to file.
72
+ Process sim_telarray events and write tables to file.
73
+
74
+ Extracts essential information from sim_telarray output files:
75
+
76
+ - Shower parameters (energy, core location, direction)
77
+ - Trigger patterns
78
+ - Telescope pointing
54
79
 
55
80
  Attributes
56
81
  ----------
57
82
  input_files : list
58
83
  List of input file paths to process.
59
- output_file : str
60
- Path to the output file.
61
84
  max_files : int, optional
62
85
  Maximum number of files to process.
63
86
  """
64
87
 
65
- def __init__(self, input_files, output_file, max_files=100):
88
+ def __init__(self, input_files, max_files=100):
66
89
  """Initialize class."""
67
90
  self._logger = logging.getLogger(__name__)
68
91
  self.input_files = input_files
69
- self.output_file = output_file
70
92
  try:
71
93
  self.max_files = max_files if max_files < len(input_files) else len(input_files)
72
94
  except TypeError as exc:
73
95
  raise TypeError("No input files provided.") from exc
74
- self.shower = None
96
+
75
97
  self.n_use = None
76
- self.shower_id_offset = 0
77
- self.event_data = ShowerEventData()
78
- self.triggered_data = TriggeredEventData()
79
- self.file_names = []
98
+ self.shower_data = []
99
+ self.trigger_data = []
100
+ self.file_info = []
101
+ self.telescope_id_to_name = {}
80
102
 
81
103
  def process_files(self):
82
- """Process the input files and store them in an file."""
83
- self.shower_id_offset = 0
84
-
85
- for i, file in enumerate(self.input_files[: self.max_files], start=1):
86
- self._logger.info(f"Processing file {i}/{self.max_files}: {file}")
87
- self._process_file(file)
88
- if i == 1 or len(self.event_data.simulated_energy) >= 1e7:
89
- self._write_data(mode="w" if i == 1 else "a")
90
- self.shower_id_offset += len(self.event_data.simulated_energy)
91
- self._reset_data()
92
-
93
- self._write_data(mode="a")
94
-
95
- def get_event_data(self):
96
104
  """
97
- Return shower and triggered event data.
105
+ Process input files and return tables.
98
106
 
99
107
  Returns
100
108
  -------
101
- ShowerEventData, TriggeredEventData
102
- Shower and triggered event data.
109
+ list
110
+ List of astropy tables containing processed data.
103
111
  """
104
- return self.event_data, self.triggered_data
105
-
106
- def _process_file(self, file):
112
+ for i, file in enumerate(self.input_files[: self.max_files]):
113
+ self._logger.info(f"Processing file {i + 1}/{self.max_files}: {file}")
114
+ self._process_file(i, file)
115
+
116
+ return self.create_tables()
117
+
118
+ def create_tables(self):
119
+ """Create astropy tables from collected data."""
120
+ tables = []
121
+ for data, schema, name in [
122
+ (self.shower_data, TableSchemas.shower_schema, "SHOWERS"),
123
+ (self.trigger_data, TableSchemas.trigger_schema, "TRIGGERS"),
124
+ (self.file_info, TableSchemas.file_info_schema, "FILE_INFO"),
125
+ ]:
126
+ table = Table(rows=data, names=schema.keys())
127
+ table.meta["EXTNAME"] = name
128
+ self._add_units_to_table(table, schema)
129
+ tables.append(table)
130
+ return tables
131
+
132
+ def _add_units_to_table(self, table, schema):
133
+ """Add units to a single table's columns."""
134
+ for col, (_, unit) in schema.items():
135
+ if unit is not None:
136
+ table[col].unit = unit
137
+
138
+ def _process_file(self, file_id, file):
107
139
  """Process a single file and update data lists."""
140
+ self._process_file_info(file_id, file)
108
141
  with EventIOFile(file) as f:
109
142
  for eventio_object in f:
110
143
  if isinstance(eventio_object, MCRunHeader):
111
144
  self._process_mc_run_header(eventio_object)
112
145
  elif isinstance(eventio_object, MCShower):
113
- self._process_mc_shower(eventio_object)
146
+ self._process_mc_shower(eventio_object, file_id)
114
147
  elif isinstance(eventio_object, MCEvent):
115
148
  self._process_mc_event(eventio_object)
116
149
  elif isinstance(eventio_object, ArrayEvent):
117
- self._process_array_event(eventio_object)
118
- self.file_names.append(str(file))
150
+ self._process_array_event(eventio_object, file_id)
119
151
 
120
152
  def _process_mc_run_header(self, eventio_object):
121
153
  """Process MC run header and update data lists."""
@@ -123,36 +155,94 @@ class SimtelIOEventDataWriter:
123
155
  self.n_use = mc_head["n_use"] # reuse factor n_use needed to extend the values below
124
156
  self._logger.info(f"Shower reuse factor: {self.n_use} (viewcone: {mc_head['viewcone']})")
125
157
 
126
- def _process_mc_shower(self, eventio_object):
158
+ def _process_file_info(self, file_id, file):
159
+ """Process file information and append to file info list."""
160
+ run_info = get_corsika_run_header(file)
161
+ self.telescope_id_to_name = get_sim_telarray_telescope_id_to_telescope_name_mapping(file)
162
+ particle = PrimaryParticle(
163
+ particle_id_type="eventio_id", particle_id=run_info.get("primary_id", 1)
164
+ )
165
+ self.file_info.append(
166
+ {
167
+ "file_name": str(file),
168
+ "file_id": file_id,
169
+ "particle_id": particle.corsika7_id,
170
+ "energy_min": run_info["E_range"][0],
171
+ "energy_max": run_info["E_range"][1],
172
+ "viewcone_min": run_info["viewcone"][0],
173
+ "viewcone_max": run_info["viewcone"][1],
174
+ "core_scatter_min": run_info["core_range"][0],
175
+ "core_scatter_max": run_info["core_range"][1],
176
+ "zenith": 90.0 - np.degrees(run_info["direction"][1]),
177
+ "azimuth": np.degrees(run_info["direction"][0]),
178
+ "nsb_level": self._get_preliminary_nsb_level(str(file)),
179
+ }
180
+ )
181
+
182
+ def _process_mc_shower(self, eventio_object, file_id):
127
183
  """
128
184
  Process MC shower and update shower event list.
129
185
 
130
186
  Duplicated entries 'self.n_use' times to match the number simulated events with
131
187
  different core positions.
132
188
  """
133
- self.shower = eventio_object.parse()
134
-
135
- self.event_data.simulated_energy.extend([self.shower["energy"]] * self.n_use)
136
- self.event_data.shower_azimuth.extend([self.shower["azimuth"]] * self.n_use)
137
- self.event_data.shower_altitude.extend([self.shower["altitude"]] * self.n_use)
189
+ shower = eventio_object.parse()
190
+
191
+ self.shower_data.extend(
192
+ {
193
+ "shower_id": shower["shower"],
194
+ "event_id": None, # filled in _process_mc_event
195
+ "file_id": file_id,
196
+ "simulated_energy": shower["energy"],
197
+ "x_core": None, # filled in _process_mc_event
198
+ "y_core": None, # filled in _process_mc_event
199
+ "shower_azimuth": shower["azimuth"],
200
+ "shower_altitude": shower["altitude"],
201
+ "area_weight": None, # filled in _process_mc_event
202
+ }
203
+ for _ in range(self.n_use)
204
+ )
138
205
 
139
206
  def _process_mc_event(self, eventio_object):
140
- """Process MC event and update shower event list."""
207
+ """
208
+ Process MC event and update shower event list.
209
+
210
+ Expected to be called n_use times after _process_shower.
211
+ """
141
212
  event = eventio_object.parse()
142
213
 
143
- self.event_data.shower_id.append(event["shower_num"])
144
- self.event_data.x_core.append(event["xcore"])
145
- self.event_data.y_core.append(event["ycore"])
146
- self.event_data.area_weight.append(event["aweight"])
214
+ shower_data_index = len(self.shower_data) - self.n_use + event["event_id"] % 100
147
215
 
148
- def _process_array_event(self, eventio_object):
216
+ try:
217
+ if self.shower_data[shower_data_index]["shower_id"] != event["shower_num"]:
218
+ raise IndexError
219
+ except IndexError as exc:
220
+ raise IndexError(
221
+ f"Inconsistent shower and MC event data for shower id {event['shower_num']}"
222
+ ) from exc
223
+
224
+ self.shower_data[shower_data_index].update(
225
+ {
226
+ "event_id": event["event_id"],
227
+ "x_core": event["xcore"],
228
+ "y_core": event["ycore"],
229
+ "area_weight": event["aweight"],
230
+ }
231
+ )
232
+
233
+ def _process_array_event(self, eventio_object, file_id):
149
234
  """Process array event and update triggered event list."""
150
235
  tracking_positions = []
236
+ telescopes = []
151
237
 
152
- for _, obj in enumerate(eventio_object):
238
+ for obj in eventio_object:
153
239
  if isinstance(obj, TriggerInformation):
154
- self._process_trigger_information(obj)
155
-
240
+ trigger_info = obj.parse()
241
+ telescopes = (
242
+ trigger_info["triggered_telescopes"]
243
+ if len(trigger_info["triggered_telescopes"]) > 0
244
+ else []
245
+ )
156
246
  if isinstance(obj, TrackingPosition):
157
247
  tracking_position = obj.parse()
158
248
  tracking_positions.append(
@@ -162,156 +252,82 @@ class SimtelIOEventDataWriter:
162
252
  }
163
253
  )
164
254
 
165
- if tracking_positions:
166
- self._process_tracking_positions(tracking_positions)
167
-
168
- def _process_tracking_positions(self, tracking_positions):
169
- """
170
- Process collected tracking positions and update triggered event list.
255
+ if len(telescopes) > 0 and tracking_positions:
256
+ self._fill_array_event(
257
+ self._map_telescope_names(telescopes),
258
+ tracking_positions,
259
+ eventio_object.event_id,
260
+ file_id,
261
+ )
171
262
 
172
- Use mean telescope tracking positions, averaged over all triggered telescopes.
173
- """
263
+ def _fill_array_event(self, telescopes, tracking_positions, event_id, file_id):
264
+ """Add array event triggered events with tracking positions."""
174
265
  altitudes = [pos["altitude"] for pos in tracking_positions]
175
266
  azimuths = [pos["azimuth"] for pos in tracking_positions]
176
267
 
177
- self.triggered_data.array_altitudes.append(np.mean(altitudes))
178
- self.triggered_data.array_azimuths.append(calculate_circular_mean(azimuths))
179
-
180
- def _process_trigger_information(self, trigger_info):
181
- """Process trigger information and update triggered event list."""
182
- trigger_info = trigger_info.parse()
183
- telescopes = trigger_info["triggered_telescopes"]
184
- if len(telescopes) > 0:
185
- # add offset to obtain unique shower IDs among all files
186
- self.triggered_data.triggered_id.append(self.shower["shower"] + self.shower_id_offset)
187
- self.triggered_data.trigger_telescope_list_list.append(
188
- np.array(telescopes, dtype=np.int16)
189
- )
190
-
191
- def _table_descriptions(self):
192
- """HDF5 table descriptions for shower data, triggered data, and file names."""
193
- shower_data_desc = {
194
- "shower_id": tables.Int32Col(),
195
- "simulated_energy": tables.Float32Col(),
196
- "x_core": tables.Float32Col(),
197
- "y_core": tables.Float32Col(),
198
- "area_weight": tables.Float32Col(),
199
- "shower_azimuth": tables.Float32Col(),
200
- "shower_altitude": tables.Float32Col(),
201
- }
202
- triggered_data_desc = {
203
- "triggered_id": tables.Int32Col(),
204
- "array_altitudes": tables.Float32Col(),
205
- "array_azimuths": tables.Float32Col(),
206
- "telescope_list_index": tables.Int32Col(), # Index into VLArray
207
- }
208
- file_names_desc = {
209
- "file_names": tables.StringCol(256),
210
- }
211
- return shower_data_desc, triggered_data_desc, file_names_desc
212
-
213
- def _tables(self, output_file, data_group, mode="a"):
214
- """Create or get HDF5 tables."""
215
- descriptions = self._table_descriptions()
216
- table_names = ["reduced_data", "triggered_data", "file_names"]
217
-
218
- table_dict = {}
219
- for name, desc in zip(table_names, descriptions):
220
- path = f"/data/{name}"
221
- table_dict[name] = (
222
- output_file.create_table(
223
- data_group, name, desc, name.replace("_", " ").title(), filters=DEFAULT_FILTERS
224
- )
225
- if mode == "w" or path not in output_file
226
- else output_file.get_node(path)
227
- )
268
+ self.trigger_data.append(
269
+ {
270
+ "shower_id": self.shower_data[-1]["shower_id"],
271
+ "event_id": event_id,
272
+ "file_id": file_id,
273
+ "array_altitude": float(np.mean(altitudes)),
274
+ "array_azimuth": float(calculate_circular_mean(azimuths)),
275
+ "telescope_list": ",".join(map(str, telescopes)),
276
+ "telescope_list_common_id": ",".join(
277
+ [
278
+ str(get_common_identifier_from_array_element_name(tel, 0))
279
+ for tel in telescopes
280
+ ]
281
+ ),
282
+ }
283
+ )
284
+
285
+ def _map_telescope_names(self, telescope_ids):
286
+ """
287
+ Map sim_telarray telescopes IDs to CTAO array element names.
228
288
 
229
- return table_dict["reduced_data"], table_dict["triggered_data"], table_dict["file_names"]
289
+ Parameters
290
+ ----------
291
+ telescope_ids : list
292
+ List of telescope IDs.
230
293
 
231
- def _write_event_data(self, reduced_table):
232
- """Fill event data tables."""
233
- if len(self.event_data.simulated_energy) == 0:
234
- return
235
- row = reduced_table.row
236
- for i, energy in enumerate(self.event_data.simulated_energy):
237
- row["shower_id"] = (
238
- self.event_data.shower_id[i] if i < len(self.event_data.shower_id) else 0
239
- )
240
- row["simulated_energy"] = energy
241
- row["x_core"] = self.event_data.x_core[i] if i < len(self.event_data.x_core) else 0
242
- row["y_core"] = self.event_data.y_core[i] if i < len(self.event_data.y_core) else 0
243
- row["area_weight"] = (
244
- self.event_data.area_weight[i] if i < len(self.event_data.area_weight) else 0
245
- )
246
- row["shower_azimuth"] = (
247
- self.event_data.shower_azimuth[i] if i < len(self.event_data.shower_azimuth) else 0
248
- )
249
- row["shower_altitude"] = (
250
- self.event_data.shower_altitude[i]
251
- if i < len(self.event_data.shower_altitude)
252
- else 0
253
- )
254
- row.append()
255
- reduced_table.flush()
256
-
257
- def _writer_triggered_data(self, triggered_table, vlarray):
258
- """Fill triggered event data tables."""
259
- # Get or create VLArray for telescope lists
260
- if len(self.triggered_data.triggered_id) == 0:
261
- return
262
- row = triggered_table.row
263
- start_idx = vlarray.nrows
264
- for i, triggered_id in enumerate(self.triggered_data.triggered_id):
265
- row["triggered_id"] = triggered_id
266
- row["array_altitudes"] = (
267
- self.triggered_data.array_altitudes[i]
268
- if i < len(self.triggered_data.array_altitudes)
269
- else 0
270
- )
271
- row["array_azimuths"] = (
272
- self.triggered_data.array_azimuths[i]
273
- if i < len(self.triggered_data.array_azimuths)
274
- else 0
275
- )
276
- row["telescope_list_index"] = start_idx + i # Index into the VLArray
277
- row.append()
278
- vlarray.append(
279
- self.triggered_data.trigger_telescope_list_list[i]
280
- if i < len(self.triggered_data.trigger_telescope_list_list)
281
- else []
282
- )
283
- triggered_table.flush()
284
-
285
- def _write_data(self, mode="a"):
286
- """Write data to HDF5 file."""
287
- with tables.open_file(self.output_file, mode=mode) as f:
288
- data_group = (
289
- f.create_group("/", "data", "Data group")
290
- if mode == "w" or "/data" not in f
291
- else f.get_node("/data")
292
- )
294
+ Returns
295
+ -------
296
+ list
297
+ List of telescope names corresponding to the IDs.
298
+ """
299
+ return [
300
+ self.telescope_id_to_name.get(tel_id, f"Unknown_{tel_id}") for tel_id in telescope_ids
301
+ ]
293
302
 
294
- reduced_table, triggered_table, file_names_table = self._tables(f, data_group, mode)
295
- self._write_event_data(reduced_table)
303
+ def _get_preliminary_nsb_level(self, file):
304
+ """
305
+ Return preliminary NSB level from file name.
296
306
 
297
- vlarray = (
298
- f.create_vlarray(
299
- data_group,
300
- "trigger_telescope_list_list",
301
- tables.Int16Atom(),
302
- "List of triggered telescope IDs",
303
- )
304
- if mode == "w" or "/data/trigger_telescope_list_list" not in f
305
- else f.get_node("/data/trigger_telescope_list_list")
306
- )
307
- self._writer_triggered_data(triggered_table, vlarray)
307
+ Hardwired values are used for "dark", "half", and "full" NSB levels
308
+ (actual values are made up for this example). Will be replaced with
309
+ reading of sim_telarray metadata entry for NSB level (to be implemented,
310
+ see issue #1572).
308
311
 
309
- if self.file_names:
310
- file_names_table.append([[name] for name in self.file_names])
311
- file_names_table.flush()
312
+ Parameters
313
+ ----------
314
+ file : str
315
+ File name to extract NSB level from.
312
316
 
313
- def _reset_data(self):
314
- """Reset data structures for batch processing."""
315
- self.event_data = ShowerEventData()
316
- self.triggered_data = TriggeredEventData()
317
- self.file_names = []
317
+ Returns
318
+ -------
319
+ float
320
+ NSB level extracted from file name.
321
+ """
322
+ nsb_levels = {"dark": 1.0, "half": 2.0, "full": 5.0}
323
+
324
+ for key, value in nsb_levels.items():
325
+ try:
326
+ if key in file.lower():
327
+ self._logger.warning(f"NSB level set to hardwired value of {value}")
328
+ return value
329
+ except AttributeError as exc:
330
+ raise AttributeError("Invalid file name.") from exc
331
+
332
+ self._logger.warning("No NSB level found in file name, defaulting to 1.0")
333
+ return 1.0
@@ -2,7 +2,7 @@
2
2
  """Read file info and run headers from sim_telarray files."""
3
3
 
4
4
  from eventio import EventIOFile
5
- from eventio.simtel import MCRunHeader, RunHeader
5
+ from eventio.simtel import MCRunHeader, MCShower, RunHeader
6
6
 
7
7
 
8
8
  def get_corsika_run_number(file):
@@ -25,10 +25,10 @@ def get_corsika_run_number(file):
25
25
 
26
26
  def get_corsika_run_header(file):
27
27
  """
28
- Return the CORSIKA run header from a sim_telarray file.
28
+ Return the CORSIKA run header information from a sim_telarray file.
29
29
 
30
30
  Reads both RunHeader and MCRunHeader object from file and
31
- returns a merged dictionary.
31
+ returns a merged dictionary. Adds primary id from the first event.
32
32
 
33
33
  Parameters
34
34
  ----------
@@ -42,6 +42,7 @@ def get_corsika_run_header(file):
42
42
  """
43
43
  run_header = None
44
44
  mc_run_header = None
45
+ primary_id = None
45
46
 
46
47
  with EventIOFile(file) as f:
47
48
  for o in f:
@@ -49,9 +50,13 @@ def get_corsika_run_header(file):
49
50
  run_header = o.parse()
50
51
  elif isinstance(o, MCRunHeader) and mc_run_header is None:
51
52
  mc_run_header = o.parse()
52
- if run_header and mc_run_header:
53
+ elif isinstance(o, MCShower): # get primary_id from first MCShower
54
+ primary_id = o.parse().get("primary_id")
55
+ if run_header and mc_run_header and primary_id is not None:
53
56
  break
54
57
 
55
58
  run_header = run_header or {}
56
59
  mc_run_header = mc_run_header or {}
60
+ if primary_id is not None:
61
+ mc_run_header["primary_id"] = primary_id
57
62
  return run_header | mc_run_header or None