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
@@ -12,39 +12,22 @@ from simtools.testing import assertions
12
12
  _logger = logging.getLogger(__name__)
13
13
 
14
14
 
15
- def validate_all_tests(config, request, config_file_model_version):
16
- """
17
- Validate test output for all integration tests.
18
-
19
- Parameters
20
- ----------
21
- config: dict
22
- Integration test configuration dictionary.
23
- request: request
24
- Request object.
25
- config_file_model_version: str
26
- Model version from the configuration file.
27
-
28
- """
29
- if request.config.getoption("--model_version") is None:
30
- validate_application_output(config)
31
- elif config_file_model_version is not None:
32
- _from_command_line = request.config.getoption("--model_version")
33
- _from_config_file = config_file_model_version
34
- if _from_command_line == _from_config_file:
35
- validate_application_output(config)
36
-
37
-
38
- def validate_application_output(config):
15
+ def validate_application_output(config, from_command_line=None, from_config_file=None):
39
16
  """
40
17
  Validate application output against expected output.
41
18
 
42
19
  Expected output is defined in configuration file.
20
+ Some tests run only if the model version from the command line
21
+ equals the model version from the configuration file.
43
22
 
44
23
  Parameters
45
24
  ----------
46
25
  config: dict
47
26
  dictionary with the configuration for the application test.
27
+ from_command_line: str
28
+ Model version from the command line.
29
+ from_config_file: str
30
+ Model version from the configuration file.
48
31
 
49
32
  """
50
33
  if "INTEGRATION_TESTS" not in config:
@@ -52,24 +35,40 @@ def validate_application_output(config):
52
35
 
53
36
  for integration_test in config["INTEGRATION_TESTS"]:
54
37
  _logger.info(f"Testing application output: {integration_test}")
55
- if "REFERENCE_OUTPUT_FILE" in integration_test:
56
- _validate_reference_output_file(config, integration_test)
57
-
58
- if "TEST_OUTPUT_FILES" in integration_test:
59
- _validate_output_path_and_file(config, integration_test["TEST_OUTPUT_FILES"])
60
- if "OUTPUT_FILE" in integration_test:
61
- _validate_output_path_and_file(
62
- config,
63
- [{"PATH_DESCRIPTOR": "OUTPUT_PATH", "FILE": integration_test["OUTPUT_FILE"]}],
64
- )
65
38
 
66
- if "FILE_TYPE" in integration_test:
67
- assert assertions.assert_file_type(
68
- integration_test["FILE_TYPE"],
69
- Path(config["CONFIGURATION"]["OUTPUT_PATH"]).joinpath(
70
- config["CONFIGURATION"]["OUTPUT_FILE"]
71
- ),
72
- )
39
+ if from_command_line == from_config_file:
40
+ _validate_output_files(config, integration_test)
41
+
42
+ if "FILE_TYPE" in integration_test:
43
+ assert assertions.assert_file_type(
44
+ integration_test["FILE_TYPE"],
45
+ Path(config["CONFIGURATION"]["OUTPUT_PATH"]).joinpath(
46
+ config["CONFIGURATION"]["OUTPUT_FILE"]
47
+ ),
48
+ )
49
+ _test_simtel_cfg_files(config, integration_test, from_command_line, from_config_file)
50
+
51
+
52
+ def _validate_output_files(config, integration_test):
53
+ """Validate output files."""
54
+ if "REFERENCE_OUTPUT_FILE" in integration_test:
55
+ _validate_reference_output_file(config, integration_test)
56
+ if "TEST_OUTPUT_FILES" in integration_test:
57
+ _validate_output_path_and_file(config, integration_test["TEST_OUTPUT_FILES"])
58
+ if "OUTPUT_FILE" in integration_test:
59
+ _validate_output_path_and_file(
60
+ config,
61
+ [{"PATH_DESCRIPTOR": "OUTPUT_PATH", "FILE": integration_test["OUTPUT_FILE"]}],
62
+ )
63
+
64
+
65
+ def _test_simtel_cfg_files(config, integration_test, from_command_line, from_config_file):
66
+ """Test simtel cfg files."""
67
+ test_simtel_cfg_file = integration_test.get("TEST_SIMTEL_CFG_FILES", {}).get(
68
+ from_command_line or from_config_file
69
+ )
70
+ if test_simtel_cfg_file:
71
+ _validate_simtel_cfg_files(config, test_simtel_cfg_file)
73
72
 
74
73
 
75
74
  def _validate_reference_output_file(config, integration_test):
@@ -128,12 +127,13 @@ def compare_files(file1, file2, tolerance=1.0e-5, test_columns=None):
128
127
  """
129
128
  _file1_suffix = Path(file1).suffix
130
129
  _file2_suffix = Path(file2).suffix
130
+ _logger.info("Comparing files: %s and %s", file1, file2)
131
131
  if _file1_suffix != _file2_suffix:
132
132
  raise ValueError(f"File suffixes do not match: {file1} and {file2}")
133
133
  if _file1_suffix == ".ecsv":
134
134
  return compare_ecsv_files(file1, file2, tolerance, test_columns)
135
135
  if _file1_suffix in (".json", ".yaml", ".yml"):
136
- return compare_json_or_yaml_files(file1, file2)
136
+ return compare_json_or_yaml_files(file1, file2, tolerance)
137
137
 
138
138
  _logger.warning(f"Unknown file type for files: {file1} and {file2}")
139
139
  return False
@@ -144,6 +144,7 @@ def compare_json_or_yaml_files(file1, file2, tolerance=1.0e-2):
144
144
  Compare two json or yaml files.
145
145
 
146
146
  Take into account float comparison for sim_telarray string-embedded floats.
147
+ Allow differences in 'schema_version' field.
147
148
 
148
149
  Parameters
149
150
  ----------
@@ -162,17 +163,47 @@ def compare_json_or_yaml_files(file1, file2, tolerance=1.0e-2):
162
163
  """
163
164
  data1 = gen.collect_data_from_file(file1)
164
165
  data2 = gen.collect_data_from_file(file2)
166
+ data1.pop("schema_version", None)
167
+ data2.pop("schema_version", None)
165
168
 
166
169
  _logger.debug(f"Comparing json/yaml files: {file1} and {file2}")
167
170
 
168
171
  if data1 == data2:
169
172
  return True
170
173
 
171
- if "value" in data1 and isinstance(data1["value"], str):
172
- value_list_1 = gen.convert_string_to_list(data1.pop("value"))
173
- value_list_2 = gen.convert_string_to_list(data2.pop("value"))
174
- return np.allclose(value_list_1, value_list_2, rtol=tolerance)
175
- return data1 == data2
174
+ if data1.keys() != data2.keys():
175
+ _logger.error(f"Keys do not match: {data1.keys()} and {data2.keys()}")
176
+ return False
177
+ _comparison = all(
178
+ (
179
+ _compare_value_from_parameter_dict(data1[k], data2[k], tolerance)
180
+ if k == "value"
181
+ else data1[k] == data2[k]
182
+ )
183
+ for k in data1
184
+ )
185
+ if not _comparison:
186
+ _logger.error(f"Values do not match: {data1} and {data2} (tolerance: {tolerance})")
187
+ return _comparison
188
+
189
+
190
+ def _compare_value_from_parameter_dict(data1, data2, tolerance=1.0e-5):
191
+ """Compare value fields given in different formats."""
192
+
193
+ def _as_list(value):
194
+ if isinstance(value, str):
195
+ return gen.convert_string_to_list(value)
196
+ if isinstance(value, list | np.ndarray):
197
+ return value
198
+ return [value]
199
+
200
+ _logger.info(f"Comparing values: {data1} and {data2} (tolerance: {tolerance})")
201
+
202
+ _as_list_1 = _as_list(data1)
203
+ _as_list_2 = _as_list(data2)
204
+ if isinstance(_as_list_1, str):
205
+ return _as_list_1 == _as_list_2
206
+ return np.allclose(_as_list_1, _as_list_2, rtol=tolerance)
176
207
 
177
208
 
178
209
  def compare_ecsv_files(file1, file2, tolerance=1.0e-5, test_columns=None):
@@ -238,3 +269,66 @@ def compare_ecsv_files(file1, file2, tolerance=1.0e-5, test_columns=None):
238
269
  return False
239
270
 
240
271
  return True
272
+
273
+
274
+ def _validate_simtel_cfg_files(config, simtel_cfg_file):
275
+ """
276
+ Check sim_telarray configuration files and compare with reference file.
277
+
278
+ Note the finetuned naming of configuration files by simtools.
279
+
280
+ """
281
+ reference_file = Path(simtel_cfg_file)
282
+ test_file = Path(config["CONFIGURATION"]["OUTPUT_PATH"]) / reference_file.name.replace(
283
+ "_test", f"_{config['CONFIGURATION']['LABEL']}"
284
+ )
285
+ _logger.info(
286
+ f"Comparing simtel cfg files: {reference_file} and {test_file} "
287
+ f"for model version {config['CONFIGURATION']['MODEL_VERSION']}"
288
+ )
289
+ return _compare_simtel_cfg_files(reference_file, test_file)
290
+
291
+
292
+ def _compare_simtel_cfg_files(reference_file, test_file):
293
+ """
294
+ Compare two sim_telarray configuration files.
295
+
296
+ Line-by-line string comparison. Requires similar sequence of
297
+ parameters in the files. Ignore lines containing 'config_release'
298
+ (as it contains the simtools package version).
299
+
300
+ Parameters
301
+ ----------
302
+ reference_file: Path
303
+ Reference sim_telarray configuration file.
304
+ test_file: Path
305
+ Test sim_telarray configuration file.
306
+
307
+ Returns
308
+ -------
309
+ bool
310
+ True if the files are equal.
311
+
312
+ """
313
+ with open(reference_file, encoding="utf-8") as f1, open(test_file, encoding="utf-8") as f2:
314
+ reference_cfg = [line.rstrip() for line in f1 if line.strip()]
315
+ test_cfg = [line.rstrip() for line in f2 if line.strip()]
316
+
317
+ if len(reference_cfg) != len(test_cfg):
318
+ _logger.error(
319
+ f"Line counts differ: {reference_file} ({len(reference_cfg)} lines), "
320
+ f"{test_file} ({len(test_cfg)} lines)."
321
+ )
322
+ return False
323
+
324
+ for ref_line, test_line in zip(reference_cfg, test_cfg):
325
+ if any(ignore in ref_line for ignore in ("config_release", "Label")):
326
+ continue
327
+ if ref_line != test_line:
328
+ _logger.error(
329
+ f"Configuration files {reference_file} and {test_file} do not match: "
330
+ f"'{ref_line}' and '{test_line}'"
331
+ )
332
+ return False
333
+
334
+ return True
simtools/utils/general.py CHANGED
@@ -1,6 +1,7 @@
1
1
  """General functions useful across different parts of the code."""
2
2
 
3
3
  import copy
4
+ import datetime
4
5
  import json
5
6
  import logging
6
7
  import os
@@ -17,6 +18,7 @@ import yaml
17
18
  __all__ = [
18
19
  "InvalidConfigDataError",
19
20
  "change_dict_keys_case",
21
+ "clear_default_sim_telarray_cfg_directories",
20
22
  "collect_data_from_file",
21
23
  "collect_final_lines",
22
24
  "collect_kwargs",
@@ -81,6 +83,28 @@ def is_url(url):
81
83
  return False
82
84
 
83
85
 
86
+ def url_exists(url):
87
+ """
88
+ Check if a URL exists.
89
+
90
+ Parameters
91
+ ----------
92
+ url: str
93
+ URL to be checked.
94
+
95
+ Returns
96
+ -------
97
+ bool
98
+ True if URL exists.
99
+ """
100
+ try:
101
+ with urllib.request.urlopen(url, timeout=5) as response:
102
+ return response.status == 200
103
+ except (urllib.error.URLError, AttributeError) as e:
104
+ _logger.error(f"URL {url} does not exist: {e}")
105
+ return False
106
+
107
+
84
108
  def collect_data_from_http(url):
85
109
  """
86
110
  Download yaml or json file from url and return it contents as dict.
@@ -135,7 +159,7 @@ def collect_data_from_http(url):
135
159
  return data
136
160
 
137
161
 
138
- def collect_data_from_file(file_name):
162
+ def collect_data_from_file(file_name, yaml_document=None):
139
163
  """
140
164
  Collect data from file based on its extension.
141
165
 
@@ -143,6 +167,8 @@ def collect_data_from_file(file_name):
143
167
  ----------
144
168
  file_name: str
145
169
  Name of the yaml/json/ascii file.
170
+ yaml_document: None, int
171
+ Return list of yaml documents or a single document (for yaml files with several documents).
146
172
 
147
173
  Returns
148
174
  -------
@@ -152,21 +178,34 @@ def collect_data_from_file(file_name):
152
178
  if is_url(file_name):
153
179
  return collect_data_from_http(file_name)
154
180
 
181
+ suffix = Path(file_name).suffix.lower()
155
182
  with open(file_name, encoding="utf-8") as file:
156
- if Path(file_name).suffix.lower() == ".json":
183
+ if suffix == ".json":
157
184
  return json.load(file)
185
+ if suffix == ".list":
186
+ return [line.strip() for line in file.readlines()]
187
+ if suffix in [".yml", ".yaml"]:
188
+ return _collect_data_from_yaml_file(file, file_name, yaml_document)
189
+ return None
158
190
 
159
- if Path(file_name).suffix.lower() == ".list":
160
- lines = file.readlines()
161
- return [line.strip() for line in lines]
162
191
 
163
- try:
164
- return yaml.safe_load(file)
165
- except yaml.constructor.ConstructorError:
166
- return _load_yaml_using_astropy(file)
167
- except yaml.composer.ComposerError:
168
- file.seek(0)
169
- return list(yaml.safe_load_all(file))
192
+ def _collect_data_from_yaml_file(file, file_name, yaml_document):
193
+ """Collect data from a yaml file."""
194
+ try:
195
+ return yaml.safe_load(file)
196
+ except yaml.constructor.ConstructorError:
197
+ return _load_yaml_using_astropy(file)
198
+ except yaml.composer.ComposerError:
199
+ pass
200
+ file.seek(0)
201
+ if yaml_document is None:
202
+ return list(yaml.safe_load_all(file))
203
+ try:
204
+ return list(yaml.safe_load_all(file))[yaml_document]
205
+ except IndexError as exc:
206
+ raise InvalidConfigDataError(
207
+ f"YAML file {file_name} does not contain {yaml_document} documents."
208
+ ) from exc
170
209
 
171
210
 
172
211
  def collect_kwargs(label, in_kwargs):
@@ -705,7 +744,7 @@ def convert_list_to_string(data, comma_separated=False, shorten_list=False, coll
705
744
  return " ".join(str(item) for item in data)
706
745
 
707
746
 
708
- def convert_string_to_list(data_string, is_float=True):
747
+ def convert_string_to_list(data_string, is_float=True, force_comma_separation=False):
709
748
  """
710
749
  Convert string (as used e.g. in sim_telarray) to list.
711
750
 
@@ -715,6 +754,10 @@ def convert_string_to_list(data_string, is_float=True):
715
754
  ----------
716
755
  data_string: object
717
756
  String to be converted
757
+ is_float: bool
758
+ If True, convert to float, otherwise to int.
759
+ force_comma_separation: bool
760
+ If True, force comma separation.
718
761
 
719
762
  Returns
720
763
  -------
@@ -732,7 +775,7 @@ def convert_string_to_list(data_string, is_float=True):
732
775
  if "," in data_string:
733
776
  result = data_string.split(",")
734
777
  return [item.strip() for item in result]
735
- if " " in data_string:
778
+ if " " in data_string and not force_comma_separation:
736
779
  return data_string.split()
737
780
  return data_string
738
781
 
@@ -832,3 +875,60 @@ def convert_keys_in_dict_to_lowercase(data):
832
875
  if isinstance(data, list):
833
876
  return [convert_keys_in_dict_to_lowercase(i) for i in data]
834
877
  return data
878
+
879
+
880
+ def clear_default_sim_telarray_cfg_directories(command):
881
+ """Prefix the command to clear default sim_telarray configuration directories.
882
+
883
+ Parameters
884
+ ----------
885
+ command: str
886
+ Command to be prefixed.
887
+
888
+ Returns
889
+ -------
890
+ str
891
+ Prefixed command.
892
+
893
+ """
894
+ return f"SIM_TELARRAY_CONFIG_PATH='' {command}"
895
+
896
+
897
+ def get_list_of_files_from_command_line(file_names, suffix_list):
898
+ """
899
+ Get a list of files from the command line.
900
+
901
+ Files can be given as a list of file names or as a text file containing the list of files.
902
+ The list of suffixes restrict the files types to be returned. Note that a file list must
903
+ have a different suffix than those in the suffix list.
904
+
905
+ Parameters
906
+ ----------
907
+ file_names: list
908
+ List of file names to be checked.
909
+ suffix_list: list
910
+ List of suffixes to be checked.
911
+
912
+ Returns
913
+ -------
914
+ list
915
+ List of files with the given suffixes.
916
+ """
917
+ _files = []
918
+ for one_file in file_names:
919
+ path = Path(one_file)
920
+ try:
921
+ if path.suffix in suffix_list:
922
+ _files.append(one_file)
923
+ elif len(file_names) == 1:
924
+ with open(one_file, encoding="utf-8") as file:
925
+ _files.extend(line.strip() for line in file)
926
+ except FileNotFoundError as exc:
927
+ _logger.error(f"{one_file} is not a file.")
928
+ raise FileNotFoundError from exc
929
+ return _files
930
+
931
+
932
+ def now_date_time_in_isoformat():
933
+ """Return date and time in isoformat and second accuracy."""
934
+ return datetime.datetime.now(datetime.UTC).isoformat(timespec="seconds")