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
@@ -1,4 +1,4 @@
1
- """Generate a reduced dataset from sim_telarray output files using astropy tables."""
1
+ """Generate a reduced dataset from simulation files (CORSIKA/sim_telarray) using astropy tables."""
2
2
 
3
3
  import logging
4
4
  from dataclasses import dataclass
@@ -6,7 +6,7 @@ from dataclasses import dataclass
6
6
  import astropy.units as u
7
7
  import numpy as np
8
8
  from astropy.table import Table
9
- from eventio import EventIOFile
9
+ from eventio import EventIOFile, iact
10
10
  from eventio.simtel import (
11
11
  ArrayEvent,
12
12
  MCEvent,
@@ -17,7 +17,10 @@ from eventio.simtel import (
17
17
  )
18
18
 
19
19
  from simtools.corsika.primary_particle import PrimaryParticle
20
- from simtools.io.eventio_handler import get_combined_corsika_run_header
20
+ from simtools.sim_events.file_info import (
21
+ get_combined_eventio_run_header,
22
+ get_corsika_run_and_event_headers,
23
+ )
21
24
  from simtools.simtel.simtel_io_metadata import (
22
25
  get_sim_telarray_telescope_id_to_telescope_name_mapping,
23
26
  read_sim_telarray_metadata,
@@ -68,11 +71,11 @@ class TableSchemas:
68
71
  }
69
72
 
70
73
 
71
- class SimtelIOEventDataWriter:
74
+ class EventDataWriter:
72
75
  """
73
- Process sim_telarray events and write tables to file.
76
+ Process simulation events (CORSIKA/sim_telarray) and write tables to file.
74
77
 
75
- Extracts essential information from sim_telarray output files:
78
+ Extracts essential information from simulation files, including:
76
79
 
77
80
  - Shower parameters (energy, core location, direction)
78
81
  - Trigger patterns
@@ -108,7 +111,7 @@ class SimtelIOEventDataWriter:
108
111
  Returns
109
112
  -------
110
113
  list
111
- List of astropy tables containing processed data.
114
+ List of tables containing processed data.
112
115
  """
113
116
  for i, file in enumerate(self.input_files[: self.max_files]):
114
117
  self._logger.info(f"Processing file {i + 1}/{self.max_files}: {file}")
@@ -117,13 +120,15 @@ class SimtelIOEventDataWriter:
117
120
  return self.create_tables()
118
121
 
119
122
  def create_tables(self):
120
- """Create astropy tables from collected data."""
123
+ """Create tables from collected data."""
121
124
  tables = []
122
125
  for data, schema, name in [
123
126
  (self.shower_data, TableSchemas.shower_schema, "SHOWERS"),
124
127
  (self.trigger_data, TableSchemas.trigger_schema, "TRIGGERS"),
125
128
  (self.file_info, TableSchemas.file_info_schema, "FILE_INFO"),
126
129
  ]:
130
+ if len(data) == 0:
131
+ continue
127
132
  table = Table(rows=data, names=schema.keys())
128
133
  table.meta["EXTNAME"] = name
129
134
  self._add_units_to_table(table, schema)
@@ -149,40 +154,69 @@ class SimtelIOEventDataWriter:
149
154
  self._process_mc_event(eventio_object)
150
155
  elif isinstance(eventio_object, ArrayEvent):
151
156
  self._process_array_event(eventio_object, file_id)
157
+ elif isinstance(eventio_object, iact.EventHeader):
158
+ self._process_mc_shower_from_iact(eventio_object, file_id)
152
159
 
153
160
  def _process_mc_run_header(self, eventio_object):
154
- """Process MC run header and update data lists."""
161
+ """Process MC run header (sim_telarray file)."""
155
162
  mc_head = eventio_object.parse()
156
163
  self.n_use = mc_head["n_use"] # reuse factor n_use needed to extend the values below
157
164
  self._logger.info(f"Shower reuse factor: {self.n_use} (viewcone: {mc_head['viewcone']})")
158
165
 
159
166
  def _process_file_info(self, file_id, file):
160
167
  """Process file information and append to file info list."""
161
- run_info = get_combined_corsika_run_header(file)
162
- self.telescope_id_to_name = get_sim_telarray_telescope_id_to_telescope_name_mapping(file)
163
- particle = PrimaryParticle(
164
- particle_id_type="eventio_id", particle_id=run_info.get("primary_id", 1)
165
- )
168
+ run_info = get_combined_eventio_run_header(file)
169
+ if run_info: # sim_telarray file
170
+ self.telescope_id_to_name = get_sim_telarray_telescope_id_to_telescope_name_mapping(
171
+ file
172
+ )
173
+ corsika7_id = PrimaryParticle(
174
+ particle_id_type="eventio_id",
175
+ particle_id=run_info.get("primary_id", 1),
176
+ ).corsika7_id
177
+ nsb = self.get_nsb_level_from_sim_telarray_metadata(file)
178
+
179
+ e_min, e_max = run_info["E_range"]
180
+ view_cone_min, view_cone_max = run_info["viewcone"]
181
+ core_min, core_max = run_info["core_range"]
182
+ azimuth, el = np.degrees(run_info["direction"])
183
+ zenith = 90.0 - el
184
+ else: # CORSIKA IACT file
185
+ run_header, event_header = get_corsika_run_and_event_headers(file)
186
+ corsika7_id = int(event_header["particle_id"])
187
+ e_min = event_header["energy_min"]
188
+ e_max = event_header["energy_max"]
189
+ zenith = np.degrees(event_header["zenith"])
190
+ # Rotate to geographic north
191
+ azimuth = np.degrees(
192
+ event_header["azimuth"] - event_header["angle_array_x_magnetic_north"]
193
+ )
194
+ view_cone_min = event_header["viewcone_inner_angle"]
195
+ view_cone_max = event_header["viewcone_outer_angle"]
196
+ core_min = 0.0
197
+ core_max = run_header["x_scatter"] / 1.0e2 # cm to m
198
+ nsb = 0.0
199
+
166
200
  self.file_info.append(
167
201
  {
168
202
  "file_name": str(file),
169
203
  "file_id": file_id,
170
- "particle_id": particle.corsika7_id,
171
- "energy_min": run_info["E_range"][0],
172
- "energy_max": run_info["E_range"][1],
173
- "viewcone_min": run_info["viewcone"][0],
174
- "viewcone_max": run_info["viewcone"][1],
175
- "core_scatter_min": run_info["core_range"][0],
176
- "core_scatter_max": run_info["core_range"][1],
177
- "zenith": 90.0 - np.degrees(run_info["direction"][1]),
178
- "azimuth": np.degrees(run_info["direction"][0]),
179
- "nsb_level": self.get_nsb_level_from_sim_telarray_metadata(file),
204
+ "particle_id": corsika7_id,
205
+ "energy_min": e_min,
206
+ "energy_max": e_max,
207
+ "viewcone_min": view_cone_min,
208
+ "viewcone_max": view_cone_max,
209
+ "core_scatter_min": core_min,
210
+ "core_scatter_max": core_max,
211
+ "zenith": zenith,
212
+ "azimuth": azimuth,
213
+ "nsb_level": nsb,
180
214
  }
181
215
  )
182
216
 
183
217
  def _process_mc_shower(self, eventio_object, file_id):
184
218
  """
185
- Process MC shower and update shower event list.
219
+ Process MC shower from sim_telarray file and update shower event list.
186
220
 
187
221
  Duplicated entries 'self.n_use' times to match the number simulated events with
188
222
  different core positions.
@@ -204,6 +238,31 @@ class SimtelIOEventDataWriter:
204
238
  for _ in range(self.n_use)
205
239
  )
206
240
 
241
+ def _process_mc_shower_from_iact(self, eventio_object, file_id):
242
+ """
243
+ Process MC shower from IACT file and update shower event list.
244
+
245
+ Duplicated entries 'self.n_use' times to match the number simulated events with
246
+ different core positions.
247
+ """
248
+ shower_header = eventio_object.parse()
249
+ self.n_use = int(shower_header["n_reuse"])
250
+
251
+ self.shower_data.extend(
252
+ {
253
+ "shower_id": shower_header["event_number"],
254
+ "event_id": shower_header["event_number"] * 100 + i,
255
+ "file_id": file_id,
256
+ "simulated_energy": shower_header["total_energy"],
257
+ "x_core": shower_header["reuse_x"][i] / 1.0e2,
258
+ "y_core": shower_header["reuse_y"][i] / 1.0e2,
259
+ "shower_azimuth": np.degrees(shower_header["azimuth"]),
260
+ "shower_altitude": 90.0 - np.degrees(shower_header["zenith"]),
261
+ "area_weight": 1.0,
262
+ }
263
+ for i in range(self.n_use)
264
+ )
265
+
207
266
  def _process_mc_event(self, eventio_object):
208
267
  """
209
268
  Process MC event and update shower event list.
@@ -94,8 +94,13 @@ def _exp_decay(t, tau):
94
94
  numpy.ndarray
95
95
  Exponential values at ``t`` (unitless), zero for ``t < 0``.
96
96
  """
97
- tau = max(tau, 1e-9)
98
- return np.where(t >= 0, np.exp(-t / tau), 0.0)
97
+ tau = max(float(tau), 1e-9)
98
+ t_arr = np.asarray(t, dtype=float)
99
+ expo = -t_arr / tau
100
+ expo = np.minimum(expo, 0.0)
101
+ with np.errstate(over="ignore", under="ignore", invalid="ignore"):
102
+ e = np.exp(expo)
103
+ return np.where(t_arr >= 0, e, 0.0)
99
104
 
100
105
 
101
106
  def generate_gauss_expconv_pulse(
@@ -10,7 +10,7 @@ import numpy as np
10
10
 
11
11
  import simtools.utils.general as gen
12
12
  import simtools.version
13
- from simtools.io import ascii_handler
13
+ from simtools import dependencies, settings
14
14
  from simtools.simtel.pulse_shapes import generate_pulse_from_rise_fall_times
15
15
  from simtools.utils import names
16
16
 
@@ -56,8 +56,6 @@ class SimtelConfigWriter:
56
56
  Layout name.
57
57
  label: str
58
58
  Instance label. Important for output file naming.
59
- simtel_path: str or Path
60
- Path to the sim_telarray installation directory.
61
59
  """
62
60
 
63
61
  TAB = " " * 3
@@ -70,7 +68,6 @@ class SimtelConfigWriter:
70
68
  telescope_model_name=None,
71
69
  telescope_design_model=None,
72
70
  label=None,
73
- simtel_path=None,
74
71
  ):
75
72
  """Initialize SimtelConfigWriter."""
76
73
  self._logger = logging.getLogger(__name__)
@@ -82,7 +79,6 @@ class SimtelConfigWriter:
82
79
  self._layout_name = layout_name
83
80
  self._telescope_model_name = telescope_model_name
84
81
  self._telescope_design_model = telescope_design_model
85
- self._simtel_path = simtel_path
86
82
 
87
83
  def write_telescope_config_file(
88
84
  self, config_file_path, parameters, telescope_name=None, telescope_design_model=None
@@ -125,14 +121,14 @@ class SimtelConfigWriter:
125
121
  file.write(f"{meta}\n")
126
122
 
127
123
  @staticmethod
128
- def write_lightpulse_table_gauss_expconv(
124
+ def write_light_pulse_table_gauss_exp_conv(
129
125
  file_path,
130
- width_ns=None,
131
- exp_decay_ns=None,
126
+ width_ns,
127
+ exp_decay_ns,
128
+ fadc_sum_bins,
132
129
  dt_ns=0.1,
133
130
  rise_range=(0.1, 0.9),
134
131
  fall_range=(0.9, 0.1),
135
- fadc_sum_bins=None,
136
132
  time_margin_ns=10.0,
137
133
  ):
138
134
  """Write a pulse table for a Gaussian convolved with a causal exponential.
@@ -143,22 +139,19 @@ class SimtelConfigWriter:
143
139
  Destination path of the ASCII pulse table to write. Parent directory must exist.
144
140
  width_ns : float
145
141
  Target rise time in ns between the fractional levels defined by ``rise_range``.
146
- Defaults correspond to 10-90% rise time.
147
142
  exp_decay_ns : float
148
143
  Target fall time in ns between the fractional levels defined by ``fall_range``.
149
- Defaults correspond to 90-10% fall time.
150
- dt_ns : float, optional
151
- Time sampling step in ns for the generated pulse table. Default is 0.1.
152
- rise_range : tuple[float, float], optional
153
- Fractional amplitude bounds (low, high) for rise-time definition. Default (0.1, 0.9).
154
- fall_range : tuple[float, float], optional
155
- Fractional amplitude bounds (high, low) for fall-time definition. Default (0.9, 0.1).
156
144
  fadc_sum_bins : int
157
145
  Length of the FADC integration window (treated as ns here) used to derive
158
146
  the internal time sampling window of the solver as [-(margin), bins + margin].
147
+ dt_ns : float, optional
148
+ Time sampling step in ns for the generated pulse table.
149
+ rise_range : tuple[float, float], optional
150
+ Fractional amplitude bounds (low, high) for rise-time definition.
151
+ fall_range : tuple[float, float], optional
152
+ Fractional amplitude bounds (high, low) for fall-time definition.
159
153
  time_margin_ns : float, optional
160
154
  Margin in ns to add to both ends of the FADC window when ``fadc_sum_bins`` is given.
161
- Default is 5.0 ns.
162
155
 
163
156
  Returns
164
157
  -------
@@ -174,8 +167,10 @@ class SimtelConfigWriter:
174
167
  if width_ns is None or exp_decay_ns is None:
175
168
  raise ValueError("width_ns (rise 10-90) and exp_decay_ns (fall 90-10) are required")
176
169
  logger.info(
177
- f"Generating lightpulse table with rise10-90={width_ns} ns, "
178
- f"fall90-10={exp_decay_ns} ns, dt={dt_ns} ns"
170
+ "Generating pulse-shape table with "
171
+ f"rise{int(rise_range[0] * 100)}-{int(rise_range[1] * 100)}={width_ns} ns, "
172
+ f"fall{int(fall_range[0] * 100)}-{int(fall_range[1] * 100)}={exp_decay_ns} ns, "
173
+ f"dt={dt_ns} ns"
179
174
  )
180
175
  width = float(fadc_sum_bins)
181
176
  t_start_ns = -abs(time_margin_ns + width)
@@ -193,6 +188,42 @@ class SimtelConfigWriter:
193
188
 
194
189
  return SimtelConfigWriter._write_ascii_pulse_table(file_path, t, y)
195
190
 
191
+ @staticmethod
192
+ def write_angular_distribution_table_lambertian(
193
+ file_path,
194
+ max_angle_deg,
195
+ n_samples=100,
196
+ ):
197
+ """Write a Lambertian angular distribution table (I(t) ~ cos(t)).
198
+
199
+ Parameters
200
+ ----------
201
+ file_path : str or pathlib.Path
202
+ Destination path of the ASCII table to write. Parent directory must exist.
203
+ max_angle_deg : float
204
+ Maximum angle (deg) for the distribution sampling range [0, max_angle_deg].
205
+ n_samples : int, optional
206
+ Number of samples (including end point) from 0 to max_angle_deg. Default 100.
207
+
208
+ Returns
209
+ -------
210
+ pathlib.Path
211
+ Path to created angular distribution table.
212
+ """
213
+ logger.info(
214
+ f"Generating Lambertian angular distribution table up to {max_angle_deg} deg "
215
+ f"with {n_samples} samples"
216
+ )
217
+ angles = np.linspace(0.0, float(max_angle_deg), int(n_samples), dtype=float)
218
+ intensities = np.cos(np.deg2rad(angles))
219
+ intensities[intensities < 0] = 0.0
220
+ if intensities.max() > 0:
221
+ intensities /= intensities.max()
222
+
223
+ return SimtelConfigWriter._write_ascii_angle_distribution_table(
224
+ file_path, angles, intensities
225
+ )
226
+
196
227
  @staticmethod
197
228
  def _write_ascii_pulse_table(file_path, t, y):
198
229
  """Write two-column ASCII pulse table."""
@@ -202,6 +233,15 @@ class SimtelConfigWriter:
202
233
  fh.write(f"{ti:.6f} {yi:.8f}\n")
203
234
  return Path(file_path)
204
235
 
236
+ @staticmethod
237
+ def _write_ascii_angle_distribution_table(file_path, angles, intensities):
238
+ """Write two-column ASCII angular distribution table."""
239
+ with open(file_path, "w", encoding="utf-8") as fh:
240
+ fh.write("# angle[deg] relative_intensity\n")
241
+ for a, i in zip(angles, intensities):
242
+ fh.write(f"{a:.6f} {i:.8f}\n")
243
+ return Path(file_path)
244
+
205
245
  def _get_parameters_for_sim_telarray(self, parameters, config_file_path):
206
246
  """
207
247
  Convert parameter dictionary to sim_telarray configuration file format.
@@ -257,28 +297,24 @@ class SimtelConfigWriter:
257
297
  Model parameters in sim_telarray format including flasher parameters.
258
298
 
259
299
  """
260
- if "flasher_pulse_shape" not in parameters and "flasher_pulse_width" not in parameters:
300
+ if "flasher_pulse_shape" not in parameters:
261
301
  return simtel_par
262
302
 
263
303
  mapping = {
264
304
  "gauss": "laser_pulse_sigtime",
265
305
  "tophat": "laser_pulse_twidth",
306
+ "gauss-exponential": "laser_pulse_sigtime",
266
307
  }
267
308
 
268
- shape = parameters.get("flasher_pulse_shape", {}).get("value", "").lower()
269
- if "exponential" in shape:
270
- simtel_par["laser_pulse_exptime"] = parameters.get("flasher_pulse_exp_decay", {}).get(
271
- "value", 0.0
272
- )
273
- else:
274
- simtel_par["laser_pulse_exptime"] = 0.0
309
+ shape_value = parameters.get("flasher_pulse_shape", {}).get("value")
310
+ shape = shape_value[0].lower()
311
+ width = shape_value[1]
312
+ exp_decay = shape_value[2]
275
313
 
276
- width = parameters.get("flasher_pulse_width", {}).get("value", 0.0)
314
+ simtel_par["laser_pulse_exptime"] = exp_decay if ("exponential" in shape) else 0.0
277
315
 
278
316
  simtel_par.update(dict.fromkeys(mapping.values(), 0.0))
279
- if shape == "gauss-exponential":
280
- simtel_par["laser_pulse_sigtime"] = width
281
- elif shape in mapping:
317
+ if shape in mapping:
282
318
  simtel_par[mapping[shape]] = width
283
319
  else:
284
320
  self._logger.warning(f"Flasher pulse shape '{shape}' without width definition")
@@ -576,17 +612,24 @@ class SimtelConfigWriter:
576
612
  "simtools_model_production_version": self._model_version,
577
613
  }
578
614
  try:
579
- build_opts = ascii_handler.collect_data_from_file(
580
- Path(self._simtel_path) / "build_opts.yml"
581
- )
615
+ build_opts = dependencies.get_build_options()
582
616
  for key, value in build_opts.items():
583
617
  meta_items[f"simtools_{key}"] = value
584
618
  except (FileNotFoundError, TypeError):
585
619
  pass # don't expect build_opts.yml to be present on all systems
586
620
 
621
+ # CORSIKA executable without _flat/_curved suffix (do not know here if curved or flat)
622
+ try:
623
+ meta_items["simtools_corsika_exec"] = settings.config.corsika_exe.name.removesuffix(
624
+ "_flat"
625
+ )
626
+ except AttributeError as exc:
627
+ raise AttributeError("CORSIKA executable path is not set in settings.") from exc
628
+
587
629
  file.write(f"{self.TAB}% Simtools parameters\n")
588
630
  for key, value in meta_items.items():
589
- file.write(f"{self.TAB}metaparam global set {key} = {value}\n")
631
+ if not isinstance(value, list):
632
+ file.write(f"{self.TAB}metaparam global set {key} = {value}\n")
590
633
 
591
634
  def _write_site_parameters(
592
635
  self, file, site_parameters, model_path, telescope_model, additional_metadata=None
@@ -154,14 +154,16 @@ def _data_columns_mirror_reflectivity(n_columns, n_dim):
154
154
  {"name": "wavelength", "description": "Wavelength", "unit": "nm"},
155
155
  ]
156
156
  if n_dim:
157
- for angle in n_dim:
158
- _columns.append(
157
+ _columns.extend(
158
+ [
159
159
  {
160
160
  "name": f"reflectivity_{angle}deg",
161
161
  "description": f"Mirror reflectivity at {angle} deg",
162
162
  "unit": None,
163
- },
164
- )
163
+ }
164
+ for angle in n_dim
165
+ ]
166
+ )
165
167
  else:
166
168
  _columns.append(
167
169
  {
@@ -3,6 +3,7 @@
3
3
  import logging
4
4
  import stat
5
5
 
6
+ from simtools import settings
6
7
  from simtools.io import io_handler
7
8
  from simtools.runners.simtel_runner import InvalidOutputFileError, SimtelRunner
8
9
  from simtools.utils.general import clear_default_sim_telarray_cfg_directories
@@ -16,8 +17,6 @@ class SimulatorArray(SimtelRunner):
16
17
  ----------
17
18
  corsika_config_data: CorsikaConfig
18
19
  CORSIKA configuration.
19
- simtel_path: str or Path
20
- Location of source of the sim_telarray/CORSIKA package.
21
20
  label: str
22
21
  Instance label.
23
22
  use_multipipe: bool
@@ -29,7 +28,6 @@ class SimulatorArray(SimtelRunner):
29
28
  def __init__(
30
29
  self,
31
30
  corsika_config,
32
- simtel_path,
33
31
  label=None,
34
32
  use_multipipe=False,
35
33
  sim_telarray_seeds=None,
@@ -40,7 +38,6 @@ class SimulatorArray(SimtelRunner):
40
38
  self._logger.debug("Init SimulatorArray")
41
39
  super().__init__(
42
40
  label=label,
43
- simtel_path=simtel_path,
44
41
  corsika_config=corsika_config,
45
42
  use_multipipe=use_multipipe,
46
43
  calibration_run_mode=calibration_config.get("run_mode") if calibration_config else None,
@@ -181,7 +178,7 @@ class SimulatorArray(SimtelRunner):
181
178
  output_file = self.get_file_name(file_type="simtel_output", run_number=run_number)
182
179
  self.corsika_config.array_model.export_all_simtel_config_files()
183
180
 
184
- command = str(self._simtel_path.joinpath("sim_telarray/bin/sim_telarray"))
181
+ command = str(settings.config.sim_telarray_exe)
185
182
  command += f" -c {self.corsika_config.array_model.config_file_path}"
186
183
  command += f" -I{config_dir}"
187
184
  command += super().get_config_option(
@@ -222,9 +219,7 @@ class SimulatorArray(SimtelRunner):
222
219
  "fadc_sysvar_pedestal",
223
220
  "fadc_dev_pedestal",
224
221
  ]
225
- null_command_parts = []
226
- for param in null_values:
227
- null_command_parts.append(super().get_config_option(param, 0.0))
222
+ null_command_parts = [super().get_config_option(param, 0.0) for param in null_values]
228
223
  command = " ".join(null_command_parts)
229
224
 
230
225
  one_values = [
@@ -233,9 +228,7 @@ class SimulatorArray(SimtelRunner):
233
228
  "fadc_lg_dev_pedestal",
234
229
  "fadc_lg_sysvar_pedestal",
235
230
  ]
236
- one_command_parts = []
237
- for param in one_values:
238
- one_command_parts.append(super().get_config_option(param, -1.0))
231
+ one_command_parts = [super().get_config_option(param, -1.0) for param in one_values]
239
232
  command += " " + " ".join(one_command_parts)
240
233
  return command
241
234
 
@@ -3,6 +3,7 @@
3
3
  import logging
4
4
  from pathlib import Path
5
5
 
6
+ from simtools import settings
6
7
  from simtools.io import ascii_handler
7
8
  from simtools.runners.simtel_runner import SimtelRunner
8
9
  from simtools.utils import general
@@ -20,8 +21,6 @@ class SimulatorCameraEfficiency(SimtelRunner):
20
21
  Instance of SiteModel class.
21
22
  label: str
22
23
  Instance label. Important for output file naming.
23
- simtel_path: str or Path
24
- Location of sim_telarray installation.
25
24
  file_simtel: str or Path
26
25
  Location of the sim_telarray testeff tool output file.
27
26
  zenith_angle: float
@@ -37,7 +36,6 @@ class SimulatorCameraEfficiency(SimtelRunner):
37
36
  telescope_model,
38
37
  site_model,
39
38
  label=None,
40
- simtel_path=None,
41
39
  file_simtel=None,
42
40
  file_log=None,
43
41
  zenith_angle=None,
@@ -48,7 +46,7 @@ class SimulatorCameraEfficiency(SimtelRunner):
48
46
  self._logger = logging.getLogger(__name__)
49
47
  self._logger.debug("Init SimulatorCameraEfficiency")
50
48
 
51
- super().__init__(label=label, simtel_path=simtel_path)
49
+ super().__init__(label=label)
52
50
 
53
51
  self._telescope_model = telescope_model
54
52
  self._site_model = site_model
@@ -109,7 +107,7 @@ class SimulatorCameraEfficiency(SimtelRunner):
109
107
  "mirror_reflectivity", "secondary_mirror_incidence_angle"
110
108
  )
111
109
 
112
- command = str(self._simtel_path.joinpath("sim_telarray/testeff"))
110
+ command = str(settings.config.sim_telarray_path / "bin/testeff")
113
111
  if self.skip_correction_to_nsb_spectrum:
114
112
  command += " -nc" # Do not apply correction to original altitude where B&E was derived
115
113
  command += " -I" # Clear the fall-back configuration directories
@@ -150,7 +148,7 @@ class SimulatorCameraEfficiency(SimtelRunner):
150
148
  command = general.clear_default_sim_telarray_cfg_directories(command)
151
149
 
152
150
  return (
153
- f"cd {self._simtel_path.joinpath('sim_telarray')} && {command}",
151
+ f"cd {settings.config.sim_telarray_path} && {command}",
154
152
  self._file_simtel,
155
153
  self._file_log,
156
154
  )