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.
Files changed (138) hide show
  1. {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/METADATA +6 -1
  2. {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/RECORD +135 -130
  3. {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/WHEEL +1 -1
  4. {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/entry_points.txt +3 -2
  5. {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/licenses/LICENSE +1 -1
  6. simtools/_version.py +2 -2
  7. simtools/application_control.py +35 -7
  8. simtools/applications/convert_geo_coordinates_of_array_elements.py +3 -3
  9. simtools/applications/db_add_file_to_db.py +1 -1
  10. simtools/applications/db_add_simulation_model_from_repository_to_db.py +1 -1
  11. simtools/applications/db_add_value_from_json_to_db.py +1 -1
  12. simtools/applications/db_generate_compound_indexes.py +1 -1
  13. simtools/applications/db_get_array_layouts_from_db.py +3 -7
  14. simtools/applications/db_get_file_from_db.py +1 -1
  15. simtools/applications/db_get_parameter_from_db.py +1 -1
  16. simtools/applications/db_inspect_databases.py +1 -1
  17. simtools/applications/db_upload_model_repository.py +1 -1
  18. simtools/applications/derive_ctao_array_layouts.py +1 -2
  19. simtools/applications/{calculate_incident_angles.py → derive_incident_angle.py} +16 -18
  20. simtools/applications/derive_mirror_rnda.py +112 -180
  21. simtools/applications/derive_psf_parameters.py +0 -1
  22. simtools/applications/derive_pulse_shape_parameters.py +0 -1
  23. simtools/applications/derive_trigger_rates.py +1 -1
  24. simtools/applications/docs_produce_array_element_report.py +2 -8
  25. simtools/applications/docs_produce_calibration_reports.py +1 -3
  26. simtools/applications/docs_produce_model_parameter_reports.py +0 -2
  27. simtools/applications/docs_produce_simulation_configuration_report.py +1 -3
  28. simtools/applications/generate_array_config.py +0 -1
  29. simtools/applications/generate_corsika_histograms.py +79 -229
  30. simtools/applications/generate_regular_arrays.py +76 -69
  31. simtools/applications/generate_simtel_event_data.py +2 -2
  32. simtools/applications/maintain_simulation_model_add_production.py +2 -2
  33. simtools/applications/maintain_simulation_model_write_array_element_positions.py +87 -0
  34. simtools/applications/plot_array_layout.py +5 -111
  35. simtools/applications/plot_simulated_event_distributions.py +57 -0
  36. simtools/applications/plot_tabular_data.py +0 -1
  37. simtools/applications/plot_tabular_data_for_model_parameter.py +1 -6
  38. simtools/applications/production_derive_corsika_limits.py +1 -1
  39. simtools/applications/production_generate_grid.py +0 -1
  40. simtools/applications/run_application.py +1 -1
  41. simtools/applications/simulate_flasher.py +3 -15
  42. simtools/applications/simulate_illuminator.py +2 -11
  43. simtools/applications/simulate_pedestals.py +1 -5
  44. simtools/applications/simulate_prod.py +8 -11
  45. simtools/applications/simulate_prod_htcondor_generator.py +1 -1
  46. simtools/applications/submit_array_layouts.py +2 -4
  47. simtools/applications/submit_data_from_external.py +2 -1
  48. simtools/applications/submit_model_parameter_from_external.py +1 -3
  49. simtools/applications/validate_camera_efficiency.py +28 -28
  50. simtools/applications/validate_camera_fov.py +0 -1
  51. simtools/applications/validate_cumulative_psf.py +1 -5
  52. simtools/applications/validate_optics.py +2 -14
  53. simtools/atmosphere.py +83 -0
  54. simtools/camera/camera_efficiency.py +171 -53
  55. simtools/camera/single_photon_electron_spectrum.py +8 -7
  56. simtools/configuration/commandline_parser.py +82 -11
  57. simtools/configuration/configurator.py +6 -11
  58. simtools/constants.py +5 -0
  59. simtools/corsika/corsika_config.py +100 -202
  60. simtools/corsika/corsika_histograms.py +561 -1708
  61. simtools/corsika/primary_particle.py +1 -1
  62. simtools/data_model/metadata_collector.py +5 -2
  63. simtools/data_model/metadata_model.py +0 -4
  64. simtools/data_model/model_data_writer.py +59 -64
  65. simtools/data_model/schema.py +2 -0
  66. simtools/data_model/validate_data.py +1 -3
  67. simtools/db/db_handler.py +23 -10
  68. simtools/db/mongo_db.py +2 -2
  69. simtools/dependencies.py +81 -38
  70. simtools/io/ascii_handler.py +55 -5
  71. simtools/io/io_handler.py +23 -12
  72. simtools/io/table_handler.py +1 -1
  73. simtools/job_execution/job_manager.py +154 -79
  74. simtools/job_execution/process_pool.py +137 -0
  75. simtools/layout/array_layout.py +4 -13
  76. simtools/layout/array_layout_utils.py +348 -57
  77. simtools/model/array_model.py +23 -63
  78. simtools/model/calibration_model.py +4 -8
  79. simtools/model/legacy_model_parameter.py +134 -0
  80. simtools/model/model_parameter.py +147 -86
  81. simtools/model/model_utils.py +40 -6
  82. simtools/model/site_model.py +4 -8
  83. simtools/model/telescope_model.py +10 -16
  84. simtools/production_configuration/derive_corsika_limits.py +6 -11
  85. simtools/production_configuration/interpolation_handler.py +16 -16
  86. simtools/ray_tracing/incident_angles.py +92 -17
  87. simtools/ray_tracing/mirror_panel_psf.py +338 -222
  88. simtools/ray_tracing/psf_analysis.py +62 -48
  89. simtools/ray_tracing/psf_parameter_optimisation.py +3 -3
  90. simtools/ray_tracing/ray_tracing.py +43 -25
  91. simtools/reporting/docs_auto_report_generator.py +8 -13
  92. simtools/reporting/docs_read_parameters.py +2 -8
  93. simtools/runners/corsika_runner.py +52 -195
  94. simtools/runners/corsika_simtel_runner.py +77 -108
  95. simtools/runners/runner_services.py +214 -213
  96. simtools/runners/simtel_runner.py +27 -160
  97. simtools/runners/simtools_runner.py +11 -73
  98. simtools/schemas/application_workflow.metaschema.yml +8 -0
  99. simtools/settings.py +173 -0
  100. simtools/{io/eventio_handler.py → sim_events/file_info.py} +3 -3
  101. simtools/{simtel/simtel_io_event_histograms.py → sim_events/histograms.py} +25 -15
  102. simtools/{simtel/simtel_io_event_reader.py → sim_events/reader.py} +20 -17
  103. simtools/{simtel/simtel_io_event_writer.py → sim_events/writer.py} +84 -25
  104. simtools/simtel/pulse_shapes.py +7 -2
  105. simtools/simtel/simtel_config_writer.py +79 -91
  106. simtools/simtel/simtel_seeds.py +184 -0
  107. simtools/simtel/simtel_table_reader.py +6 -4
  108. simtools/simtel/simulator_array.py +114 -109
  109. simtools/simtel/simulator_camera_efficiency.py +68 -46
  110. simtools/simtel/simulator_light_emission.py +164 -132
  111. simtools/simtel/simulator_ray_tracing.py +80 -71
  112. simtools/simulator.py +137 -355
  113. simtools/telescope_trigger_rates.py +3 -4
  114. simtools/testing/assertions.py +84 -33
  115. simtools/testing/configuration.py +1 -2
  116. simtools/testing/helpers.py +2 -3
  117. simtools/testing/log_inspector.py +1 -0
  118. simtools/testing/sim_telarray_metadata.py +14 -12
  119. simtools/testing/validate_output.py +121 -42
  120. simtools/utils/general.py +43 -17
  121. simtools/utils/geometry.py +0 -77
  122. simtools/utils/names.py +5 -5
  123. simtools/utils/random.py +36 -0
  124. simtools/visualization/legend_handlers.py +7 -6
  125. simtools/visualization/plot_array_layout.py +91 -16
  126. simtools/visualization/plot_corsika_histograms.py +145 -605
  127. simtools/visualization/plot_incident_angles.py +48 -1
  128. simtools/visualization/plot_mirrors.py +1 -4
  129. simtools/visualization/plot_pixels.py +2 -4
  130. simtools/visualization/plot_psf.py +160 -19
  131. simtools/visualization/plot_simtel_event_histograms.py +4 -4
  132. simtools/visualization/plot_simtel_events.py +6 -11
  133. simtools/visualization/plot_tables.py +8 -19
  134. simtools/visualization/visualize.py +22 -2
  135. simtools/applications/db_development_tools/write_array_elements_positions_to_repository.py +0 -160
  136. simtools/applications/print_version.py +0 -53
  137. simtools/io/hdf5_handler.py +0 -139
  138. {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/top_level.txt +0 -0
@@ -2,12 +2,10 @@
2
2
 
3
3
  import argparse
4
4
  import logging
5
- import os
6
5
  import sys
7
6
  import uuid
8
7
 
9
8
  import astropy.units as u
10
- from dotenv import load_dotenv
11
9
 
12
10
  import simtools.configuration.commandline_parser as argparser
13
11
  from simtools.db.mongo_db import jsonschema_db_dict
@@ -305,19 +303,16 @@ class Configurator:
305
303
  Only parameters which are not already configured are changed (i.e., parameter is None).
306
304
 
307
305
  """
306
+ _all_env_dict = gen.load_environment_variables(
307
+ env_file=self.config.get("env_file", None), env_list=self.config.keys()
308
+ )
309
+
308
310
  _env_dict = {}
309
- try:
310
- load_dotenv(self.config["env_file"])
311
- except KeyError:
312
- pass
313
311
  for key, value in self.config.items():
314
- # environmental variables for simtools should always start with SIMTOOLS_
315
- env_variable_to_read = f"SIMTOOLS_{key.upper()}"
316
312
  if value is None:
317
- env_value = os.environ.get(env_variable_to_read)
313
+ env_value = _all_env_dict.get(key)
318
314
  if env_value is not None:
319
- env_value = env_value.split("#")[0].strip().replace('"', "").replace("'", "")
320
- _env_dict[key] = env_value
315
+ _env_dict[key] = env_value
321
316
 
322
317
  self._fill_from_config_dict(_env_dict)
323
318
 
simtools/constants.py CHANGED
@@ -23,3 +23,8 @@ MODEL_PARAMETER_SCHEMA_URL = (
23
23
  )
24
24
  # Path to resource files
25
25
  RESOURCE_PATH = files("simtools") / "resources"
26
+
27
+ # Maximum value allowed for random seeds in sim_telarray
28
+ SIMTEL_MAX_SEED = 2147483647
29
+ # Maximum value allowed for random seeds in CORSIKA
30
+ CORSIKA_MAX_SEED = 900000000
@@ -1,15 +1,20 @@
1
1
  """CORSIKA configuration."""
2
2
 
3
3
  import logging
4
+ from collections.abc import Mapping
4
5
  from pathlib import Path
5
6
 
6
7
  import numpy as np
7
8
  from astropy import units as u
8
9
 
10
+ from simtools import settings
11
+ from simtools.constants import CORSIKA_MAX_SEED
9
12
  from simtools.corsika.primary_particle import PrimaryParticle
10
- from simtools.io import eventio_handler, io_handler
13
+ from simtools.io import io_handler
11
14
  from simtools.model.model_parameter import ModelParameter
15
+ from simtools.sim_events import file_info
12
16
  from simtools.utils import general as gen
17
+ from simtools.utils.random import seeds
13
18
 
14
19
 
15
20
  class CorsikaConfig:
@@ -25,37 +30,31 @@ class CorsikaConfig:
25
30
  ----------
26
31
  array_model : ArrayModel
27
32
  Array model.
28
- args_dict : dict
29
- Configuration dictionary.
30
- db_config : dict
31
- MongoDB configuration.
33
+ run_number : int
34
+ Run number.
32
35
  label : str
33
36
  Instance label.
34
- dummy_simulations : bool
35
- If True, the configuration is generated for dummy simulations
36
- (e.g., sim_telarray requires for some run modes a valid CORSIKA input file).
37
37
  """
38
38
 
39
- def __init__(self, array_model, args_dict, db_config=None, label=None, dummy_simulations=False):
39
+ def __init__(self, array_model, run_number, label=None):
40
40
  """Initialize CorsikaConfig."""
41
41
  self._logger = logging.getLogger(__name__)
42
- self._logger.debug("Init CorsikaConfig")
43
42
 
44
43
  self.label = label
45
44
  self.shower_events = self.mc_events = None
46
45
  self.zenith_angle = self.azimuth_angle = None
47
46
  self.curved_atmosphere_min_zenith_angle = None
48
- self._run_number = None
49
- self.config_file_path = None
50
- self.primary_particle = args_dict # see setter for primary_particle
51
- self.use_curved_atmosphere = args_dict # see setter for use_curved_atmosphere
52
- self.dummy_simulations = dummy_simulations
47
+ self.run_number = run_number
48
+ self.primary_particle = settings.config.args # see setter for primary_particle
49
+ self.use_curved_atmosphere = settings.config.args # see setter for use_curved_atmosphere
50
+ self.run_mode = settings.config.args.get("run_mode")
53
51
 
54
52
  self.io_handler = io_handler.IOHandler()
55
53
  self.array_model = array_model
56
- self.config = self._fill_corsika_configuration(args_dict, db_config)
57
- self._initialize_from_config(args_dict)
58
- self.is_file_updated = False
54
+ self.corsika_exec = settings.config.corsika_exe
55
+ self.interaction_table_path = settings.config.corsika_interaction_table_path
56
+ self.config = self._fill_corsika_configuration(settings.config.args)
57
+ self._initialize_from_config(settings.config.args)
59
58
 
60
59
  @property
61
60
  def primary_particle(self):
@@ -76,7 +75,7 @@ class CorsikaConfig:
76
75
  Configuration dictionary
77
76
  """
78
77
  if (
79
- isinstance(args, dict)
78
+ isinstance(args, Mapping) # dict-like (includes mappingproxy)
80
79
  and args.get("primary_id_type") is not None
81
80
  and args.get("primary") is not None
82
81
  ):
@@ -101,7 +100,7 @@ class CorsikaConfig:
101
100
  self._use_curved_atmosphere = False
102
101
  if isinstance(args, bool):
103
102
  self._use_curved_atmosphere = args
104
- elif isinstance(args, dict):
103
+ elif isinstance(args, Mapping): # dict-like (includes mappingproxy)
105
104
  try:
106
105
  self._use_curved_atmosphere = (
107
106
  args.get("zenith_angle", 0.0 * u.deg).to("deg").value
@@ -110,7 +109,7 @@ class CorsikaConfig:
110
109
  except KeyError:
111
110
  self._use_curved_atmosphere = False
112
111
 
113
- def _fill_corsika_configuration(self, args_dict, db_config=None):
112
+ def _fill_corsika_configuration(self, args):
114
113
  """
115
114
  Fill CORSIKA configuration.
116
115
 
@@ -119,48 +118,48 @@ class CorsikaConfig:
119
118
 
120
119
  Parameters
121
120
  ----------
122
- args_dict : dict
121
+ args: dict
123
122
  Configuration dictionary.
124
- db_config: dict
125
- Database configuration.
126
123
 
127
124
  Returns
128
125
  -------
129
126
  dict
130
127
  Dictionary with CORSIKA parameters.
131
128
  """
132
- if args_dict is None:
129
+ if args is None:
133
130
  return {}
134
131
 
135
132
  config = {}
136
- if self.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:
133
+ config["RUNNR"] = [self.run_number]
134
+ config["USER"] = [settings.config.user]
135
+ config["HOST"] = [settings.config.hostname]
136
+ if self.is_calibration_run():
137
+ config["USER_INPUT"] = self._corsika_configuration_for_dummy_simulations(args)
138
+ elif args.get("corsika_file", None) is not None:
139
139
  config["USER_INPUT"] = self._corsika_configuration_from_corsika_file(
140
- args_dict["corsika_file"]
140
+ args["corsika_file"]
141
141
  )
142
142
  else:
143
- config["USER_INPUT"] = self._corsika_configuration_from_user_input(args_dict)
143
+ config["USER_INPUT"] = self._corsika_configuration_from_user_input(args)
144
144
 
145
145
  config.update(
146
- self._fill_corsika_configuration_from_db(
147
- gen.ensure_iterable(args_dict.get("model_version")), db_config
148
- )
146
+ self._fill_corsika_configuration_from_db(gen.ensure_iterable(args.get("model_version")))
149
147
  )
150
148
  return config
151
149
 
152
- def _fill_corsika_configuration_from_db(self, model_versions, db_config):
150
+ def _fill_corsika_configuration_from_db(self, model_versions):
153
151
  """Fill CORSIKA configuration from database."""
154
152
  config = {}
155
- if db_config is None: # all following parameter require DB
153
+ # all following parameters require DB
154
+ if settings.config.db_config is None or not model_versions:
156
155
  return config
157
156
 
158
157
  # For multiple model versions, check that CORSIKA parameters are identical
159
- self.assert_corsika_configurations_match(model_versions, db_config=db_config)
158
+ self.assert_corsika_configurations_match(model_versions)
160
159
  model_version = model_versions[0]
161
160
 
162
161
  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)
162
+ db_model_parameters = ModelParameter(model_version=model_version)
164
163
  parameters_from_db = db_model_parameters.get_simulation_software_parameters("corsika")
165
164
 
166
165
  config["INTERACTION_FLAGS"] = self._corsika_configuration_interaction_flags(
@@ -173,13 +172,17 @@ class CorsikaConfig:
173
172
  config["IACT_PARAMETERS"] = self._corsika_configuration_iact_parameters(parameters_from_db)
174
173
  return config
175
174
 
176
- def _initialize_from_config(self, args_dict):
175
+ def _initialize_from_config(self, args):
177
176
  """
178
177
  Initialize additional parameters either from command line args or from derived config.
179
178
 
180
179
  Takes into account that in the case of a given CORSIKA input file, some parameters are read
181
180
  from the file instead of the command line args.
182
181
 
182
+ Parameters
183
+ ----------
184
+ args: dict
185
+ Command line arguments.
183
186
  """
184
187
  self.primary_particle = int(self.config.get("USER_INPUT", {}).get("PRMPAR", [1])[0])
185
188
  self.shower_events = int(self.config.get("USER_INPUT", {}).get("NSHOW", [0])[0])
@@ -187,7 +190,7 @@ class CorsikaConfig:
187
190
  self.shower_events * self.config.get("USER_INPUT", {}).get("CSCAT", [1])[0]
188
191
  )
189
192
 
190
- if args_dict.get("corsika_file", None) is not None:
193
+ if args.get("corsika_file", None) is not None:
191
194
  azimuth = self._rotate_azimuth_by_180deg(
192
195
  0.5 * (self.config["USER_INPUT"]["PHIP"][0] + self.config["USER_INPUT"]["PHIP"][1]),
193
196
  invert_operation=True,
@@ -196,17 +199,17 @@ class CorsikaConfig:
196
199
  self.config["USER_INPUT"]["THETAP"][0] + self.config["USER_INPUT"]["THETAP"][1]
197
200
  )
198
201
  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
202
+ azimuth = args.get("azimuth_angle", 0.0 * u.deg).to("deg").value
203
+ zenith = args.get("zenith_angle", 20.0 * u.deg).to("deg").value
201
204
 
202
205
  self.azimuth_angle = round(azimuth)
203
206
  self.zenith_angle = round(zenith)
204
207
 
205
208
  self.curved_atmosphere_min_zenith_angle = (
206
- args_dict.get("curved_atmosphere_min_zenith_angle", 90.0 * u.deg).to("deg").value
209
+ args.get("curved_atmosphere_min_zenith_angle", 90.0 * u.deg).to("deg").value
207
210
  )
208
211
 
209
- def assert_corsika_configurations_match(self, model_versions, db_config=None):
212
+ def assert_corsika_configurations_match(self, model_versions):
210
213
  """
211
214
  Assert that CORSIKA configurations match across all model versions.
212
215
 
@@ -214,8 +217,6 @@ class CorsikaConfig:
214
217
  ----------
215
218
  model_versions : list
216
219
  List of model versions to check.
217
- db_config : dict, optional
218
- Database configuration.
219
220
 
220
221
  Raises
221
222
  ------
@@ -229,7 +230,7 @@ class CorsikaConfig:
229
230
 
230
231
  # Get parameters for all model versions
231
232
  for model_version in model_versions:
232
- db_model_parameters = ModelParameter(db_config=db_config, model_version=model_version)
233
+ db_model_parameters = ModelParameter(model_version=model_version)
233
234
  parameters_from_db_list.append(
234
235
  db_model_parameters.get_simulation_software_parameters("corsika")
235
236
  )
@@ -264,6 +265,7 @@ class CorsikaConfig:
264
265
 
265
266
  Settings are such that that the simulations are fast
266
267
  and none (or not many) Cherenkov photons are generated.
268
+ This is e.g. used for some calibration run modes in sim_telarray.
267
269
 
268
270
  Returns
269
271
  -------
@@ -271,6 +273,7 @@ class CorsikaConfig:
271
273
  Dictionary with CORSIKA parameters for dummy simulations.
272
274
  """
273
275
  theta, phi = self._get_corsika_theta_phi(args_dict)
276
+ self._logger.info("Using CORSIKA configuration for dummy simulations.")
274
277
  return {
275
278
  "EVTNR": [1],
276
279
  "NSHOW": [1],
@@ -301,9 +304,7 @@ class CorsikaConfig:
301
304
  dict
302
305
  Dictionary with CORSIKA parameters from input file.
303
306
  """
304
- run_header, event_header = eventio_handler.get_corsika_run_and_event_headers(
305
- corsika_input_file
306
- )
307
+ run_header, event_header = file_info.get_corsika_run_and_event_headers(corsika_input_file)
307
308
  self._logger.debug(f"CORSIKA run header from {corsika_input_file}")
308
309
 
309
310
  def to_float32(value):
@@ -432,9 +433,25 @@ class CorsikaConfig:
432
433
  parameters["MAXPRT"] = ["10"]
433
434
  parameters["ECTMAP"] = ["1.e6"]
434
435
 
436
+ if "epos" in str(self.corsika_exec).lower():
437
+ parameters.update(self._epos_flags())
438
+
435
439
  self._logger.debug(f"Interaction parameters: {parameters}")
436
440
  return parameters
437
441
 
442
+ def _epos_flags(self):
443
+ """EPOS interaction model flags."""
444
+ epos_par = {}
445
+ epos_path = Path(self.interaction_table_path)
446
+ epos_par["EPOPAR fname pathnx"] = [f"{epos_path}/"]
447
+ for epos_file in ["inics", "iniev", "inirj", "initl"]:
448
+ epos_par[f"EPOPAR fname {epos_file}"] = [str(epos_path / f"epos.{epos_file}")]
449
+ epos_par["EPOPAR fname hpf"] = [str(epos_path / "urqmd34/tables.dat")]
450
+ for dummy_output in ["check", "histo", "data", "copy"]:
451
+ epos_par[f"EPOPAR fname {dummy_output}"] = ["none"]
452
+
453
+ return epos_par
454
+
438
455
  def _input_config_first_interaction_height(self, entry):
439
456
  """Return FIXHEI parameter CORSIKA format."""
440
457
  return [f"{entry['value'] * u.Unit(entry['unit']).to('cm'):.2f}", "0"]
@@ -553,7 +570,7 @@ class CorsikaConfig:
553
570
  """Return CORSIKA debugging output parameters."""
554
571
  return {
555
572
  "DEBUG": ["F", 6, "F", 1000000],
556
- "DATBAS": ["yes"],
573
+ "DATBAS": ["F"],
557
574
  "DIRECT": ["./"],
558
575
  "PAROUT": ["F", "F"],
559
576
  }
@@ -596,11 +613,6 @@ class CorsikaConfig:
596
613
  return (az - 180 - b_field_declination) % 360
597
614
  return (az + 180 + b_field_declination) % 360
598
615
 
599
- @property
600
- def primary(self):
601
- """Primary particle name."""
602
- return self.primary_particle.name
603
-
604
616
  def get_config_parameter(self, par_name):
605
617
  """
606
618
  Get value of CORSIKA configuration parameter.
@@ -621,7 +633,7 @@ class CorsikaConfig:
621
633
  Value(s) of the parameter.
622
634
  """
623
635
  par_value = []
624
- for _, values in self.config.items():
636
+ for values in self.config.values():
625
637
  if par_name in values:
626
638
  par_value = values[par_name]
627
639
  if len(par_value) == 0:
@@ -652,7 +664,7 @@ class CorsikaConfig:
652
664
  text += line
653
665
  return text
654
666
 
655
- def generate_corsika_input_file(self, use_multipipe=False, use_test_seeds=False):
667
+ def generate_corsika_input_file(self, use_multipipe, input_file, output_file):
656
668
  """
657
669
  Generate a CORSIKA input file.
658
670
 
@@ -661,16 +673,14 @@ class CorsikaConfig:
661
673
  use_multipipe: bool
662
674
  Whether to set the CORSIKA Inputs file to pipe
663
675
  the output directly to sim_telarray.
664
-
676
+ input_file: Path
677
+ Path to the input file to be generated.
678
+ output_file: Path
679
+ Path to the output file to be generated.
665
680
  """
666
- if self.is_file_updated:
667
- self._logger.debug(f"CORSIKA input file already updated: {self.config_file_path}")
668
- return self.config_file_path
669
- self._logger.info(f"Exporting CORSIKA input file to {self.config_file_path}")
670
- _output_generic_file_name = self.set_output_file_and_directory(use_multipipe=use_multipipe)
671
- self._logger.info(f"Output generic file name: {_output_generic_file_name}")
681
+ self._logger.info(f"Exporting CORSIKA input file to {input_file}")
672
682
 
673
- with open(self.config_file_path, "w", encoding="utf-8") as file:
683
+ with open(input_file, "w", encoding="utf-8") as file:
674
684
  file.write("\n* [ RUN PARAMETERS ]\n")
675
685
  text_parameters = self._get_text_single_line(self.config["USER_INPUT"])
676
686
  file.write(text_parameters)
@@ -689,7 +699,7 @@ class CorsikaConfig:
689
699
  file.write(f"IACT setenv AZM {self.azimuth_angle}\n")
690
700
 
691
701
  file.write("\n* [ SEEDS ]\n")
692
- self._write_seeds(file, use_test_seeds)
702
+ self._write_seeds(file)
693
703
 
694
704
  file.write("\n* [ TELESCOPES ]\n")
695
705
  telescope_list_text = self.get_corsika_telescope_list()
@@ -698,6 +708,7 @@ class CorsikaConfig:
698
708
  file.write("\n* [ INTERACTION FLAGS ]\n")
699
709
  text_interaction_flags = self._get_text_single_line(self.config["INTERACTION_FLAGS"])
700
710
  file.write(text_interaction_flags)
711
+ file.write(f"DATDIR {self.interaction_table_path}\n")
701
712
 
702
713
  file.write("\n* [ CHERENKOV EMISSION PARAMETERS ]\n")
703
714
  text_cherenkov = self._get_text_single_line(
@@ -711,10 +722,9 @@ class CorsikaConfig:
711
722
 
712
723
  file.write("\n* [ OUTPUT FILE ]\n")
713
724
  if use_multipipe:
714
- run_cta_script = Path(self.config_file_path.parent).joinpath("run_cta_multipipe")
715
- file.write(f"TELFIL |{run_cta_script!s}\n")
725
+ file.write(f"TELFIL |{output_file!s}\n")
716
726
  else:
717
- file.write(f"TELFIL {_output_generic_file_name}\n")
727
+ file.write(f"TELFIL {output_file.name}\n")
718
728
 
719
729
  file.write("\n* [ IACT TUNING PARAMETERS ]\n")
720
730
  text_iact = self._get_text_single_line(
@@ -731,111 +741,22 @@ class CorsikaConfig:
731
741
  model_directory=self.array_model.get_config_directory()
732
742
  )
733
743
 
734
- self.is_file_updated = True
735
- return self.config_file_path
736
-
737
- def get_corsika_config_file_name(self, file_type, run_number=None):
738
- """
739
- Get a CORSIKA config style file name for various configuration file types.
740
-
741
- Parameters
742
- ----------
743
- file_type: str
744
- The type of file (determines the file suffix).
745
- Choices are config_tmp, config or output_generic.
746
- run_number: int
747
- Run number.
748
-
749
- Returns
750
- -------
751
- str
752
- for file_type="config_tmp":
753
- Return CORSIKA input file name for one specific run.
754
- This is the input file after being pre-processed by sim_telarray (pfp).
755
- for file_type="config":
756
- Return generic CORSIKA config input file name.
757
- for file_type="output_generic"
758
- Return generic file name for the TELFIL option in the CORSIKA inputs file.
759
- for file_type="multipipe"
760
- Return multipipe "file name" for the TELFIL option in the CORSIKA inputs file.
761
-
762
- Raises
763
- ------
764
- ValueError
765
- If file_type is unknown or if the run number is not given for file_type==config_tmp.
766
- """
767
- if file_type == "config_tmp" and run_number is None:
768
- raise ValueError("Must provide a run number for a temporary CORSIKA config file")
769
-
770
- file_label = f"_{self.label}" if self.label is not None else ""
771
-
772
- _vc_low = self.get_config_parameter("VIEWCONE")[0]
773
- _vc_high = self.get_config_parameter("VIEWCONE")[1]
774
- view_cone = (
775
- f"_cone{int(_vc_low):d}-{int(_vc_high):d}" if _vc_low != 0 or _vc_high != 0 else ""
776
- )
777
-
778
- run_number_in_file_name = ""
779
- if file_type == "output_generic":
780
- # The XXXXXX will be replaced by the run number after the pfp step with sed
781
- run_number_in_file_name = "runXXXXXX_"
782
- if file_type == "config_tmp":
783
- run_number_in_file_name = f"run{run_number:06}_"
784
-
785
- base_name = (
786
- f"{self.primary_particle.name}_{run_number_in_file_name}"
787
- f"za{int(self.get_config_parameter('THETAP')[0]):02}deg_"
788
- f"azm{self.azimuth_angle:03}deg{view_cone}_"
789
- f"{self.array_model.site}_{self.array_model.layout_name}_"
790
- f"{self.array_model.model_version}{file_label}"
791
- )
792
-
793
- if file_type == "config_tmp":
794
- return f"corsika_config_{base_name}.txt"
795
- if file_type == "config":
796
- return f"corsika_config_{base_name}.input"
797
- if file_type == "output_generic":
798
- return f"{base_name}.corsika.zst"
799
- if file_type == "multipipe":
800
- return f"multi_cta-{self.array_model.site}-{self.array_model.layout_name}.cfg"
801
-
802
- raise ValueError(f"The requested file type ({file_type}) is unknown")
803
-
804
- def set_output_file_and_directory(self, use_multipipe=False):
805
- """
806
- Set output file names and directories.
807
-
808
- Parameters
809
- ----------
810
- use_multipipe: bool
811
- Whether to set the CORSIKA Inputs file to pipe
812
- the output directly to sim_telarray. Defines directory names.
813
-
814
- Returns
815
- -------
816
- str
817
- Output file name.
818
- """
819
- self.config_file_path = self.io_handler.get_output_file(
820
- file_name=self.get_corsika_config_file_name(file_type="config"),
821
- sub_dir="corsika_sim_telarray" if use_multipipe else "corsika",
822
- )
823
- return self.get_corsika_config_file_name(file_type="output_generic")
824
-
825
- def _write_seeds(self, file, use_test_seeds=False):
744
+ def _write_seeds(self, file):
826
745
  """
827
746
  Generate and write seeds in the CORSIKA input file.
828
747
 
748
+ CORSIKA seeds consist of 4 integers.
749
+
829
750
  Parameters
830
751
  ----------
831
752
  file: stream
832
753
  File where the telescope positions will be written.
833
754
  """
834
- random_seed = self.get_config_parameter("PRMPAR") + self.run_number
835
- rng = np.random.default_rng(random_seed)
836
- corsika_seeds = [534, 220, 1104, 382]
837
- if not use_test_seeds:
838
- corsika_seeds = [int(rng.uniform(0, 1e7)) for _ in range(4)]
755
+ corsika_seeds = settings.config.args.get("corsika_seeds", False)
756
+ if not corsika_seeds:
757
+ corsika_seeds = seeds(n_seeds=4, max_seed=CORSIKA_MAX_SEED)
758
+ if len(corsika_seeds) != 4:
759
+ raise ValueError("Exactly 4 CORSIKA seeds must be provided.")
839
760
  for s in corsika_seeds:
840
761
  file.write(f"SEED {s} 0 0\n")
841
762
 
@@ -862,46 +783,23 @@ class CorsikaConfig:
862
783
 
863
784
  return corsika_input_list
864
785
 
865
- @property
866
- def run_number(self):
867
- """Set run number."""
868
- return self._run_number
869
-
870
- @run_number.setter
871
- def run_number(self, run_number):
872
- """
873
- Set run number and validate it.
874
-
875
- Parameters
876
- ----------
877
- run_number: int
878
- Run number.
879
- """
880
- self._run_number = self.validate_run_number(run_number)
881
-
882
- def validate_run_number(self, run_number):
786
+ def is_calibration_run(self):
883
787
  """
884
- Validate run number and return it. Return run number from configuration if None.
788
+ Check if this simulation is a calibration run.
885
789
 
886
790
  Parameters
887
791
  ----------
888
- run_number: int
889
- Run number.
792
+ run_mode: str
793
+ Run mode of the simulation.
890
794
 
891
795
  Returns
892
796
  -------
893
- int
894
- Run number.
895
-
896
- Raises
897
- ------
898
- ValueError
899
- If run_number is not a valid value (< 1 or > 999999).
900
- """
901
- if run_number is None:
902
- return self.run_number
903
- if not float(run_number).is_integer() or run_number < 1 or run_number > 999999:
904
- raise ValueError(
905
- f"Invalid type of run number ({run_number}) - it must be an uint < 1000000."
906
- )
907
- return run_number
797
+ bool
798
+ True if it is a calibration run, False otherwise.
799
+ """
800
+ return self.run_mode in [
801
+ "pedestals",
802
+ "pedestals_dark",
803
+ "pedestals_nsb_only",
804
+ "direct_injection",
805
+ ]