gammasimtools 0.25.0__py3-none-any.whl → 0.27.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.25.0.dist-info → gammasimtools-0.27.0.dist-info}/METADATA +6 -1
- {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/RECORD +135 -130
- {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/WHEEL +1 -1
- {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/entry_points.txt +3 -2
- {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/licenses/LICENSE +1 -1
- simtools/_version.py +2 -2
- simtools/application_control.py +35 -7
- simtools/applications/convert_geo_coordinates_of_array_elements.py +3 -3
- simtools/applications/db_add_file_to_db.py +1 -1
- simtools/applications/db_add_simulation_model_from_repository_to_db.py +1 -1
- simtools/applications/db_add_value_from_json_to_db.py +1 -1
- simtools/applications/db_generate_compound_indexes.py +1 -1
- simtools/applications/db_get_array_layouts_from_db.py +3 -7
- simtools/applications/db_get_file_from_db.py +1 -1
- simtools/applications/db_get_parameter_from_db.py +1 -1
- simtools/applications/db_inspect_databases.py +1 -1
- simtools/applications/db_upload_model_repository.py +1 -1
- simtools/applications/derive_ctao_array_layouts.py +1 -2
- simtools/applications/{calculate_incident_angles.py → derive_incident_angle.py} +16 -18
- simtools/applications/derive_mirror_rnda.py +112 -180
- simtools/applications/derive_psf_parameters.py +0 -1
- simtools/applications/derive_pulse_shape_parameters.py +0 -1
- simtools/applications/derive_trigger_rates.py +1 -1
- simtools/applications/docs_produce_array_element_report.py +2 -8
- simtools/applications/docs_produce_calibration_reports.py +1 -3
- simtools/applications/docs_produce_model_parameter_reports.py +0 -2
- simtools/applications/docs_produce_simulation_configuration_report.py +1 -3
- simtools/applications/generate_array_config.py +0 -1
- simtools/applications/generate_corsika_histograms.py +79 -229
- simtools/applications/generate_regular_arrays.py +76 -69
- simtools/applications/generate_simtel_event_data.py +2 -2
- simtools/applications/maintain_simulation_model_add_production.py +2 -2
- simtools/applications/maintain_simulation_model_write_array_element_positions.py +87 -0
- simtools/applications/plot_array_layout.py +5 -111
- simtools/applications/plot_simulated_event_distributions.py +57 -0
- simtools/applications/plot_tabular_data.py +0 -1
- simtools/applications/plot_tabular_data_for_model_parameter.py +1 -6
- simtools/applications/production_derive_corsika_limits.py +1 -1
- simtools/applications/production_generate_grid.py +0 -1
- simtools/applications/run_application.py +1 -1
- simtools/applications/simulate_flasher.py +3 -15
- simtools/applications/simulate_illuminator.py +2 -11
- simtools/applications/simulate_pedestals.py +1 -5
- simtools/applications/simulate_prod.py +8 -11
- simtools/applications/simulate_prod_htcondor_generator.py +1 -1
- simtools/applications/submit_array_layouts.py +2 -4
- simtools/applications/submit_data_from_external.py +2 -1
- simtools/applications/submit_model_parameter_from_external.py +1 -3
- simtools/applications/validate_camera_efficiency.py +28 -28
- simtools/applications/validate_camera_fov.py +0 -1
- simtools/applications/validate_cumulative_psf.py +1 -5
- simtools/applications/validate_optics.py +2 -14
- simtools/atmosphere.py +83 -0
- simtools/camera/camera_efficiency.py +171 -53
- simtools/camera/single_photon_electron_spectrum.py +8 -7
- simtools/configuration/commandline_parser.py +82 -11
- simtools/configuration/configurator.py +6 -11
- simtools/constants.py +5 -0
- simtools/corsika/corsika_config.py +100 -202
- simtools/corsika/corsika_histograms.py +561 -1708
- simtools/corsika/primary_particle.py +1 -1
- simtools/data_model/metadata_collector.py +5 -2
- simtools/data_model/metadata_model.py +0 -4
- simtools/data_model/model_data_writer.py +59 -64
- simtools/data_model/schema.py +2 -0
- simtools/data_model/validate_data.py +1 -3
- simtools/db/db_handler.py +23 -10
- simtools/db/mongo_db.py +2 -2
- simtools/dependencies.py +81 -38
- simtools/io/ascii_handler.py +55 -5
- simtools/io/io_handler.py +23 -12
- simtools/io/table_handler.py +1 -1
- simtools/job_execution/job_manager.py +154 -79
- simtools/job_execution/process_pool.py +137 -0
- simtools/layout/array_layout.py +4 -13
- simtools/layout/array_layout_utils.py +348 -57
- simtools/model/array_model.py +23 -63
- simtools/model/calibration_model.py +4 -8
- simtools/model/legacy_model_parameter.py +134 -0
- simtools/model/model_parameter.py +147 -86
- simtools/model/model_utils.py +40 -6
- simtools/model/site_model.py +4 -8
- simtools/model/telescope_model.py +10 -16
- simtools/production_configuration/derive_corsika_limits.py +6 -11
- simtools/production_configuration/interpolation_handler.py +16 -16
- simtools/ray_tracing/incident_angles.py +92 -17
- simtools/ray_tracing/mirror_panel_psf.py +338 -222
- simtools/ray_tracing/psf_analysis.py +62 -48
- simtools/ray_tracing/psf_parameter_optimisation.py +3 -3
- simtools/ray_tracing/ray_tracing.py +43 -25
- simtools/reporting/docs_auto_report_generator.py +8 -13
- simtools/reporting/docs_read_parameters.py +2 -8
- simtools/runners/corsika_runner.py +52 -195
- simtools/runners/corsika_simtel_runner.py +77 -108
- simtools/runners/runner_services.py +214 -213
- simtools/runners/simtel_runner.py +27 -160
- simtools/runners/simtools_runner.py +11 -73
- simtools/schemas/application_workflow.metaschema.yml +8 -0
- simtools/settings.py +173 -0
- simtools/{io/eventio_handler.py → sim_events/file_info.py} +3 -3
- simtools/{simtel/simtel_io_event_histograms.py → sim_events/histograms.py} +25 -15
- simtools/{simtel/simtel_io_event_reader.py → sim_events/reader.py} +20 -17
- simtools/{simtel/simtel_io_event_writer.py → sim_events/writer.py} +84 -25
- simtools/simtel/pulse_shapes.py +7 -2
- simtools/simtel/simtel_config_writer.py +79 -91
- simtools/simtel/simtel_seeds.py +184 -0
- simtools/simtel/simtel_table_reader.py +6 -4
- simtools/simtel/simulator_array.py +114 -109
- simtools/simtel/simulator_camera_efficiency.py +68 -46
- simtools/simtel/simulator_light_emission.py +164 -132
- simtools/simtel/simulator_ray_tracing.py +80 -71
- simtools/simulator.py +137 -355
- simtools/telescope_trigger_rates.py +3 -4
- simtools/testing/assertions.py +84 -33
- simtools/testing/configuration.py +1 -2
- simtools/testing/helpers.py +2 -3
- simtools/testing/log_inspector.py +1 -0
- simtools/testing/sim_telarray_metadata.py +14 -12
- simtools/testing/validate_output.py +121 -42
- simtools/utils/general.py +43 -17
- simtools/utils/geometry.py +0 -77
- simtools/utils/names.py +5 -5
- simtools/utils/random.py +36 -0
- simtools/visualization/legend_handlers.py +7 -6
- simtools/visualization/plot_array_layout.py +91 -16
- simtools/visualization/plot_corsika_histograms.py +145 -605
- simtools/visualization/plot_incident_angles.py +48 -1
- simtools/visualization/plot_mirrors.py +1 -4
- simtools/visualization/plot_pixels.py +2 -4
- simtools/visualization/plot_psf.py +160 -19
- simtools/visualization/plot_simtel_event_histograms.py +4 -4
- simtools/visualization/plot_simtel_events.py +6 -11
- simtools/visualization/plot_tables.py +8 -19
- simtools/visualization/visualize.py +22 -2
- simtools/applications/db_development_tools/write_array_elements_positions_to_repository.py +0 -160
- simtools/applications/print_version.py +0 -53
- simtools/io/hdf5_handler.py +0 -139
- {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/top_level.txt +0 -0
|
@@ -8,13 +8,13 @@ from ctao_cr_spectra.definitions import IRFDOC_PROTON_SPECTRUM
|
|
|
8
8
|
|
|
9
9
|
from simtools.io import ascii_handler, io_handler
|
|
10
10
|
from simtools.layout.array_layout_utils import get_array_elements_from_db_for_layouts
|
|
11
|
-
from simtools.
|
|
11
|
+
from simtools.sim_events.histograms import EventDataHistograms
|
|
12
12
|
from simtools.visualization import plot_simtel_event_histograms
|
|
13
13
|
|
|
14
14
|
_logger = logging.getLogger(__name__)
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
def telescope_trigger_rates(args_dict
|
|
17
|
+
def telescope_trigger_rates(args_dict):
|
|
18
18
|
"""
|
|
19
19
|
Calculate trigger rates for single telescopes or arrays of telescopes.
|
|
20
20
|
|
|
@@ -27,7 +27,6 @@ def telescope_trigger_rates(args_dict, db_config):
|
|
|
27
27
|
args_dict["array_layout_name"],
|
|
28
28
|
args_dict.get("site"),
|
|
29
29
|
args_dict.get("model_version"),
|
|
30
|
-
db_config,
|
|
31
30
|
)
|
|
32
31
|
else:
|
|
33
32
|
telescope_configs = ascii_handler.collect_data_from_file(args_dict["telescope_ids"])[
|
|
@@ -38,7 +37,7 @@ def telescope_trigger_rates(args_dict, db_config):
|
|
|
38
37
|
_logger.info(
|
|
39
38
|
f"Processing file: {args_dict['event_data_file']} with telescope config: {array_name}"
|
|
40
39
|
)
|
|
41
|
-
histograms =
|
|
40
|
+
histograms = EventDataHistograms(
|
|
42
41
|
args_dict["event_data_file"], array_name=array_name, telescope_list=telescope_ids
|
|
43
42
|
)
|
|
44
43
|
histograms.fill()
|
simtools/testing/assertions.py
CHANGED
|
@@ -68,8 +68,7 @@ def assert_n_showers_and_energy_range(file):
|
|
|
68
68
|
simulation_config = {}
|
|
69
69
|
with SimTelFile(file, skip_non_triggered=False) as f:
|
|
70
70
|
simulation_config = f.mc_run_headers[0]
|
|
71
|
-
for event in f
|
|
72
|
-
simulated_energies.append(event["mc_shower"]["energy"])
|
|
71
|
+
simulated_energies.extend(event["mc_shower"]["energy"] for event in f)
|
|
73
72
|
|
|
74
73
|
# The relative tolerance is set to 1% because ~0.5% shower simulations do not
|
|
75
74
|
# succeed, without resulting in an error. This tolerance therefore is not an issue.
|
|
@@ -191,54 +190,94 @@ def check_output_from_sim_telarray(file, file_test):
|
|
|
191
190
|
return assert_n_showers_and_energy_range(file=file) and assert_output and assert_metadata
|
|
192
191
|
|
|
193
192
|
|
|
194
|
-
def _find_patterns(text, patterns):
|
|
195
|
-
"""Find patterns in text."""
|
|
196
|
-
return {p for p in patterns if p in text}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
def _read_log(member, tar):
|
|
200
|
-
"""Read and decode a gzipped log file from a tar archive."""
|
|
201
|
-
with tar.extractfile(member) as gz, gzip.open(gz, "rb") as f:
|
|
202
|
-
return f.read().decode("utf-8", "ignore")
|
|
203
|
-
|
|
204
|
-
|
|
205
193
|
def check_simulation_logs(tar_file, file_test):
|
|
206
194
|
"""
|
|
207
|
-
Check
|
|
195
|
+
Check simulation logs for wanted and forbidden patterns.
|
|
208
196
|
|
|
209
197
|
Parameters
|
|
210
198
|
----------
|
|
211
|
-
tar_file:
|
|
212
|
-
Path to
|
|
213
|
-
file_test: dict
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
199
|
+
tar_file : str
|
|
200
|
+
Path to the tar file.
|
|
201
|
+
file_test : dict
|
|
202
|
+
Dictionary with the test configuration.
|
|
203
|
+
|
|
204
|
+
Returns
|
|
205
|
+
-------
|
|
206
|
+
bool
|
|
207
|
+
True if the logs are correct.
|
|
220
208
|
"""
|
|
221
|
-
|
|
222
|
-
wanted
|
|
223
|
-
forbidden = expected_log.get("forbidden_pattern", [])
|
|
224
|
-
|
|
225
|
-
if not (wanted or forbidden):
|
|
226
|
-
_logger.debug(f"No expected log output provided, skipping checks {file_test}")
|
|
209
|
+
wanted, forbidden = _get_expected_patterns(file_test)
|
|
210
|
+
if wanted is None:
|
|
227
211
|
return True
|
|
228
212
|
|
|
229
213
|
if not tarfile.is_tarfile(tar_file):
|
|
230
|
-
raise ValueError(f"
|
|
214
|
+
raise ValueError(f"{tar_file} is not a tar file")
|
|
231
215
|
|
|
232
|
-
|
|
216
|
+
found_wanted = set()
|
|
217
|
+
found_forbidden = set()
|
|
233
218
|
with tarfile.open(tar_file, "r:*") as tar:
|
|
234
219
|
for member in tar.getmembers():
|
|
235
220
|
if not member.name.endswith(".log.gz"):
|
|
236
221
|
continue
|
|
237
222
|
_logger.info(f"Scanning {member.name}")
|
|
238
223
|
text = _read_log(member, tar)
|
|
239
|
-
|
|
240
|
-
|
|
224
|
+
found_wanted |= _find_patterns(text, wanted)
|
|
225
|
+
found_forbidden |= _find_patterns(text, forbidden)
|
|
226
|
+
|
|
227
|
+
return _validate_patterns(found_wanted, found_forbidden, wanted)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def check_plain_log(log_file, file_test):
|
|
231
|
+
"""
|
|
232
|
+
Check plain log file for wanted and forbidden patterns.
|
|
233
|
+
|
|
234
|
+
Parameters
|
|
235
|
+
----------
|
|
236
|
+
log_file : str
|
|
237
|
+
Path to the log file.
|
|
238
|
+
file_test : dict
|
|
239
|
+
Dictionary with the test configuration.
|
|
240
|
+
|
|
241
|
+
Returns
|
|
242
|
+
-------
|
|
243
|
+
bool
|
|
244
|
+
True if the logs are correct.
|
|
245
|
+
"""
|
|
246
|
+
wanted, forbidden = _get_expected_patterns(file_test)
|
|
247
|
+
if wanted is None:
|
|
248
|
+
return True
|
|
249
|
+
|
|
250
|
+
try:
|
|
251
|
+
with open(log_file, encoding="utf-8") as f:
|
|
252
|
+
text = f.read()
|
|
253
|
+
except FileNotFoundError:
|
|
254
|
+
_logger.error(f"Log file {log_file} not found")
|
|
255
|
+
return False
|
|
256
|
+
|
|
257
|
+
found = _find_patterns(text, wanted)
|
|
258
|
+
bad = _find_patterns(text, forbidden)
|
|
259
|
+
|
|
260
|
+
return _validate_patterns(found, bad, wanted)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def _get_expected_patterns(file_test):
|
|
264
|
+
"""Get wanted and forbidden patterns from file test configuration."""
|
|
265
|
+
expected_log = file_test.get("expected_log_output")
|
|
266
|
+
if isinstance(expected_log, dict):
|
|
267
|
+
wanted = expected_log.get("pattern", [])
|
|
268
|
+
forbidden = expected_log.get("forbidden_pattern", [])
|
|
269
|
+
else:
|
|
270
|
+
wanted = file_test.get("pattern", [])
|
|
271
|
+
forbidden = file_test.get("forbidden_pattern", [])
|
|
272
|
+
if not (wanted or forbidden):
|
|
273
|
+
_logger.debug(f"No expected log output provided, skipping checks {file_test}")
|
|
274
|
+
return None, None
|
|
241
275
|
|
|
276
|
+
return wanted, forbidden
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _validate_patterns(found, bad, wanted):
|
|
280
|
+
"""Validate found patterns against wanted and forbidden ones."""
|
|
242
281
|
if bad:
|
|
243
282
|
_logger.error(f"Forbidden patterns found: {list(bad)}")
|
|
244
283
|
return False
|
|
@@ -249,3 +288,15 @@ def check_simulation_logs(tar_file, file_test):
|
|
|
249
288
|
|
|
250
289
|
_logger.debug(f"All expected patterns found: {wanted}")
|
|
251
290
|
return True
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def _find_patterns(text, patterns):
|
|
294
|
+
"""Find patterns in text (case insensitive)."""
|
|
295
|
+
text_lower = text.lower()
|
|
296
|
+
return {p for p in patterns if p.lower() in text_lower}
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _read_log(member, tar):
|
|
300
|
+
"""Read and decode a gzipped log file from a tar archive."""
|
|
301
|
+
with tar.extractfile(member) as gz, gzip.open(gz, "rb") as f:
|
|
302
|
+
return f.read().decode("utf-8", "ignore")
|
|
@@ -76,8 +76,7 @@ def _read_configs_from_files(config_files):
|
|
|
76
76
|
_dict = gen.remove_substring_recursively_from_dict(
|
|
77
77
|
ascii_handler.collect_data_from_file(file_name=config_file), substring="\n"
|
|
78
78
|
)
|
|
79
|
-
|
|
80
|
-
configs.append(application)
|
|
79
|
+
configs.extend(_dict.get("applications", []))
|
|
81
80
|
return configs
|
|
82
81
|
|
|
83
82
|
|
simtools/testing/helpers.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"""Helper functions for integration testing."""
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
from pathlib import Path
|
|
3
|
+
from simtools import settings
|
|
5
4
|
|
|
6
5
|
|
|
7
6
|
def skip_camera_efficiency(config):
|
|
@@ -21,7 +20,7 @@ def _new_testeff_version():
|
|
|
21
20
|
|
|
22
21
|
This test checks if the new version is used.
|
|
23
22
|
"""
|
|
24
|
-
testeff_path =
|
|
23
|
+
testeff_path = settings.config.sim_telarray_path / "testeff.c"
|
|
25
24
|
try:
|
|
26
25
|
with open(testeff_path, encoding="utf-8") as file:
|
|
27
26
|
file_content = file.read()
|
|
@@ -18,6 +18,7 @@ ERROR_PATTERNS = [
|
|
|
18
18
|
IGNORE_PATTERNS = [
|
|
19
19
|
re.compile(r"Falling back to 'utf-8' with errors='ignore'", re.IGNORECASE),
|
|
20
20
|
re.compile(r"Failed to get user name[^\n]*setting it to UNKNOWN_USER", re.IGNORECASE),
|
|
21
|
+
re.compile(r"adjust_text::Error", re.IGNORECASE),
|
|
21
22
|
]
|
|
22
23
|
|
|
23
24
|
|
|
@@ -4,13 +4,13 @@ import logging
|
|
|
4
4
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
|
|
7
|
-
from simtools.
|
|
7
|
+
from simtools.sim_events.file_info import get_corsika_run_number
|
|
8
8
|
from simtools.simtel.simtel_config_reader import SimtelConfigReader
|
|
9
|
-
from simtools.simtel.simtel_config_writer import sim_telarray_random_seeds
|
|
10
9
|
from simtools.simtel.simtel_io_metadata import (
|
|
11
10
|
get_sim_telarray_telescope_id,
|
|
12
11
|
read_sim_telarray_metadata,
|
|
13
12
|
)
|
|
13
|
+
from simtools.utils import random
|
|
14
14
|
|
|
15
15
|
_logger = logging.getLogger(__name__)
|
|
16
16
|
|
|
@@ -30,7 +30,7 @@ def assert_sim_telarray_metadata(file, array_model):
|
|
|
30
30
|
_logger.info(f"Found metadata in sim_telarray file for {len(telescope_meta)} telescopes")
|
|
31
31
|
site_parameter_mismatch = _assert_model_parameters(global_meta, array_model.site_model)
|
|
32
32
|
sim_telarray_seed_mismatch = _assert_sim_telarray_seed(
|
|
33
|
-
global_meta, array_model.
|
|
33
|
+
global_meta, array_model.sim_telarray_seed, file
|
|
34
34
|
)
|
|
35
35
|
if sim_telarray_seed_mismatch:
|
|
36
36
|
site_parameter_mismatch.append(sim_telarray_seed_mismatch)
|
|
@@ -101,7 +101,7 @@ def _assert_model_parameters(metadata, model):
|
|
|
101
101
|
return invalid_parameter_list
|
|
102
102
|
|
|
103
103
|
|
|
104
|
-
def _assert_sim_telarray_seed(metadata,
|
|
104
|
+
def _assert_sim_telarray_seed(metadata, sim_telarray_seed, file=None):
|
|
105
105
|
"""
|
|
106
106
|
Assert that sim_telarray seed matches the values in the sim_telarray metadata.
|
|
107
107
|
|
|
@@ -111,8 +111,8 @@ def _assert_sim_telarray_seed(metadata, sim_telarray_seeds, file=None):
|
|
|
111
111
|
----------
|
|
112
112
|
metadata: dict
|
|
113
113
|
Metadata dictionary.
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
sim_telarray_seed: SimtelSeeds
|
|
115
|
+
sim_telarray seed.
|
|
116
116
|
file : Path
|
|
117
117
|
Path to the sim_telarray file.
|
|
118
118
|
|
|
@@ -122,23 +122,25 @@ def _assert_sim_telarray_seed(metadata, sim_telarray_seeds, file=None):
|
|
|
122
122
|
Error message if sim_telarray seeds do not match.
|
|
123
123
|
|
|
124
124
|
"""
|
|
125
|
-
if
|
|
125
|
+
if sim_telarray_seed is None:
|
|
126
126
|
return None
|
|
127
127
|
|
|
128
128
|
if "instrument_seed" in metadata.keys() and "instrument_instances" in metadata.keys():
|
|
129
|
-
if str(metadata.get("instrument_seed")) != str(
|
|
129
|
+
if str(metadata.get("instrument_seed")) != str(sim_telarray_seed.instrument_seed):
|
|
130
130
|
return (
|
|
131
131
|
"Parameter instrument_seed mismatch between sim_telarray file: "
|
|
132
|
-
f"{metadata['instrument_seed']}, and model: {
|
|
132
|
+
f"{metadata['instrument_seed']}, and model: {sim_telarray_seed.instrument_seed}"
|
|
133
133
|
)
|
|
134
134
|
_logger.info(
|
|
135
135
|
f"sim_telarray_seed in sim_telarray file: {metadata['instrument_seed']}, "
|
|
136
|
-
f"and model: {
|
|
136
|
+
f"and model: {sim_telarray_seed.instrument_seed}"
|
|
137
137
|
)
|
|
138
138
|
if file:
|
|
139
139
|
run_number_modified = get_corsika_run_number(file) - 1
|
|
140
|
-
test_seeds =
|
|
141
|
-
int(metadata["
|
|
140
|
+
test_seeds = random.seeds(
|
|
141
|
+
n_seeds=int(metadata["instrument_instances"]),
|
|
142
|
+
max_seed=np.iinfo(np.int32).max,
|
|
143
|
+
fixed_seed=int(metadata["instrument_seed"]),
|
|
142
144
|
)
|
|
143
145
|
# no +1 as in sim_telarray (as we count from 0)
|
|
144
146
|
seed_used = run_number_modified % int(metadata["instrument_instances"])
|
|
@@ -13,30 +13,40 @@ from simtools.testing import assertions
|
|
|
13
13
|
|
|
14
14
|
_logger = logging.getLogger(__name__)
|
|
15
15
|
|
|
16
|
+
|
|
17
|
+
def _versions_match(from_command_line, from_config_file):
|
|
18
|
+
"""Return True if validations should run for the given versions.
|
|
19
|
+
|
|
20
|
+
Behavior:
|
|
21
|
+
- If no version is provided from the command line, run validations.
|
|
22
|
+
- If a filter is provided from the command line, run only when it matches
|
|
23
|
+
the version(s) from the config file.
|
|
24
|
+
"""
|
|
25
|
+
if from_command_line is None:
|
|
26
|
+
return True
|
|
27
|
+
|
|
28
|
+
# Normalize to collections for comparison
|
|
29
|
+
cmd_versions = from_command_line if isinstance(from_command_line, list) else [from_command_line]
|
|
30
|
+
cfg_versions = from_config_file if isinstance(from_config_file, list) else [from_config_file]
|
|
31
|
+
|
|
32
|
+
# Consider a match if any overlap exists
|
|
33
|
+
return any(cv in cmd_versions for cv in cfg_versions)
|
|
34
|
+
|
|
35
|
+
|
|
16
36
|
# Keys to ignore when comparing sim_telarray configuration files
|
|
17
37
|
# (e.g., version numbers, system dependent parameters, CORSIKA options)
|
|
18
38
|
cfg_ignore_keys = [
|
|
19
39
|
"config_release",
|
|
20
40
|
"Label",
|
|
21
|
-
"
|
|
22
|
-
"simtools_model_production_version",
|
|
23
|
-
"simtools_build_opt",
|
|
24
|
-
"simtools_extra_def",
|
|
25
|
-
"simtools_hadronic_model",
|
|
26
|
-
"simtools_avx_flag",
|
|
27
|
-
"simtools_corsika_version",
|
|
28
|
-
"simtools_corsika_opt_patch_version",
|
|
29
|
-
"simtools_bernlohr_version",
|
|
41
|
+
"simtools_", # ignore all simtools_ keys - version/build info dependence
|
|
30
42
|
]
|
|
31
43
|
|
|
32
44
|
|
|
33
|
-
def validate_application_output(
|
|
34
|
-
config, from_command_line=None, from_config_file=None, db_config=None
|
|
35
|
-
):
|
|
45
|
+
def validate_application_output(config, from_command_line=None, from_config_file=None):
|
|
36
46
|
"""
|
|
37
47
|
Validate application output against expected output.
|
|
38
48
|
|
|
39
|
-
Expected output is defined in configuration file.
|
|
49
|
+
Expected output is defined in the test configuration file.
|
|
40
50
|
Some tests run only if the model version from the command line
|
|
41
51
|
equals the model version from the configuration file.
|
|
42
52
|
|
|
@@ -60,8 +70,8 @@ def validate_application_output(
|
|
|
60
70
|
f"from config file: {from_config_file}"
|
|
61
71
|
)
|
|
62
72
|
|
|
63
|
-
if from_command_line
|
|
64
|
-
_validate_output_files(config, integration_test
|
|
73
|
+
if _versions_match(from_command_line, from_config_file):
|
|
74
|
+
_validate_output_files(config, integration_test)
|
|
65
75
|
|
|
66
76
|
if "file_type" in integration_test:
|
|
67
77
|
assert assertions.assert_file_type(
|
|
@@ -73,11 +83,13 @@ def validate_application_output(
|
|
|
73
83
|
_test_simtel_cfg_files(config, integration_test, from_command_line, from_config_file)
|
|
74
84
|
|
|
75
85
|
|
|
76
|
-
def _validate_output_files(config, integration_test
|
|
86
|
+
def _validate_output_files(config, integration_test):
|
|
77
87
|
"""Validate output files."""
|
|
78
88
|
if "reference_output_file" in integration_test:
|
|
79
89
|
_validate_reference_output_file(config, integration_test)
|
|
80
90
|
if "test_output_files" in integration_test:
|
|
91
|
+
if isinstance(integration_test["test_output_files"], dict):
|
|
92
|
+
integration_test["test_output_files"] = [integration_test["test_output_files"]]
|
|
81
93
|
_validate_output_path_and_file(config, integration_test["test_output_files"])
|
|
82
94
|
if "output_file" in integration_test:
|
|
83
95
|
_validate_output_path_and_file(
|
|
@@ -85,11 +97,7 @@ def _validate_output_files(config, integration_test, db_config):
|
|
|
85
97
|
[{"path_descriptor": "output_path", "file": integration_test["output_file"]}],
|
|
86
98
|
)
|
|
87
99
|
if "model_parameter_validation" in integration_test:
|
|
88
|
-
_validate_model_parameter_json_file(
|
|
89
|
-
config,
|
|
90
|
-
integration_test["model_parameter_validation"],
|
|
91
|
-
db_config,
|
|
92
|
-
)
|
|
100
|
+
_validate_model_parameter_json_file(config, integration_test["model_parameter_validation"])
|
|
93
101
|
|
|
94
102
|
|
|
95
103
|
def _test_simtel_cfg_files(config, integration_test, from_command_line, from_config_file):
|
|
@@ -110,11 +118,10 @@ def _test_simtel_cfg_files(config, integration_test, from_command_line, from_con
|
|
|
110
118
|
|
|
111
119
|
def _validate_reference_output_file(config, integration_test):
|
|
112
120
|
"""Compare with reference output file."""
|
|
121
|
+
test_file = integration_test.get("test_output_file") or config["configuration"]["output_file"]
|
|
113
122
|
assert compare_files(
|
|
114
123
|
integration_test["reference_output_file"],
|
|
115
|
-
Path(config["configuration"]["output_path"]).joinpath(
|
|
116
|
-
config["configuration"]["output_file"]
|
|
117
|
-
),
|
|
124
|
+
Path(config["configuration"]["output_path"]).joinpath(test_file),
|
|
118
125
|
integration_test.get("tolerance", 1.0e-5),
|
|
119
126
|
integration_test.get("test_columns", None),
|
|
120
127
|
)
|
|
@@ -135,15 +142,20 @@ def _validate_output_path_and_file(config, integration_file_tests):
|
|
|
135
142
|
try:
|
|
136
143
|
assert output_file_path.exists()
|
|
137
144
|
except AssertionError as exc:
|
|
138
|
-
raise AssertionError(
|
|
145
|
+
raise AssertionError(
|
|
146
|
+
f"Output file {output_file_path} does not exist. "
|
|
147
|
+
f"Directory contents: {list(output_file_path.parent.iterdir())}"
|
|
148
|
+
) from exc
|
|
139
149
|
|
|
140
150
|
if output_file_path.name.endswith(".simtel.zst"):
|
|
141
151
|
assert assertions.check_output_from_sim_telarray(output_file_path, file_test)
|
|
142
152
|
elif output_file_path.name.endswith(".log_hist.tar.gz"):
|
|
143
153
|
assert assertions.check_simulation_logs(output_file_path, file_test)
|
|
154
|
+
elif output_file_path.suffix == ".log":
|
|
155
|
+
assert assertions.check_plain_log(output_file_path, file_test)
|
|
144
156
|
|
|
145
157
|
|
|
146
|
-
def _validate_model_parameter_json_file(config, model_parameter_validation
|
|
158
|
+
def _validate_model_parameter_json_file(config, model_parameter_validation):
|
|
147
159
|
"""
|
|
148
160
|
Validate model parameter json file and compare it with a reference parameter from the database.
|
|
149
161
|
|
|
@@ -158,7 +170,7 @@ def _validate_model_parameter_json_file(config, model_parameter_validation, db_c
|
|
|
158
170
|
|
|
159
171
|
"""
|
|
160
172
|
_logger.info(f"Checking model parameter json file: {model_parameter_validation}")
|
|
161
|
-
db = db_handler.DatabaseHandler(
|
|
173
|
+
db = db_handler.DatabaseHandler()
|
|
162
174
|
|
|
163
175
|
reference_parameter_name = model_parameter_validation.get("reference_parameter_name")
|
|
164
176
|
|
|
@@ -178,6 +190,7 @@ def _validate_model_parameter_json_file(config, model_parameter_validation, db_c
|
|
|
178
190
|
model_parameter["value"],
|
|
179
191
|
reference_model_parameter[reference_parameter_name]["value"],
|
|
180
192
|
model_parameter_validation["tolerance"],
|
|
193
|
+
model_parameter_validation.get("scaling", 1.0),
|
|
181
194
|
)
|
|
182
195
|
|
|
183
196
|
|
|
@@ -216,12 +229,56 @@ def compare_files(file1, file2, tolerance=1.0e-5, test_columns=None):
|
|
|
216
229
|
return False
|
|
217
230
|
|
|
218
231
|
|
|
232
|
+
def _compare_nested_dicts_with_tolerance(data1, data2, tolerance, is_value_field=False):
|
|
233
|
+
"""
|
|
234
|
+
Recursively compare nested dictionaries, applying allclose to "value" fields.
|
|
235
|
+
|
|
236
|
+
Parameters
|
|
237
|
+
----------
|
|
238
|
+
data1 : dict, list, or scalar
|
|
239
|
+
First data to compare
|
|
240
|
+
data2 : dict, list, or scalar
|
|
241
|
+
Second data to compare
|
|
242
|
+
tolerance : float
|
|
243
|
+
Tolerance for comparing numerical values
|
|
244
|
+
is_value_field : bool
|
|
245
|
+
Whether this data is from a "value" key (applies tolerance to scalars)
|
|
246
|
+
|
|
247
|
+
Returns
|
|
248
|
+
-------
|
|
249
|
+
bool
|
|
250
|
+
True if the data are equal within tolerance
|
|
251
|
+
"""
|
|
252
|
+
if isinstance(data1, dict) and isinstance(data2, dict):
|
|
253
|
+
return data1.keys() == data2.keys() and all(
|
|
254
|
+
_compare_nested_dicts_with_tolerance(
|
|
255
|
+
data1[k], data2[k], tolerance, is_value_field=(k == "value")
|
|
256
|
+
)
|
|
257
|
+
for k in data1
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
if isinstance(data1, (list, tuple)) and isinstance(data2, (list, tuple)):
|
|
261
|
+
return len(data1) == len(data2) and all(
|
|
262
|
+
_compare_nested_dicts_with_tolerance(v1, v2, tolerance, is_value_field)
|
|
263
|
+
for v1, v2 in zip(data1, data2)
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
# Apply tolerance if this is a "value" field, otherwise use exact equality
|
|
267
|
+
if is_value_field:
|
|
268
|
+
try:
|
|
269
|
+
return _compare_value_from_parameter_dict(data1, data2, tolerance)
|
|
270
|
+
except (TypeError, ValueError):
|
|
271
|
+
return data1 == data2
|
|
272
|
+
return data1 == data2
|
|
273
|
+
|
|
274
|
+
|
|
219
275
|
def compare_json_or_yaml_files(file1, file2, tolerance=1.0e-2):
|
|
220
276
|
"""
|
|
221
277
|
Compare two json or yaml files.
|
|
222
278
|
|
|
223
279
|
Take into account float comparison for sim_telarray string-embedded floats.
|
|
224
280
|
Allow differences in 'schema_version' field.
|
|
281
|
+
Works recursively for nested dicts with "value" fields on any level.
|
|
225
282
|
|
|
226
283
|
Parameters
|
|
227
284
|
----------
|
|
@@ -248,24 +305,45 @@ def compare_json_or_yaml_files(file1, file2, tolerance=1.0e-2):
|
|
|
248
305
|
if data1 == data2:
|
|
249
306
|
return True
|
|
250
307
|
|
|
251
|
-
if data1
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
else data1[k] == data2[k]
|
|
259
|
-
)
|
|
260
|
-
for k in data1
|
|
308
|
+
if isinstance(data1, dict) and isinstance(data2, dict):
|
|
309
|
+
if data1.keys() != data2.keys():
|
|
310
|
+
_logger.error(f"Keys do not match: {data1.keys()} and {data2.keys()}")
|
|
311
|
+
return False
|
|
312
|
+
|
|
313
|
+
_comparison = _compare_nested_dicts_with_tolerance(
|
|
314
|
+
data1, data2, tolerance, is_value_field=False
|
|
261
315
|
)
|
|
262
316
|
if not _comparison:
|
|
263
317
|
_logger.error(f"Values do not match: {data1} and {data2} (tolerance: {tolerance})")
|
|
264
318
|
return _comparison
|
|
265
319
|
|
|
266
320
|
|
|
267
|
-
def _compare_value_from_parameter_dict(
|
|
268
|
-
"""
|
|
321
|
+
def _compare_value_from_parameter_dict(data_1, data_2, tolerance=1.0e-5, factor_1=1.0):
|
|
322
|
+
"""
|
|
323
|
+
Compare value fields given in different formats.
|
|
324
|
+
|
|
325
|
+
Parameters
|
|
326
|
+
----------
|
|
327
|
+
data_1 : float, int, str, list, numpy.ndarray
|
|
328
|
+
First value or collection of values to compare. May be a scalar,
|
|
329
|
+
a sequence, a numpy array, or a string representation of a list.
|
|
330
|
+
data_2 : float, int, str, list, numpy.ndarray
|
|
331
|
+
Second value or collection of values to compare, with the same
|
|
332
|
+
allowed formats as ``data_2``.
|
|
333
|
+
tolerance : float, optional
|
|
334
|
+
Relative tolerance used when comparing numerical values via
|
|
335
|
+
``numpy.allclose``.
|
|
336
|
+
factor1 : float, optional
|
|
337
|
+
Multiplicative factor applied to ``data_1`` before comparison. This
|
|
338
|
+
can be used to account for unit conversions or normalisation
|
|
339
|
+
differences between ``data_1`` and ``data_2``.
|
|
340
|
+
|
|
341
|
+
Returns
|
|
342
|
+
-------
|
|
343
|
+
bool
|
|
344
|
+
True if the two values are considered equal within the given
|
|
345
|
+
tolerance, False otherwise.
|
|
346
|
+
"""
|
|
269
347
|
|
|
270
348
|
def _as_list(value):
|
|
271
349
|
if isinstance(value, str):
|
|
@@ -274,12 +352,13 @@ def _compare_value_from_parameter_dict(data1, data2, tolerance=1.0e-5):
|
|
|
274
352
|
return value
|
|
275
353
|
return [value]
|
|
276
354
|
|
|
277
|
-
_logger.info(f"Comparing values: {
|
|
355
|
+
_logger.info(f"Comparing values: {data_1} and {data_2} (tolerance: {tolerance})")
|
|
278
356
|
|
|
279
|
-
_as_list_1 = _as_list(
|
|
280
|
-
_as_list_2 = _as_list(
|
|
357
|
+
_as_list_1 = _as_list(data_1)
|
|
358
|
+
_as_list_2 = _as_list(data_2)
|
|
281
359
|
if isinstance(_as_list_1, str):
|
|
282
360
|
return _as_list_1 == _as_list_2
|
|
361
|
+
_as_list_1 = np.array(_as_list_1) * factor_1
|
|
283
362
|
return np.allclose(_as_list_1, _as_list_2, rtol=tolerance)
|
|
284
363
|
|
|
285
364
|
|
simtools/utils/general.py
CHANGED
|
@@ -11,6 +11,7 @@ import urllib.request
|
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
from urllib.parse import urlparse
|
|
13
13
|
|
|
14
|
+
import dotenv
|
|
14
15
|
import numpy as np
|
|
15
16
|
|
|
16
17
|
_logger = logging.getLogger(__name__)
|
|
@@ -581,6 +582,12 @@ def validate_data_type(reference_dtype, value=None, dtype=None, allow_subtypes=T
|
|
|
581
582
|
if reference_dtype in ("boolean", "bool"):
|
|
582
583
|
return _is_valid_boolean_type(dtype, value)
|
|
583
584
|
|
|
585
|
+
if reference_dtype == "dict":
|
|
586
|
+
return isinstance(value, dict)
|
|
587
|
+
|
|
588
|
+
if reference_dtype == "list":
|
|
589
|
+
return isinstance(value, list)
|
|
590
|
+
|
|
584
591
|
return _is_valid_numeric_type(dtype, reference_dtype)
|
|
585
592
|
|
|
586
593
|
|
|
@@ -795,23 +802,6 @@ def find_differences_in_json_objects(obj1, obj2, path=""):
|
|
|
795
802
|
return diffs
|
|
796
803
|
|
|
797
804
|
|
|
798
|
-
def clear_default_sim_telarray_cfg_directories(command):
|
|
799
|
-
"""Prefix the command to clear default sim_telarray configuration directories.
|
|
800
|
-
|
|
801
|
-
Parameters
|
|
802
|
-
----------
|
|
803
|
-
command: str
|
|
804
|
-
Command to be prefixed.
|
|
805
|
-
|
|
806
|
-
Returns
|
|
807
|
-
-------
|
|
808
|
-
str
|
|
809
|
-
Prefixed command.
|
|
810
|
-
|
|
811
|
-
"""
|
|
812
|
-
return f"SIM_TELARRAY_CONFIG_PATH='' {command}"
|
|
813
|
-
|
|
814
|
-
|
|
815
805
|
def get_list_of_files_from_command_line(file_names, suffix_list):
|
|
816
806
|
"""
|
|
817
807
|
Get a list of files from the command line.
|
|
@@ -850,3 +840,39 @@ def get_list_of_files_from_command_line(file_names, suffix_list):
|
|
|
850
840
|
def now_date_time_in_isoformat():
|
|
851
841
|
"""Return date and time in isoformat and second accuracy."""
|
|
852
842
|
return datetime.datetime.now(datetime.UTC).isoformat(timespec="seconds")
|
|
843
|
+
|
|
844
|
+
|
|
845
|
+
def load_environment_variables(env_file=".env", env_list=None):
|
|
846
|
+
"""
|
|
847
|
+
Load environment variables (from a .env file or directly from the environment).
|
|
848
|
+
|
|
849
|
+
Allow to read a specific list of variables or all variables from the .env file.
|
|
850
|
+
|
|
851
|
+
Parameters
|
|
852
|
+
----------
|
|
853
|
+
env_file: str
|
|
854
|
+
Path to the .env file.
|
|
855
|
+
env_list: list, optional
|
|
856
|
+
List of environment variables to be read. If None, all variables are read.
|
|
857
|
+
|
|
858
|
+
Returns
|
|
859
|
+
-------
|
|
860
|
+
dict
|
|
861
|
+
Dictionary mapping environment variable names (lowercase, without the
|
|
862
|
+
``SIMTOOLS_`` prefix) to their cleaned string values.
|
|
863
|
+
"""
|
|
864
|
+
dotenv.load_dotenv(env_file or None)
|
|
865
|
+
keys = (
|
|
866
|
+
list(dotenv.dotenv_values(env_file).keys())
|
|
867
|
+
if env_list is None
|
|
868
|
+
else [f"SIMTOOLS_{s.upper()}" for s in env_list]
|
|
869
|
+
)
|
|
870
|
+
|
|
871
|
+
env_values = {}
|
|
872
|
+
for key in keys:
|
|
873
|
+
env_value = os.environ.get(key)
|
|
874
|
+
if env_value is None:
|
|
875
|
+
continue
|
|
876
|
+
cleaned_value = env_value.split("#")[0].strip().replace('"', "").replace("'", "")
|
|
877
|
+
env_values[key.removeprefix("SIMTOOLS_").lower()] = cleaned_value
|
|
878
|
+
return env_values
|