gammasimtools 0.24.0__py3-none-any.whl → 0.25.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.24.0.dist-info → gammasimtools-0.25.0.dist-info}/METADATA +1 -1
- {gammasimtools-0.24.0.dist-info → gammasimtools-0.25.0.dist-info}/RECORD +58 -55
- {gammasimtools-0.24.0.dist-info → gammasimtools-0.25.0.dist-info}/entry_points.txt +1 -0
- simtools/_version.py +2 -2
- simtools/application_control.py +50 -0
- simtools/applications/derive_psf_parameters.py +5 -0
- simtools/applications/derive_pulse_shape_parameters.py +195 -0
- simtools/applications/plot_array_layout.py +63 -1
- simtools/applications/simulate_flasher.py +3 -2
- simtools/applications/simulate_pedestals.py +1 -1
- simtools/applications/simulate_prod.py +8 -23
- simtools/applications/simulate_prod_htcondor_generator.py +7 -0
- simtools/applications/submit_array_layouts.py +5 -3
- simtools/applications/validate_file_using_schema.py +49 -123
- simtools/configuration/commandline_parser.py +8 -6
- simtools/corsika/corsika_config.py +197 -87
- simtools/data_model/model_data_writer.py +14 -2
- simtools/data_model/schema.py +112 -5
- simtools/data_model/validate_data.py +82 -48
- simtools/db/db_model_upload.py +2 -1
- simtools/db/mongo_db.py +133 -42
- simtools/dependencies.py +5 -9
- simtools/io/eventio_handler.py +128 -0
- simtools/job_execution/htcondor_script_generator.py +0 -2
- simtools/layout/array_layout_utils.py +1 -1
- simtools/model/array_model.py +36 -5
- simtools/model/model_parameter.py +0 -1
- simtools/model/model_repository.py +18 -5
- simtools/ray_tracing/psf_analysis.py +11 -8
- simtools/ray_tracing/psf_parameter_optimisation.py +822 -679
- simtools/reporting/docs_read_parameters.py +69 -9
- simtools/runners/corsika_runner.py +12 -3
- simtools/runners/corsika_simtel_runner.py +6 -0
- simtools/runners/runner_services.py +17 -7
- simtools/runners/simtel_runner.py +12 -54
- simtools/schemas/model_parameters/flasher_pulse_exp_decay.schema.yml +2 -0
- simtools/schemas/model_parameters/flasher_pulse_shape.schema.yml +50 -0
- simtools/schemas/model_parameters/flasher_pulse_width.schema.yml +2 -0
- simtools/schemas/simulation_models_info.schema.yml +2 -0
- simtools/simtel/pulse_shapes.py +268 -0
- simtools/simtel/simtel_config_writer.py +82 -1
- simtools/simtel/simtel_io_event_writer.py +2 -2
- simtools/simtel/simulator_array.py +58 -12
- simtools/simtel/simulator_light_emission.py +45 -8
- simtools/simulator.py +361 -347
- simtools/testing/assertions.py +62 -6
- simtools/testing/configuration.py +1 -1
- simtools/testing/log_inspector.py +4 -1
- simtools/testing/sim_telarray_metadata.py +1 -1
- simtools/testing/validate_output.py +44 -9
- simtools/utils/names.py +2 -4
- simtools/version.py +37 -0
- simtools/visualization/legend_handlers.py +14 -4
- simtools/visualization/plot_array_layout.py +229 -33
- simtools/visualization/plot_mirrors.py +837 -0
- simtools/simtel/simtel_io_file_info.py +0 -62
- {gammasimtools-0.24.0.dist-info → gammasimtools-0.25.0.dist-info}/WHEEL +0 -0
- {gammasimtools-0.24.0.dist-info → gammasimtools-0.25.0.dist-info}/licenses/LICENSE +0 -0
- {gammasimtools-0.24.0.dist-info → gammasimtools-0.25.0.dist-info}/top_level.txt +0 -0
|
@@ -7,12 +7,9 @@ import numpy as np
|
|
|
7
7
|
from astropy import units as u
|
|
8
8
|
|
|
9
9
|
from simtools.corsika.primary_particle import PrimaryParticle
|
|
10
|
-
from simtools.io import io_handler
|
|
10
|
+
from simtools.io import eventio_handler, io_handler
|
|
11
11
|
from simtools.model.model_parameter import ModelParameter
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class InvalidCorsikaInputError(Exception):
|
|
15
|
-
"""Exception for invalid corsika input."""
|
|
12
|
+
from simtools.utils import general as gen
|
|
16
13
|
|
|
17
14
|
|
|
18
15
|
class CorsikaConfig:
|
|
@@ -45,53 +42,80 @@ class CorsikaConfig:
|
|
|
45
42
|
self._logger.debug("Init CorsikaConfig")
|
|
46
43
|
|
|
47
44
|
self.label = label
|
|
48
|
-
self.
|
|
49
|
-
self.azimuth_angle = None
|
|
45
|
+
self.shower_events = self.mc_events = None
|
|
46
|
+
self.zenith_angle = self.azimuth_angle = None
|
|
47
|
+
self.curved_atmosphere_min_zenith_angle = None
|
|
50
48
|
self._run_number = None
|
|
51
49
|
self.config_file_path = None
|
|
52
50
|
self.primary_particle = args_dict # see setter for primary_particle
|
|
51
|
+
self.use_curved_atmosphere = args_dict # see setter for use_curved_atmosphere
|
|
53
52
|
self.dummy_simulations = dummy_simulations
|
|
54
53
|
|
|
55
54
|
self.io_handler = io_handler.IOHandler()
|
|
56
55
|
self.array_model = array_model
|
|
57
|
-
self.config = self.
|
|
56
|
+
self.config = self._fill_corsika_configuration(args_dict, db_config)
|
|
57
|
+
self._initialize_from_config(args_dict)
|
|
58
58
|
self.is_file_updated = False
|
|
59
59
|
|
|
60
|
-
def __repr__(self):
|
|
61
|
-
"""CorsikaConfig class representation."""
|
|
62
|
-
return (
|
|
63
|
-
f"<class {self.__class__.__name__}> "
|
|
64
|
-
f"(site={self.array_model.site}, "
|
|
65
|
-
f"layout={self.array_model.layout_name}, label={self.label})"
|
|
66
|
-
)
|
|
67
|
-
|
|
68
60
|
@property
|
|
69
61
|
def primary_particle(self):
|
|
70
62
|
"""Primary particle."""
|
|
71
63
|
return self._primary_particle
|
|
72
64
|
|
|
73
65
|
@primary_particle.setter
|
|
74
|
-
def primary_particle(self,
|
|
66
|
+
def primary_particle(self, args):
|
|
75
67
|
"""
|
|
76
|
-
Set primary particle from input dictionary.
|
|
68
|
+
Set primary particle from input dictionary or CORSIKA 7 particle ID.
|
|
77
69
|
|
|
78
70
|
This is to make sure that when setting the primary particle,
|
|
79
71
|
we get the full PrimaryParticle object expected.
|
|
80
72
|
|
|
81
73
|
Parameters
|
|
82
74
|
----------
|
|
83
|
-
|
|
75
|
+
args: dict, corsika particle ID, or None
|
|
84
76
|
Configuration dictionary
|
|
85
77
|
"""
|
|
86
|
-
|
|
78
|
+
if (
|
|
79
|
+
isinstance(args, dict)
|
|
80
|
+
and args.get("primary_id_type") is not None
|
|
81
|
+
and args.get("primary") is not None
|
|
82
|
+
):
|
|
83
|
+
self._primary_particle = PrimaryParticle(
|
|
84
|
+
particle_id_type=args.get("primary_id_type"), particle_id=args.get("primary")
|
|
85
|
+
)
|
|
86
|
+
elif isinstance(args, int):
|
|
87
|
+
self._primary_particle = PrimaryParticle(
|
|
88
|
+
particle_id_type="corsika7_id", particle_id=args
|
|
89
|
+
)
|
|
90
|
+
else:
|
|
91
|
+
self._primary_particle = PrimaryParticle()
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def use_curved_atmosphere(self):
|
|
95
|
+
"""Check if zenith angle condition for curved atmosphere usage for CORSIKA is met."""
|
|
96
|
+
return self._use_curved_atmosphere
|
|
97
|
+
|
|
98
|
+
@use_curved_atmosphere.setter
|
|
99
|
+
def use_curved_atmosphere(self, args):
|
|
100
|
+
"""Check if zenith angle condition for curved atmosphere usage for CORSIKA is met."""
|
|
101
|
+
self._use_curved_atmosphere = False
|
|
102
|
+
if isinstance(args, bool):
|
|
103
|
+
self._use_curved_atmosphere = args
|
|
104
|
+
elif isinstance(args, dict):
|
|
105
|
+
try:
|
|
106
|
+
self._use_curved_atmosphere = (
|
|
107
|
+
args.get("zenith_angle", 0.0 * u.deg).to("deg").value
|
|
108
|
+
> args["curved_atmosphere_min_zenith_angle"].to("deg").value
|
|
109
|
+
)
|
|
110
|
+
except KeyError:
|
|
111
|
+
self._use_curved_atmosphere = False
|
|
87
112
|
|
|
88
|
-
def
|
|
113
|
+
def _fill_corsika_configuration(self, args_dict, db_config=None):
|
|
89
114
|
"""
|
|
90
115
|
Fill CORSIKA configuration.
|
|
91
116
|
|
|
92
|
-
Dictionary keys are CORSIKA parameter names.
|
|
93
|
-
|
|
94
|
-
|
|
117
|
+
Dictionary keys are CORSIKA parameter names. Values are converted to
|
|
118
|
+
CORSIKA-consistent units.
|
|
95
119
|
|
|
96
120
|
Parameters
|
|
97
121
|
----------
|
|
@@ -108,31 +132,34 @@ class CorsikaConfig:
|
|
|
108
132
|
if args_dict is None:
|
|
109
133
|
return {}
|
|
110
134
|
|
|
111
|
-
self.is_file_updated = False
|
|
112
|
-
self.azimuth_angle = int(args_dict.get("azimuth_angle", 0.0 * u.deg).to("deg").value)
|
|
113
|
-
self.zenith_angle = int(args_dict.get("zenith_angle", 0.0 * u.deg).to("deg").value)
|
|
114
|
-
|
|
115
|
-
self._logger.debug(
|
|
116
|
-
f"Setting CORSIKA parameters from database ({args_dict['model_version']})"
|
|
117
|
-
)
|
|
118
|
-
|
|
119
135
|
config = {}
|
|
120
136
|
if self.dummy_simulations:
|
|
121
|
-
config["USER_INPUT"] = self._corsika_configuration_for_dummy_simulations()
|
|
137
|
+
config["USER_INPUT"] = self._corsika_configuration_for_dummy_simulations(args_dict)
|
|
138
|
+
elif args_dict.get("corsika_file", None) is not None:
|
|
139
|
+
config["USER_INPUT"] = self._corsika_configuration_from_corsika_file(
|
|
140
|
+
args_dict["corsika_file"]
|
|
141
|
+
)
|
|
122
142
|
else:
|
|
123
143
|
config["USER_INPUT"] = self._corsika_configuration_from_user_input(args_dict)
|
|
124
144
|
|
|
145
|
+
config.update(
|
|
146
|
+
self._fill_corsika_configuration_from_db(
|
|
147
|
+
gen.ensure_iterable(args_dict.get("model_version")), db_config
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
return config
|
|
151
|
+
|
|
152
|
+
def _fill_corsika_configuration_from_db(self, model_versions, db_config):
|
|
153
|
+
"""Fill CORSIKA configuration from database."""
|
|
154
|
+
config = {}
|
|
125
155
|
if db_config is None: # all following parameter require DB
|
|
126
156
|
return config
|
|
127
157
|
|
|
128
|
-
#
|
|
129
|
-
# because for CORSIKA config we need only one and it doesn't matter which
|
|
130
|
-
model_versions = args_dict.get("model_version", None)
|
|
131
|
-
if not isinstance(model_versions, list):
|
|
132
|
-
model_versions = [model_versions]
|
|
158
|
+
# For multiple model versions, check that CORSIKA parameters are identical
|
|
133
159
|
self.assert_corsika_configurations_match(model_versions, db_config=db_config)
|
|
134
160
|
model_version = model_versions[0]
|
|
135
|
-
|
|
161
|
+
|
|
162
|
+
self._logger.debug(f"Using model version {model_version} for CORSIKA parameters from DB")
|
|
136
163
|
db_model_parameters = ModelParameter(db_config=db_config, model_version=model_version)
|
|
137
164
|
parameters_from_db = db_model_parameters.get_simulation_software_parameters("corsika")
|
|
138
165
|
|
|
@@ -144,9 +171,41 @@ class CorsikaConfig:
|
|
|
144
171
|
)
|
|
145
172
|
config["DEBUGGING_OUTPUT_PARAMETERS"] = self._corsika_configuration_debugging_parameters()
|
|
146
173
|
config["IACT_PARAMETERS"] = self._corsika_configuration_iact_parameters(parameters_from_db)
|
|
147
|
-
|
|
148
174
|
return config
|
|
149
175
|
|
|
176
|
+
def _initialize_from_config(self, args_dict):
|
|
177
|
+
"""
|
|
178
|
+
Initialize additional parameters either from command line args or from derived config.
|
|
179
|
+
|
|
180
|
+
Takes into account that in the case of a given CORSIKA input file, some parameters are read
|
|
181
|
+
from the file instead of the command line args.
|
|
182
|
+
|
|
183
|
+
"""
|
|
184
|
+
self.primary_particle = int(self.config.get("USER_INPUT", {}).get("PRMPAR", [1])[0])
|
|
185
|
+
self.shower_events = int(self.config.get("USER_INPUT", {}).get("NSHOW", [0])[0])
|
|
186
|
+
self.mc_events = int(
|
|
187
|
+
self.shower_events * self.config.get("USER_INPUT", {}).get("CSCAT", [1])[0]
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
if args_dict.get("corsika_file", None) is not None:
|
|
191
|
+
azimuth = self._rotate_azimuth_by_180deg(
|
|
192
|
+
0.5 * (self.config["USER_INPUT"]["PHIP"][0] + self.config["USER_INPUT"]["PHIP"][1]),
|
|
193
|
+
invert_operation=True,
|
|
194
|
+
)
|
|
195
|
+
zenith = 0.5 * (
|
|
196
|
+
self.config["USER_INPUT"]["THETAP"][0] + self.config["USER_INPUT"]["THETAP"][1]
|
|
197
|
+
)
|
|
198
|
+
else:
|
|
199
|
+
azimuth = args_dict.get("azimuth_angle", 0.0 * u.deg).to("deg").value
|
|
200
|
+
zenith = args_dict.get("zenith_angle", 20.0 * u.deg).to("deg").value
|
|
201
|
+
|
|
202
|
+
self.azimuth_angle = round(azimuth)
|
|
203
|
+
self.zenith_angle = round(zenith)
|
|
204
|
+
|
|
205
|
+
self.curved_atmosphere_min_zenith_angle = (
|
|
206
|
+
args_dict.get("curved_atmosphere_min_zenith_angle", 90.0 * u.deg).to("deg").value
|
|
207
|
+
)
|
|
208
|
+
|
|
150
209
|
def assert_corsika_configurations_match(self, model_versions, db_config=None):
|
|
151
210
|
"""
|
|
152
211
|
Assert that CORSIKA configurations match across all model versions.
|
|
@@ -193,13 +252,13 @@ class CorsikaConfig:
|
|
|
193
252
|
f" {model_versions[i]}: {current_value}\n"
|
|
194
253
|
f" {model_versions[i + 1]}: {next_value}"
|
|
195
254
|
)
|
|
196
|
-
raise
|
|
255
|
+
raise ValueError(
|
|
197
256
|
f"CORSIKA parameter '{key}' differs between model versions "
|
|
198
257
|
f"{model_versions[i]} and {model_versions[i + 1]}. "
|
|
199
258
|
f"Values are {current_value} and {next_value} respectively."
|
|
200
259
|
)
|
|
201
260
|
|
|
202
|
-
def _corsika_configuration_for_dummy_simulations(self):
|
|
261
|
+
def _corsika_configuration_for_dummy_simulations(self, args_dict):
|
|
203
262
|
"""
|
|
204
263
|
Return CORSIKA configuration for dummy simulations.
|
|
205
264
|
|
|
@@ -211,18 +270,75 @@ class CorsikaConfig:
|
|
|
211
270
|
dict
|
|
212
271
|
Dictionary with CORSIKA parameters for dummy simulations.
|
|
213
272
|
"""
|
|
273
|
+
theta, phi = self._get_corsika_theta_phi(args_dict)
|
|
214
274
|
return {
|
|
215
275
|
"EVTNR": [1],
|
|
216
276
|
"NSHOW": [1],
|
|
217
277
|
"PRMPAR": [1], # CORSIKA ID 1 for primary gamma
|
|
218
278
|
"ESLOPE": [-2.0],
|
|
219
279
|
"ERANGE": [0.1, 0.1],
|
|
220
|
-
"THETAP": [
|
|
221
|
-
"PHIP": [
|
|
280
|
+
"THETAP": [theta, theta],
|
|
281
|
+
"PHIP": [phi, phi],
|
|
222
282
|
"VIEWCONE": [0.0, 0.0],
|
|
223
283
|
"CSCAT": [1, 0.0, 10.0],
|
|
224
284
|
}
|
|
225
285
|
|
|
286
|
+
def _corsika_configuration_from_corsika_file(self, corsika_input_file):
|
|
287
|
+
"""
|
|
288
|
+
Get CORSIKA configuration run header of provided input files.
|
|
289
|
+
|
|
290
|
+
Reads configuration from the run and event headers from the CORSIKA input file
|
|
291
|
+
(unfortunately quite fine tuned to the pycorsikaio run and event
|
|
292
|
+
header implementation).
|
|
293
|
+
|
|
294
|
+
Parameters
|
|
295
|
+
----------
|
|
296
|
+
corsika_input_file : str, path
|
|
297
|
+
Path to the CORSIKA input file.
|
|
298
|
+
|
|
299
|
+
Returns
|
|
300
|
+
-------
|
|
301
|
+
dict
|
|
302
|
+
Dictionary with CORSIKA parameters from input file.
|
|
303
|
+
"""
|
|
304
|
+
run_header, event_header = eventio_handler.get_corsika_run_and_event_headers(
|
|
305
|
+
corsika_input_file
|
|
306
|
+
)
|
|
307
|
+
self._logger.debug(f"CORSIKA run header from {corsika_input_file}")
|
|
308
|
+
|
|
309
|
+
def to_float32(value):
|
|
310
|
+
"""Convert value to numpy float32."""
|
|
311
|
+
return np.float32(value) if value is not None else 0.0
|
|
312
|
+
|
|
313
|
+
def to_int32(value):
|
|
314
|
+
"""Convert value to numpy int32."""
|
|
315
|
+
return np.int32(value) if value is not None else 0
|
|
316
|
+
|
|
317
|
+
if run_header["n_observation_levels"] > 0:
|
|
318
|
+
self._check_altitude_and_site(run_header["observation_height"][0])
|
|
319
|
+
|
|
320
|
+
return {
|
|
321
|
+
"EVTNR": [to_int32(event_header["event_number"])],
|
|
322
|
+
"NSHOW": [to_int32(run_header["n_showers"])],
|
|
323
|
+
"PRMPAR": [to_int32(event_header["particle_id"])],
|
|
324
|
+
"ESLOPE": [to_float32(run_header["energy_spectrum_slope"])],
|
|
325
|
+
"ERANGE": [to_float32(run_header["energy_min"]), to_float32(run_header["energy_max"])],
|
|
326
|
+
"THETAP": [
|
|
327
|
+
to_float32(event_header["theta_min"]),
|
|
328
|
+
to_float32(event_header["theta_max"]),
|
|
329
|
+
],
|
|
330
|
+
"PHIP": [to_float32(event_header["phi_min"]), to_float32(event_header["phi_max"])],
|
|
331
|
+
"VIEWCONE": [
|
|
332
|
+
to_float32(event_header["viewcone_inner_angle"]),
|
|
333
|
+
to_float32(event_header["viewcone_outer_angle"]),
|
|
334
|
+
],
|
|
335
|
+
"CSCAT": [
|
|
336
|
+
to_int32(event_header["n_reuse"]),
|
|
337
|
+
to_float32(event_header["reuse_x"]),
|
|
338
|
+
to_float32(event_header["reuse_y"]),
|
|
339
|
+
],
|
|
340
|
+
}
|
|
341
|
+
|
|
226
342
|
def _corsika_configuration_from_user_input(self, args_dict):
|
|
227
343
|
"""
|
|
228
344
|
Get CORSIKA configuration from user input.
|
|
@@ -237,6 +353,7 @@ class CorsikaConfig:
|
|
|
237
353
|
dict
|
|
238
354
|
Dictionary with CORSIKA parameters.
|
|
239
355
|
"""
|
|
356
|
+
theta, phi = self._get_corsika_theta_phi(args_dict)
|
|
240
357
|
return {
|
|
241
358
|
"EVTNR": [args_dict["event_number_first_shower"]],
|
|
242
359
|
"NSHOW": [args_dict["nshow"]],
|
|
@@ -246,24 +363,8 @@ class CorsikaConfig:
|
|
|
246
363
|
args_dict["energy_range"][0].to("GeV").value,
|
|
247
364
|
args_dict["energy_range"][1].to("GeV").value,
|
|
248
365
|
],
|
|
249
|
-
"THETAP": [
|
|
250
|
-
|
|
251
|
-
float(args_dict["zenith_angle"].to("deg").value),
|
|
252
|
-
],
|
|
253
|
-
"PHIP": [
|
|
254
|
-
self._rotate_azimuth_by_180deg(
|
|
255
|
-
args_dict["azimuth_angle"].to("deg").value,
|
|
256
|
-
correct_for_geomagnetic_field_alignment=args_dict[
|
|
257
|
-
"correct_for_b_field_alignment"
|
|
258
|
-
],
|
|
259
|
-
),
|
|
260
|
-
self._rotate_azimuth_by_180deg(
|
|
261
|
-
args_dict["azimuth_angle"].to("deg").value,
|
|
262
|
-
correct_for_geomagnetic_field_alignment=args_dict[
|
|
263
|
-
"correct_for_b_field_alignment"
|
|
264
|
-
],
|
|
265
|
-
),
|
|
266
|
-
],
|
|
366
|
+
"THETAP": [theta, theta],
|
|
367
|
+
"PHIP": [phi, phi],
|
|
267
368
|
"VIEWCONE": [
|
|
268
369
|
args_dict["view_cone"][0].to("deg").value,
|
|
269
370
|
args_dict["view_cone"][1].to("deg").value,
|
|
@@ -275,6 +376,26 @@ class CorsikaConfig:
|
|
|
275
376
|
],
|
|
276
377
|
}
|
|
277
378
|
|
|
379
|
+
def _check_altitude_and_site(self, observation_height):
|
|
380
|
+
"""Check that observation height from CORSIKA file matches site model."""
|
|
381
|
+
site_altitude = self.array_model.site_model.get_parameter_value("corsika_observation_level")
|
|
382
|
+
if not np.isclose(observation_height / 1.0e2, site_altitude, atol=1.0):
|
|
383
|
+
raise ValueError(
|
|
384
|
+
"Observatory altitude does not match CORSIKA file observation height: "
|
|
385
|
+
f"{site_altitude} m (site model) != {observation_height / 1.0e2} m (CORSIKA file)"
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
def _get_corsika_theta_phi(self, args_dict):
|
|
389
|
+
"""Get CORSIKA theta and phi angles from args_dict."""
|
|
390
|
+
theta = args_dict.get("zenith_angle", 20.0 * u.deg).to("deg").value
|
|
391
|
+
phi = self._rotate_azimuth_by_180deg(
|
|
392
|
+
args_dict.get("azimuth_angle", 0.0 * u.deg).to("deg").value,
|
|
393
|
+
correct_for_geomagnetic_field_alignment=args_dict.get(
|
|
394
|
+
"correct_for_b_field_alignment", True
|
|
395
|
+
),
|
|
396
|
+
)
|
|
397
|
+
return theta, phi
|
|
398
|
+
|
|
278
399
|
def _corsika_configuration_interaction_flags(self, parameters_from_db):
|
|
279
400
|
"""
|
|
280
401
|
Return CORSIKA interaction flags / parameters.
|
|
@@ -298,7 +419,8 @@ class CorsikaConfig:
|
|
|
298
419
|
parameters_from_db["corsika_starting_grammage"]
|
|
299
420
|
)
|
|
300
421
|
]
|
|
301
|
-
|
|
422
|
+
if not self.use_curved_atmosphere:
|
|
423
|
+
parameters["TSTART"] = ["T"]
|
|
302
424
|
parameters["ECUTS"] = self._input_config_corsika_particle_kinetic_energy_cutoff(
|
|
303
425
|
parameters_from_db["corsika_particle_kinetic_energy_cutoff"]
|
|
304
426
|
)
|
|
@@ -444,16 +566,23 @@ class CorsikaConfig:
|
|
|
444
566
|
return f"{int(value)}MB"
|
|
445
567
|
return f"{int(entry['value'] * u.Unit(entry['unit']).to('byte'))}"
|
|
446
568
|
|
|
447
|
-
def _rotate_azimuth_by_180deg(
|
|
569
|
+
def _rotate_azimuth_by_180deg(
|
|
570
|
+
self, az, correct_for_geomagnetic_field_alignment=True, invert_operation=False
|
|
571
|
+
):
|
|
448
572
|
"""
|
|
449
573
|
Convert azimuth angle to the CORSIKA coordinate system.
|
|
450
574
|
|
|
575
|
+
Corresponds to a rotation by 180 degrees, and optionally a correction for the
|
|
576
|
+
for the differences between the geographic and geomagnetic north pole.
|
|
577
|
+
|
|
451
578
|
Parameters
|
|
452
579
|
----------
|
|
453
580
|
az: float
|
|
454
581
|
Azimuth angle in degrees.
|
|
455
582
|
correct_for_geomagnetic_field_alignment: bool
|
|
456
583
|
Whether to correct for the geomagnetic field alignment.
|
|
584
|
+
invert_operation: bool
|
|
585
|
+
Whether to invert the operation (i.e., convert from CORSIKA to geographic system).
|
|
457
586
|
|
|
458
587
|
Returns
|
|
459
588
|
-------
|
|
@@ -463,6 +592,8 @@ class CorsikaConfig:
|
|
|
463
592
|
b_field_declination = 0
|
|
464
593
|
if correct_for_geomagnetic_field_alignment:
|
|
465
594
|
b_field_declination = self.array_model.site_model.get_parameter_value("geomag_rotation")
|
|
595
|
+
if invert_operation:
|
|
596
|
+
return (az - 180 - b_field_declination) % 360
|
|
466
597
|
return (az + 180 + b_field_declination) % 360
|
|
467
598
|
|
|
468
599
|
@property
|
|
@@ -470,27 +601,6 @@ class CorsikaConfig:
|
|
|
470
601
|
"""Primary particle name."""
|
|
471
602
|
return self.primary_particle.name
|
|
472
603
|
|
|
473
|
-
def _set_primary_particle(self, args_dict):
|
|
474
|
-
"""
|
|
475
|
-
Set primary particle from input dictionary.
|
|
476
|
-
|
|
477
|
-
Parameters
|
|
478
|
-
----------
|
|
479
|
-
args_dict: dict
|
|
480
|
-
Input dictionary.
|
|
481
|
-
|
|
482
|
-
Returns
|
|
483
|
-
-------
|
|
484
|
-
PrimaryParticle
|
|
485
|
-
Primary particle.
|
|
486
|
-
|
|
487
|
-
"""
|
|
488
|
-
if not args_dict or args_dict.get("primary_id_type") is None:
|
|
489
|
-
return PrimaryParticle()
|
|
490
|
-
return PrimaryParticle(
|
|
491
|
-
particle_id_type=args_dict.get("primary_id_type"), particle_id=args_dict.get("primary")
|
|
492
|
-
)
|
|
493
|
-
|
|
494
604
|
def get_config_parameter(self, par_name):
|
|
495
605
|
"""
|
|
496
606
|
Get value of CORSIKA configuration parameter.
|
|
@@ -674,7 +784,7 @@ class CorsikaConfig:
|
|
|
674
784
|
|
|
675
785
|
base_name = (
|
|
676
786
|
f"{self.primary_particle.name}_{run_number_in_file_name}"
|
|
677
|
-
f"za{int(self.get_config_parameter('THETAP')[0]):
|
|
787
|
+
f"za{int(self.get_config_parameter('THETAP')[0]):02}deg_"
|
|
678
788
|
f"azm{self.azimuth_angle:03}deg{view_cone}_"
|
|
679
789
|
f"{self.array_model.site}_{self.array_model.layout_name}_"
|
|
680
790
|
f"{self.array_model.model_version}{file_label}"
|
|
@@ -685,7 +795,7 @@ class CorsikaConfig:
|
|
|
685
795
|
if file_type == "config":
|
|
686
796
|
return f"corsika_config_{base_name}.input"
|
|
687
797
|
if file_type == "output_generic":
|
|
688
|
-
return f"{base_name}.zst"
|
|
798
|
+
return f"{base_name}.corsika.zst"
|
|
689
799
|
if file_type == "multipipe":
|
|
690
800
|
return f"multi_cta-{self.array_model.site}-{self.array_model.layout_name}.cfg"
|
|
691
801
|
|
|
@@ -101,6 +101,7 @@ class ModelDataWriter:
|
|
|
101
101
|
db_config=None,
|
|
102
102
|
unit=None,
|
|
103
103
|
meta_parameter=False,
|
|
104
|
+
model_parameter_schema_version=None,
|
|
104
105
|
):
|
|
105
106
|
"""
|
|
106
107
|
Generate DB-style model parameter dict and write it to json file.
|
|
@@ -125,6 +126,10 @@ class ModelDataWriter:
|
|
|
125
126
|
Database configuration. If not None, check if parameter with the same version exists.
|
|
126
127
|
unit: str
|
|
127
128
|
Unit of the parameter value (if applicable and value is not of type astropy Quantity).
|
|
129
|
+
meta_parameter: bool
|
|
130
|
+
Setting for meta parameter flag.
|
|
131
|
+
model_parameter_schema_version: str, None
|
|
132
|
+
Version of the model parameter schema (if None, use schema version from schema dict).
|
|
128
133
|
|
|
129
134
|
Returns
|
|
130
135
|
-------
|
|
@@ -158,6 +163,7 @@ class ModelDataWriter:
|
|
|
158
163
|
instrument,
|
|
159
164
|
parameter_version,
|
|
160
165
|
unique_id,
|
|
166
|
+
model_parameter_schema_version=model_parameter_schema_version,
|
|
161
167
|
unit=unit,
|
|
162
168
|
meta_parameter=meta_parameter,
|
|
163
169
|
)
|
|
@@ -211,6 +217,7 @@ class ModelDataWriter:
|
|
|
211
217
|
schema_version=None,
|
|
212
218
|
unit=None,
|
|
213
219
|
meta_parameter=False,
|
|
220
|
+
model_parameter_schema_version=None,
|
|
214
221
|
):
|
|
215
222
|
"""
|
|
216
223
|
Get validated parameter dictionary.
|
|
@@ -233,6 +240,8 @@ class ModelDataWriter:
|
|
|
233
240
|
Unit of the parameter value (if applicable and value is not an astropy Quantity).
|
|
234
241
|
meta_parameter: bool
|
|
235
242
|
Setting for meta parameter flag.
|
|
243
|
+
model_parameter_schema_version: str, None
|
|
244
|
+
Version of the model parameter schema (if None, use schema version from schema dict).
|
|
236
245
|
|
|
237
246
|
Returns
|
|
238
247
|
-------
|
|
@@ -240,7 +249,9 @@ class ModelDataWriter:
|
|
|
240
249
|
Validated parameter dictionary.
|
|
241
250
|
"""
|
|
242
251
|
self._logger.debug(f"Getting validated parameter dictionary for {instrument}")
|
|
243
|
-
self.schema_dict, schema_file = self._read_schema_dict(
|
|
252
|
+
self.schema_dict, schema_file = self._read_schema_dict(
|
|
253
|
+
parameter_name, model_parameter_schema_version
|
|
254
|
+
)
|
|
244
255
|
|
|
245
256
|
if unit is None:
|
|
246
257
|
value, unit = value_conversion.split_value_and_unit(value)
|
|
@@ -257,7 +268,8 @@ class ModelDataWriter:
|
|
|
257
268
|
"type": self._get_parameter_type(),
|
|
258
269
|
"file": self._parameter_is_a_file(),
|
|
259
270
|
"meta_parameter": meta_parameter,
|
|
260
|
-
"model_parameter_schema_version":
|
|
271
|
+
"model_parameter_schema_version": model_parameter_schema_version
|
|
272
|
+
or self.schema_dict.get("schema_version", "0.1.0"),
|
|
261
273
|
}
|
|
262
274
|
return self.validate_and_transform(
|
|
263
275
|
product_data_dict=data_dict,
|
simtools/data_model/schema.py
CHANGED
|
@@ -146,11 +146,10 @@ def validate_dict_using_schema(
|
|
|
146
146
|
|
|
147
147
|
def _validate_meta_schema_url(data):
|
|
148
148
|
"""Validate meta_schema_url if present in data."""
|
|
149
|
-
if (
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
):
|
|
149
|
+
if not isinstance(data, dict):
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
if data.get("meta_schema_url") is not None and not gen.url_exists(data["meta_schema_url"]):
|
|
154
153
|
raise FileNotFoundError(f"Meta schema URL does not exist: {data['meta_schema_url']}")
|
|
155
154
|
|
|
156
155
|
|
|
@@ -355,3 +354,111 @@ def validate_deprecation_and_version(data, software_name=None, ignore_software_v
|
|
|
355
354
|
_logger.warning(f"{msg}, but version check is ignored.")
|
|
356
355
|
else:
|
|
357
356
|
raise ValueError(msg)
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def validate_schema_from_files(
|
|
360
|
+
file_directory, file_name=None, schema_file=None, ignore_software_version=False
|
|
361
|
+
):
|
|
362
|
+
"""
|
|
363
|
+
Validate a schema file or several files in a directory.
|
|
364
|
+
|
|
365
|
+
Files to be validated are taken from file_directory and file_name pattern.
|
|
366
|
+
The schema is either given as command line argument, read from the meta_schema_url or from
|
|
367
|
+
the metadata section of the data dictionary.
|
|
368
|
+
|
|
369
|
+
Parameters
|
|
370
|
+
----------
|
|
371
|
+
file_directory : str or Path, optional
|
|
372
|
+
Directory with files to be validated.
|
|
373
|
+
file_name : str or Path, optional
|
|
374
|
+
File name pattern to be validated.
|
|
375
|
+
schema_file : str, optional
|
|
376
|
+
Schema file name provided directly.
|
|
377
|
+
ignore_software_version : bool
|
|
378
|
+
If True, ignore software version check.
|
|
379
|
+
"""
|
|
380
|
+
if file_directory and file_name:
|
|
381
|
+
file_list = sorted(Path(file_directory).rglob(file_name))
|
|
382
|
+
else:
|
|
383
|
+
file_list = [Path(file_name)] if file_name else []
|
|
384
|
+
|
|
385
|
+
for _file_name in file_list:
|
|
386
|
+
try:
|
|
387
|
+
data = ascii_handler.collect_data_from_file(file_name=_file_name)
|
|
388
|
+
except FileNotFoundError as exc:
|
|
389
|
+
raise FileNotFoundError(f"Error reading schema file from {_file_name}") from exc
|
|
390
|
+
data = data if isinstance(data, list) else [data]
|
|
391
|
+
try:
|
|
392
|
+
for data_dict in data:
|
|
393
|
+
validate_dict_using_schema(
|
|
394
|
+
data_dict,
|
|
395
|
+
_get_schema_file_name(schema_file, _file_name, data_dict),
|
|
396
|
+
ignore_software_version=ignore_software_version,
|
|
397
|
+
)
|
|
398
|
+
except Exception as exc:
|
|
399
|
+
raise ValueError(f"Validation of file {_file_name} failed") from exc
|
|
400
|
+
_logger.info(f"Successful validation of file {_file_name}")
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def _get_schema_file_name(schema_file=None, file_name=None, data_dict=None):
|
|
404
|
+
"""
|
|
405
|
+
Get schema file name from metadata, data dict, or from file.
|
|
406
|
+
|
|
407
|
+
Parameters
|
|
408
|
+
----------
|
|
409
|
+
schema_file : str, optional
|
|
410
|
+
Schema file name provided directly.
|
|
411
|
+
file_name : str or Path, optional
|
|
412
|
+
File name to extract schema information from.
|
|
413
|
+
data_dict : dict, optional
|
|
414
|
+
Dictionary with metaschema information.
|
|
415
|
+
|
|
416
|
+
Returns
|
|
417
|
+
-------
|
|
418
|
+
str or None
|
|
419
|
+
Schema file name.
|
|
420
|
+
"""
|
|
421
|
+
if schema_file is not None:
|
|
422
|
+
return schema_file
|
|
423
|
+
|
|
424
|
+
if data_dict and (url := data_dict.get("meta_schema_url")):
|
|
425
|
+
return url
|
|
426
|
+
|
|
427
|
+
if file_name:
|
|
428
|
+
return _extract_schema_from_file(file_name)
|
|
429
|
+
|
|
430
|
+
return None
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def _extract_schema_url_from_metadata_dict(metadata, observatory="cta"):
|
|
434
|
+
"""Extract schema URL from metadata dictionary."""
|
|
435
|
+
for key in (observatory, observatory.lower()):
|
|
436
|
+
url = metadata.get(key, {}).get("product", {}).get("data", {}).get("model", {}).get("url")
|
|
437
|
+
if url:
|
|
438
|
+
return url
|
|
439
|
+
return None
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def _extract_schema_from_file(file_name, observatory="cta"):
|
|
443
|
+
"""
|
|
444
|
+
Extract schema file name from a metadata or data file.
|
|
445
|
+
|
|
446
|
+
Parameters
|
|
447
|
+
----------
|
|
448
|
+
file_name : str or Path
|
|
449
|
+
File name to extract schema information from.
|
|
450
|
+
observatory : str
|
|
451
|
+
Observatory name (default: "cta").
|
|
452
|
+
|
|
453
|
+
Returns
|
|
454
|
+
-------
|
|
455
|
+
str or None
|
|
456
|
+
Schema file name or None if not found.
|
|
457
|
+
|
|
458
|
+
"""
|
|
459
|
+
try:
|
|
460
|
+
metadata = ascii_handler.collect_data_from_file(file_name=file_name, yaml_document=0)
|
|
461
|
+
except FileNotFoundError:
|
|
462
|
+
return None
|
|
463
|
+
|
|
464
|
+
return _extract_schema_url_from_metadata_dict(metadata, observatory)
|