gammasimtools 0.16.0__py3-none-any.whl → 0.18.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 (85) hide show
  1. {gammasimtools-0.16.0.dist-info → gammasimtools-0.18.0.dist-info}/METADATA +5 -2
  2. {gammasimtools-0.16.0.dist-info → gammasimtools-0.18.0.dist-info}/RECORD +82 -74
  3. {gammasimtools-0.16.0.dist-info → gammasimtools-0.18.0.dist-info}/WHEEL +1 -1
  4. {gammasimtools-0.16.0.dist-info → gammasimtools-0.18.0.dist-info}/entry_points.txt +4 -1
  5. simtools/_version.py +2 -2
  6. simtools/applications/db_add_simulation_model_from_repository_to_db.py +10 -1
  7. simtools/applications/derive_ctao_array_layouts.py +5 -5
  8. simtools/applications/derive_mirror_rnda.py +1 -1
  9. simtools/applications/generate_simtel_event_data.py +128 -46
  10. simtools/applications/merge_tables.py +102 -0
  11. simtools/applications/plot_array_layout.py +145 -258
  12. simtools/applications/plot_tabular_data.py +12 -1
  13. simtools/applications/plot_tabular_data_for_model_parameter.py +103 -0
  14. simtools/applications/production_derive_corsika_limits.py +78 -225
  15. simtools/applications/production_derive_statistics.py +77 -43
  16. simtools/applications/simulate_light_emission.py +1 -0
  17. simtools/applications/simulate_prod.py +30 -18
  18. simtools/applications/simulate_prod_htcondor_generator.py +0 -1
  19. simtools/applications/submit_array_layouts.py +93 -0
  20. simtools/applications/verify_simulation_model_production_tables.py +52 -0
  21. simtools/camera/camera_efficiency.py +3 -3
  22. simtools/configuration/commandline_parser.py +30 -35
  23. simtools/configuration/configurator.py +0 -4
  24. simtools/constants.py +2 -0
  25. simtools/corsika/corsika_config.py +17 -12
  26. simtools/corsika/primary_particle.py +46 -13
  27. simtools/data_model/metadata_collector.py +7 -3
  28. simtools/data_model/schema.py +15 -1
  29. simtools/db/db_handler.py +16 -11
  30. simtools/db/db_model_upload.py +2 -2
  31. simtools/io_operations/io_handler.py +2 -2
  32. simtools/io_operations/io_table_handler.py +345 -0
  33. simtools/job_execution/htcondor_script_generator.py +2 -2
  34. simtools/job_execution/job_manager.py +7 -121
  35. simtools/layout/array_layout_utils.py +389 -0
  36. simtools/model/array_model.py +10 -1
  37. simtools/model/model_repository.py +134 -0
  38. simtools/production_configuration/{calculate_statistical_errors_grid_point.py → calculate_statistical_uncertainties_grid_point.py} +101 -112
  39. simtools/production_configuration/derive_corsika_limits.py +239 -111
  40. simtools/production_configuration/derive_corsika_limits_grid.py +232 -0
  41. simtools/production_configuration/derive_production_statistics.py +57 -26
  42. simtools/production_configuration/derive_production_statistics_handler.py +70 -37
  43. simtools/production_configuration/interpolation_handler.py +296 -94
  44. simtools/ray_tracing/ray_tracing.py +7 -6
  45. simtools/reporting/docs_read_parameters.py +104 -62
  46. simtools/resources/array-element-ids.json +126 -0
  47. simtools/runners/corsika_simtel_runner.py +4 -1
  48. simtools/runners/runner_services.py +5 -4
  49. simtools/schemas/model_parameter_and_data_schema.metaschema.yml +5 -1
  50. simtools/schemas/model_parameters/atmospheric_profile.schema.yml +41 -0
  51. simtools/schemas/model_parameters/atmospheric_transmission.schema.yml +43 -0
  52. simtools/schemas/model_parameters/camera_filter.schema.yml +10 -0
  53. simtools/schemas/model_parameters/camera_filter_incidence_angle.schema.yml +10 -0
  54. simtools/schemas/model_parameters/discriminator_pulse_shape.schema.yml +31 -0
  55. simtools/schemas/model_parameters/dsum_threshold.schema.yml +41 -0
  56. simtools/schemas/model_parameters/fadc_pulse_shape.schema.yml +12 -0
  57. simtools/schemas/model_parameters/lightguide_efficiency_vs_incidence_angle.schema.yml +10 -0
  58. simtools/schemas/model_parameters/mirror_reflectivity.schema.yml +10 -0
  59. simtools/schemas/model_parameters/nsb_reference_spectrum.schema.yml +12 -0
  60. simtools/schemas/model_parameters/pm_photoelectron_spectrum.schema.yml +19 -0
  61. simtools/schemas/model_parameters/quantum_efficiency.schema.yml +10 -0
  62. simtools/schemas/plot_configuration.metaschema.yml +46 -57
  63. simtools/schemas/production_configuration_metrics.schema.yml +2 -2
  64. simtools/simtel/simtel_config_writer.py +34 -14
  65. simtools/simtel/simtel_io_event_reader.py +301 -194
  66. simtools/simtel/simtel_io_event_writer.py +237 -221
  67. simtools/simtel/simtel_io_file_info.py +9 -4
  68. simtools/simtel/simtel_io_metadata.py +119 -8
  69. simtools/simtel/simulator_array.py +2 -2
  70. simtools/simtel/simulator_light_emission.py +79 -34
  71. simtools/simtel/simulator_ray_tracing.py +2 -2
  72. simtools/simulator.py +101 -68
  73. simtools/testing/validate_output.py +4 -1
  74. simtools/utils/general.py +1 -3
  75. simtools/utils/names.py +76 -7
  76. simtools/visualization/plot_array_layout.py +242 -0
  77. simtools/visualization/plot_pixels.py +680 -0
  78. simtools/visualization/plot_tables.py +81 -2
  79. simtools/visualization/visualize.py +3 -219
  80. simtools/applications/production_generate_simulation_config.py +0 -152
  81. simtools/layout/ctao_array_layouts.py +0 -172
  82. simtools/production_configuration/generate_simulation_config.py +0 -158
  83. {gammasimtools-0.16.0.dist-info → gammasimtools-0.18.0.dist-info}/licenses/LICENSE +0 -0
  84. {gammasimtools-0.16.0.dist-info → gammasimtools-0.18.0.dist-info}/top_level.txt +0 -0
  85. /simtools/{schemas → resources}/array_elements.yml +0 -0
simtools/simulator.py CHANGED
@@ -12,11 +12,12 @@ import numpy as np
12
12
 
13
13
  import simtools.utils.general as gen
14
14
  from simtools.corsika.corsika_config import CorsikaConfig
15
- from simtools.io_operations import io_handler
15
+ from simtools.io_operations import io_handler, io_table_handler
16
16
  from simtools.job_execution.job_manager import JobManager
17
17
  from simtools.model.array_model import ArrayModel
18
18
  from simtools.runners.corsika_runner import CorsikaRunner
19
19
  from simtools.runners.corsika_simtel_runner import CorsikaSimtelRunner
20
+ from simtools.simtel.simtel_io_event_writer import SimtelIOEventDataWriter
20
21
  from simtools.simtel.simulator_array import SimulatorArray
21
22
  from simtools.testing.sim_telarray_metadata import assert_sim_telarray_metadata
22
23
 
@@ -72,8 +73,6 @@ class Simulator:
72
73
  self.runs = self._initialize_run_list()
73
74
  self._results = defaultdict(list)
74
75
  self._test = self.args_dict.get("test", False)
75
- self.submit_engine = self.args_dict.get("submit_engine", "local")
76
- self._submit_options = self.args_dict.get("submit_options", None)
77
76
  self._extra_commands = extra_commands
78
77
 
79
78
  self.sim_telarray_seeds = {
@@ -145,6 +144,7 @@ class Simulator:
145
144
  ],
146
145
  "seed_file_name": self.sim_telarray_seeds["seed_file_name"],
147
146
  },
147
+ simtel_path=self.args_dict.get("simtel_path", None),
148
148
  )
149
149
  for version in versions
150
150
  ]
@@ -179,7 +179,10 @@ class Simulator:
179
179
 
180
180
  def _initialize_run_list(self):
181
181
  """
182
- Initialize run list using the configuration values 'run_number_start' and 'number_of_runs'.
182
+ Initialize run list using the configuration values.
183
+
184
+ Uses 'run_number', 'run_number_offset' and 'number_of_runs' arguments
185
+ to create a list of run numbers.
183
186
 
184
187
  Returns
185
188
  -------
@@ -189,19 +192,27 @@ class Simulator:
189
192
  Raises
190
193
  ------
191
194
  KeyError
192
- If 'run_number_start' or 'number_of_runs' are not found in the configuration.
195
+ If 'run_number', 'run_number_offset' and 'number_of_runs' are
196
+ not found in the configuration.
193
197
  """
194
198
  try:
199
+ offset_run_number = self.args_dict["run_number_offset"] + self.args_dict["run_number"]
200
+ if self.args_dict["number_of_runs"] <= 1:
201
+ return self._validate_run_list_and_range(
202
+ run_list=offset_run_number,
203
+ run_range=None,
204
+ )
195
205
  return self._validate_run_list_and_range(
196
206
  run_list=None,
197
207
  run_range=[
198
- self.args_dict["run_number_start"],
199
- self.args_dict["run_number_start"] + self.args_dict["number_of_runs"],
208
+ offset_run_number,
209
+ offset_run_number + self.args_dict["number_of_runs"],
200
210
  ],
201
211
  )
202
212
  except KeyError as exc:
203
213
  self._logger.error(
204
- "Error in initializing run list (missing 'run_number_start' or 'number_of_runs')"
214
+ "Error in initializing run list "
215
+ "(missing 'run_number', 'run_number_offset' or 'number_of_runs')."
205
216
  )
206
217
  raise exc
207
218
 
@@ -301,6 +312,8 @@ class Simulator:
301
312
  runner_args["keep_seeds"] = self.args_dict.get("corsika_test_seeds", False)
302
313
  if runner_class is not CorsikaRunner:
303
314
  runner_args["sim_telarray_seeds"] = self.sim_telarray_seeds
315
+ if runner_class is CorsikaSimtelRunner:
316
+ runner_args["sequential"] = self.args_dict.get("sequential", False)
304
317
 
305
318
  return runner_class(**runner_args)
306
319
 
@@ -330,8 +343,6 @@ class Simulator:
330
343
  input_file_list: str or list of str
331
344
  Single file or list of files of shower simulations.
332
345
  """
333
- self._logger.info(f"Submission command: {self.submit_engine}")
334
-
335
346
  runs_and_files_to_submit = self._get_runs_and_files_to_submit(
336
347
  input_file_list=input_file_list
337
348
  )
@@ -345,11 +356,7 @@ class Simulator:
345
356
  run_number=run_number, input_file=input_file, extra_commands=self._extra_commands
346
357
  )
347
358
 
348
- job_manager = JobManager(
349
- submit_engine=self.submit_engine,
350
- submit_options=self._submit_options,
351
- test=self._test,
352
- )
359
+ job_manager = JobManager(test=self._test)
353
360
  job_manager.submit(
354
361
  run_script=run_script,
355
362
  run_out_file=self._simulation_runner.get_file_name(
@@ -454,73 +461,66 @@ class Simulator:
454
461
  run number
455
462
 
456
463
  """
457
- keys = ["output", "sub_out", "log", "input", "hist", "corsika_log"]
464
+ keys = ["simtel_output", "sub_out", "log", "input", "hist", "corsika_log", "event_data"]
458
465
  results = {key: [] for key in keys}
459
466
 
467
+ def get_file_name(name, **kwargs):
468
+ return str(self._simulation_runner.get_file_name(file_type=name, **kwargs))
469
+
460
470
  if "sim_telarray" in self.simulation_software:
461
471
  results["input"].append(str(file))
462
472
 
463
- results["sub_out"].append(
464
- str(
465
- self._simulation_runner.get_file_name(
466
- file_type="sub_log",
467
- mode="out",
468
- run_number=run_number,
469
- )
470
- )
471
- )
473
+ results["sub_out"].append(get_file_name("sub_log", mode="out", run_number=run_number))
472
474
 
473
- for model_version_index, _ in enumerate(self.array_models):
474
- results["output"].append(
475
- str(
476
- self._simulation_runner.get_file_name(
477
- file_type="output",
478
- run_number=run_number,
479
- model_version_index=model_version_index,
480
- )
481
- )
475
+ for i in range(len(self.array_models)):
476
+ results["simtel_output"].append(
477
+ get_file_name("simtel_output", run_number=run_number, model_version_index=i)
482
478
  )
479
+
483
480
  if "sim_telarray" in self.simulation_software:
484
481
  results["log"].append(
485
- str(
486
- self._simulation_runner.get_file_name(
487
- file_type="log",
488
- simulation_software="sim_telarray",
489
- run_number=run_number,
490
- model_version_index=model_version_index,
491
- )
482
+ get_file_name(
483
+ "log",
484
+ simulation_software="sim_telarray",
485
+ run_number=run_number,
486
+ model_version_index=i,
492
487
  )
493
488
  )
494
489
  results["hist"].append(
495
- str(
496
- self._simulation_runner.get_file_name(
497
- file_type="histogram",
498
- simulation_software="sim_telarray",
499
- run_number=run_number,
500
- model_version_index=model_version_index,
501
- )
490
+ get_file_name(
491
+ "histogram",
492
+ simulation_software="sim_telarray",
493
+ run_number=run_number,
494
+ model_version_index=i,
502
495
  )
503
496
  )
497
+ results["event_data"].append(
498
+ get_file_name(
499
+ "event_data",
500
+ simulation_software="sim_telarray",
501
+ run_number=run_number,
502
+ model_version_index=i,
503
+ )
504
+ )
505
+
504
506
  if "corsika" in self.simulation_software:
505
507
  results["corsika_log"].append(
506
- str(
507
- self._simulation_runner.get_file_name(
508
- file_type="corsika_log",
509
- simulation_software="corsika",
510
- run_number=run_number,
511
- model_version_index=model_version_index,
512
- )
508
+ get_file_name(
509
+ "corsika_log",
510
+ simulation_software="corsika",
511
+ run_number=run_number,
512
+ model_version_index=i,
513
513
  )
514
514
  )
515
515
 
516
516
  for key in keys:
517
517
  self._results[key].extend(results[key])
518
518
 
519
- def get_file_list(self, file_type="output"):
519
+ def get_file_list(self, file_type="simtel_output"):
520
520
  """
521
521
  Get list of files generated by simulations.
522
522
 
523
- Options are "input", "output", "hist", "log", "corsika_log".
523
+ Options are "input", "simtel_output", "hist", "log", "corsika_log".
524
524
  Not all file types are available for all simulation types.
525
525
  Returns an empty list for an unknown file type.
526
526
 
@@ -538,11 +538,11 @@ class Simulator:
538
538
  self._logger.info(f"Getting list of {file_type} files")
539
539
  return self._results[file_type]
540
540
 
541
- def print_list_of_files(self, file_type="output"):
541
+ def print_list_of_files(self, file_type="simtel_output"):
542
542
  """
543
543
  Print list of output files generated by simulations.
544
544
 
545
- Options are "input", "output", "hist", "log".
545
+ Options are "input", "simtel_output", "hist", "log".
546
546
 
547
547
  Parameters
548
548
  ----------
@@ -636,7 +636,7 @@ class Simulator:
636
636
 
637
637
  def save_file_lists(self):
638
638
  """Save files lists for output and log files."""
639
- for file_type in ["output", "log", "corsika_log", "hist"]:
639
+ for file_type in ["simtel_output", "log", "corsika_log", "hist"]:
640
640
  file_name = self.io_handler.get_output_directory(label=self.label).joinpath(
641
641
  f"{file_type}_files.txt"
642
642
  )
@@ -649,6 +649,29 @@ class Simulator:
649
649
  else:
650
650
  self._logger.debug(f"No files to save for {file_type} files.")
651
651
 
652
+ def save_reduced_event_lists(self):
653
+ """
654
+ Save reduced event lists with event data on simulated and triggered events.
655
+
656
+ The files are saved with the same name as the sim_telarray output file
657
+ but with a 'hdf5' extension.
658
+ """
659
+ if "sim_telarray" not in self.simulation_software:
660
+ self._logger.warning(
661
+ "Reduced event lists can only be saved for sim_telarray simulations."
662
+ )
663
+ return
664
+
665
+ input_files = self.get_file_list(file_type="simtel_output")
666
+ output_files = self.get_file_list(file_type="event_data")
667
+ for input_file, output_file in zip(input_files, output_files):
668
+ generator = SimtelIOEventDataWriter([input_file])
669
+ io_table_handler.write_tables(
670
+ tables=generator.process_files(),
671
+ output_file=Path(output_file),
672
+ overwrite_existing=True,
673
+ )
674
+
652
675
  def pack_for_register(self, directory_for_grid_upload=None):
653
676
  """
654
677
  Pack simulation output files for registering on the grid.
@@ -664,10 +687,15 @@ class Simulator:
664
687
  self._logger.info(
665
688
  f"Packing the output files for registering on the grid ({directory_for_grid_upload})"
666
689
  )
667
- output_files = self.get_file_list(file_type="output")
690
+ output_files = self.get_file_list(file_type="simtel_output")
668
691
  log_files = self.get_file_list(file_type="log")
669
692
  corsika_log_files = self.get_file_list(file_type="corsika_log")
670
693
  histogram_files = self.get_file_list(file_type="hist")
694
+ reduced_event_files = (
695
+ self.get_file_list(file_type="event_data")
696
+ if self.args_dict.get("save_reduced_event_lists")
697
+ else []
698
+ )
671
699
 
672
700
  directory_for_grid_upload = (
673
701
  Path(directory_for_grid_upload)
@@ -697,11 +725,12 @@ class Simulator:
697
725
  tar_file_path = directory_for_grid_upload.joinpath(tar_file_name)
698
726
 
699
727
  with tarfile.open(tar_file_path, "w:gz") as tar:
728
+ # Add all relevant log, histogram, and CORSIKA log files to the tarball
700
729
  files_to_tar = model_logs + model_hists + model_corsika_logs
701
730
  for file_to_tar in files_to_tar:
702
731
  tar.add(file_to_tar, arcname=Path(file_to_tar).name)
703
732
 
704
- for file_to_move in output_files:
733
+ for file_to_move in output_files + reduced_event_files:
705
734
  source_file = Path(file_to_move)
706
735
  destination_file = directory_for_grid_upload / source_file.name
707
736
  if destination_file.exists():
@@ -717,7 +746,7 @@ class Simulator:
717
746
  return
718
747
 
719
748
  for model in self.array_models:
720
- files = self.get_file_list(file_type="output")
749
+ files = self.get_file_list(file_type="simtel_output")
721
750
  output_file = next((f for f in files if model.model_version in f), None)
722
751
  if output_file:
723
752
  self._logger.info(f"Validating metadata for {output_file}")
@@ -738,7 +767,6 @@ class Simulator:
738
767
  ----------
739
768
  corsika_log_files: list
740
769
  List containing the original CORSIKA log file path.
741
-
742
770
  """
743
771
  original_log = Path(corsika_log_files[0])
744
772
  # Find which model version the original log belongs to
@@ -759,15 +787,20 @@ class Simulator:
759
787
  original_version, model.model_version
760
788
  )
761
789
 
762
- with gzip.open(new_log, "wt") as new_file:
763
- new_file.write(
790
+ with gzip.open(new_log, "wt", encoding="utf-8") as new_file:
791
+ # Write the header to the new file
792
+ header = (
764
793
  f"###############################################################\n"
765
794
  f"Copy of CORSIKA log file from model version {original_version}.\n"
766
795
  f"Applicable also for {model.model_version} (same CORSIKA configuration,\n"
767
796
  f"different sim_telarray model versions in the same run).\n"
768
797
  f"###############################################################\n\n"
769
798
  )
770
- with gzip.open(original_log, "rt") as orig_file:
771
- shutil.copyfileobj(orig_file, new_file)
799
+ new_file.write(header)
800
+
801
+ # Copy the content of the original log file, ignoring invalid characters
802
+ with gzip.open(original_log, "rt", encoding="utf-8", errors="ignore") as orig_file:
803
+ for line in orig_file:
804
+ new_file.write(line)
772
805
 
773
806
  corsika_log_files.append(str(new_log))
@@ -102,7 +102,10 @@ def _validate_output_path_and_file(config, integration_file_tests):
102
102
 
103
103
  output_file_path = Path(output_path) / file_test["FILE"]
104
104
  _logger.info(f"Checking path: {output_file_path}")
105
- assert output_file_path.exists()
105
+ try:
106
+ assert output_file_path.exists()
107
+ except AssertionError as exc:
108
+ raise AssertionError(f"Output file {output_file_path} does not exist. ") from exc
106
109
 
107
110
  if "EXPECTED_OUTPUT" in file_test:
108
111
  assert assertions.check_output_from_sim_telarray(
simtools/utils/general.py CHANGED
@@ -178,7 +178,7 @@ def _collect_data_from_different_file_types(file, file_name, suffix, yaml_docume
178
178
  """Collect data from different file types."""
179
179
  if suffix == ".json":
180
180
  return json.load(file)
181
- if suffix == ".list":
181
+ if suffix in (".list", ".txt"):
182
182
  return [line.strip() for line in file.readlines()]
183
183
  if suffix in [".yml", ".yaml"]:
184
184
  return _collect_data_from_yaml_file(file, file_name, yaml_document)
@@ -324,10 +324,8 @@ def get_log_level_from_user(log_level):
324
324
  possible_levels = {
325
325
  "info": logging.INFO,
326
326
  "debug": logging.DEBUG,
327
- "warn": logging.WARNING,
328
327
  "warning": logging.WARNING,
329
328
  "error": logging.ERROR,
330
- "critical": logging.CRITICAL,
331
329
  }
332
330
  try:
333
331
  log_level_lower = log_level.lower()
simtools/utils/names.py CHANGED
@@ -12,6 +12,7 @@ Naming in simtools:
12
12
 
13
13
  """
14
14
 
15
+ import json
15
16
  import logging
16
17
  import re
17
18
  from functools import cache
@@ -22,7 +23,7 @@ import yaml
22
23
  from simtools.constants import (
23
24
  MODEL_PARAMETER_DESCRIPTION_METASCHEMA,
24
25
  MODEL_PARAMETER_SCHEMA_PATH,
25
- SCHEMA_PATH,
26
+ RESOURCE_PATH,
26
27
  )
27
28
 
28
29
  _logger = logging.getLogger(__name__)
@@ -59,10 +60,30 @@ def array_elements():
59
60
  dict
60
61
  Array elements.
61
62
  """
62
- with open(Path(SCHEMA_PATH) / "array_elements.yml", encoding="utf-8") as file:
63
+ # for efficiency reason, no functions from simtools.utils.general are used here
64
+ with open(Path(RESOURCE_PATH) / "array_elements.yml", encoding="utf-8") as file:
63
65
  return yaml.safe_load(file)["data"]
64
66
 
65
67
 
68
+ @cache
69
+ def array_element_common_identifiers():
70
+ """
71
+ Get array element IDs from CTAO common identifier.
72
+
73
+ Returns
74
+ -------
75
+ dict, dict
76
+ Dictionary mapping array element names to their IDs and vice versa.
77
+ """
78
+ # for efficiency reason, no functions from simtools.utils.general are used here
79
+ id_to_name = {}
80
+ with open(Path(RESOURCE_PATH) / "array-element-ids.json", encoding="utf-8") as file:
81
+ data = json.load(file)
82
+ id_to_name = {e["id"]: e["name"] for e in data["array_elements"]}
83
+ name_to_id = {e["name"]: e["id"] for e in data["array_elements"]}
84
+ return id_to_name, name_to_id
85
+
86
+
66
87
  @cache
67
88
  def simulation_software():
68
89
  """
@@ -415,6 +436,54 @@ def get_array_element_id_from_name(array_element_name):
415
436
  raise ValueError(f"Invalid name {array_element_name}") from exc
416
437
 
417
438
 
439
+ def get_common_identifier_from_array_element_name(array_element_name, default_return=None):
440
+ """
441
+ Get numerical common identifier from array element name as used by CTAO.
442
+
443
+ Common identifiers are numerical IDs used by the CTAO ACADA and DPPS systems.
444
+
445
+ Parameters
446
+ ----------
447
+ array_element_name: str
448
+ Array element name (e.g. LSTN-01)
449
+
450
+ Returns
451
+ -------
452
+ int
453
+ Common identifier.
454
+ """
455
+ _, name_to_id = array_element_common_identifiers()
456
+ try:
457
+ return name_to_id[array_element_name]
458
+ except KeyError as exc:
459
+ if default_return is not None:
460
+ return default_return
461
+ raise ValueError(f"Unknown array element name {array_element_name}") from exc
462
+
463
+
464
+ def get_array_element_name_from_common_identifier(common_identifier):
465
+ """
466
+ Get array element name from common identifier as used by CTAO.
467
+
468
+ Common identifiers are numerical IDs used by the CTAO ACADA and DPPS systems.
469
+
470
+ Parameters
471
+ ----------
472
+ common_identifier: int
473
+ Common identifier.
474
+
475
+ Returns
476
+ -------
477
+ str
478
+ Array element name.
479
+ """
480
+ id_to_name, _ = array_element_common_identifiers()
481
+ try:
482
+ return id_to_name[common_identifier]
483
+ except KeyError as exc:
484
+ raise ValueError(f"Unknown common identifier {common_identifier}") from exc
485
+
486
+
418
487
  def get_list_of_array_element_types(
419
488
  array_element_class="telescopes", site=None, observatory="CTAO"
420
489
  ):
@@ -658,7 +727,7 @@ def generate_file_name(
658
727
  """
659
728
  Generate a file name for output, config, or plotting.
660
729
 
661
- Used e.g., to generate camera-efficiency and ray-tracing output files.
730
+ Used e.g., to generate camera_efficiency and ray_tracing output files.
662
731
 
663
732
  Parameters
664
733
  ----------
@@ -690,10 +759,10 @@ def generate_file_name(
690
759
  str
691
760
  File name.
692
761
  """
693
- name = f"{file_type}-{site}-{telescope_model_name}"
694
- name += f"-d{source_distance:.1f}km" if source_distance is not None else ""
695
- name += f"-za{float(zenith_angle):.1f}deg"
696
- name += f"-off{off_axis_angle:.3f}deg" if off_axis_angle is not None else ""
762
+ name = f"{file_type}_{site}_{telescope_model_name}"
763
+ name += f"_d{source_distance:.1f}km" if source_distance is not None else ""
764
+ name += f"_za{float(zenith_angle):.1f}deg"
765
+ name += f"_off{off_axis_angle:.3f}deg" if off_axis_angle is not None else ""
697
766
  name += f"_azm{round(azimuth_angle):03}deg" if azimuth_angle is not None else ""
698
767
  name += f"_mirror{mirror_number}" if mirror_number is not None else ""
699
768
  name += f"_{label}" if label is not None else ""