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
@@ -1,8 +1,15 @@
1
1
  """Utilities for managing the simulation models repository.
2
2
 
3
3
  Simulation model parameters and production tables are managed through
4
- a gitlab repository ('SimulationModels'). This module provides service
4
+ a gitlab repository ('simulation_models'). This module provides service
5
5
  functions to interact with and verify the repository.
6
+
7
+ Main functionalities are:
8
+
9
+ - validation of production tables against model parameters
10
+ - generation of new production tables and model parameters based on
11
+ updates defined in a configuration file
12
+
6
13
  """
7
14
 
8
15
  import logging
@@ -18,6 +25,44 @@ from simtools.utils import names
18
25
  _logger = logging.getLogger(__name__)
19
26
 
20
27
 
28
+ def get_production_directory(simulation_models_path, model_version=None):
29
+ """
30
+ Get the production directory for a specific model version.
31
+
32
+ Parameters
33
+ ----------
34
+ simulation_models_path : str
35
+ Path to the simulation models repository.
36
+ model_version : str, optional
37
+ Specific model version to get the production directory for.
38
+
39
+ Returns
40
+ -------
41
+ Path
42
+ Path to the production directory.
43
+ """
44
+ if model_version:
45
+ return Path(simulation_models_path) / "simulation-models/productions" / str(model_version)
46
+ return Path(simulation_models_path) / "simulation-models/productions"
47
+
48
+
49
+ def get_model_parameter_directory(simulation_models_path):
50
+ """
51
+ Get the model parameters directory.
52
+
53
+ Parameters
54
+ ----------
55
+ simulation_models_path : str
56
+ Path to the simulation models repository.
57
+
58
+ Returns
59
+ -------
60
+ Path
61
+ Path to the model parameters directory.
62
+ """
63
+ return Path(simulation_models_path) / "simulation-models/model_parameters"
64
+
65
+
21
66
  def verify_simulation_model_production_tables(simulation_models_path):
22
67
  """
23
68
  Verify the simulation model production tables in the specified path.
@@ -35,7 +80,7 @@ def verify_simulation_model_production_tables(simulation_models_path):
35
80
  bool
36
81
  True if all parameters found, False if any missing.
37
82
  """
38
- productions_path = Path(simulation_models_path) / "simulation-models" / "productions"
83
+ productions_path = get_production_directory(simulation_models_path)
39
84
  production_files = list(productions_path.rglob("*.json"))
40
85
 
41
86
  _logger.info(
@@ -124,9 +169,7 @@ def _get_model_parameter_file_path(
124
169
  """
125
170
  collection = names.get_collection_name_from_parameter_name(parameter_name)
126
171
  return (
127
- Path(simulation_models_path)
128
- / "simulation-models"
129
- / "model_parameters"
172
+ get_model_parameter_directory(simulation_models_path)
130
173
  / (
131
174
  collection
132
175
  if collection in ("configuration_sim_telarray", "configuration_corsika")
@@ -138,39 +181,36 @@ def _get_model_parameter_file_path(
138
181
  )
139
182
 
140
183
 
141
- def generate_new_production(modifications, simulation_models_path):
184
+ def generate_new_production(model_version, simulation_models_path):
142
185
  """
143
186
  Generate a new production definition (production tables and model parameters).
144
187
 
145
188
  The following steps are performed:
146
189
 
147
190
  - copy of production tables from an existing base model version
148
- - update production tables with changes defined in a YAML file
191
+ - update production tables with changes defined in a configuration file (expected
192
+ to be called 'info.yml' in the target production directory)
149
193
  - generate new model parameter entries for changed parameters
150
194
  - allows for full or patch updates
151
195
 
152
196
  Parameters
153
197
  ----------
154
- modifications: str
155
- Path to the YAML file defining the changes to be applied.
198
+ model_version: str
199
+ Model version to be created or updated.
156
200
  simulation_models_path: str
157
201
  Path to the simulation models repository.
158
202
  """
159
- modifications = ascii_handler.collect_data_from_file(modifications)
160
- model_version_history = modifications.get("model_version_history", [])
161
- try:
162
- # oldest version is the base version
163
- base_model_version = min(set(model_version_history), key=Version)
164
- except ValueError as exc:
165
- raise ValueError(f"Base model version not found in {modifications}") from exc
166
- model_version = modifications["model_version"]
167
- changes = modifications.get("changes", {})
203
+ modification_dict = _get_changes_dict(model_version, simulation_models_path)
204
+ update_type = modification_dict.get("model_update", "full_update")
205
+ changes, base_model_version = _get_changes_to_production(
206
+ modification_dict, simulation_models_path, update_type
207
+ )
168
208
 
169
209
  _apply_changes_to_production_tables(
170
210
  changes,
171
211
  base_model_version,
172
- model_version,
173
- modifications.get("model_update", "full_update"),
212
+ modification_dict["model_version"],
213
+ update_type,
174
214
  simulation_models_path,
175
215
  )
176
216
 
@@ -181,27 +221,27 @@ def _apply_changes_to_production_tables(
181
221
  changes, base_model_version, model_version, update_type, simulation_models_path
182
222
  ):
183
223
  """
184
- Apply changes to production tables and write them to target directory.
224
+ Apply changes to or generate new production tables and write them to target directory.
185
225
 
186
226
  Parameters
187
227
  ----------
188
228
  changes: dict
189
- The changes to be applied.
229
+ Changes to be applied.
190
230
  base_model_version: str
191
- The base model version (source directory for production tables).
231
+ Base model version (source directory for production tables).
192
232
  model_version: str
193
- The model version to be set in the JSON data.
233
+ Model version of the new production tables.
194
234
  update_type: str
195
- Update mode, either 'full_update' or 'patch_update'.
235
+ Update type (e.g., 'full_update' or 'patch_update').
196
236
  simulation_models_path: Path
197
237
  Path to the simulation models repository.
198
238
  """
199
- source = simulation_models_path / "productions" / base_model_version
200
- target = simulation_models_path / "productions" / model_version
239
+ source = get_production_directory(simulation_models_path, base_model_version)
240
+ target = get_production_directory(simulation_models_path, model_version)
201
241
  _logger.info(f"Production tables {update_type} from {source} to {target}")
202
242
  target.mkdir(parents=True, exist_ok=True)
203
243
 
204
- # load existing tables
244
+ # load existing tables from source
205
245
  tables = {}
206
246
  for file_path in Path(source).rglob("*.json"):
207
247
  data = ascii_handler.collect_data_from_file(file_path)
@@ -217,9 +257,10 @@ def _apply_changes_to_production_tables(
217
257
  if _apply_changes_to_production_table(
218
258
  table_name, data, changes, model_version, update_type == "patch_update"
219
259
  ):
220
- _logger.info(f"Writing updated production table '{table_name}'")
260
+ target_file = target / f"{table_name}.json"
261
+ _logger.info(f"Writing updated production table '{target_file}'")
221
262
  data["production_table_name"] = table_name
222
- ascii_handler.write_data_to_file(data, target / f"{table_name}.json", sort_keys=True)
263
+ ascii_handler.write_data_to_file(data, target_file, sort_keys=True)
223
264
 
224
265
 
225
266
  def _apply_changes_to_production_table(table_name, data, changes, model_version, patch_update):
@@ -228,12 +269,14 @@ def _apply_changes_to_production_table(table_name, data, changes, model_version,
228
269
 
229
270
  Parameters
230
271
  ----------
272
+ table_name: str
273
+ Name of the production table.
231
274
  data: dict
232
- The data to be updated.
275
+ Data to be updated.
233
276
  changes: dict
234
- The changes to be applied.
277
+ Changes to be applied.
235
278
  model_version: str
236
- The model version to be set in the JSON data.
279
+ Model version of the new production tables.
237
280
  patch_update: bool
238
281
  True if patch update (modify only changed parameters), False for full update.
239
282
 
@@ -248,7 +291,7 @@ def _apply_changes_to_production_table(table_name, data, changes, model_version,
248
291
  table_parameters = {} if patch_update else data.get("parameters", {}).get(table_name, {})
249
292
  parameters, deprecated = _update_parameters_dict(table_parameters, changes, table_name)
250
293
  data["parameters"] = parameters
251
- if deprecated:
294
+ if deprecated and patch_update:
252
295
  data["deprecated_parameters"] = deprecated
253
296
  elif patch_update:
254
297
  return False
@@ -256,6 +299,86 @@ def _apply_changes_to_production_table(table_name, data, changes, model_version,
256
299
  return True
257
300
 
258
301
 
302
+ def _get_changes_dict(model_version, simulation_models_path):
303
+ """
304
+ Load the changes dictionary from 'info.yml' files in production directories.
305
+
306
+ Parameters
307
+ ----------
308
+ model_version: str
309
+ Model version of the new production tables.
310
+ simulation_models_path: Path
311
+ Path to the simulation models directory.
312
+
313
+ Returns
314
+ -------
315
+ dict
316
+ Changes dictionary.
317
+ """
318
+ return ascii_handler.collect_data_from_file(
319
+ get_production_directory(simulation_models_path, model_version) / "info.yml"
320
+ )
321
+
322
+
323
+ def _get_changes_to_production(
324
+ modification_dict, simulation_models_path, update_type="full_update"
325
+ ):
326
+ """
327
+ Prepare changes applied to production tables.
328
+
329
+ For full updates, this includes the combination of changes to be applied
330
+ for all model versions in the history, starting from the base version.
331
+
332
+ Parameters
333
+ ----------
334
+ modification_dict: dict
335
+ Modifications dictionary.
336
+ simulation_models_path: Path
337
+ Path to the simulation models directory.
338
+ update_type: str
339
+ Update mode.
340
+
341
+ Returns
342
+ -------
343
+ dict, str
344
+ Changes dictionary and base model version.
345
+ """
346
+ model_version_history = modification_dict.get("model_version_history", [])
347
+
348
+ try:
349
+ # oldest version is the base version
350
+ base_model_version = min(set(model_version_history), key=Version)
351
+ except ValueError:
352
+ _logger.debug(f"Base model version not found in {model_version_history}")
353
+ return {}, modification_dict.get("model_version")
354
+
355
+ changes = modification_dict.get("changes", {})
356
+ if update_type == "patch_update":
357
+ return changes, base_model_version
358
+
359
+ for version_mod in reversed(model_version_history):
360
+ _changes_dict = _get_changes_dict(version_mod, simulation_models_path)
361
+ _version_changes, base_model_version = _get_changes_to_production(
362
+ _changes_dict, simulation_models_path, update_type="full_update"
363
+ )
364
+ changes = _update_two_levels_in_changes_dict(_version_changes, changes)
365
+ # stop iterative loop after reaching first full version of production tables
366
+ if _changes_dict.get("model_update", "full_update") == "full_update":
367
+ break
368
+
369
+ return changes, base_model_version
370
+
371
+
372
+ def _update_two_levels_in_changes_dict(d, u):
373
+ """Update changes dict, e.g. {"LSTN-design": { "parameter_name: { ... } } }."""
374
+ for k, v in u.items():
375
+ if isinstance(v, dict) and isinstance(d.get(k), dict):
376
+ d[k].update(v)
377
+ else:
378
+ d[k] = v
379
+ return d
380
+
381
+
259
382
  def _update_parameters_dict(table_parameters, changes, table_name):
260
383
  """
261
384
  Create a new parameters dictionary for the production tables.
@@ -285,6 +408,7 @@ def _update_parameters_dict(table_parameters, changes, table_name):
285
408
  if data.get("deprecated", False):
286
409
  _logger.info(f"Removing model parameter '{table_name} - {param}'")
287
410
  deprecated_params.append(param)
411
+ new_params[table_name].pop(param, None)
288
412
  else:
289
413
  version = data["version"]
290
414
  _logger.info(f"Setting '{table_name} - {param}' to version {version}")
@@ -330,14 +454,14 @@ def _create_new_model_parameter_entry(telescope, param, param_data, simulation_m
330
454
  simulation_models_path: Path
331
455
  Path to the simulation models directory.
332
456
  """
333
- telescope_dir = simulation_models_path / "model_parameters" / telescope
457
+ telescope_dir = get_model_parameter_directory(simulation_models_path) / telescope
334
458
  if not telescope_dir.exists():
335
459
  _logger.info(f"Create directory for array element '{telescope}': '{telescope_dir}'.")
336
460
  telescope_dir.mkdir(parents=True, exist_ok=True)
337
461
 
338
462
  param_dir = telescope_dir / param
339
463
  try:
340
- latest_file = _get_latest_model_parameter_file(param_dir, param)
464
+ latest_file = _get_latest_model_parameter_file(param_dir, param, param_data["version"])
341
465
  except FileNotFoundError:
342
466
  latest_file = None
343
467
 
@@ -360,10 +484,11 @@ def _create_new_model_parameter_entry(telescope, param, param_data, simulation_m
360
484
  output_path=param_dir,
361
485
  unit=param_data.get("unit"),
362
486
  meta_parameter=param_data.get("meta_parameter", False),
487
+ model_parameter_schema_version=param_data.get("model_parameter_schema_version", None),
363
488
  )
364
489
 
365
490
 
366
- def _get_latest_model_parameter_file(directory, parameter):
491
+ def _get_latest_model_parameter_file(directory, parameter, max_version):
367
492
  """
368
493
  Get the latest model parameter JSON file for a parameter in the given directory.
369
494
 
@@ -375,11 +500,14 @@ def _get_latest_model_parameter_file(directory, parameter):
375
500
  Path to the directory containing parameter JSON files.
376
501
  parameter: str
377
502
  Name of the parameter to find.
503
+ max_version: str
504
+ Maximum version to consider (inclusive). Files with versions greater than
505
+ this will be excluded.
378
506
 
379
507
  Returns
380
508
  -------
381
509
  str
382
- Path to the latest JSON file for the parameter.
510
+ Path to the latest JSON file for the parameter with version <= max_version.
383
511
 
384
512
  Raises
385
513
  ------
@@ -397,7 +525,16 @@ def _get_latest_model_parameter_file(directory, parameter):
397
525
  # version is part after first '-'
398
526
  return parse_version(path.stem.split("-", 1)[1])
399
527
 
400
- latest_file = max(files, key=extract_version)
528
+ max_ver = parse_version(max_version)
529
+ filtered_files = [f for f in files if extract_version(f) <= max_ver]
530
+
531
+ if not filtered_files:
532
+ raise FileNotFoundError(
533
+ f"No JSON files found for parameter '{parameter}' with version <= {max_version} "
534
+ f"in directory '{directory}'."
535
+ )
536
+
537
+ latest_file = max(filtered_files, key=extract_version)
401
538
  return str(latest_file)
402
539
 
403
540
 
@@ -38,21 +38,21 @@ def initialize_simulation_models(
38
38
  tel_model = TelescopeModel(
39
39
  site=site,
40
40
  telescope_name=telescope_name,
41
- mongo_db_config=db_config,
41
+ db_config=db_config,
42
42
  model_version=model_version,
43
43
  label=label,
44
44
  )
45
45
  site_model = SiteModel(
46
46
  site=site,
47
47
  model_version=model_version,
48
- mongo_db_config=db_config,
48
+ db_config=db_config,
49
49
  label=label,
50
50
  )
51
51
  if calibration_device_name is not None:
52
52
  calibration_model = CalibrationModel(
53
53
  site=site,
54
54
  calibration_device_model_name=calibration_device_name,
55
- mongo_db_config=db_config,
55
+ db_config=db_config,
56
56
  model_version=model_version,
57
57
  label=label,
58
58
  )
@@ -12,47 +12,64 @@ from simtools.model.model_parameter import ModelParameter
12
12
 
13
13
  class SiteModel(ModelParameter):
14
14
  """
15
- SiteModel represents the MC model of an observatory site.
15
+ Representation of an observatory site model.
16
+
17
+ The site model includes (among others):
18
+
19
+ - Reference point coordinates
20
+ - Array elements
21
+ - Geomagnetic field parameters
22
+ - Atmospheric parameters
23
+ - NSB parameters
16
24
 
17
25
  Parameters
18
26
  ----------
19
27
  site: str
20
28
  Site name (e.g., South or North).
21
- mongo_db_config: dict
22
- MongoDB configuration.
29
+ db_config: dict
30
+ Database configuration.
23
31
  model_version: str or list
24
32
  Model version or list of model versions (in which case only the first one is used).
25
33
  label: str, optional
26
- Instance label. Important for output file naming.
34
+ Instance label.
35
+ overwrite_model_parameters: str, optional
36
+ File name to overwrite model parameters from DB with provided values.
37
+ ignore_software_version: bool, optional
38
+ If True, ignore software version checks for deprecated parameters.
27
39
  """
28
40
 
29
41
  def __init__(
30
42
  self,
31
- site: str,
32
- mongo_db_config: dict,
33
- model_version: str,
34
- label: str | None = None,
43
+ site,
44
+ db_config,
45
+ model_version,
46
+ label=None,
47
+ overwrite_model_parameters=None,
48
+ ignore_software_version=False,
35
49
  ):
36
50
  """Initialize SiteModel."""
37
51
  self._logger = logging.getLogger(__name__)
38
52
  self._logger.debug("Init SiteModel for site %s", site)
39
53
  super().__init__(
40
54
  site=site,
41
- mongo_db_config=mongo_db_config,
55
+ db_config=db_config,
42
56
  model_version=model_version,
43
- db=None,
44
57
  label=label,
45
58
  collection="sites",
59
+ overwrite_model_parameters=overwrite_model_parameters,
60
+ ignore_software_version=ignore_software_version,
46
61
  )
47
62
 
48
- def get_reference_point(self) -> dict:
63
+ def get_reference_point(self):
49
64
  """
50
- Get reference point coordinates as dict.
65
+ Get reference point coordinates.
66
+
67
+ Ground coordinates are calculated relative to this point.
51
68
 
52
69
  Returns
53
70
  -------
54
71
  dict
55
- Reference point coordinates as dict
72
+ Reference point coordinates.
56
73
  """
57
74
  return {
58
75
  "center_altitude": self.get_parameter_value_with_unit("reference_point_altitude"),
@@ -61,13 +78,12 @@ class SiteModel(ModelParameter):
61
78
  "epsg_code": self.get_parameter_value("epsg_code"),
62
79
  }
63
80
 
64
- def get_corsika_site_parameters(
65
- self, config_file_style: bool = False, model_directory: Path | None = None
66
- ) -> dict:
81
+ def get_corsika_site_parameters(self, config_file_style=False, model_directory=None):
67
82
  """
68
- Get site-related CORSIKA parameters as dict.
83
+ Get site-related CORSIKA parameters.
69
84
 
70
- Parameters are returned with units wherever possible.
85
+ Parameters are returned with units wherever possible ('config_file_style=False')
86
+ or in CORSIKA-expected coordinates.
71
87
 
72
88
  Parameters
73
89
  ----------
@@ -79,7 +95,7 @@ class SiteModel(ModelParameter):
79
95
  Returns
80
96
  -------
81
97
  dict
82
- Site-related CORSIKA parameters as dict
98
+ Site-related CORSIKA parameters.
83
99
  """
84
100
  if config_file_style:
85
101
  model_directory = model_directory or Path()
@@ -108,7 +124,7 @@ class SiteModel(ModelParameter):
108
124
  "geomag_rotation": self.get_parameter_value_with_unit("geomag_rotation"),
109
125
  }
110
126
 
111
- def get_array_elements_for_layout(self, layout_name: str) -> list:
127
+ def get_array_elements_for_layout(self, layout_name):
112
128
  """
113
129
  Return list of array elements for a given array layout.
114
130
 
@@ -128,7 +144,27 @@ class SiteModel(ModelParameter):
128
144
  return layout["elements"]
129
145
  raise ValueError(f"Array layout '{layout_name}' not found in '{self.site}' site model.")
130
146
 
131
- def get_list_of_array_layouts(self) -> list:
147
+ def get_array_elements_of_type(self, array_element_type):
148
+ """
149
+ Get all array elements of a given type.
150
+
151
+ Parameters
152
+ ----------
153
+ array_element_type : str
154
+ Type of the array element (e.g. LSTN, MSTS)
155
+
156
+ Returns
157
+ -------
158
+ dict
159
+ Dict with array elements.
160
+ """
161
+ return self.db.get_array_elements_of_type(
162
+ array_element_type=array_element_type,
163
+ model_version=self.model_version,
164
+ collection="telescopes",
165
+ )
166
+
167
+ def get_list_of_array_layouts(self):
132
168
  """
133
169
  Get list of available array layouts.
134
170
 
@@ -139,13 +175,9 @@ class SiteModel(ModelParameter):
139
175
  """
140
176
  return [layout["name"] for layout in self.get_parameter_value("array_layouts")]
141
177
 
142
- def export_atmospheric_transmission_file(self, model_directory: Path):
178
+ def export_atmospheric_transmission_file(self, model_directory):
143
179
  """
144
- Export atmospheric transmission file.
145
-
146
- This method is needed because when CORSIKA is not piped to sim_telarray,
147
- the atmospheric transmission file is not written out to the model directory.
148
- This method allows to export it explicitly.
180
+ Export atmospheric transmission file from database to the given directory.
149
181
 
150
182
  Parameters
151
183
  ----------
@@ -26,30 +26,37 @@ class TelescopeModel(ModelParameter):
26
26
  Site name (e.g., South or North).
27
27
  telescope_name: str
28
28
  Telescope name (ex. LSTN-01, LSTN-design, ...).
29
- mongo_db_config: dict
30
- MongoDB configuration.
29
+ db_config: dict
30
+ Database configuration.
31
31
  model_version: str
32
32
  Model version.
33
33
  label: str, optional
34
- Instance label. Important for output file naming.
34
+ Instance label.
35
+ overwrite_model_parameters: str, optional
36
+ File name to overwrite model parameters from DB with provided values.
37
+ ignore_software_version: bool, optional
38
+ If True, ignore software version checks for deprecated parameters.
35
39
  """
36
40
 
37
41
  def __init__(
38
42
  self,
39
- site: str,
40
- telescope_name: str,
41
- mongo_db_config: dict,
42
- model_version: str,
43
- label: str | None = None,
43
+ site,
44
+ telescope_name,
45
+ db_config,
46
+ model_version,
47
+ label=None,
48
+ overwrite_model_parameters=None,
49
+ ignore_software_version=False,
44
50
  ):
45
51
  """Initialize TelescopeModel."""
46
52
  super().__init__(
47
53
  site=site,
48
54
  array_element_name=telescope_name,
49
- mongo_db_config=mongo_db_config,
55
+ db_config=db_config,
50
56
  model_version=model_version,
51
- db=None,
52
57
  label=label,
58
+ overwrite_model_parameters=overwrite_model_parameters,
59
+ ignore_software_version=ignore_software_version,
53
60
  )
54
61
 
55
62
  self._logger = logging.getLogger(__name__)
@@ -142,7 +149,7 @@ class TelescopeModel(ModelParameter):
142
149
  except TypeError as exc:
143
150
  raise TypeError("Undefined mirror list") from exc
144
151
 
145
- self._mirrors = Mirrors(mirror_list_file, parameters=self._parameters)
152
+ self._mirrors = Mirrors(mirror_list_file, parameters=self.parameters)
146
153
 
147
154
  def get_telescope_effective_focal_length(
148
155
  self, unit: str = "m", return_focal_length_if_zero: bool = False
@@ -374,8 +381,9 @@ class TelescopeModel(ModelParameter):
374
381
  try:
375
382
  return self.get_parameter_value_with_unit(f"array_element_position_{coordinate_system}")
376
383
  except InvalidModelParameterError as exc:
377
- self._logger.error(f"Coordinate system {coordinate_system} not found.")
378
- raise exc
384
+ raise InvalidModelParameterError(
385
+ f"Coordinate system {coordinate_system} not found."
386
+ ) from exc
379
387
 
380
388
  def get_calibration_device_name(self, device_type):
381
389
  """
@@ -81,10 +81,10 @@ class MirrorPanelPSF:
81
81
  mirror_list_file = gen.find_file(
82
82
  name=self.args_dict["mirror_list"], loc=self.args_dict["model_path"]
83
83
  )
84
- tel_model.change_parameter("mirror_list", self.args_dict["mirror_list"])
85
- tel_model.export_parameter_file("mirror_list", mirror_list_file)
84
+ tel_model.overwrite_model_parameter("mirror_list", self.args_dict["mirror_list"])
85
+ tel_model.overwrite_model_file("mirror_list", mirror_list_file)
86
86
  if self.args_dict["random_focal_length"] is not None:
87
- tel_model.change_parameter(
87
+ tel_model.overwrite_model_parameter(
88
88
  "random_focal_length", str(self.args_dict["random_focal_length"])
89
89
  )
90
90
 
@@ -221,7 +221,7 @@ class MirrorPanelPSF:
221
221
  sig_d80: float
222
222
  Standard deviation of D80 in cm.
223
223
  """
224
- self.telescope_model.change_parameter("mirror_reflection_random_angle", rnda)
224
+ self.telescope_model.overwrite_model_parameter("mirror_reflection_random_angle", rnda)
225
225
  ray = RayTracing(
226
226
  telescope_model=self.telescope_model,
227
227
  site_model=self.site_model,
@@ -516,18 +516,21 @@ class PSFImage:
516
516
 
517
517
  Parameters
518
518
  ----------
519
+ file_name: str
520
+ Name of the file to save the plot to.
521
+ d80: float
522
+ d80 value to be marked in the plot (in cm).
519
523
  **kwargs:
520
- image_* for the histogram plot and psf_* for the psf circle.
524
+ Customization of line plot (e.g., color, linestyle, linewidth).
521
525
  """
522
526
  data = self.get_cumulative_data()
523
- ax = plt.gca()
524
- plt.tight_layout(pad=1.5)
527
+ fig, ax = plt.subplots(constrained_layout=True)
525
528
  ax.set_xlabel("Radius (cm)")
526
529
  ax.set_ylabel("Contained light %")
527
- plt.plot(data[self.__PSF_RADIUS], data[self.__PSF_CUMULATIVE], **kwargs)
528
- plt.axvline(x=self.get_psf(0.8) / 2, color="b", linestyle="--", linewidth=1)
530
+ ax.plot(data[self.__PSF_RADIUS], data[self.__PSF_CUMULATIVE], **kwargs)
531
+ ax.axvline(x=self.get_psf(0.8) / 2, color="b", linestyle="--", linewidth=1)
529
532
  if d80 is not None:
530
- plt.axvline(x=d80 / 2.0, color="r", linestyle="--", linewidth=1)
533
+ ax.axvline(x=d80 / 2.0, color="r", linestyle="--", linewidth=1)
531
534
  if file_name is not None:
532
- plt.savefig(file_name)
533
- plt.close()
535
+ fig.savefig(file_name)
536
+ plt.close(fig)