gammasimtools 0.9.0__py3-none-any.whl → 0.11.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 (135) hide show
  1. {gammasimtools-0.9.0.dist-info → gammasimtools-0.11.0.dist-info}/METADATA +4 -2
  2. {gammasimtools-0.9.0.dist-info → gammasimtools-0.11.0.dist-info}/RECORD +133 -117
  3. {gammasimtools-0.9.0.dist-info → gammasimtools-0.11.0.dist-info}/WHEEL +1 -1
  4. {gammasimtools-0.9.0.dist-info → gammasimtools-0.11.0.dist-info}/entry_points.txt +6 -1
  5. simtools/_version.py +9 -4
  6. simtools/applications/calculate_trigger_rate.py +15 -38
  7. simtools/applications/convert_all_model_parameters_from_simtel.py +9 -29
  8. simtools/applications/convert_geo_coordinates_of_array_elements.py +47 -45
  9. simtools/applications/convert_model_parameter_from_simtel.py +2 -3
  10. simtools/applications/db_add_file_to_db.py +1 -3
  11. simtools/applications/db_add_simulation_model_from_repository_to_db.py +110 -0
  12. simtools/applications/db_add_value_from_json_to_db.py +1 -2
  13. simtools/applications/db_development_tools/write_array_elements_positions_to_repository.py +6 -6
  14. simtools/applications/db_get_file_from_db.py +11 -12
  15. simtools/applications/db_get_parameter_from_db.py +26 -35
  16. simtools/applications/derive_mirror_rnda.py +1 -2
  17. simtools/applications/derive_photon_electron_spectrum.py +99 -0
  18. simtools/applications/derive_psf_parameters.py +1 -0
  19. simtools/applications/docs_produce_array_element_report.py +71 -0
  20. simtools/applications/docs_produce_model_parameter_reports.py +63 -0
  21. simtools/applications/generate_array_config.py +17 -17
  22. simtools/applications/generate_corsika_histograms.py +2 -2
  23. simtools/applications/generate_regular_arrays.py +19 -17
  24. simtools/applications/generate_simtel_array_histograms.py +11 -48
  25. simtools/applications/production_derive_limits.py +95 -0
  26. simtools/applications/production_generate_simulation_config.py +37 -33
  27. simtools/applications/production_scale_events.py +4 -9
  28. simtools/applications/run_application.py +165 -0
  29. simtools/applications/simulate_light_emission.py +0 -4
  30. simtools/applications/simulate_prod.py +1 -1
  31. simtools/applications/simulate_prod_htcondor_generator.py +26 -26
  32. simtools/applications/submit_data_from_external.py +12 -4
  33. simtools/applications/submit_model_parameter_from_external.py +18 -11
  34. simtools/applications/validate_camera_efficiency.py +2 -2
  35. simtools/applications/validate_file_using_schema.py +26 -22
  36. simtools/camera/single_photon_electron_spectrum.py +168 -0
  37. simtools/configuration/commandline_parser.py +37 -1
  38. simtools/configuration/configurator.py +8 -10
  39. simtools/constants.py +10 -3
  40. simtools/corsika/corsika_config.py +19 -17
  41. simtools/corsika/corsika_histograms.py +5 -7
  42. simtools/corsika/corsika_histograms_visualize.py +2 -4
  43. simtools/data_model/data_reader.py +0 -3
  44. simtools/data_model/metadata_collector.py +20 -12
  45. simtools/data_model/metadata_model.py +8 -124
  46. simtools/data_model/model_data_writer.py +81 -75
  47. simtools/data_model/schema.py +220 -0
  48. simtools/data_model/validate_data.py +79 -68
  49. simtools/db/db_handler.py +350 -492
  50. simtools/db/db_model_upload.py +139 -0
  51. simtools/dependencies.py +112 -0
  52. simtools/io_operations/hdf5_handler.py +54 -24
  53. simtools/layout/array_layout.py +38 -32
  54. simtools/model/array_model.py +13 -7
  55. simtools/model/model_parameter.py +55 -54
  56. simtools/model/site_model.py +2 -2
  57. simtools/production_configuration/calculate_statistical_errors_grid_point.py +119 -145
  58. simtools/production_configuration/event_scaler.py +9 -35
  59. simtools/production_configuration/generate_simulation_config.py +9 -44
  60. simtools/production_configuration/interpolation_handler.py +9 -15
  61. simtools/production_configuration/limits_calculation.py +202 -0
  62. simtools/reporting/docs_read_parameters.py +310 -0
  63. simtools/runners/corsika_simtel_runner.py +4 -4
  64. simtools/schemas/{integration_tests_config.metaschema.yml → application_workflow.metaschema.yml} +61 -27
  65. simtools/schemas/array_elements.yml +8 -0
  66. simtools/schemas/input/MST_mirror_2f_measurements.schema.yml +39 -0
  67. simtools/schemas/input/single_pe_spectrum.schema.yml +38 -0
  68. simtools/schemas/model_parameter.metaschema.yml +103 -2
  69. simtools/schemas/model_parameter_and_data_schema.metaschema.yml +4 -1
  70. simtools/schemas/model_parameters/array_element_position_utm.schema.yml +1 -1
  71. simtools/schemas/model_parameters/array_window.schema.yml +37 -0
  72. simtools/schemas/model_parameters/asum_clipping.schema.yml +0 -4
  73. simtools/schemas/model_parameters/channels_per_chip.schema.yml +1 -1
  74. simtools/schemas/model_parameters/correct_nsb_spectrum_to_telescope_altitude.schema.yml +1 -1
  75. simtools/schemas/model_parameters/corsika_cherenkov_photon_bunch_size.schema.yml +2 -0
  76. simtools/schemas/model_parameters/corsika_cherenkov_photon_wavelength_range.schema.yml +2 -0
  77. simtools/schemas/model_parameters/corsika_first_interaction_height.schema.yml +2 -0
  78. simtools/schemas/model_parameters/corsika_iact_io_buffer.schema.yml +4 -2
  79. simtools/schemas/model_parameters/corsika_iact_max_bunches.schema.yml +2 -0
  80. simtools/schemas/model_parameters/corsika_iact_split_auto.schema.yml +2 -0
  81. simtools/schemas/model_parameters/corsika_longitudinal_shower_development.schema.yml +2 -0
  82. simtools/schemas/model_parameters/corsika_particle_kinetic_energy_cutoff.schema.yml +2 -0
  83. simtools/schemas/model_parameters/corsika_starting_grammage.schema.yml +2 -0
  84. simtools/schemas/model_parameters/dsum_clipping.schema.yml +0 -2
  85. simtools/schemas/model_parameters/dsum_ignore_below.schema.yml +0 -2
  86. simtools/schemas/model_parameters/dsum_offset.schema.yml +0 -2
  87. simtools/schemas/model_parameters/dsum_pedsub.schema.yml +0 -2
  88. simtools/schemas/model_parameters/dsum_pre_clipping.schema.yml +0 -2
  89. simtools/schemas/model_parameters/dsum_prescale.schema.yml +0 -2
  90. simtools/schemas/model_parameters/dsum_presum_max.schema.yml +0 -2
  91. simtools/schemas/model_parameters/dsum_presum_shift.schema.yml +0 -2
  92. simtools/schemas/model_parameters/dsum_shaping.schema.yml +0 -2
  93. simtools/schemas/model_parameters/dsum_shaping_renormalize.schema.yml +0 -2
  94. simtools/schemas/model_parameters/dsum_threshold.schema.yml +0 -2
  95. simtools/schemas/model_parameters/dsum_zero_clip.schema.yml +0 -2
  96. simtools/schemas/model_parameters/fadc_compensate_pedestal.schema.yml +1 -1
  97. simtools/schemas/model_parameters/fadc_lg_compensate_pedestal.schema.yml +1 -1
  98. simtools/schemas/model_parameters/fadc_noise.schema.yml +3 -3
  99. simtools/schemas/model_parameters/fake_mirror_list.schema.yml +33 -0
  100. simtools/schemas/model_parameters/iobuf_maximum.schema.yml +1 -1
  101. simtools/schemas/model_parameters/iobuf_output_maximum.schema.yml +1 -1
  102. simtools/schemas/model_parameters/laser_photons.schema.yml +2 -2
  103. simtools/schemas/model_parameters/lightguide_efficiency_vs_incidence_angle.schema.yml +1 -1
  104. simtools/schemas/model_parameters/lightguide_efficiency_vs_wavelength.schema.yml +1 -1
  105. simtools/schemas/model_parameters/min_photoelectrons.schema.yml +1 -1
  106. simtools/schemas/model_parameters/min_photons.schema.yml +1 -1
  107. simtools/schemas/model_parameters/random_generator.schema.yml +1 -1
  108. simtools/schemas/model_parameters/sampled_output.schema.yml +1 -1
  109. simtools/schemas/model_parameters/save_pe_with_amplitude.schema.yml +1 -1
  110. simtools/schemas/model_parameters/secondary_mirror_degraded_reflection.schema.yml +1 -1
  111. simtools/schemas/model_parameters/store_photoelectrons.schema.yml +1 -1
  112. simtools/schemas/model_parameters/tailcut_scale.schema.yml +1 -1
  113. simtools/schemas/production_configuration_metrics.schema.yml +68 -0
  114. simtools/schemas/production_tables.schema.yml +41 -0
  115. simtools/simtel/simtel_config_reader.py +1 -2
  116. simtools/simtel/simtel_config_writer.py +6 -8
  117. simtools/simtel/simtel_io_histogram.py +32 -68
  118. simtools/simtel/simtel_io_histograms.py +17 -34
  119. simtools/simtel/simulator_array.py +2 -1
  120. simtools/simtel/simulator_camera_efficiency.py +6 -3
  121. simtools/simtel/simulator_light_emission.py +5 -6
  122. simtools/simtel/simulator_ray_tracing.py +3 -4
  123. simtools/testing/configuration.py +2 -1
  124. simtools/testing/helpers.py +6 -13
  125. simtools/testing/validate_output.py +141 -47
  126. simtools/utils/general.py +114 -14
  127. simtools/utils/names.py +299 -157
  128. simtools/utils/value_conversion.py +17 -13
  129. simtools/version.py +2 -2
  130. simtools/visualization/legend_handlers.py +2 -0
  131. simtools/applications/db_add_model_parameters_from_repository_to_db.py +0 -176
  132. simtools/db/db_array_elements.py +0 -130
  133. {gammasimtools-0.9.0.dist-info → gammasimtools-0.11.0.dist-info}/LICENSE +0 -0
  134. {gammasimtools-0.9.0.dist-info → gammasimtools-0.11.0.dist-info}/top_level.txt +0 -0
  135. /simtools/{camera_efficiency.py → camera/camera_efficiency.py} +0 -0
simtools/utils/names.py CHANGED
@@ -1,20 +1,32 @@
1
- """Validation of names."""
1
+ """Name utilities for array elements, sites, and model parameters.
2
+
3
+ Naming in simtools:
4
+
5
+ * 'site': South or North
6
+ * 'array element': e.g., LSTN-01, MSTN-01, ...
7
+ * 'array element type': e.g., LSTN, MSTN, ...
8
+ * 'array element ID': e.g., 01, 02, ...
9
+ * 'array element design type': e.g., design, test
10
+ * 'instrument class key': e.g., telescope, camera, structure
11
+ * 'db collection': e.g., telescopes, sites, calibration_devices
12
+
13
+ """
2
14
 
3
15
  import logging
4
16
  import re
5
17
  from functools import cache
6
- from importlib.resources import files
7
18
  from pathlib import Path
8
19
 
9
20
  import yaml
10
21
 
22
+ from simtools.constants import MODEL_PARAMETER_SCHEMA_PATH, SCHEMA_PATH
23
+
11
24
  _logger = logging.getLogger(__name__)
12
25
 
13
26
  __all__ = [
14
27
  "generate_file_name",
15
28
  "get_array_element_type_from_name",
16
29
  "get_site_from_array_element_name",
17
- "layout_telescope_list_file_name",
18
30
  "sanitize_name",
19
31
  "simtel_config_file_name",
20
32
  "simtel_single_mirror_list_file_name",
@@ -23,68 +35,170 @@ __all__ = [
23
35
  "validate_site_name",
24
36
  ]
25
37
 
38
+ # Mapping of db collection names to class keys
39
+ db_collections_to_class_keys = {
40
+ "sites": ["Site"],
41
+ "telescopes": ["Structure", "Camera", "Telescope"],
42
+ "calibration_devices": ["Calibration"],
43
+ "configuration_sim_telarray": ["configuration_sim_telarray"],
44
+ "configuration_corsika": ["configuration_corsika"],
45
+ }
46
+
26
47
 
27
48
  @cache
28
49
  def array_elements():
29
50
  """
30
- Load array elements from reference files and keep in cache.
51
+ Get array elements and their properties.
31
52
 
32
53
  Returns
33
54
  -------
34
55
  dict
35
56
  Array elements.
36
57
  """
37
- with open(files("simtools") / "schemas/array_elements.yml", encoding="utf-8") as file:
58
+ with open(Path(SCHEMA_PATH) / "array_elements.yml", encoding="utf-8") as file:
38
59
  return yaml.safe_load(file)["data"]
39
60
 
40
61
 
41
62
  @cache
42
63
  def site_names():
43
64
  """
44
- Site names from reference file.
65
+ Get site names.
45
66
 
46
- The list of sites is derived from the sites listed in the model parameter
47
- schema files. Return a dictionary for compatibility with the validation routines.
67
+ The list of sites is derived from the sites listed in array element definition file.
68
+ Return a dictionary for compatibility with the validation '_validate_name' routine.
48
69
 
49
70
  Returns
50
71
  -------
51
72
  dict
52
73
  Site names.
53
74
  """
54
- _array_elements = array_elements()
55
- _sites = {entry["site"] for entry in _array_elements.values()}
56
- return {site: [site.lower()] for site in _sites}
75
+ return {
76
+ site: [site.lower()]
77
+ for entry in array_elements().values()
78
+ for site in (entry["site"] if isinstance(entry["site"], list) else [entry["site"]])
79
+ }
57
80
 
58
81
 
59
82
  @cache
60
- def load_model_parameters(class_key_list):
61
- model_parameters = {}
62
- schema_files = list(Path(files("simtools") / "schemas/model_parameters").rglob("*.yml"))
63
- for schema_file in schema_files:
83
+ def array_element_design_types(array_element_type):
84
+ """
85
+ Get array element site types (e.g., 'design' or 'flashcam').
86
+
87
+ Default values are ['design', 'test'].
88
+
89
+ Parameters
90
+ ----------
91
+ array_element_type
92
+ Array element type
93
+
94
+ Returns
95
+ -------
96
+ list
97
+ Array element design types.
98
+ """
99
+ default_types = ["design", "test"]
100
+ if array_element_type is None:
101
+ return default_types
102
+ try:
103
+ return array_elements()[array_element_type].get("design_types", default_types)
104
+ except KeyError as exc:
105
+ raise ValueError(f"Invalid name {array_element_type}") from exc
106
+
107
+
108
+ def is_design_type(array_element_name):
109
+ """
110
+ Check if array element is a design type (e.g., "MSTS-FlashCam" or "LSTN-design").
111
+
112
+ Parameters
113
+ ----------
114
+ array_element_name: str
115
+ Array element name.
116
+
117
+ Returns
118
+ -------
119
+ bool
120
+ True if array element is a design type.
121
+ """
122
+ return get_array_element_id_from_name(array_element_name) in array_element_design_types(
123
+ get_array_element_type_from_name(array_element_name)
124
+ )
125
+
126
+
127
+ @cache
128
+ def _load_model_parameters():
129
+ """
130
+ Get model parameters properties from schema files.
131
+
132
+ Returns
133
+ -------
134
+ dict
135
+ Model parameters definitions for all model parameters.
136
+ """
137
+ _parameters = {}
138
+ for schema_file in list(Path(MODEL_PARAMETER_SCHEMA_PATH).rglob("*.yml")):
64
139
  with open(schema_file, encoding="utf-8") as f:
65
140
  data = yaml.safe_load(f)
66
- try:
67
- if data["instrument"]["class"] in class_key_list:
68
- model_parameters[data["name"]] = data
69
- except KeyError:
70
- pass
71
- return model_parameters
141
+ _parameters[data["name"]] = data
142
+ return _parameters
143
+
144
+
145
+ def model_parameters(class_key_list=None):
146
+ """
147
+ Get model parameters and their properties for a given instrument class key.
148
+
149
+ Returns all model parameters if class_key is None.
150
+
151
+ Parameters
152
+ ----------
153
+ class_key: str, None
154
+ Class key (e.g., "telescope", "camera", structure").
155
+
156
+ Returns
157
+ -------
158
+ dict
159
+ Model parameters definitions.
160
+ """
161
+ _parameters = {}
162
+ if class_key_list is None:
163
+ return _load_model_parameters()
164
+ for key, value in _load_model_parameters().items():
165
+ if value.get("instrument", {}).get("class", "") in class_key_list:
166
+ _parameters[key] = value
167
+ return _parameters
72
168
 
73
169
 
74
170
  def site_parameters():
75
- return load_model_parameters(class_key_list="Site")
171
+ """Return site model parameters."""
172
+ return model_parameters(class_key_list=tuple(db_collections_to_class_keys["sites"]))
76
173
 
77
174
 
78
175
  def telescope_parameters():
79
- return load_model_parameters(class_key_list=("Structure", "Camera", "Telescope"))
176
+ """Return telescope model parameters."""
177
+ return model_parameters(class_key_list=tuple(db_collections_to_class_keys["telescopes"]))
178
+
80
179
 
180
+ def instrument_class_key_to_db_collection(class_name):
181
+ """Convert instrument class key to collection name."""
182
+ for collection, classes in db_collections_to_class_keys.items():
183
+ if class_name in classes:
184
+ return collection
185
+ raise ValueError(f"Class {class_name} not found")
81
186
 
82
- def validate_array_element_id_name(name):
187
+
188
+ def db_collection_to_instrument_class_key(collection_name="telescopes"):
189
+ """Return list of instrument classes for a given collection."""
190
+ try:
191
+ return db_collections_to_class_keys[collection_name]
192
+ except KeyError as exc:
193
+ raise KeyError(f"Invalid collection name {collection_name}") from exc
194
+
195
+
196
+ def validate_array_element_id_name(array_element_id, array_element_type=None):
83
197
  """
84
198
  Validate array element ID.
85
199
 
86
200
  Allowed IDs are
87
- - design (for design array elements or testing)
201
+ - design types (for design array elements or testing)
88
202
  - array element ID (e.g., 1, 5, 15)
89
203
  - test (for testing)
90
204
 
@@ -92,6 +206,8 @@ def validate_array_element_id_name(name):
92
206
  ----------
93
207
  name: str or int
94
208
  Array element ID name.
209
+ array_element_type: str
210
+ Array element type (e.g., LSTN, MSTN).
95
211
 
96
212
  Returns
97
213
  -------
@@ -103,23 +219,20 @@ def validate_array_element_id_name(name):
103
219
  ValueError
104
220
  If name is not valid.
105
221
  """
106
- if isinstance(name, int) or name.isdigit():
107
- return f"{int(name):02d}"
108
- if name.lower() in ("design", "test"):
109
- return str(name).lower()
222
+ if isinstance(array_element_id, int) or array_element_id.isdigit():
223
+ return f"{int(array_element_id):02d}"
224
+ if array_element_id in array_element_design_types(array_element_type):
225
+ return str(array_element_id)
226
+ raise ValueError(f"Invalid array element ID name {array_element_id}")
110
227
 
111
- msg = f"Invalid array element ID name {name}"
112
- _logger.error(msg)
113
- raise ValueError(msg)
114
228
 
115
-
116
- def validate_site_name(name):
229
+ def validate_site_name(site_name):
117
230
  """
118
231
  Validate site name.
119
232
 
120
233
  Parameters
121
234
  ----------
122
- name: str
235
+ site_name: str
123
236
  Site name.
124
237
 
125
238
  Returns
@@ -127,7 +240,7 @@ def validate_site_name(name):
127
240
  str
128
241
  Validated name.
129
242
  """
130
- return _validate_name(name, site_names())
243
+ return _validate_name(site_name, site_names())
131
244
 
132
245
 
133
246
  def _validate_name(name, all_names):
@@ -166,13 +279,13 @@ def _validate_name(name, all_names):
166
279
  raise ValueError(msg)
167
280
 
168
281
 
169
- def validate_array_element_type(name):
282
+ def validate_array_element_type(array_element_type):
170
283
  """
171
284
  Validate array element type (e.g., LSTN, MSTN).
172
285
 
173
286
  Parameters
174
287
  ----------
175
- name: str
288
+ array_element_type: str
176
289
  Array element type.
177
290
 
178
291
  Returns
@@ -180,16 +293,18 @@ def validate_array_element_type(name):
180
293
  str
181
294
  Validated name.
182
295
  """
183
- return _validate_name(name, array_elements())
296
+ return _validate_name(array_element_type, array_elements())
184
297
 
185
298
 
186
- def validate_array_element_name(name):
299
+ def validate_array_element_name(array_element_name):
187
300
  """
188
- Validate array element name (e.g., MSTN-design, MSTN-01).
301
+ Validate array element name (e.g., MSTx-NectarCam, MSTN-01).
302
+
303
+ Forgiving validation, is it allows also to give a site name (e.g., OBS-North).
189
304
 
190
305
  Parameters
191
306
  ----------
192
- name: str
307
+ array_element_name: str
193
308
  Array element name.
194
309
 
195
310
  Returns
@@ -198,20 +313,22 @@ def validate_array_element_name(name):
198
313
  Validated name.
199
314
  """
200
315
  try:
201
- _array_element_type, _array_element_id = name.split("-")
316
+ _array_element_type, _array_element_id = array_element_name.split("-")
202
317
  except ValueError as exc:
203
- msg = f"Invalid name {name}"
318
+ msg = f"Invalid name {array_element_name}"
204
319
  raise ValueError(msg) from exc
320
+ if _array_element_type == "OBS":
321
+ return validate_site_name(_array_element_id)
205
322
  return (
206
323
  _validate_name(_array_element_type, array_elements())
207
324
  + "-"
208
- + validate_array_element_id_name(_array_element_id)
325
+ + validate_array_element_id_name(_array_element_id, _array_element_type)
209
326
  )
210
327
 
211
328
 
212
- def get_array_element_name_from_type_site_id(array_element_type, site, array_element_id):
329
+ def generate_array_element_name_from_type_site_id(array_element_type, site, array_element_id):
213
330
  """
214
- Get array element name from type, site and ID.
331
+ Generate a new array element name from array element type, site, and array element ID.
215
332
 
216
333
  Parameters
217
334
  ----------
@@ -228,17 +345,17 @@ def get_array_element_name_from_type_site_id(array_element_type, site, array_ele
228
345
  Array element name.
229
346
  """
230
347
  _short_site = validate_site_name(site)[0]
231
- _val_id = validate_array_element_id_name(array_element_id)
348
+ _val_id = validate_array_element_id_name(array_element_id, array_element_type)
232
349
  return f"{array_element_type}{_short_site}-{_val_id}"
233
350
 
234
351
 
235
- def get_array_element_type_from_name(name):
352
+ def get_array_element_type_from_name(array_element_name):
236
353
  """
237
- Get array element type from name, e.g. "LSTN", "MSTN".
354
+ Get array element type from array element name (e.g "MSTN" from "MSTN-01").
238
355
 
239
356
  Parameters
240
357
  ----------
241
- name: str
358
+ array_element_name: str
242
359
  Array element name
243
360
 
244
361
  Returns
@@ -246,14 +363,36 @@ def get_array_element_type_from_name(name):
246
363
  str
247
364
  Array element type.
248
365
  """
249
- return _validate_name(name.split("-")[0], array_elements())
366
+ return _validate_name(array_element_name.split("-")[0], array_elements())
367
+
368
+
369
+ def get_array_element_id_from_name(array_element_name):
370
+ """
371
+ Get array element ID from array element name, (e.g. "01" from "MSTN-01").
372
+
373
+ Parameters
374
+ ----------
375
+ array_element_name: str
376
+ Array element name
377
+
378
+ Returns
379
+ -------
380
+ str
381
+ Array element ID.
382
+ """
383
+ try:
384
+ return validate_array_element_id_name(
385
+ array_element_name.split("-")[1], array_element_name.split("-")[0]
386
+ )
387
+ except IndexError as exc:
388
+ raise ValueError(f"Invalid name {array_element_name}") from exc
250
389
 
251
390
 
252
391
  def get_list_of_array_element_types(
253
392
  array_element_class="telescopes", site=None, observatory="CTAO"
254
393
  ):
255
394
  """
256
- Get list of array element types.
395
+ Get list of array element types (e.g., ["LSTN", "MSTN"] for the Northern site).
257
396
 
258
397
  Parameters
259
398
  ----------
@@ -267,135 +406,136 @@ def get_list_of_array_element_types(
267
406
  list
268
407
  List of array element types.
269
408
  """
270
- return [
271
- key
272
- for key, value in array_elements().items()
273
- if value["collection"] == array_element_class
274
- and (site is None or value["site"] == site)
275
- and (observatory is None or value["observatory"] == observatory)
276
- ]
409
+ return sorted(
410
+ [
411
+ key
412
+ for key, value in array_elements().items()
413
+ if value["collection"] == array_element_class
414
+ and (site is None or value["site"] == site)
415
+ and (observatory is None or value["observatory"] == observatory)
416
+ ]
417
+ )
277
418
 
278
419
 
279
- def get_site_from_array_element_name(name):
420
+ def get_site_from_array_element_name(array_element_name):
280
421
  """
281
- Get site name from array element name.
422
+ Get site name from array element name (e.g., "South" from "MSTS-01").
282
423
 
283
424
  Parameters
284
425
  ----------
285
- name: str
426
+ array_element_name: str
286
427
  Array element name.
287
428
 
288
429
  Returns
289
430
  -------
290
- str
291
- Site name (South or North).
431
+ str, list
432
+ Site name(s).
292
433
  """
293
- return array_elements()[get_array_element_type_from_name(name)]["site"]
434
+ try: # e.g. instrument is 'North' as given for the site parameters
435
+ return validate_site_name(array_element_name)
436
+ except ValueError: # e.g. instrument is 'LSTN' as given for the array element types
437
+ return array_elements()[get_array_element_type_from_name(array_element_name)]["site"]
294
438
 
295
439
 
296
- def get_collection_name_from_array_element_name(name):
440
+ def get_collection_name_from_array_element_name(array_element_name, array_elements_only=True):
297
441
  """
298
- Get collection name (e.g., telescopes, calibration_devices, sites) of array element from name.
442
+ Get collection name (e.g., telescopes, calibration_devices) of an array element from its name.
299
443
 
300
444
  Parameters
301
445
  ----------
302
- name: str
303
- Array element name.
446
+ array_element_name: str
447
+ Array element name (e.g. LSTN-01)
448
+ array_elements_only: bool
449
+ If True, only array elements are considered (e.g. "OBS-North" will raise a ValueError).
304
450
 
305
451
  Returns
306
452
  -------
307
453
  str
308
454
  Collection name .
455
+
456
+ Raises
457
+ ------
458
+ ValueError
459
+ If name is not a valid array element name.
309
460
  """
310
461
  try:
311
- return array_elements()[get_array_element_type_from_name(name)]["collection"]
462
+ return array_elements()[get_array_element_type_from_name(array_element_name)]["collection"]
463
+ except ValueError as exc:
464
+ if array_elements_only:
465
+ raise ValueError(f"Invalid array element name {array_element_name}") from exc
466
+ try:
467
+ if array_element_name.startswith("OBS") or validate_site_name(array_element_name):
468
+ return "sites"
312
469
  except ValueError:
313
470
  pass
314
- try:
315
- validate_site_name(name)
316
- return "sites"
317
- except ValueError as exc:
318
- raise ValueError(f"Invalid array element name {name}: {exc}") from exc
471
+ if array_element_name in {
472
+ "configuration_sim_telarray",
473
+ "configuration_corsika",
474
+ "Files",
475
+ "Dummy-Telescope",
476
+ }:
477
+ return array_element_name
478
+ raise ValueError(f"Invalid array element name {array_element_name}")
319
479
 
320
480
 
321
- def get_simulation_software_name_from_parameter_name(
322
- par_name,
323
- simulation_software="sim_telarray",
324
- search_telescope_parameters=True,
325
- search_site_parameters=True,
326
- ):
481
+ def get_collection_name_from_parameter_name(parameter_name):
327
482
  """
328
- Get the name used in the simulation software from the model parameter name.
329
-
330
- Name convention is expected to be defined in the schema.
331
- Returns the parameter name if no simulation software name is found.
483
+ Get the db collection name for a given parameter.
332
484
 
333
485
  Parameters
334
486
  ----------
335
- par_name: str
336
- Model parameter name.
337
- simulation_software: str
338
- Simulation software name.
339
- search_telescope_parameters: bool
340
- If True, telescope model parameters are included.
341
- search_site_parameters: bool
342
- If True, site model parameters are included.
487
+ parameter_name: str
488
+ Name of the parameter.
343
489
 
344
490
  Returns
345
491
  -------
346
492
  str
347
- Simtel parameter name.
348
- """
349
- _parameter_names = {}
350
- if search_telescope_parameters:
351
- _parameter_names.update(telescope_parameters())
352
- if search_site_parameters:
353
- _parameter_names.update(site_parameters())
354
-
355
- try:
356
- _parameter = _parameter_names[par_name]
357
- except KeyError as err:
358
- _logger.error(f"Parameter {par_name} without schema definition")
359
- raise err
493
+ Collection name.
360
494
 
495
+ Raises
496
+ ------
497
+ KeyError
498
+ If the parameter name is not found in the list of model parameters
499
+ """
500
+ _parameter_names = model_parameters()
361
501
  try:
362
- for software in _parameter.get("simulation_software", []):
363
- if software.get("name") == simulation_software:
364
- return software.get("internal_parameter_name", par_name)
365
- except TypeError: # catches cases for which 'simulation_software' is None
366
- pass
367
- return None
502
+ class_key = _parameter_names[parameter_name].get("instrument", {}).get("class")
503
+ except KeyError as exc:
504
+ raise KeyError(f"Parameter {parameter_name} without schema definition") from exc
505
+ return instrument_class_key_to_db_collection(class_key)
368
506
 
369
507
 
370
- def get_parameter_name_from_simtel_name(simtel_name):
508
+ def get_simulation_software_name_from_parameter_name(
509
+ parameter_name,
510
+ simulation_software="sim_telarray",
511
+ ):
371
512
  """
372
- Get the model parameter name from the simtel parameter name.
513
+ Get the name used in the given simulation software from the model parameter name.
373
514
 
374
- Assumes that both names are equal if not defined otherwise in names.py.
515
+ Name convention is expected to be defined in the model parameter schema.
516
+ Returns the parameter name if no simulation software name is found.
375
517
 
376
518
  Parameters
377
519
  ----------
378
- simtel_name: str
379
- Simtel parameter name.
520
+ parameter_name: str
521
+ Model parameter name.
522
+ simulation_software: str
523
+ Simulation software name.
380
524
 
381
525
  Returns
382
526
  -------
383
527
  str
384
- Model parameter name.
528
+ Simtel parameter name.
385
529
  """
386
- _parameters = {**telescope_parameters(), **site_parameters()}
530
+ _parameter = model_parameters().get(parameter_name)
531
+ if not _parameter:
532
+ raise KeyError(f"Parameter {parameter_name} without schema definition")
533
+
534
+ for software in _parameter.get("simulation_software", []):
535
+ if software.get("name") == simulation_software:
536
+ return software.get("internal_parameter_name", parameter_name)
387
537
 
388
- for par_name, par_info in _parameters.items():
389
- try:
390
- for software in par_info["simulation_software"]:
391
- if (
392
- software["name"] == "sim_telarray"
393
- and software["internal_parameter_name"] == simtel_name
394
- ):
395
- return par_name
396
- except (KeyError, TypeError): # catches cases for which 'simulation_software' is None
397
- pass
398
- return simtel_name
538
+ return None
399
539
 
400
540
 
401
541
  def simtel_config_file_name(
@@ -469,28 +609,6 @@ def simtel_single_mirror_list_file_name(
469
609
  return name
470
610
 
471
611
 
472
- def layout_telescope_list_file_name(name, label):
473
- """
474
- File name for files required at the RayTracing class.
475
-
476
- Parameters
477
- ----------
478
- name: str
479
- Name of the array.
480
- label: str
481
- Instance label.
482
-
483
- Returns
484
- -------
485
- str
486
- File name.
487
- """
488
- file_name = f"telescope_positions-{name}"
489
- file_name += f"_{label}" if label is not None else ""
490
- file_name += ".ecsv"
491
- return file_name
492
-
493
-
494
612
  def generate_file_name(
495
613
  file_type,
496
614
  suffix,
@@ -575,12 +693,10 @@ def sanitize_name(name):
575
693
  ValueError:
576
694
  if the string name can not be sanitized.
577
695
  """
578
- # Convert to lowercase
696
+ if name is None:
697
+ return None
579
698
  sanitized = name.lower()
580
-
581
- # Replace spaces with underscores
582
699
  sanitized = sanitized.replace(" ", "_")
583
-
584
700
  # Remove characters that are not alphanumerics or underscores
585
701
  sanitized = re.sub(r"\W|^(?=\d)", "_", sanitized)
586
702
  if not sanitized.isidentifier():
@@ -588,3 +704,29 @@ def sanitize_name(name):
588
704
  _logger.error(msg)
589
705
  raise ValueError(msg)
590
706
  return sanitized
707
+
708
+
709
+ def file_name_with_version(file_name, suffix):
710
+ """
711
+ Return a file name including a semantic version with the correct suffix.
712
+
713
+ Replaces 'Path.suffix()', which removes trailing numbers (and therefore version numbers).
714
+
715
+ Parameters
716
+ ----------
717
+ file_name: str
718
+ File name.
719
+ suffix: str
720
+ File suffix.
721
+
722
+ Returns
723
+ -------
724
+ Path
725
+ File name with version number.
726
+ """
727
+ if file_name is None or suffix is None:
728
+ return None
729
+ file_name = str(file_name)
730
+ if bool(re.search(r"\d+\.\d+\.\d+$", file_name)):
731
+ return Path(file_name + suffix)
732
+ return Path(file_name).with_suffix(suffix)