gammasimtools 0.23.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.23.0.dist-info → gammasimtools-0.25.0.dist-info}/METADATA +1 -1
- {gammasimtools-0.23.0.dist-info → gammasimtools-0.25.0.dist-info}/RECORD +89 -85
- {gammasimtools-0.23.0.dist-info → gammasimtools-0.25.0.dist-info}/entry_points.txt +1 -0
- simtools/_version.py +2 -2
- simtools/application_control.py +54 -4
- simtools/applications/convert_geo_coordinates_of_array_elements.py +1 -1
- simtools/applications/db_add_file_to_db.py +2 -2
- simtools/applications/db_add_simulation_model_from_repository_to_db.py +1 -1
- simtools/applications/db_add_value_from_json_to_db.py +2 -2
- simtools/applications/db_development_tools/write_array_elements_positions_to_repository.py +1 -1
- simtools/applications/db_generate_compound_indexes.py +1 -1
- simtools/applications/db_get_array_layouts_from_db.py +2 -2
- 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 +4 -2
- simtools/applications/db_upload_model_repository.py +1 -1
- simtools/applications/derive_ctao_array_layouts.py +1 -1
- simtools/applications/derive_psf_parameters.py +5 -0
- simtools/applications/derive_pulse_shape_parameters.py +195 -0
- simtools/applications/generate_array_config.py +1 -1
- simtools/applications/maintain_simulation_model_add_production.py +11 -21
- simtools/applications/plot_array_layout.py +63 -1
- simtools/applications/production_generate_grid.py +1 -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 +7 -5
- simtools/applications/validate_camera_fov.py +1 -1
- simtools/applications/validate_cumulative_psf.py +2 -2
- simtools/applications/validate_file_using_schema.py +49 -123
- simtools/applications/validate_optics.py +1 -1
- simtools/configuration/commandline_parser.py +15 -15
- simtools/configuration/configurator.py +1 -1
- simtools/corsika/corsika_config.py +199 -91
- simtools/data_model/model_data_writer.py +15 -3
- simtools/data_model/schema.py +145 -36
- simtools/data_model/validate_data.py +82 -48
- simtools/db/db_handler.py +61 -294
- simtools/db/db_model_upload.py +3 -2
- simtools/db/mongo_db.py +626 -0
- simtools/dependencies.py +38 -17
- simtools/io/eventio_handler.py +128 -0
- simtools/job_execution/htcondor_script_generator.py +0 -2
- simtools/layout/array_layout.py +7 -7
- simtools/layout/array_layout_utils.py +4 -4
- simtools/model/array_model.py +72 -72
- simtools/model/calibration_model.py +12 -9
- simtools/model/model_parameter.py +196 -160
- simtools/model/model_repository.py +176 -39
- simtools/model/model_utils.py +3 -3
- simtools/model/site_model.py +59 -27
- simtools/model/telescope_model.py +21 -13
- simtools/ray_tracing/mirror_panel_psf.py +4 -4
- simtools/ray_tracing/psf_analysis.py +11 -8
- simtools/ray_tracing/psf_parameter_optimisation.py +823 -680
- simtools/reporting/docs_auto_report_generator.py +1 -1
- simtools/reporting/docs_read_parameters.py +72 -11
- 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 +4 -1
- simtools/simtel/pulse_shapes.py +268 -0
- simtools/simtel/simtel_config_writer.py +179 -21
- 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 -346
- simtools/testing/assertions.py +110 -10
- 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 +46 -15
- simtools/utils/names.py +2 -4
- simtools/utils/value_conversion.py +10 -5
- simtools/version.py +61 -0
- simtools/visualization/legend_handlers.py +14 -4
- simtools/visualization/plot_array_layout.py +229 -33
- simtools/visualization/plot_mirrors.py +837 -0
- simtools/visualization/plot_pixels.py +1 -1
- simtools/visualization/plot_psf.py +1 -1
- simtools/visualization/plot_tables.py +1 -1
- simtools/simtel/simtel_io_file_info.py +0 -62
- {gammasimtools-0.23.0.dist-info → gammasimtools-0.25.0.dist-info}/WHEEL +0 -0
- {gammasimtools-0.23.0.dist-info → gammasimtools-0.25.0.dist-info}/licenses/LICENSE +0 -0
- {gammasimtools-0.23.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()
|
|
87
92
|
|
|
88
|
-
|
|
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
|
|
112
|
+
|
|
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,32 +132,35 @@ 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
|
-
|
|
136
|
-
|
|
161
|
+
|
|
162
|
+
self._logger.debug(f"Using model version {model_version} for CORSIKA parameters from DB")
|
|
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
|
|
|
139
166
|
config["INTERACTION_FLAGS"] = self._corsika_configuration_interaction_flags(
|
|
@@ -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.
|
|
@@ -170,9 +229,7 @@ class CorsikaConfig:
|
|
|
170
229
|
|
|
171
230
|
# Get parameters for all model versions
|
|
172
231
|
for model_version in model_versions:
|
|
173
|
-
db_model_parameters = ModelParameter(
|
|
174
|
-
mongo_db_config=db_config, model_version=model_version
|
|
175
|
-
)
|
|
232
|
+
db_model_parameters = ModelParameter(db_config=db_config, model_version=model_version)
|
|
176
233
|
parameters_from_db_list.append(
|
|
177
234
|
db_model_parameters.get_simulation_software_parameters("corsika")
|
|
178
235
|
)
|
|
@@ -195,13 +252,13 @@ class CorsikaConfig:
|
|
|
195
252
|
f" {model_versions[i]}: {current_value}\n"
|
|
196
253
|
f" {model_versions[i + 1]}: {next_value}"
|
|
197
254
|
)
|
|
198
|
-
raise
|
|
255
|
+
raise ValueError(
|
|
199
256
|
f"CORSIKA parameter '{key}' differs between model versions "
|
|
200
257
|
f"{model_versions[i]} and {model_versions[i + 1]}. "
|
|
201
258
|
f"Values are {current_value} and {next_value} respectively."
|
|
202
259
|
)
|
|
203
260
|
|
|
204
|
-
def _corsika_configuration_for_dummy_simulations(self):
|
|
261
|
+
def _corsika_configuration_for_dummy_simulations(self, args_dict):
|
|
205
262
|
"""
|
|
206
263
|
Return CORSIKA configuration for dummy simulations.
|
|
207
264
|
|
|
@@ -213,18 +270,75 @@ class CorsikaConfig:
|
|
|
213
270
|
dict
|
|
214
271
|
Dictionary with CORSIKA parameters for dummy simulations.
|
|
215
272
|
"""
|
|
273
|
+
theta, phi = self._get_corsika_theta_phi(args_dict)
|
|
216
274
|
return {
|
|
217
275
|
"EVTNR": [1],
|
|
218
276
|
"NSHOW": [1],
|
|
219
277
|
"PRMPAR": [1], # CORSIKA ID 1 for primary gamma
|
|
220
278
|
"ESLOPE": [-2.0],
|
|
221
279
|
"ERANGE": [0.1, 0.1],
|
|
222
|
-
"THETAP": [
|
|
223
|
-
"PHIP": [
|
|
280
|
+
"THETAP": [theta, theta],
|
|
281
|
+
"PHIP": [phi, phi],
|
|
224
282
|
"VIEWCONE": [0.0, 0.0],
|
|
225
283
|
"CSCAT": [1, 0.0, 10.0],
|
|
226
284
|
}
|
|
227
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
|
+
|
|
228
342
|
def _corsika_configuration_from_user_input(self, args_dict):
|
|
229
343
|
"""
|
|
230
344
|
Get CORSIKA configuration from user input.
|
|
@@ -239,6 +353,7 @@ class CorsikaConfig:
|
|
|
239
353
|
dict
|
|
240
354
|
Dictionary with CORSIKA parameters.
|
|
241
355
|
"""
|
|
356
|
+
theta, phi = self._get_corsika_theta_phi(args_dict)
|
|
242
357
|
return {
|
|
243
358
|
"EVTNR": [args_dict["event_number_first_shower"]],
|
|
244
359
|
"NSHOW": [args_dict["nshow"]],
|
|
@@ -248,24 +363,8 @@ class CorsikaConfig:
|
|
|
248
363
|
args_dict["energy_range"][0].to("GeV").value,
|
|
249
364
|
args_dict["energy_range"][1].to("GeV").value,
|
|
250
365
|
],
|
|
251
|
-
"THETAP": [
|
|
252
|
-
|
|
253
|
-
float(args_dict["zenith_angle"].to("deg").value),
|
|
254
|
-
],
|
|
255
|
-
"PHIP": [
|
|
256
|
-
self._rotate_azimuth_by_180deg(
|
|
257
|
-
args_dict["azimuth_angle"].to("deg").value,
|
|
258
|
-
correct_for_geomagnetic_field_alignment=args_dict[
|
|
259
|
-
"correct_for_b_field_alignment"
|
|
260
|
-
],
|
|
261
|
-
),
|
|
262
|
-
self._rotate_azimuth_by_180deg(
|
|
263
|
-
args_dict["azimuth_angle"].to("deg").value,
|
|
264
|
-
correct_for_geomagnetic_field_alignment=args_dict[
|
|
265
|
-
"correct_for_b_field_alignment"
|
|
266
|
-
],
|
|
267
|
-
),
|
|
268
|
-
],
|
|
366
|
+
"THETAP": [theta, theta],
|
|
367
|
+
"PHIP": [phi, phi],
|
|
269
368
|
"VIEWCONE": [
|
|
270
369
|
args_dict["view_cone"][0].to("deg").value,
|
|
271
370
|
args_dict["view_cone"][1].to("deg").value,
|
|
@@ -277,6 +376,26 @@ class CorsikaConfig:
|
|
|
277
376
|
],
|
|
278
377
|
}
|
|
279
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
|
+
|
|
280
399
|
def _corsika_configuration_interaction_flags(self, parameters_from_db):
|
|
281
400
|
"""
|
|
282
401
|
Return CORSIKA interaction flags / parameters.
|
|
@@ -300,7 +419,8 @@ class CorsikaConfig:
|
|
|
300
419
|
parameters_from_db["corsika_starting_grammage"]
|
|
301
420
|
)
|
|
302
421
|
]
|
|
303
|
-
|
|
422
|
+
if not self.use_curved_atmosphere:
|
|
423
|
+
parameters["TSTART"] = ["T"]
|
|
304
424
|
parameters["ECUTS"] = self._input_config_corsika_particle_kinetic_energy_cutoff(
|
|
305
425
|
parameters_from_db["corsika_particle_kinetic_energy_cutoff"]
|
|
306
426
|
)
|
|
@@ -446,16 +566,23 @@ class CorsikaConfig:
|
|
|
446
566
|
return f"{int(value)}MB"
|
|
447
567
|
return f"{int(entry['value'] * u.Unit(entry['unit']).to('byte'))}"
|
|
448
568
|
|
|
449
|
-
def _rotate_azimuth_by_180deg(
|
|
569
|
+
def _rotate_azimuth_by_180deg(
|
|
570
|
+
self, az, correct_for_geomagnetic_field_alignment=True, invert_operation=False
|
|
571
|
+
):
|
|
450
572
|
"""
|
|
451
573
|
Convert azimuth angle to the CORSIKA coordinate system.
|
|
452
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
|
+
|
|
453
578
|
Parameters
|
|
454
579
|
----------
|
|
455
580
|
az: float
|
|
456
581
|
Azimuth angle in degrees.
|
|
457
582
|
correct_for_geomagnetic_field_alignment: bool
|
|
458
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).
|
|
459
586
|
|
|
460
587
|
Returns
|
|
461
588
|
-------
|
|
@@ -465,6 +592,8 @@ class CorsikaConfig:
|
|
|
465
592
|
b_field_declination = 0
|
|
466
593
|
if correct_for_geomagnetic_field_alignment:
|
|
467
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
|
|
468
597
|
return (az + 180 + b_field_declination) % 360
|
|
469
598
|
|
|
470
599
|
@property
|
|
@@ -472,27 +601,6 @@ class CorsikaConfig:
|
|
|
472
601
|
"""Primary particle name."""
|
|
473
602
|
return self.primary_particle.name
|
|
474
603
|
|
|
475
|
-
def _set_primary_particle(self, args_dict):
|
|
476
|
-
"""
|
|
477
|
-
Set primary particle from input dictionary.
|
|
478
|
-
|
|
479
|
-
Parameters
|
|
480
|
-
----------
|
|
481
|
-
args_dict: dict
|
|
482
|
-
Input dictionary.
|
|
483
|
-
|
|
484
|
-
Returns
|
|
485
|
-
-------
|
|
486
|
-
PrimaryParticle
|
|
487
|
-
Primary particle.
|
|
488
|
-
|
|
489
|
-
"""
|
|
490
|
-
if not args_dict or args_dict.get("primary_id_type") is None:
|
|
491
|
-
return PrimaryParticle()
|
|
492
|
-
return PrimaryParticle(
|
|
493
|
-
particle_id_type=args_dict.get("primary_id_type"), particle_id=args_dict.get("primary")
|
|
494
|
-
)
|
|
495
|
-
|
|
496
604
|
def get_config_parameter(self, par_name):
|
|
497
605
|
"""
|
|
498
606
|
Get value of CORSIKA configuration parameter.
|
|
@@ -676,7 +784,7 @@ class CorsikaConfig:
|
|
|
676
784
|
|
|
677
785
|
base_name = (
|
|
678
786
|
f"{self.primary_particle.name}_{run_number_in_file_name}"
|
|
679
|
-
f"za{int(self.get_config_parameter('THETAP')[0]):
|
|
787
|
+
f"za{int(self.get_config_parameter('THETAP')[0]):02}deg_"
|
|
680
788
|
f"azm{self.azimuth_angle:03}deg{view_cone}_"
|
|
681
789
|
f"{self.array_model.site}_{self.array_model.layout_name}_"
|
|
682
790
|
f"{self.array_model.model_version}{file_label}"
|
|
@@ -687,7 +795,7 @@ class CorsikaConfig:
|
|
|
687
795
|
if file_type == "config":
|
|
688
796
|
return f"corsika_config_{base_name}.input"
|
|
689
797
|
if file_type == "output_generic":
|
|
690
|
-
return f"{base_name}.zst"
|
|
798
|
+
return f"{base_name}.corsika.zst"
|
|
691
799
|
if file_type == "multipipe":
|
|
692
800
|
return f"multi_cta-{self.array_model.site}-{self.array_model.layout_name}.cfg"
|
|
693
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
|
)
|
|
@@ -186,7 +192,7 @@ class ModelDataWriter:
|
|
|
186
192
|
ValueError
|
|
187
193
|
If parameter with the same version exists in the database.
|
|
188
194
|
"""
|
|
189
|
-
db = db_handler.DatabaseHandler(
|
|
195
|
+
db = db_handler.DatabaseHandler(db_config=db_config)
|
|
190
196
|
try:
|
|
191
197
|
db.get_model_parameter(
|
|
192
198
|
parameter=parameter_name,
|
|
@@ -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,
|