gammasimtools 0.8.2__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.
- {gammasimtools-0.8.2.dist-info → gammasimtools-0.10.0.dist-info}/METADATA +4 -4
- {gammasimtools-0.8.2.dist-info → gammasimtools-0.10.0.dist-info}/RECORD +119 -105
- {gammasimtools-0.8.2.dist-info → gammasimtools-0.10.0.dist-info}/WHEEL +1 -1
- {gammasimtools-0.8.2.dist-info → gammasimtools-0.10.0.dist-info}/entry_points.txt +4 -1
- simtools/_version.py +2 -2
- simtools/applications/calculate_trigger_rate.py +15 -38
- simtools/applications/convert_all_model_parameters_from_simtel.py +9 -28
- simtools/applications/convert_geo_coordinates_of_array_elements.py +54 -53
- simtools/applications/convert_model_parameter_from_simtel.py +2 -2
- simtools/applications/db_add_file_to_db.py +1 -2
- simtools/applications/db_add_simulation_model_from_repository_to_db.py +110 -0
- simtools/applications/db_add_value_from_json_to_db.py +2 -11
- simtools/applications/db_development_tools/write_array_elements_positions_to_repository.py +6 -6
- simtools/applications/db_get_array_layouts_from_db.py +3 -1
- simtools/applications/db_get_file_from_db.py +11 -12
- simtools/applications/db_get_parameter_from_db.py +44 -32
- simtools/applications/derive_mirror_rnda.py +10 -1
- simtools/applications/derive_photon_electron_spectrum.py +99 -0
- simtools/applications/derive_psf_parameters.py +1 -1
- simtools/applications/generate_array_config.py +18 -22
- simtools/applications/generate_regular_arrays.py +24 -21
- simtools/applications/generate_simtel_array_histograms.py +11 -48
- simtools/applications/plot_array_layout.py +3 -1
- simtools/applications/plot_tabular_data.py +84 -0
- simtools/applications/production_generate_simulation_config.py +25 -7
- simtools/applications/production_scale_events.py +3 -4
- simtools/applications/simulate_light_emission.py +2 -2
- simtools/applications/simulate_prod.py +25 -60
- simtools/applications/simulate_prod_htcondor_generator.py +95 -0
- simtools/applications/submit_data_from_external.py +12 -4
- simtools/applications/submit_model_parameter_from_external.py +8 -6
- simtools/applications/validate_camera_efficiency.py +3 -3
- simtools/applications/validate_camera_fov.py +3 -7
- simtools/applications/validate_cumulative_psf.py +3 -7
- simtools/applications/validate_file_using_schema.py +38 -24
- simtools/applications/validate_optics.py +3 -4
- simtools/{camera_efficiency.py → camera/camera_efficiency.py} +1 -4
- simtools/camera/single_photon_electron_spectrum.py +168 -0
- simtools/configuration/commandline_parser.py +14 -13
- simtools/configuration/configurator.py +6 -19
- simtools/constants.py +10 -3
- simtools/corsika/corsika_config.py +8 -7
- simtools/corsika/corsika_histograms.py +1 -1
- simtools/data_model/data_reader.py +0 -3
- simtools/data_model/metadata_collector.py +21 -4
- simtools/data_model/metadata_model.py +8 -111
- simtools/data_model/model_data_writer.py +18 -64
- simtools/data_model/schema.py +213 -0
- simtools/data_model/validate_data.py +73 -51
- simtools/db/db_handler.py +395 -790
- simtools/db/db_model_upload.py +139 -0
- simtools/io_operations/hdf5_handler.py +54 -24
- simtools/io_operations/legacy_data_handler.py +61 -0
- simtools/job_execution/htcondor_script_generator.py +133 -0
- simtools/job_execution/job_manager.py +77 -50
- simtools/layout/array_layout.py +33 -28
- simtools/model/array_model.py +13 -7
- simtools/model/camera.py +4 -2
- simtools/model/model_parameter.py +61 -63
- simtools/model/site_model.py +3 -3
- simtools/production_configuration/calculate_statistical_errors_grid_point.py +119 -144
- simtools/production_configuration/event_scaler.py +7 -17
- simtools/production_configuration/generate_simulation_config.py +5 -32
- simtools/production_configuration/interpolation_handler.py +8 -11
- simtools/ray_tracing/mirror_panel_psf.py +47 -27
- simtools/runners/corsika_runner.py +14 -3
- simtools/runners/corsika_simtel_runner.py +3 -1
- simtools/runners/runner_services.py +3 -3
- simtools/runners/simtel_runner.py +27 -8
- simtools/schemas/input/MST_mirror_2f_measurements.schema.yml +39 -0
- simtools/schemas/input/single_pe_spectrum.schema.yml +38 -0
- simtools/schemas/integration_tests_config.metaschema.yml +23 -3
- simtools/schemas/model_parameter.metaschema.yml +95 -2
- simtools/schemas/model_parameter_and_data_schema.metaschema.yml +2 -0
- simtools/schemas/model_parameters/array_element_position_utm.schema.yml +1 -1
- simtools/schemas/model_parameters/array_window.schema.yml +37 -0
- simtools/schemas/model_parameters/asum_clipping.schema.yml +0 -4
- simtools/schemas/model_parameters/channels_per_chip.schema.yml +1 -1
- simtools/schemas/model_parameters/corsika_iact_io_buffer.schema.yml +2 -2
- simtools/schemas/model_parameters/dsum_clipping.schema.yml +0 -2
- simtools/schemas/model_parameters/dsum_ignore_below.schema.yml +0 -2
- simtools/schemas/model_parameters/dsum_offset.schema.yml +0 -2
- simtools/schemas/model_parameters/dsum_pedsub.schema.yml +0 -2
- simtools/schemas/model_parameters/dsum_pre_clipping.schema.yml +0 -2
- simtools/schemas/model_parameters/dsum_prescale.schema.yml +0 -2
- simtools/schemas/model_parameters/dsum_presum_max.schema.yml +0 -2
- simtools/schemas/model_parameters/dsum_presum_shift.schema.yml +0 -2
- simtools/schemas/model_parameters/dsum_shaping.schema.yml +0 -2
- simtools/schemas/model_parameters/dsum_shaping_renormalize.schema.yml +0 -2
- simtools/schemas/model_parameters/dsum_threshold.schema.yml +0 -2
- simtools/schemas/model_parameters/dsum_zero_clip.schema.yml +0 -2
- simtools/schemas/model_parameters/effective_focal_length.schema.yml +31 -1
- simtools/schemas/model_parameters/fadc_compensate_pedestal.schema.yml +1 -1
- simtools/schemas/model_parameters/fadc_lg_compensate_pedestal.schema.yml +1 -1
- simtools/schemas/model_parameters/fadc_noise.schema.yml +3 -3
- simtools/schemas/model_parameters/fake_mirror_list.schema.yml +33 -0
- simtools/schemas/model_parameters/laser_photons.schema.yml +2 -2
- simtools/schemas/model_parameters/secondary_mirror_degraded_reflection.schema.yml +1 -1
- simtools/schemas/production_configuration_metrics.schema.yml +68 -0
- simtools/schemas/production_tables.schema.yml +41 -0
- simtools/simtel/simtel_config_writer.py +5 -6
- simtools/simtel/simtel_io_histogram.py +32 -67
- simtools/simtel/simtel_io_histograms.py +15 -30
- simtools/simtel/simtel_table_reader.py +410 -0
- simtools/simtel/simulator_array.py +2 -1
- simtools/simtel/simulator_camera_efficiency.py +11 -4
- simtools/simtel/simulator_light_emission.py +5 -3
- simtools/simtel/simulator_ray_tracing.py +2 -2
- simtools/simulator.py +80 -33
- simtools/testing/configuration.py +12 -8
- simtools/testing/helpers.py +9 -16
- simtools/testing/validate_output.py +152 -68
- simtools/utils/general.py +149 -12
- simtools/utils/names.py +25 -21
- simtools/utils/value_conversion.py +9 -1
- simtools/visualization/plot_tables.py +106 -0
- simtools/visualization/visualize.py +43 -5
- simtools/applications/db_add_model_parameters_from_repository_to_db.py +0 -184
- simtools/db/db_array_elements.py +0 -130
- simtools/db/db_from_repo_handler.py +0 -106
- {gammasimtools-0.8.2.dist-info → gammasimtools-0.10.0.dist-info}/LICENSE +0 -0
- {gammasimtools-0.8.2.dist-info → gammasimtools-0.10.0.dist-info}/top_level.txt +0 -0
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
import logging
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
|
|
6
|
-
import pytest
|
|
7
6
|
import yaml
|
|
8
7
|
|
|
9
8
|
import simtools.utils.general as gen
|
|
@@ -11,6 +10,10 @@ import simtools.utils.general as gen
|
|
|
11
10
|
_logger = logging.getLogger(__name__)
|
|
12
11
|
|
|
13
12
|
|
|
13
|
+
class VersionError(Exception):
|
|
14
|
+
"""Raise if model version requested is not supported."""
|
|
15
|
+
|
|
16
|
+
|
|
14
17
|
def get_list_of_test_configurations(config_files):
|
|
15
18
|
"""
|
|
16
19
|
Return list of test configuration dictionaries or test names.
|
|
@@ -121,7 +124,7 @@ def _skip_test_for_model_version(config, model_version_requested):
|
|
|
121
124
|
return
|
|
122
125
|
model_version_config = config["CONFIGURATION"]["MODEL_VERSION"]
|
|
123
126
|
if model_version_requested != model_version_config:
|
|
124
|
-
|
|
127
|
+
raise VersionError(
|
|
125
128
|
f"Model version requested {model_version_requested} not supported for this test"
|
|
126
129
|
)
|
|
127
130
|
|
|
@@ -158,13 +161,14 @@ def _prepare_test_options(config, output_path, model_version=None):
|
|
|
158
161
|
|
|
159
162
|
tmp_config_file = output_path / "tmp_config.yml"
|
|
160
163
|
config_file_model_version = config.get("MODEL_VERSION")
|
|
161
|
-
if model_version
|
|
164
|
+
if model_version and "MODEL_VERSION" in config:
|
|
162
165
|
config.update({"MODEL_VERSION": model_version})
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
config
|
|
166
|
-
|
|
167
|
-
|
|
166
|
+
|
|
167
|
+
for key in ["OUTPUT_PATH", "DATA_DIRECTORY", "PACK_FOR_GRID_REGISTER"]:
|
|
168
|
+
if key in config:
|
|
169
|
+
config[key] = str(Path(output_path).joinpath(config[key]))
|
|
170
|
+
if key == "OUTPUT_PATH":
|
|
171
|
+
config["USE_PLAIN_OUTPUT_PATH"] = True
|
|
168
172
|
|
|
169
173
|
_logger.info(f"Writing config file: {tmp_config_file}")
|
|
170
174
|
with open(tmp_config_file, "w", encoding="utf-8") as file:
|
simtools/testing/helpers.py
CHANGED
|
@@ -1,25 +1,18 @@
|
|
|
1
1
|
"""Helper functions for integration testing."""
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
-
|
|
5
|
-
import pytest
|
|
4
|
+
from pathlib import Path
|
|
6
5
|
|
|
7
6
|
|
|
8
7
|
def skip_camera_efficiency(config):
|
|
9
8
|
"""Skip camera efficiency tests if the old version of testeff is used."""
|
|
10
|
-
if "camera-efficiency" in config["APPLICATION"]:
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
full_test_name = f"{config['APPLICATION']}_{config['TEST_NAME']}"
|
|
18
|
-
if "simtools-validate-camera-efficiency_SSTS" == full_test_name:
|
|
19
|
-
pytest.skip(
|
|
20
|
-
"The test simtools-validate-camera-efficiency_SSTS is skipped "
|
|
21
|
-
"since the fake SST mirrors are not yet implemented (#1155)"
|
|
22
|
-
)
|
|
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
|
+
)
|
|
15
|
+
return None
|
|
23
16
|
|
|
24
17
|
|
|
25
18
|
def _new_testeff_version():
|
|
@@ -28,7 +21,7 @@ def _new_testeff_version():
|
|
|
28
21
|
|
|
29
22
|
This test checks if the new version is used.
|
|
30
23
|
"""
|
|
31
|
-
testeff_path =
|
|
24
|
+
testeff_path = Path(os.getenv("SIMTOOLS_SIMTEL_PATH")) / "sim_telarray/testeff.c"
|
|
32
25
|
try:
|
|
33
26
|
with open(testeff_path, encoding="utf-8") as file:
|
|
34
27
|
file_content = file.read()
|
|
@@ -12,39 +12,22 @@ from simtools.testing import assertions
|
|
|
12
12
|
_logger = logging.getLogger(__name__)
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
def
|
|
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,19 +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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
config
|
|
66
|
-
|
|
67
|
-
|
|
38
|
+
|
|
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)
|
|
68
69
|
|
|
69
70
|
|
|
70
71
|
def _validate_reference_output_file(config, integration_test):
|
|
@@ -79,31 +80,25 @@ def _validate_reference_output_file(config, integration_test):
|
|
|
79
80
|
)
|
|
80
81
|
|
|
81
82
|
|
|
82
|
-
def _validate_output_path_and_file(config,
|
|
83
|
-
"""Check if output
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
_logger.info(
|
|
102
|
-
f"Checking the output of {integration_test['OUTPUT_FILE']} "
|
|
103
|
-
"complies with the expected output: "
|
|
104
|
-
f"{expected_output}"
|
|
105
|
-
)
|
|
106
|
-
assert assertions.check_output_from_sim_telarray(output_file_path, expected_output)
|
|
83
|
+
def _validate_output_path_and_file(config, integration_file_tests):
|
|
84
|
+
"""Check if output paths and files exist."""
|
|
85
|
+
for file_test in integration_file_tests:
|
|
86
|
+
try:
|
|
87
|
+
output_path = config["CONFIGURATION"][file_test["PATH_DESCRIPTOR"]]
|
|
88
|
+
except KeyError as exc:
|
|
89
|
+
raise KeyError(
|
|
90
|
+
f"Path {file_test['PATH_DESCRIPTOR']} not found in integration test configuration."
|
|
91
|
+
) from exc
|
|
92
|
+
|
|
93
|
+
output_file_path = Path(output_path) / file_test["FILE"]
|
|
94
|
+
_logger.info(f"Checking path: {output_file_path}")
|
|
95
|
+
assert output_file_path.exists()
|
|
96
|
+
|
|
97
|
+
if "EXPECTED_OUTPUT" in file_test:
|
|
98
|
+
assert assertions.check_output_from_sim_telarray(
|
|
99
|
+
output_file_path,
|
|
100
|
+
file_test["EXPECTED_OUTPUT"],
|
|
101
|
+
)
|
|
107
102
|
|
|
108
103
|
|
|
109
104
|
def compare_files(file1, file2, tolerance=1.0e-5, test_columns=None):
|
|
@@ -129,12 +124,13 @@ def compare_files(file1, file2, tolerance=1.0e-5, test_columns=None):
|
|
|
129
124
|
"""
|
|
130
125
|
_file1_suffix = Path(file1).suffix
|
|
131
126
|
_file2_suffix = Path(file2).suffix
|
|
127
|
+
_logger.info("Comparing files: %s and %s", file1, file2)
|
|
132
128
|
if _file1_suffix != _file2_suffix:
|
|
133
129
|
raise ValueError(f"File suffixes do not match: {file1} and {file2}")
|
|
134
130
|
if _file1_suffix == ".ecsv":
|
|
135
131
|
return compare_ecsv_files(file1, file2, tolerance, test_columns)
|
|
136
132
|
if _file1_suffix in (".json", ".yaml", ".yml"):
|
|
137
|
-
return compare_json_or_yaml_files(file1, file2)
|
|
133
|
+
return compare_json_or_yaml_files(file1, file2, tolerance)
|
|
138
134
|
|
|
139
135
|
_logger.warning(f"Unknown file type for files: {file1} and {file2}")
|
|
140
136
|
return False
|
|
@@ -169,11 +165,35 @@ def compare_json_or_yaml_files(file1, file2, tolerance=1.0e-2):
|
|
|
169
165
|
if data1 == data2:
|
|
170
166
|
return True
|
|
171
167
|
|
|
172
|
-
if
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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)
|
|
177
197
|
|
|
178
198
|
|
|
179
199
|
def compare_ecsv_files(file1, file2, tolerance=1.0e-5, test_columns=None):
|
|
@@ -235,6 +255,70 @@ def compare_ecsv_files(file1, file2, tolerance=1.0e-5, test_columns=None):
|
|
|
235
255
|
|
|
236
256
|
if np.issubdtype(table1_masked[col_name].dtype, np.floating):
|
|
237
257
|
if not np.allclose(table1_masked[col_name], table2_masked[col_name], rtol=tolerance):
|
|
258
|
+
_logger.warning(f"Column {col_name} outside of relative tolerance {tolerance}")
|
|
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,18 +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
|
|
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
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
|
167
208
|
|
|
168
209
|
|
|
169
210
|
def collect_kwargs(label, in_kwargs):
|
|
@@ -333,9 +374,10 @@ def program_is_executable(program):
|
|
|
333
374
|
Follows https://stackoverflow.com/questions/377017/
|
|
334
375
|
|
|
335
376
|
"""
|
|
377
|
+
program = Path(program)
|
|
336
378
|
|
|
337
379
|
def is_exe(fpath):
|
|
338
|
-
return
|
|
380
|
+
return fpath.is_file() and os.access(fpath, os.X_OK)
|
|
339
381
|
|
|
340
382
|
fpath, _ = os.path.split(program)
|
|
341
383
|
if fpath:
|
|
@@ -344,7 +386,7 @@ def program_is_executable(program):
|
|
|
344
386
|
else:
|
|
345
387
|
try:
|
|
346
388
|
for path in os.environ["PATH"].split(os.pathsep):
|
|
347
|
-
exe_file =
|
|
389
|
+
exe_file = Path(path) / program
|
|
348
390
|
if is_exe(exe_file):
|
|
349
391
|
return exe_file
|
|
350
392
|
except KeyError:
|
|
@@ -442,7 +484,7 @@ def get_file_age(file_path):
|
|
|
442
484
|
if not Path(file_path).is_file():
|
|
443
485
|
raise FileNotFoundError(f"'{file_path}' does not exist or is not a file.")
|
|
444
486
|
|
|
445
|
-
file_stats =
|
|
487
|
+
file_stats = Path(file_path).stat()
|
|
446
488
|
modification_time = file_stats.st_mtime
|
|
447
489
|
current_time = time.time()
|
|
448
490
|
|
|
@@ -785,3 +827,98 @@ def read_file_encoded_in_utf_or_latin(file_name):
|
|
|
785
827
|
raise UnicodeDecodeError("Unable to decode file using UTF-8 or Latin-1.") from exc
|
|
786
828
|
|
|
787
829
|
return lines
|
|
830
|
+
|
|
831
|
+
|
|
832
|
+
def get_structure_array_from_table(table, column_names):
|
|
833
|
+
"""
|
|
834
|
+
Get a structured array from an astropy table for a selected list of columns.
|
|
835
|
+
|
|
836
|
+
Parameters
|
|
837
|
+
----------
|
|
838
|
+
table: astropy.table.Table
|
|
839
|
+
Table to be converted.
|
|
840
|
+
column_names: list
|
|
841
|
+
List of column names to be included in the structured array.
|
|
842
|
+
|
|
843
|
+
Returns
|
|
844
|
+
-------
|
|
845
|
+
numpy.ndarray
|
|
846
|
+
Structured array containing the table data.
|
|
847
|
+
"""
|
|
848
|
+
return np.array(
|
|
849
|
+
list(zip(*[np.array(table[col]) for col in column_names])),
|
|
850
|
+
dtype=[(col, np.array(table[col]).dtype) for col in column_names],
|
|
851
|
+
)
|
|
852
|
+
|
|
853
|
+
|
|
854
|
+
def convert_keys_in_dict_to_lowercase(data):
|
|
855
|
+
"""
|
|
856
|
+
Recursively convert all dictionary keys to lowercase.
|
|
857
|
+
|
|
858
|
+
Parameters
|
|
859
|
+
----------
|
|
860
|
+
data: dict
|
|
861
|
+
Dictionary to be converted.
|
|
862
|
+
|
|
863
|
+
Returns
|
|
864
|
+
-------
|
|
865
|
+
dict
|
|
866
|
+
Dictionary with all keys converted to lowercase.
|
|
867
|
+
"""
|
|
868
|
+
if isinstance(data, dict):
|
|
869
|
+
return {k.lower(): convert_keys_in_dict_to_lowercase(v) for k, v in data.items()}
|
|
870
|
+
if isinstance(data, list):
|
|
871
|
+
return [convert_keys_in_dict_to_lowercase(i) for i in data]
|
|
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
|