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.
- {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/METADATA +6 -1
- {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/RECORD +135 -130
- {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/WHEEL +1 -1
- {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/entry_points.txt +3 -2
- {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/licenses/LICENSE +1 -1
- simtools/_version.py +2 -2
- simtools/application_control.py +35 -7
- simtools/applications/convert_geo_coordinates_of_array_elements.py +3 -3
- simtools/applications/db_add_file_to_db.py +1 -1
- simtools/applications/db_add_simulation_model_from_repository_to_db.py +1 -1
- simtools/applications/db_add_value_from_json_to_db.py +1 -1
- simtools/applications/db_generate_compound_indexes.py +1 -1
- simtools/applications/db_get_array_layouts_from_db.py +3 -7
- simtools/applications/db_get_file_from_db.py +1 -1
- simtools/applications/db_get_parameter_from_db.py +1 -1
- simtools/applications/db_inspect_databases.py +1 -1
- simtools/applications/db_upload_model_repository.py +1 -1
- simtools/applications/derive_ctao_array_layouts.py +1 -2
- simtools/applications/{calculate_incident_angles.py → derive_incident_angle.py} +16 -18
- simtools/applications/derive_mirror_rnda.py +112 -180
- simtools/applications/derive_psf_parameters.py +0 -1
- simtools/applications/derive_pulse_shape_parameters.py +0 -1
- simtools/applications/derive_trigger_rates.py +1 -1
- simtools/applications/docs_produce_array_element_report.py +2 -8
- simtools/applications/docs_produce_calibration_reports.py +1 -3
- simtools/applications/docs_produce_model_parameter_reports.py +0 -2
- simtools/applications/docs_produce_simulation_configuration_report.py +1 -3
- simtools/applications/generate_array_config.py +0 -1
- simtools/applications/generate_corsika_histograms.py +79 -229
- simtools/applications/generate_regular_arrays.py +76 -69
- simtools/applications/generate_simtel_event_data.py +2 -2
- simtools/applications/maintain_simulation_model_add_production.py +2 -2
- simtools/applications/maintain_simulation_model_write_array_element_positions.py +87 -0
- simtools/applications/plot_array_layout.py +5 -111
- simtools/applications/plot_simulated_event_distributions.py +57 -0
- simtools/applications/plot_tabular_data.py +0 -1
- simtools/applications/plot_tabular_data_for_model_parameter.py +1 -6
- simtools/applications/production_derive_corsika_limits.py +1 -1
- simtools/applications/production_generate_grid.py +0 -1
- simtools/applications/run_application.py +1 -1
- simtools/applications/simulate_flasher.py +3 -15
- simtools/applications/simulate_illuminator.py +2 -11
- simtools/applications/simulate_pedestals.py +1 -5
- simtools/applications/simulate_prod.py +8 -11
- simtools/applications/simulate_prod_htcondor_generator.py +1 -1
- simtools/applications/submit_array_layouts.py +2 -4
- simtools/applications/submit_data_from_external.py +2 -1
- simtools/applications/submit_model_parameter_from_external.py +1 -3
- simtools/applications/validate_camera_efficiency.py +28 -28
- simtools/applications/validate_camera_fov.py +0 -1
- simtools/applications/validate_cumulative_psf.py +1 -5
- simtools/applications/validate_optics.py +2 -14
- simtools/atmosphere.py +83 -0
- simtools/camera/camera_efficiency.py +171 -53
- simtools/camera/single_photon_electron_spectrum.py +8 -7
- simtools/configuration/commandline_parser.py +82 -11
- simtools/configuration/configurator.py +6 -11
- simtools/constants.py +5 -0
- simtools/corsika/corsika_config.py +100 -202
- simtools/corsika/corsika_histograms.py +561 -1708
- simtools/corsika/primary_particle.py +1 -1
- simtools/data_model/metadata_collector.py +5 -2
- simtools/data_model/metadata_model.py +0 -4
- simtools/data_model/model_data_writer.py +59 -64
- simtools/data_model/schema.py +2 -0
- simtools/data_model/validate_data.py +1 -3
- simtools/db/db_handler.py +23 -10
- simtools/db/mongo_db.py +2 -2
- simtools/dependencies.py +81 -38
- simtools/io/ascii_handler.py +55 -5
- simtools/io/io_handler.py +23 -12
- simtools/io/table_handler.py +1 -1
- simtools/job_execution/job_manager.py +154 -79
- simtools/job_execution/process_pool.py +137 -0
- simtools/layout/array_layout.py +4 -13
- simtools/layout/array_layout_utils.py +348 -57
- simtools/model/array_model.py +23 -63
- simtools/model/calibration_model.py +4 -8
- simtools/model/legacy_model_parameter.py +134 -0
- simtools/model/model_parameter.py +147 -86
- simtools/model/model_utils.py +40 -6
- simtools/model/site_model.py +4 -8
- simtools/model/telescope_model.py +10 -16
- simtools/production_configuration/derive_corsika_limits.py +6 -11
- simtools/production_configuration/interpolation_handler.py +16 -16
- simtools/ray_tracing/incident_angles.py +92 -17
- simtools/ray_tracing/mirror_panel_psf.py +338 -222
- simtools/ray_tracing/psf_analysis.py +62 -48
- simtools/ray_tracing/psf_parameter_optimisation.py +3 -3
- simtools/ray_tracing/ray_tracing.py +43 -25
- simtools/reporting/docs_auto_report_generator.py +8 -13
- simtools/reporting/docs_read_parameters.py +2 -8
- simtools/runners/corsika_runner.py +52 -195
- simtools/runners/corsika_simtel_runner.py +77 -108
- simtools/runners/runner_services.py +214 -213
- simtools/runners/simtel_runner.py +27 -160
- simtools/runners/simtools_runner.py +11 -73
- simtools/schemas/application_workflow.metaschema.yml +8 -0
- simtools/settings.py +173 -0
- simtools/{io/eventio_handler.py → sim_events/file_info.py} +3 -3
- simtools/{simtel/simtel_io_event_histograms.py → sim_events/histograms.py} +25 -15
- simtools/{simtel/simtel_io_event_reader.py → sim_events/reader.py} +20 -17
- simtools/{simtel/simtel_io_event_writer.py → sim_events/writer.py} +84 -25
- simtools/simtel/pulse_shapes.py +7 -2
- simtools/simtel/simtel_config_writer.py +79 -91
- simtools/simtel/simtel_seeds.py +184 -0
- simtools/simtel/simtel_table_reader.py +6 -4
- simtools/simtel/simulator_array.py +114 -109
- simtools/simtel/simulator_camera_efficiency.py +68 -46
- simtools/simtel/simulator_light_emission.py +164 -132
- simtools/simtel/simulator_ray_tracing.py +80 -71
- simtools/simulator.py +137 -355
- simtools/telescope_trigger_rates.py +3 -4
- simtools/testing/assertions.py +84 -33
- simtools/testing/configuration.py +1 -2
- simtools/testing/helpers.py +2 -3
- simtools/testing/log_inspector.py +1 -0
- simtools/testing/sim_telarray_metadata.py +14 -12
- simtools/testing/validate_output.py +121 -42
- simtools/utils/general.py +43 -17
- simtools/utils/geometry.py +0 -77
- simtools/utils/names.py +5 -5
- simtools/utils/random.py +36 -0
- simtools/visualization/legend_handlers.py +7 -6
- simtools/visualization/plot_array_layout.py +91 -16
- simtools/visualization/plot_corsika_histograms.py +145 -605
- simtools/visualization/plot_incident_angles.py +48 -1
- simtools/visualization/plot_mirrors.py +1 -4
- simtools/visualization/plot_pixels.py +2 -4
- simtools/visualization/plot_psf.py +160 -19
- simtools/visualization/plot_simtel_event_histograms.py +4 -4
- simtools/visualization/plot_simtel_events.py +6 -11
- simtools/visualization/plot_tables.py +8 -19
- simtools/visualization/visualize.py +22 -2
- simtools/applications/db_development_tools/write_array_elements_positions_to_repository.py +0 -160
- simtools/applications/print_version.py +0 -53
- simtools/io/hdf5_handler.py +0 -139
- {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/top_level.txt +0 -0
|
@@ -44,11 +44,11 @@ class TriggeredEventData:
|
|
|
44
44
|
angular_distance: list[float] = field(default_factory=list)
|
|
45
45
|
|
|
46
46
|
|
|
47
|
-
class
|
|
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
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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(
|
|
254
|
-
return table_name_map.get(
|
|
252
|
+
def get_name(k):
|
|
253
|
+
return k if table_name_map is None else table_name_map.get(k)
|
|
255
254
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Generate a reduced dataset from
|
|
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.
|
|
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
|
|
74
|
+
class EventDataWriter:
|
|
72
75
|
"""
|
|
73
|
-
Process
|
|
76
|
+
Process simulation events (CORSIKA/sim_telarray) and write tables to file.
|
|
74
77
|
|
|
75
|
-
Extracts essential information from
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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":
|
|
171
|
-
"energy_min":
|
|
172
|
-
"energy_max":
|
|
173
|
-
"viewcone_min":
|
|
174
|
-
"viewcone_max":
|
|
175
|
-
"core_scatter_min":
|
|
176
|
-
"core_scatter_max":
|
|
177
|
-
"zenith":
|
|
178
|
-
"azimuth":
|
|
179
|
-
"nsb_level":
|
|
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.
|
simtools/simtel/pulse_shapes.py
CHANGED
|
@@ -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
|
-
|
|
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,34 +10,13 @@ import numpy as np
|
|
|
10
10
|
|
|
11
11
|
import simtools.utils.general as gen
|
|
12
12
|
import simtools.version
|
|
13
|
-
from simtools
|
|
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
|
|
|
17
17
|
logger = logging.getLogger(__name__)
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
def sim_telarray_random_seeds(seed, number):
|
|
21
|
-
"""
|
|
22
|
-
Generate random seeds to be used in sim_telarray.
|
|
23
|
-
|
|
24
|
-
Parameters
|
|
25
|
-
----------
|
|
26
|
-
seed: int
|
|
27
|
-
Seed for the random number generator.
|
|
28
|
-
number: int
|
|
29
|
-
Number of random seeds to generate.
|
|
30
|
-
|
|
31
|
-
Returns
|
|
32
|
-
-------
|
|
33
|
-
list
|
|
34
|
-
List of random seeds.
|
|
35
|
-
"""
|
|
36
|
-
rng = np.random.default_rng(seed)
|
|
37
|
-
max_int32 = np.iinfo(np.int32).max # sim_telarray requires 32 bit integers
|
|
38
|
-
return list(rng.integers(low=1, high=max_int32, size=number, dtype=np.int32))
|
|
39
|
-
|
|
40
|
-
|
|
41
20
|
class SimtelConfigWriter:
|
|
42
21
|
"""
|
|
43
22
|
SimtelConfigWriter writes sim_telarray configuration files.
|
|
@@ -56,8 +35,6 @@ class SimtelConfigWriter:
|
|
|
56
35
|
Layout name.
|
|
57
36
|
label: str
|
|
58
37
|
Instance label. Important for output file naming.
|
|
59
|
-
simtel_path: str or Path
|
|
60
|
-
Path to the sim_telarray installation directory.
|
|
61
38
|
"""
|
|
62
39
|
|
|
63
40
|
TAB = " " * 3
|
|
@@ -70,11 +47,9 @@ class SimtelConfigWriter:
|
|
|
70
47
|
telescope_model_name=None,
|
|
71
48
|
telescope_design_model=None,
|
|
72
49
|
label=None,
|
|
73
|
-
simtel_path=None,
|
|
74
50
|
):
|
|
75
51
|
"""Initialize SimtelConfigWriter."""
|
|
76
52
|
self._logger = logging.getLogger(__name__)
|
|
77
|
-
self._logger.debug("Init SimtelConfigWriter")
|
|
78
53
|
|
|
79
54
|
self._site = site
|
|
80
55
|
self._model_version = model_version
|
|
@@ -82,7 +57,6 @@ class SimtelConfigWriter:
|
|
|
82
57
|
self._layout_name = layout_name
|
|
83
58
|
self._telescope_model_name = telescope_model_name
|
|
84
59
|
self._telescope_design_model = telescope_design_model
|
|
85
|
-
self._simtel_path = simtel_path
|
|
86
60
|
|
|
87
61
|
def write_telescope_config_file(
|
|
88
62
|
self, config_file_path, parameters, telescope_name=None, telescope_design_model=None
|
|
@@ -125,14 +99,14 @@ class SimtelConfigWriter:
|
|
|
125
99
|
file.write(f"{meta}\n")
|
|
126
100
|
|
|
127
101
|
@staticmethod
|
|
128
|
-
def
|
|
102
|
+
def write_light_pulse_table_gauss_exp_conv(
|
|
129
103
|
file_path,
|
|
130
|
-
width_ns
|
|
131
|
-
exp_decay_ns
|
|
104
|
+
width_ns,
|
|
105
|
+
exp_decay_ns,
|
|
106
|
+
fadc_sum_bins,
|
|
132
107
|
dt_ns=0.1,
|
|
133
108
|
rise_range=(0.1, 0.9),
|
|
134
109
|
fall_range=(0.9, 0.1),
|
|
135
|
-
fadc_sum_bins=None,
|
|
136
110
|
time_margin_ns=10.0,
|
|
137
111
|
):
|
|
138
112
|
"""Write a pulse table for a Gaussian convolved with a causal exponential.
|
|
@@ -143,22 +117,19 @@ class SimtelConfigWriter:
|
|
|
143
117
|
Destination path of the ASCII pulse table to write. Parent directory must exist.
|
|
144
118
|
width_ns : float
|
|
145
119
|
Target rise time in ns between the fractional levels defined by ``rise_range``.
|
|
146
|
-
Defaults correspond to 10-90% rise time.
|
|
147
120
|
exp_decay_ns : float
|
|
148
121
|
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
122
|
fadc_sum_bins : int
|
|
157
123
|
Length of the FADC integration window (treated as ns here) used to derive
|
|
158
124
|
the internal time sampling window of the solver as [-(margin), bins + margin].
|
|
125
|
+
dt_ns : float, optional
|
|
126
|
+
Time sampling step in ns for the generated pulse table.
|
|
127
|
+
rise_range : tuple[float, float], optional
|
|
128
|
+
Fractional amplitude bounds (low, high) for rise-time definition.
|
|
129
|
+
fall_range : tuple[float, float], optional
|
|
130
|
+
Fractional amplitude bounds (high, low) for fall-time definition.
|
|
159
131
|
time_margin_ns : float, optional
|
|
160
132
|
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
133
|
|
|
163
134
|
Returns
|
|
164
135
|
-------
|
|
@@ -174,8 +145,10 @@ class SimtelConfigWriter:
|
|
|
174
145
|
if width_ns is None or exp_decay_ns is None:
|
|
175
146
|
raise ValueError("width_ns (rise 10-90) and exp_decay_ns (fall 90-10) are required")
|
|
176
147
|
logger.info(
|
|
177
|
-
|
|
178
|
-
f"
|
|
148
|
+
"Generating pulse-shape table with "
|
|
149
|
+
f"rise{int(rise_range[0] * 100)}-{int(rise_range[1] * 100)}={width_ns} ns, "
|
|
150
|
+
f"fall{int(fall_range[0] * 100)}-{int(fall_range[1] * 100)}={exp_decay_ns} ns, "
|
|
151
|
+
f"dt={dt_ns} ns"
|
|
179
152
|
)
|
|
180
153
|
width = float(fadc_sum_bins)
|
|
181
154
|
t_start_ns = -abs(time_margin_ns + width)
|
|
@@ -193,6 +166,42 @@ class SimtelConfigWriter:
|
|
|
193
166
|
|
|
194
167
|
return SimtelConfigWriter._write_ascii_pulse_table(file_path, t, y)
|
|
195
168
|
|
|
169
|
+
@staticmethod
|
|
170
|
+
def write_angular_distribution_table_lambertian(
|
|
171
|
+
file_path,
|
|
172
|
+
max_angle_deg,
|
|
173
|
+
n_samples=100,
|
|
174
|
+
):
|
|
175
|
+
"""Write a Lambertian angular distribution table (I(t) ~ cos(t)).
|
|
176
|
+
|
|
177
|
+
Parameters
|
|
178
|
+
----------
|
|
179
|
+
file_path : str or pathlib.Path
|
|
180
|
+
Destination path of the ASCII table to write. Parent directory must exist.
|
|
181
|
+
max_angle_deg : float
|
|
182
|
+
Maximum angle (deg) for the distribution sampling range [0, max_angle_deg].
|
|
183
|
+
n_samples : int, optional
|
|
184
|
+
Number of samples (including end point) from 0 to max_angle_deg. Default 100.
|
|
185
|
+
|
|
186
|
+
Returns
|
|
187
|
+
-------
|
|
188
|
+
pathlib.Path
|
|
189
|
+
Path to created angular distribution table.
|
|
190
|
+
"""
|
|
191
|
+
logger.info(
|
|
192
|
+
f"Generating Lambertian angular distribution table up to {max_angle_deg} deg "
|
|
193
|
+
f"with {n_samples} samples"
|
|
194
|
+
)
|
|
195
|
+
angles = np.linspace(0.0, float(max_angle_deg), int(n_samples), dtype=float)
|
|
196
|
+
intensities = np.cos(np.deg2rad(angles))
|
|
197
|
+
intensities[intensities < 0] = 0.0
|
|
198
|
+
if intensities.max() > 0:
|
|
199
|
+
intensities /= intensities.max()
|
|
200
|
+
|
|
201
|
+
return SimtelConfigWriter._write_ascii_angle_distribution_table(
|
|
202
|
+
file_path, angles, intensities
|
|
203
|
+
)
|
|
204
|
+
|
|
196
205
|
@staticmethod
|
|
197
206
|
def _write_ascii_pulse_table(file_path, t, y):
|
|
198
207
|
"""Write two-column ASCII pulse table."""
|
|
@@ -202,6 +211,15 @@ class SimtelConfigWriter:
|
|
|
202
211
|
fh.write(f"{ti:.6f} {yi:.8f}\n")
|
|
203
212
|
return Path(file_path)
|
|
204
213
|
|
|
214
|
+
@staticmethod
|
|
215
|
+
def _write_ascii_angle_distribution_table(file_path, angles, intensities):
|
|
216
|
+
"""Write two-column ASCII angular distribution table."""
|
|
217
|
+
with open(file_path, "w", encoding="utf-8") as fh:
|
|
218
|
+
fh.write("# angle[deg] relative_intensity\n")
|
|
219
|
+
for a, i in zip(angles, intensities):
|
|
220
|
+
fh.write(f"{a:.6f} {i:.8f}\n")
|
|
221
|
+
return Path(file_path)
|
|
222
|
+
|
|
205
223
|
def _get_parameters_for_sim_telarray(self, parameters, config_file_path):
|
|
206
224
|
"""
|
|
207
225
|
Convert parameter dictionary to sim_telarray configuration file format.
|
|
@@ -257,28 +275,24 @@ class SimtelConfigWriter:
|
|
|
257
275
|
Model parameters in sim_telarray format including flasher parameters.
|
|
258
276
|
|
|
259
277
|
"""
|
|
260
|
-
if "flasher_pulse_shape" not in parameters
|
|
278
|
+
if "flasher_pulse_shape" not in parameters:
|
|
261
279
|
return simtel_par
|
|
262
280
|
|
|
263
281
|
mapping = {
|
|
264
282
|
"gauss": "laser_pulse_sigtime",
|
|
265
283
|
"tophat": "laser_pulse_twidth",
|
|
284
|
+
"gauss-exponential": "laser_pulse_sigtime",
|
|
266
285
|
}
|
|
267
286
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
)
|
|
273
|
-
else:
|
|
274
|
-
simtel_par["laser_pulse_exptime"] = 0.0
|
|
287
|
+
shape_value = parameters.get("flasher_pulse_shape", {}).get("value")
|
|
288
|
+
shape = shape_value[0].lower()
|
|
289
|
+
width = shape_value[1]
|
|
290
|
+
exp_decay = shape_value[2]
|
|
275
291
|
|
|
276
|
-
|
|
292
|
+
simtel_par["laser_pulse_exptime"] = exp_decay if ("exponential" in shape) else 0.0
|
|
277
293
|
|
|
278
294
|
simtel_par.update(dict.fromkeys(mapping.values(), 0.0))
|
|
279
|
-
if shape
|
|
280
|
-
simtel_par["laser_pulse_sigtime"] = width
|
|
281
|
-
elif shape in mapping:
|
|
295
|
+
if shape in mapping:
|
|
282
296
|
simtel_par[mapping[shape]] = width
|
|
283
297
|
else:
|
|
284
298
|
self._logger.warning(f"Flasher pulse shape '{shape}' without width definition")
|
|
@@ -456,39 +470,6 @@ class SimtelConfigWriter:
|
|
|
456
470
|
file.write(f"# include <{tel_config_file}>\n\n")
|
|
457
471
|
file.write("#endif \n\n") # configuration files need to end with \n\n
|
|
458
472
|
|
|
459
|
-
if additional_metadata and additional_metadata.get("random_instrument_instances"):
|
|
460
|
-
self._write_random_seeds_file(additional_metadata, config_file_directory)
|
|
461
|
-
|
|
462
|
-
def _write_random_seeds_file(self, sim_telarray_seeds, config_file_directory):
|
|
463
|
-
"""
|
|
464
|
-
Write list of random number used to generate random instances of instrument.
|
|
465
|
-
|
|
466
|
-
Parameters
|
|
467
|
-
----------
|
|
468
|
-
random_instrument_instances: int
|
|
469
|
-
Number of random instances of the instrument.
|
|
470
|
-
"""
|
|
471
|
-
self._logger.info(
|
|
472
|
-
"Writing random seed file "
|
|
473
|
-
f"{config_file_directory}/{sim_telarray_seeds['seed_file_name']}"
|
|
474
|
-
f" (global seed {sim_telarray_seeds['seed']})"
|
|
475
|
-
)
|
|
476
|
-
if sim_telarray_seeds["random_instrument_instances"] > 1024:
|
|
477
|
-
raise ValueError("Number of random instances of instrument must be less than 1024")
|
|
478
|
-
random_integers = sim_telarray_random_seeds(
|
|
479
|
-
sim_telarray_seeds["seed"], sim_telarray_seeds["random_instrument_instances"]
|
|
480
|
-
)
|
|
481
|
-
with open(
|
|
482
|
-
config_file_directory / sim_telarray_seeds["seed_file_name"], "w", encoding="utf-8"
|
|
483
|
-
) as file:
|
|
484
|
-
file.write(
|
|
485
|
-
"# Random seeds for instrument configuration generated with seed "
|
|
486
|
-
f"{sim_telarray_seeds['seed']}"
|
|
487
|
-
f" (model version {self._model_version}, site {self._site})\n"
|
|
488
|
-
)
|
|
489
|
-
for number in random_integers:
|
|
490
|
-
file.write(f"{number}\n")
|
|
491
|
-
|
|
492
473
|
def write_single_mirror_list_file(
|
|
493
474
|
self, mirror_number, mirrors, single_mirror_list_file, set_focal_length_to_zero=False
|
|
494
475
|
):
|
|
@@ -576,17 +557,24 @@ class SimtelConfigWriter:
|
|
|
576
557
|
"simtools_model_production_version": self._model_version,
|
|
577
558
|
}
|
|
578
559
|
try:
|
|
579
|
-
build_opts =
|
|
580
|
-
Path(self._simtel_path) / "build_opts.yml"
|
|
581
|
-
)
|
|
560
|
+
build_opts = dependencies.get_build_options()
|
|
582
561
|
for key, value in build_opts.items():
|
|
583
562
|
meta_items[f"simtools_{key}"] = value
|
|
584
563
|
except (FileNotFoundError, TypeError):
|
|
585
564
|
pass # don't expect build_opts.yml to be present on all systems
|
|
586
565
|
|
|
566
|
+
# CORSIKA executable without _flat/_curved suffix (do not know here if curved or flat)
|
|
567
|
+
try:
|
|
568
|
+
meta_items["simtools_corsika_exec"] = settings.config.corsika_exe.name.removesuffix(
|
|
569
|
+
"_flat"
|
|
570
|
+
)
|
|
571
|
+
except AttributeError as exc:
|
|
572
|
+
raise AttributeError("CORSIKA executable path is not set in settings.") from exc
|
|
573
|
+
|
|
587
574
|
file.write(f"{self.TAB}% Simtools parameters\n")
|
|
588
575
|
for key, value in meta_items.items():
|
|
589
|
-
|
|
576
|
+
if not isinstance(value, list):
|
|
577
|
+
file.write(f"{self.TAB}metaparam global set {key} = {value}\n")
|
|
590
578
|
|
|
591
579
|
def _write_site_parameters(
|
|
592
580
|
self, file, site_parameters, model_path, telescope_model, additional_metadata=None
|