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.
Files changed (90) hide show
  1. {gammasimtools-0.23.0.dist-info → gammasimtools-0.25.0.dist-info}/METADATA +1 -1
  2. {gammasimtools-0.23.0.dist-info → gammasimtools-0.25.0.dist-info}/RECORD +89 -85
  3. {gammasimtools-0.23.0.dist-info → gammasimtools-0.25.0.dist-info}/entry_points.txt +1 -0
  4. simtools/_version.py +2 -2
  5. simtools/application_control.py +54 -4
  6. simtools/applications/convert_geo_coordinates_of_array_elements.py +1 -1
  7. simtools/applications/db_add_file_to_db.py +2 -2
  8. simtools/applications/db_add_simulation_model_from_repository_to_db.py +1 -1
  9. simtools/applications/db_add_value_from_json_to_db.py +2 -2
  10. simtools/applications/db_development_tools/write_array_elements_positions_to_repository.py +1 -1
  11. simtools/applications/db_generate_compound_indexes.py +1 -1
  12. simtools/applications/db_get_array_layouts_from_db.py +2 -2
  13. simtools/applications/db_get_file_from_db.py +1 -1
  14. simtools/applications/db_get_parameter_from_db.py +1 -1
  15. simtools/applications/db_inspect_databases.py +4 -2
  16. simtools/applications/db_upload_model_repository.py +1 -1
  17. simtools/applications/derive_ctao_array_layouts.py +1 -1
  18. simtools/applications/derive_psf_parameters.py +5 -0
  19. simtools/applications/derive_pulse_shape_parameters.py +195 -0
  20. simtools/applications/generate_array_config.py +1 -1
  21. simtools/applications/maintain_simulation_model_add_production.py +11 -21
  22. simtools/applications/plot_array_layout.py +63 -1
  23. simtools/applications/production_generate_grid.py +1 -1
  24. simtools/applications/simulate_flasher.py +3 -2
  25. simtools/applications/simulate_pedestals.py +1 -1
  26. simtools/applications/simulate_prod.py +8 -23
  27. simtools/applications/simulate_prod_htcondor_generator.py +7 -0
  28. simtools/applications/submit_array_layouts.py +7 -5
  29. simtools/applications/validate_camera_fov.py +1 -1
  30. simtools/applications/validate_cumulative_psf.py +2 -2
  31. simtools/applications/validate_file_using_schema.py +49 -123
  32. simtools/applications/validate_optics.py +1 -1
  33. simtools/configuration/commandline_parser.py +15 -15
  34. simtools/configuration/configurator.py +1 -1
  35. simtools/corsika/corsika_config.py +199 -91
  36. simtools/data_model/model_data_writer.py +15 -3
  37. simtools/data_model/schema.py +145 -36
  38. simtools/data_model/validate_data.py +82 -48
  39. simtools/db/db_handler.py +61 -294
  40. simtools/db/db_model_upload.py +3 -2
  41. simtools/db/mongo_db.py +626 -0
  42. simtools/dependencies.py +38 -17
  43. simtools/io/eventio_handler.py +128 -0
  44. simtools/job_execution/htcondor_script_generator.py +0 -2
  45. simtools/layout/array_layout.py +7 -7
  46. simtools/layout/array_layout_utils.py +4 -4
  47. simtools/model/array_model.py +72 -72
  48. simtools/model/calibration_model.py +12 -9
  49. simtools/model/model_parameter.py +196 -160
  50. simtools/model/model_repository.py +176 -39
  51. simtools/model/model_utils.py +3 -3
  52. simtools/model/site_model.py +59 -27
  53. simtools/model/telescope_model.py +21 -13
  54. simtools/ray_tracing/mirror_panel_psf.py +4 -4
  55. simtools/ray_tracing/psf_analysis.py +11 -8
  56. simtools/ray_tracing/psf_parameter_optimisation.py +823 -680
  57. simtools/reporting/docs_auto_report_generator.py +1 -1
  58. simtools/reporting/docs_read_parameters.py +72 -11
  59. simtools/runners/corsika_runner.py +12 -3
  60. simtools/runners/corsika_simtel_runner.py +6 -0
  61. simtools/runners/runner_services.py +17 -7
  62. simtools/runners/simtel_runner.py +12 -54
  63. simtools/schemas/model_parameters/flasher_pulse_exp_decay.schema.yml +2 -0
  64. simtools/schemas/model_parameters/flasher_pulse_shape.schema.yml +50 -0
  65. simtools/schemas/model_parameters/flasher_pulse_width.schema.yml +2 -0
  66. simtools/schemas/simulation_models_info.schema.yml +4 -1
  67. simtools/simtel/pulse_shapes.py +268 -0
  68. simtools/simtel/simtel_config_writer.py +179 -21
  69. simtools/simtel/simtel_io_event_writer.py +2 -2
  70. simtools/simtel/simulator_array.py +58 -12
  71. simtools/simtel/simulator_light_emission.py +45 -8
  72. simtools/simulator.py +361 -346
  73. simtools/testing/assertions.py +110 -10
  74. simtools/testing/configuration.py +1 -1
  75. simtools/testing/log_inspector.py +4 -1
  76. simtools/testing/sim_telarray_metadata.py +1 -1
  77. simtools/testing/validate_output.py +46 -15
  78. simtools/utils/names.py +2 -4
  79. simtools/utils/value_conversion.py +10 -5
  80. simtools/version.py +61 -0
  81. simtools/visualization/legend_handlers.py +14 -4
  82. simtools/visualization/plot_array_layout.py +229 -33
  83. simtools/visualization/plot_mirrors.py +837 -0
  84. simtools/visualization/plot_pixels.py +1 -1
  85. simtools/visualization/plot_psf.py +1 -1
  86. simtools/visualization/plot_tables.py +1 -1
  87. simtools/simtel/simtel_io_file_info.py +0 -62
  88. {gammasimtools-0.23.0.dist-info → gammasimtools-0.25.0.dist-info}/WHEEL +0 -0
  89. {gammasimtools-0.23.0.dist-info → gammasimtools-0.25.0.dist-info}/licenses/LICENSE +0 -0
  90. {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.zenith_angle = None
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.fill_corsika_configuration(args_dict, db_config)
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, args_dict):
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
- args_dict: dict
75
+ args: dict, corsika particle ID, or None
84
76
  Configuration dictionary
85
77
  """
86
- self._primary_particle = self._set_primary_particle(args_dict)
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
- def fill_corsika_configuration(self, args_dict, db_config=None):
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
- Values are converted to CORSIKA-consistent units.
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
- # If the user provided multiple model versions, we take the first one
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
- self._logger.debug(f"Using model version {model_version} for CORSIKA parameters")
136
- db_model_parameters = ModelParameter(mongo_db_config=db_config, model_version=model_version)
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 InvalidCorsikaInputError(
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": [20.0, 20.0],
223
- "PHIP": [0.0, 0.0],
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
- float(args_dict["zenith_angle"].to("deg").value),
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
- parameters["TSTART"] = ["T"]
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(self, az, correct_for_geomagnetic_field_alignment=True):
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]):03}deg_"
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(mongo_db_config=db_config)
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(parameter_name, schema_version)
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": self.schema_dict.get("schema_version", "0.1.0"),
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,