gammasimtools 0.9.0__py3-none-any.whl → 0.10.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 (96) hide show
  1. {gammasimtools-0.9.0.dist-info → gammasimtools-0.10.0.dist-info}/METADATA +2 -2
  2. {gammasimtools-0.9.0.dist-info → gammasimtools-0.10.0.dist-info}/RECORD +94 -85
  3. {gammasimtools-0.9.0.dist-info → gammasimtools-0.10.0.dist-info}/entry_points.txt +2 -1
  4. simtools/_version.py +2 -2
  5. simtools/applications/calculate_trigger_rate.py +15 -38
  6. simtools/applications/convert_all_model_parameters_from_simtel.py +9 -28
  7. simtools/applications/convert_geo_coordinates_of_array_elements.py +47 -45
  8. simtools/applications/convert_model_parameter_from_simtel.py +2 -2
  9. simtools/applications/db_add_file_to_db.py +1 -2
  10. simtools/applications/db_add_simulation_model_from_repository_to_db.py +110 -0
  11. simtools/applications/db_add_value_from_json_to_db.py +1 -2
  12. simtools/applications/db_development_tools/write_array_elements_positions_to_repository.py +6 -6
  13. simtools/applications/db_get_file_from_db.py +11 -12
  14. simtools/applications/db_get_parameter_from_db.py +44 -32
  15. simtools/applications/derive_photon_electron_spectrum.py +99 -0
  16. simtools/applications/generate_array_config.py +17 -17
  17. simtools/applications/generate_regular_arrays.py +15 -15
  18. simtools/applications/generate_simtel_array_histograms.py +11 -48
  19. simtools/applications/production_generate_simulation_config.py +25 -7
  20. simtools/applications/production_scale_events.py +2 -2
  21. simtools/applications/simulate_prod.py +1 -1
  22. simtools/applications/simulate_prod_htcondor_generator.py +26 -26
  23. simtools/applications/submit_data_from_external.py +12 -4
  24. simtools/applications/submit_model_parameter_from_external.py +8 -6
  25. simtools/applications/validate_camera_efficiency.py +2 -2
  26. simtools/applications/validate_file_using_schema.py +23 -19
  27. simtools/camera/single_photon_electron_spectrum.py +168 -0
  28. simtools/configuration/commandline_parser.py +8 -1
  29. simtools/constants.py +10 -3
  30. simtools/corsika/corsika_config.py +8 -7
  31. simtools/corsika/corsika_histograms.py +1 -1
  32. simtools/data_model/data_reader.py +0 -3
  33. simtools/data_model/metadata_collector.py +3 -4
  34. simtools/data_model/metadata_model.py +8 -124
  35. simtools/data_model/model_data_writer.py +17 -63
  36. simtools/data_model/schema.py +213 -0
  37. simtools/data_model/validate_data.py +9 -44
  38. simtools/db/db_handler.py +323 -495
  39. simtools/db/db_model_upload.py +139 -0
  40. simtools/io_operations/hdf5_handler.py +54 -24
  41. simtools/layout/array_layout.py +33 -28
  42. simtools/model/array_model.py +13 -7
  43. simtools/model/model_parameter.py +22 -54
  44. simtools/model/site_model.py +2 -2
  45. simtools/production_configuration/calculate_statistical_errors_grid_point.py +119 -144
  46. simtools/production_configuration/event_scaler.py +7 -17
  47. simtools/production_configuration/generate_simulation_config.py +5 -32
  48. simtools/production_configuration/interpolation_handler.py +8 -11
  49. simtools/runners/corsika_simtel_runner.py +3 -1
  50. simtools/schemas/input/MST_mirror_2f_measurements.schema.yml +39 -0
  51. simtools/schemas/input/single_pe_spectrum.schema.yml +38 -0
  52. simtools/schemas/integration_tests_config.metaschema.yml +10 -0
  53. simtools/schemas/model_parameter.metaschema.yml +7 -2
  54. simtools/schemas/model_parameter_and_data_schema.metaschema.yml +2 -0
  55. simtools/schemas/model_parameters/array_element_position_utm.schema.yml +1 -1
  56. simtools/schemas/model_parameters/array_window.schema.yml +37 -0
  57. simtools/schemas/model_parameters/asum_clipping.schema.yml +0 -4
  58. simtools/schemas/model_parameters/channels_per_chip.schema.yml +1 -1
  59. simtools/schemas/model_parameters/corsika_iact_io_buffer.schema.yml +2 -2
  60. simtools/schemas/model_parameters/dsum_clipping.schema.yml +0 -2
  61. simtools/schemas/model_parameters/dsum_ignore_below.schema.yml +0 -2
  62. simtools/schemas/model_parameters/dsum_offset.schema.yml +0 -2
  63. simtools/schemas/model_parameters/dsum_pedsub.schema.yml +0 -2
  64. simtools/schemas/model_parameters/dsum_pre_clipping.schema.yml +0 -2
  65. simtools/schemas/model_parameters/dsum_prescale.schema.yml +0 -2
  66. simtools/schemas/model_parameters/dsum_presum_max.schema.yml +0 -2
  67. simtools/schemas/model_parameters/dsum_presum_shift.schema.yml +0 -2
  68. simtools/schemas/model_parameters/dsum_shaping.schema.yml +0 -2
  69. simtools/schemas/model_parameters/dsum_shaping_renormalize.schema.yml +0 -2
  70. simtools/schemas/model_parameters/dsum_threshold.schema.yml +0 -2
  71. simtools/schemas/model_parameters/dsum_zero_clip.schema.yml +0 -2
  72. simtools/schemas/model_parameters/fadc_compensate_pedestal.schema.yml +1 -1
  73. simtools/schemas/model_parameters/fadc_lg_compensate_pedestal.schema.yml +1 -1
  74. simtools/schemas/model_parameters/fadc_noise.schema.yml +3 -3
  75. simtools/schemas/model_parameters/fake_mirror_list.schema.yml +33 -0
  76. simtools/schemas/model_parameters/laser_photons.schema.yml +2 -2
  77. simtools/schemas/model_parameters/secondary_mirror_degraded_reflection.schema.yml +1 -1
  78. simtools/schemas/production_configuration_metrics.schema.yml +68 -0
  79. simtools/schemas/production_tables.schema.yml +41 -0
  80. simtools/simtel/simtel_config_writer.py +5 -6
  81. simtools/simtel/simtel_io_histogram.py +32 -67
  82. simtools/simtel/simtel_io_histograms.py +15 -30
  83. simtools/simtel/simulator_array.py +2 -1
  84. simtools/simtel/simulator_camera_efficiency.py +5 -0
  85. simtools/simtel/simulator_light_emission.py +3 -1
  86. simtools/simtel/simulator_ray_tracing.py +2 -1
  87. simtools/testing/helpers.py +6 -13
  88. simtools/testing/validate_output.py +131 -47
  89. simtools/utils/general.py +102 -12
  90. simtools/utils/names.py +24 -20
  91. simtools/applications/db_add_model_parameters_from_repository_to_db.py +0 -176
  92. simtools/db/db_array_elements.py +0 -130
  93. {gammasimtools-0.9.0.dist-info → gammasimtools-0.10.0.dist-info}/LICENSE +0 -0
  94. {gammasimtools-0.9.0.dist-info → gammasimtools-0.10.0.dist-info}/WHEEL +0 -0
  95. {gammasimtools-0.9.0.dist-info → gammasimtools-0.10.0.dist-info}/top_level.txt +0 -0
  96. /simtools/{camera_efficiency.py → camera/camera_efficiency.py} +0 -0
@@ -1,10 +1,4 @@
1
- """
2
- Reads the content of multiples files from sim_telarray.
3
-
4
- Reads the content of either multiple histogram (.hdata, or .hdata.zst) or
5
- simtel_array output files (.simtel or .simtel.zst). The module is built on top of the
6
- simtel_io_histogram module and uses its class (SimtelIOHistogram) to read the individual files.
7
- """
1
+ """Reads the content of multiples files from sim_telarray."""
8
2
 
9
3
  import copy
10
4
  import logging
@@ -192,8 +186,9 @@ class SimtelIOHistograms:
192
186
  energy_range=self.energy_range,
193
187
  view_cone=self.view_cone,
194
188
  )
195
- stacked_num_simulated_events += simtel_hist_instance.total_num_simulated_events
196
- stacked_num_triggered_events += simtel_hist_instance.total_num_triggered_events
189
+ _simulated, _triggered = simtel_hist_instance.total_number_of_events
190
+ stacked_num_simulated_events += _simulated
191
+ stacked_num_triggered_events += _triggered
197
192
  return stacked_num_simulated_events, stacked_num_triggered_events
198
193
 
199
194
  def _rates_for_stacked_files(self):
@@ -245,7 +240,7 @@ class SimtelIOHistograms:
245
240
  logging.info(
246
241
  f"System trigger event rate for stacked files: "
247
242
  # pylint: disable=E1101
248
- f"{triggered_event_rate.value:.4e} \u00B1 "
243
+ f"{triggered_event_rate.value:.4e} \u00b1 "
249
244
  # pylint: disable=E1101
250
245
  f"{triggered_event_rate_uncertainty.value:.4e} Hz"
251
246
  )
@@ -282,24 +277,17 @@ class SimtelIOHistograms:
282
277
  if print_info:
283
278
  simtel_hist_instance.print_info()
284
279
 
280
+ _simulated_events, _triggered_events = simtel_hist_instance.total_number_of_events
285
281
  logging.info(f"Histogram {i_file + 1}:")
286
- logging.info(
287
- "Total number of simulated events: "
288
- f"{simtel_hist_instance.total_num_simulated_events} events"
289
- )
290
- logging.info(
291
- "Total number of triggered events: "
292
- f"{simtel_hist_instance.total_num_triggered_events} events"
293
- )
282
+ logging.info(f"Total number of simulated events: {_simulated_events} events")
283
+ logging.info(f"Total number of triggered events: {_triggered_events} events")
294
284
 
295
- obs_time = simtel_hist_instance.estimate_observation_time(
296
- simtel_hist_instance.total_num_simulated_events
297
- )
285
+ obs_time = simtel_hist_instance.estimate_observation_time(_simulated_events)
298
286
  logging.info(
299
287
  f"Estimated equivalent observation time corresponding to the number of "
300
288
  f"events simulated: {obs_time.value} s"
301
289
  )
302
- sim_event_rate = simtel_hist_instance.total_num_simulated_events / obs_time
290
+ sim_event_rate = _simulated_events / obs_time
303
291
  sim_event_rates.append(sim_event_rate)
304
292
  logging.info(f"Simulated event rate: {sim_event_rate.value:.4e} Hz")
305
293
 
@@ -310,7 +298,7 @@ class SimtelIOHistograms:
310
298
  logging.info(
311
299
  f"System trigger event rate: "
312
300
  # pylint: disable=E1101
313
- f"{triggered_event_rate.value:.4e} \u00B1 "
301
+ f"{triggered_event_rate.value:.4e} \u00b1 "
314
302
  # pylint: disable=E1101
315
303
  f"{triggered_event_rate_uncertainty.value:.4e} Hz"
316
304
  )
@@ -515,15 +503,16 @@ class SimtelIOHistograms:
515
503
 
516
504
  def export_histograms(self, hdf5_file_name, overwrite=False):
517
505
  """
518
- Export the histograms to hdf5 files.
506
+ Export sim_telarray histograms to hdf5 files.
519
507
 
520
508
  Parameters
521
509
  ----------
522
510
  hdf5_file_name: str
523
511
  Name of the file to be saved with the hdf5 tables.
524
512
  overwrite: bool
525
- If True overwrites the histograms already saved in the hdf5 file.
513
+ If True overwrites histograms already saved in the hdf5 file.
526
514
  """
515
+ self._logger.info(f"Exporting histograms to {hdf5_file_name}.")
527
516
  for histogram in self.combined_hists:
528
517
  x_bin_edges_list = np.linspace(
529
518
  histogram["lower_x"],
@@ -556,14 +545,10 @@ class SimtelIOHistograms:
556
545
  f"Writing histogram with name {self._meta_dict['Title']} to {hdf5_file_name}."
557
546
  )
558
547
  # overwrite takes precedence over append
559
- if overwrite is True:
560
- append = False
561
- else:
562
- append = True
563
548
  write_table(
564
549
  table,
565
550
  hdf5_file_name,
566
551
  f"/{self._meta_dict['Title']}",
567
- append=append,
552
+ append=not overwrite,
568
553
  overwrite=overwrite,
569
554
  )
@@ -4,6 +4,7 @@ import logging
4
4
 
5
5
  from simtools.io_operations import io_handler
6
6
  from simtools.runners.simtel_runner import InvalidOutputFileError, SimtelRunner
7
+ from simtools.utils.general import clear_default_sim_telarray_cfg_directories
7
8
 
8
9
  __all__ = ["SimulatorArray"]
9
10
 
@@ -88,7 +89,7 @@ class SimulatorArray(SimtelRunner):
88
89
  command += f" {input_file}"
89
90
  command += f" > {self._log_file} 2>&1 || exit"
90
91
 
91
- return command
92
+ return clear_default_sim_telarray_cfg_directories(command)
92
93
 
93
94
  def _check_run_result(self, run_number=None):
94
95
  """
@@ -135,6 +135,8 @@ class SimulatorCameraEfficiency(SimtelRunner):
135
135
  command += f" {pixel_shape_cmd} {pixel_diameter}"
136
136
  if mirror_class == 0:
137
137
  command += f" -fmir {self._telescope_model.get_parameter_value('mirror_list')}"
138
+ if mirror_class == 2:
139
+ command += f" -fmir {self._telescope_model.get_parameter_value('fake_mirror_list')}"
138
140
  command += f" -fref {mirror_reflectivity}"
139
141
  if mirror_class == 2:
140
142
  command += " -m2"
@@ -155,6 +157,9 @@ class SimulatorCameraEfficiency(SimtelRunner):
155
157
  command += f" {self._telescope_model.get_parameter_value('atmospheric_profile')}"
156
158
  command += f" {self.zenith_angle}"
157
159
 
160
+ # Remove the default sim_telarray configuration directories
161
+ command = general.clear_default_sim_telarray_cfg_directories(command)
162
+
158
163
  return (
159
164
  f"cd {self._simtel_path.joinpath('sim_telarray')} && {command}",
160
165
  self._file_simtel,
@@ -9,6 +9,7 @@ import numpy as np
9
9
 
10
10
  from simtools.io_operations import io_handler
11
11
  from simtools.runners.simtel_runner import SimtelRunner
12
+ from simtools.utils.general import clear_default_sim_telarray_cfg_directories
12
13
 
13
14
  __all__ = ["SimulatorLightEmission"]
14
15
 
@@ -360,7 +361,8 @@ class SimulatorLightEmission(SimtelRunner):
360
361
  f"{self.le_application[0]}_{self.le_application[1]}.ctsim.hdata\n",
361
362
  )
362
363
 
363
- return command
364
+ # Remove the default sim_telarray configuration directories
365
+ return clear_default_sim_telarray_cfg_directories(command)
364
366
 
365
367
  def _remove_line_from_config(self, file_path, line_prefix):
366
368
  """
@@ -8,6 +8,7 @@ import astropy.units as u
8
8
  from simtools.io_operations import io_handler
9
9
  from simtools.runners.simtel_runner import SimtelRunner
10
10
  from simtools.utils import names
11
+ from simtools.utils.general import clear_default_sim_telarray_cfg_directories
11
12
 
12
13
  __all__ = ["SimulatorRayTracing"]
13
14
 
@@ -192,7 +193,7 @@ class SimulatorRayTracing(SimtelRunner):
192
193
  command += super().get_config_option("mirror_align_random_vertical", "0.,28.,0.,0.")
193
194
  command += " " + str(self._corsika_file)
194
195
 
195
- return command, self._log_file, self._log_file
196
+ return clear_default_sim_telarray_cfg_directories(command), self._log_file, self._log_file
196
197
 
197
198
  def _check_run_result(self, run_number=None): # pylint: disable=unused-argument
198
199
  """
@@ -6,19 +6,12 @@ from pathlib import Path
6
6
 
7
7
  def skip_camera_efficiency(config):
8
8
  """Skip camera efficiency tests if the old version of testeff is used."""
9
- if "camera-efficiency" in config["APPLICATION"]:
10
- if not _new_testeff_version():
11
- return (
12
- "Any applications calling the old version of testeff are skipped "
13
- "due to a limitation of the old testeff not allowing to specify "
14
- "the include directory. Please update your sim_telarray tarball."
15
- )
16
- full_test_name = f"{config['APPLICATION']}_{config['TEST_NAME']}"
17
- if "simtools-validate-camera-efficiency_SSTS" == full_test_name:
18
- return (
19
- "The test simtools-validate-camera-efficiency_SSTS is skipped "
20
- "since the fake SST mirrors are not yet implemented (#1155)"
21
- )
9
+ if "camera-efficiency" in config["APPLICATION"] and not _new_testeff_version():
10
+ return (
11
+ "Any applications calling the old version of testeff are skipped "
12
+ "due to a limitation of the old testeff not allowing to specify "
13
+ "the include directory. Please update your sim_telarray tarball."
14
+ )
22
15
  return None
23
16
 
24
17
 
@@ -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,37 @@ 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
+ if "REFERENCE_OUTPUT_FILE" in integration_test:
41
+ _validate_reference_output_file(config, integration_test)
42
+
43
+ if "TEST_OUTPUT_FILES" in integration_test:
44
+ _validate_output_path_and_file(config, integration_test["TEST_OUTPUT_FILES"])
45
+
46
+ if "OUTPUT_FILE" in integration_test:
47
+ _validate_output_path_and_file(
48
+ config,
49
+ [{"PATH_DESCRIPTOR": "OUTPUT_PATH", "FILE": integration_test["OUTPUT_FILE"]}],
50
+ )
51
+
52
+ if "FILE_TYPE" in integration_test:
53
+ assert assertions.assert_file_type(
54
+ integration_test["FILE_TYPE"],
55
+ Path(config["CONFIGURATION"]["OUTPUT_PATH"]).joinpath(
56
+ config["CONFIGURATION"]["OUTPUT_FILE"]
57
+ ),
58
+ )
59
+ _test_simtel_cfg_files(config, integration_test, from_command_line, from_config_file)
60
+
61
+
62
+ def _test_simtel_cfg_files(config, integration_test, from_command_line, from_config_file):
63
+ """Test simtel cfg files."""
64
+ test_simtel_cfg_file = integration_test.get("TEST_SIMTEL_CFG_FILES", {}).get(
65
+ from_command_line or from_config_file
66
+ )
67
+ if test_simtel_cfg_file:
68
+ _validate_simtel_cfg_files(config, test_simtel_cfg_file)
73
69
 
74
70
 
75
71
  def _validate_reference_output_file(config, integration_test):
@@ -128,12 +124,13 @@ def compare_files(file1, file2, tolerance=1.0e-5, test_columns=None):
128
124
  """
129
125
  _file1_suffix = Path(file1).suffix
130
126
  _file2_suffix = Path(file2).suffix
127
+ _logger.info("Comparing files: %s and %s", file1, file2)
131
128
  if _file1_suffix != _file2_suffix:
132
129
  raise ValueError(f"File suffixes do not match: {file1} and {file2}")
133
130
  if _file1_suffix == ".ecsv":
134
131
  return compare_ecsv_files(file1, file2, tolerance, test_columns)
135
132
  if _file1_suffix in (".json", ".yaml", ".yml"):
136
- return compare_json_or_yaml_files(file1, file2)
133
+ return compare_json_or_yaml_files(file1, file2, tolerance)
137
134
 
138
135
  _logger.warning(f"Unknown file type for files: {file1} and {file2}")
139
136
  return False
@@ -168,11 +165,35 @@ def compare_json_or_yaml_files(file1, file2, tolerance=1.0e-2):
168
165
  if data1 == data2:
169
166
  return True
170
167
 
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
168
+ if data1.keys() != data2.keys():
169
+ _logger.error(f"Keys do not match: {data1.keys()} and {data2.keys()}")
170
+ return False
171
+ _comparison = all(
172
+ (
173
+ _compare_value_from_parameter_dict(data1[k], data2[k], tolerance)
174
+ if k == "value"
175
+ else data1[k] == data2[k]
176
+ )
177
+ for k in data1
178
+ )
179
+ if not _comparison:
180
+ _logger.error(f"Values do not match: {data1} and {data2} (tolerance: {tolerance})")
181
+ return _comparison
182
+
183
+
184
+ def _compare_value_from_parameter_dict(data1, data2, tolerance):
185
+ """Compare value fields given in different formats."""
186
+
187
+ def _as_list(value):
188
+ if isinstance(value, str):
189
+ return gen.convert_string_to_list(value)
190
+ if isinstance(value, list | np.ndarray):
191
+ return value
192
+ return [value]
193
+
194
+ _logger.info(f"Comparing values: {data1} and {data2} (tolerance: {tolerance})")
195
+
196
+ return np.allclose(_as_list(data1), _as_list(data2), rtol=tolerance)
176
197
 
177
198
 
178
199
  def compare_ecsv_files(file1, file2, tolerance=1.0e-5, test_columns=None):
@@ -238,3 +259,66 @@ def compare_ecsv_files(file1, file2, tolerance=1.0e-5, test_columns=None):
238
259
  return False
239
260
 
240
261
  return True
262
+
263
+
264
+ def _validate_simtel_cfg_files(config, simtel_cfg_file):
265
+ """
266
+ Check sim_telarray configuration files and compare with reference file.
267
+
268
+ Note the finetuned naming of configuration files by simtools.
269
+
270
+ """
271
+ reference_file = Path(simtel_cfg_file)
272
+ test_file = Path(config["CONFIGURATION"]["OUTPUT_PATH"]) / reference_file.name.replace(
273
+ "_test", f"_{config['CONFIGURATION']['LABEL']}"
274
+ )
275
+ _logger.info(
276
+ f"Comparing simtel cfg files: {reference_file} and {test_file} "
277
+ f"for model version {config['CONFIGURATION']['MODEL_VERSION']}"
278
+ )
279
+ return _compare_simtel_cfg_files(reference_file, test_file)
280
+
281
+
282
+ def _compare_simtel_cfg_files(reference_file, test_file):
283
+ """
284
+ Compare two sim_telarray configuration files.
285
+
286
+ Line-by-line string comparison. Requires similar sequence of
287
+ parameters in the files. Ignore lines containing 'config_release'
288
+ (as it contains the simtools package version).
289
+
290
+ Parameters
291
+ ----------
292
+ reference_file: Path
293
+ Reference sim_telarray configuration file.
294
+ test_file: Path
295
+ Test sim_telarray configuration file.
296
+
297
+ Returns
298
+ -------
299
+ bool
300
+ True if the files are equal.
301
+
302
+ """
303
+ with open(reference_file, encoding="utf-8") as f1, open(test_file, encoding="utf-8") as f2:
304
+ reference_cfg = [line.rstrip() for line in f1 if line.strip()]
305
+ test_cfg = [line.rstrip() for line in f2 if line.strip()]
306
+
307
+ if len(reference_cfg) != len(test_cfg):
308
+ _logger.error(
309
+ f"Line counts differ: {reference_file} ({len(reference_cfg)} lines), "
310
+ f"{test_file} ({len(test_cfg)} lines)."
311
+ )
312
+ return False
313
+
314
+ for ref_line, test_line in zip(reference_cfg, test_cfg):
315
+ if any(ignore in ref_line for ignore in ("config_release", "Label")):
316
+ continue
317
+ if ref_line != test_line:
318
+ _logger.error(
319
+ f"Configuration files {reference_file} and {test_file} do not match: "
320
+ f"'{ref_line}' and '{test_line}'"
321
+ )
322
+ return False
323
+
324
+ return True
simtools/utils/general.py CHANGED
@@ -17,6 +17,7 @@ import yaml
17
17
  __all__ = [
18
18
  "InvalidConfigDataError",
19
19
  "change_dict_keys_case",
20
+ "clear_default_sim_telarray_cfg_directories",
20
21
  "collect_data_from_file",
21
22
  "collect_final_lines",
22
23
  "collect_kwargs",
@@ -81,6 +82,28 @@ def is_url(url):
81
82
  return False
82
83
 
83
84
 
85
+ def url_exists(url):
86
+ """
87
+ Check if a URL exists.
88
+
89
+ Parameters
90
+ ----------
91
+ url: str
92
+ URL to be checked.
93
+
94
+ Returns
95
+ -------
96
+ bool
97
+ True if URL exists.
98
+ """
99
+ try:
100
+ with urllib.request.urlopen(url, timeout=5) as response:
101
+ return response.status == 200
102
+ except (urllib.error.URLError, AttributeError) as e:
103
+ _logger.error(f"URL {url} does not exist: {e}")
104
+ return False
105
+
106
+
84
107
  def collect_data_from_http(url):
85
108
  """
86
109
  Download yaml or json file from url and return it contents as dict.
@@ -135,7 +158,7 @@ def collect_data_from_http(url):
135
158
  return data
136
159
 
137
160
 
138
- def collect_data_from_file(file_name):
161
+ def collect_data_from_file(file_name, yaml_document=None):
139
162
  """
140
163
  Collect data from file based on its extension.
141
164
 
@@ -143,6 +166,8 @@ def collect_data_from_file(file_name):
143
166
  ----------
144
167
  file_name: str
145
168
  Name of the yaml/json/ascii file.
169
+ yaml_document: None, int
170
+ Return list of yaml documents or a single document (for yaml files with several documents).
146
171
 
147
172
  Returns
148
173
  -------
@@ -152,21 +177,34 @@ def collect_data_from_file(file_name):
152
177
  if is_url(file_name):
153
178
  return collect_data_from_http(file_name)
154
179
 
180
+ suffix = Path(file_name).suffix.lower()
155
181
  with open(file_name, encoding="utf-8") as file:
156
- if Path(file_name).suffix.lower() == ".json":
182
+ if suffix == ".json":
157
183
  return json.load(file)
184
+ if suffix == ".list":
185
+ return [line.strip() for line in file.readlines()]
186
+ if suffix in [".yml", ".yaml"]:
187
+ return _collect_data_from_yaml_file(file, file_name, yaml_document)
188
+ return None
158
189
 
159
- if Path(file_name).suffix.lower() == ".list":
160
- lines = file.readlines()
161
- return [line.strip() for line in lines]
162
190
 
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))
191
+ def _collect_data_from_yaml_file(file, file_name, yaml_document):
192
+ """Collect data from a yaml file."""
193
+ try:
194
+ return yaml.safe_load(file)
195
+ except yaml.constructor.ConstructorError:
196
+ return _load_yaml_using_astropy(file)
197
+ except yaml.composer.ComposerError:
198
+ pass
199
+ file.seek(0)
200
+ if yaml_document is None:
201
+ return list(yaml.safe_load_all(file))
202
+ try:
203
+ return list(yaml.safe_load_all(file))[yaml_document]
204
+ except IndexError as exc:
205
+ raise InvalidConfigDataError(
206
+ f"YAML file {file_name} does not contain {yaml_document} documents."
207
+ ) from exc
170
208
 
171
209
 
172
210
  def collect_kwargs(label, in_kwargs):
@@ -832,3 +870,55 @@ def convert_keys_in_dict_to_lowercase(data):
832
870
  if isinstance(data, list):
833
871
  return [convert_keys_in_dict_to_lowercase(i) for i in data]
834
872
  return data
873
+
874
+
875
+ def clear_default_sim_telarray_cfg_directories(command):
876
+ """Prefix the command to clear default sim_telarray configuration directories.
877
+
878
+ Parameters
879
+ ----------
880
+ command: str
881
+ Command to be prefixed.
882
+
883
+ Returns
884
+ -------
885
+ str
886
+ Prefixed command.
887
+
888
+ """
889
+ return f"SIM_TELARRAY_CONFIG_PATH='' {command}"
890
+
891
+
892
+ def get_list_of_files_from_command_line(file_names, suffix_list):
893
+ """
894
+ Get a list of files from the command line.
895
+
896
+ Files can be given as a list of file names or as a text file containing the list of files.
897
+ The list of suffixes restrict the files types to be returned. Note that a file list must
898
+ have a different suffix than those in the suffix list.
899
+
900
+ Parameters
901
+ ----------
902
+ file_names: list
903
+ List of file names to be checked.
904
+ suffix_list: list
905
+ List of suffixes to be checked.
906
+
907
+ Returns
908
+ -------
909
+ list
910
+ List of files with the given suffixes.
911
+ """
912
+ _files = []
913
+ for one_file in file_names:
914
+ path = Path(one_file)
915
+ try:
916
+ if path.suffix in suffix_list:
917
+ _files.append(one_file)
918
+ elif len(file_names) == 1:
919
+ with open(one_file, encoding="utf-8") as file:
920
+ _files.extend(line.strip() for line in file)
921
+ except FileNotFoundError as exc:
922
+ _logger.error(f"{one_file} is not a file.")
923
+ raise FileNotFoundError from exc
924
+ return _files