gammasimtools 0.24.0__py3-none-any.whl → 0.25.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.24.0.dist-info → gammasimtools-0.25.0.dist-info}/METADATA +1 -1
- {gammasimtools-0.24.0.dist-info → gammasimtools-0.25.0.dist-info}/RECORD +58 -55
- {gammasimtools-0.24.0.dist-info → gammasimtools-0.25.0.dist-info}/entry_points.txt +1 -0
- simtools/_version.py +2 -2
- simtools/application_control.py +50 -0
- simtools/applications/derive_psf_parameters.py +5 -0
- simtools/applications/derive_pulse_shape_parameters.py +195 -0
- simtools/applications/plot_array_layout.py +63 -1
- simtools/applications/simulate_flasher.py +3 -2
- simtools/applications/simulate_pedestals.py +1 -1
- simtools/applications/simulate_prod.py +8 -23
- simtools/applications/simulate_prod_htcondor_generator.py +7 -0
- simtools/applications/submit_array_layouts.py +5 -3
- simtools/applications/validate_file_using_schema.py +49 -123
- simtools/configuration/commandline_parser.py +8 -6
- simtools/corsika/corsika_config.py +197 -87
- simtools/data_model/model_data_writer.py +14 -2
- simtools/data_model/schema.py +112 -5
- simtools/data_model/validate_data.py +82 -48
- simtools/db/db_model_upload.py +2 -1
- simtools/db/mongo_db.py +133 -42
- simtools/dependencies.py +5 -9
- simtools/io/eventio_handler.py +128 -0
- simtools/job_execution/htcondor_script_generator.py +0 -2
- simtools/layout/array_layout_utils.py +1 -1
- simtools/model/array_model.py +36 -5
- simtools/model/model_parameter.py +0 -1
- simtools/model/model_repository.py +18 -5
- simtools/ray_tracing/psf_analysis.py +11 -8
- simtools/ray_tracing/psf_parameter_optimisation.py +822 -679
- simtools/reporting/docs_read_parameters.py +69 -9
- simtools/runners/corsika_runner.py +12 -3
- simtools/runners/corsika_simtel_runner.py +6 -0
- simtools/runners/runner_services.py +17 -7
- simtools/runners/simtel_runner.py +12 -54
- simtools/schemas/model_parameters/flasher_pulse_exp_decay.schema.yml +2 -0
- simtools/schemas/model_parameters/flasher_pulse_shape.schema.yml +50 -0
- simtools/schemas/model_parameters/flasher_pulse_width.schema.yml +2 -0
- simtools/schemas/simulation_models_info.schema.yml +2 -0
- simtools/simtel/pulse_shapes.py +268 -0
- simtools/simtel/simtel_config_writer.py +82 -1
- simtools/simtel/simtel_io_event_writer.py +2 -2
- simtools/simtel/simulator_array.py +58 -12
- simtools/simtel/simulator_light_emission.py +45 -8
- simtools/simulator.py +361 -347
- simtools/testing/assertions.py +62 -6
- simtools/testing/configuration.py +1 -1
- simtools/testing/log_inspector.py +4 -1
- simtools/testing/sim_telarray_metadata.py +1 -1
- simtools/testing/validate_output.py +44 -9
- simtools/utils/names.py +2 -4
- simtools/version.py +37 -0
- simtools/visualization/legend_handlers.py +14 -4
- simtools/visualization/plot_array_layout.py +229 -33
- simtools/visualization/plot_mirrors.py +837 -0
- simtools/simtel/simtel_io_file_info.py +0 -62
- {gammasimtools-0.24.0.dist-info → gammasimtools-0.25.0.dist-info}/WHEEL +0 -0
- {gammasimtools-0.24.0.dist-info → gammasimtools-0.25.0.dist-info}/licenses/LICENSE +0 -0
- {gammasimtools-0.24.0.dist-info → gammasimtools-0.25.0.dist-info}/top_level.txt +0 -0
simtools/testing/assertions.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"""Functions asserting certain conditions are met (used e.g., in integration tests)."""
|
|
2
2
|
|
|
3
|
+
import gzip
|
|
3
4
|
import json
|
|
4
5
|
import logging
|
|
6
|
+
import tarfile
|
|
5
7
|
from collections import defaultdict
|
|
6
8
|
from pathlib import Path
|
|
7
9
|
|
|
@@ -174,12 +176,6 @@ def check_output_from_sim_telarray(file, file_test):
|
|
|
174
176
|
_logger.debug(f"No expected output or metadata provided, skipping checks {file_test}")
|
|
175
177
|
return True
|
|
176
178
|
|
|
177
|
-
if file.suffix != ".zst":
|
|
178
|
-
raise ValueError(
|
|
179
|
-
f"Expected output file {file} is not a zstd compressed file "
|
|
180
|
-
f"(i.e., a sim_telarray file)."
|
|
181
|
-
)
|
|
182
|
-
|
|
183
179
|
assert_output = assert_metadata = True
|
|
184
180
|
|
|
185
181
|
if "expected_output" in file_test:
|
|
@@ -193,3 +189,63 @@ def check_output_from_sim_telarray(file, file_test):
|
|
|
193
189
|
)
|
|
194
190
|
|
|
195
191
|
return assert_n_showers_and_energy_range(file=file) and assert_output and assert_metadata
|
|
192
|
+
|
|
193
|
+
|
|
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
|
+
def check_simulation_logs(tar_file, file_test):
|
|
206
|
+
"""
|
|
207
|
+
Check log files of CORSIKA and sim_telarray for expected output.
|
|
208
|
+
|
|
209
|
+
Parameters
|
|
210
|
+
----------
|
|
211
|
+
tar_file: Path
|
|
212
|
+
Path to a log file tar package.
|
|
213
|
+
file_test: dict
|
|
214
|
+
File test description including expected log output.
|
|
215
|
+
|
|
216
|
+
Raises
|
|
217
|
+
------
|
|
218
|
+
ValueError
|
|
219
|
+
If the file is not a tar file.
|
|
220
|
+
"""
|
|
221
|
+
expected_log = file_test.get("expected_log_output", {})
|
|
222
|
+
wanted = expected_log.get("pattern", [])
|
|
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}")
|
|
227
|
+
return True
|
|
228
|
+
|
|
229
|
+
if not tarfile.is_tarfile(tar_file):
|
|
230
|
+
raise ValueError(f"File {tar_file} is not a tar file.")
|
|
231
|
+
|
|
232
|
+
found, bad = set(), set()
|
|
233
|
+
with tarfile.open(tar_file, "r:*") as tar:
|
|
234
|
+
for member in tar.getmembers():
|
|
235
|
+
if not member.name.endswith(".log.gz"):
|
|
236
|
+
continue
|
|
237
|
+
_logger.info(f"Scanning {member.name}")
|
|
238
|
+
text = _read_log(member, tar)
|
|
239
|
+
found |= _find_patterns(text, wanted)
|
|
240
|
+
bad |= _find_patterns(text, forbidden)
|
|
241
|
+
|
|
242
|
+
if bad:
|
|
243
|
+
_logger.error(f"Forbidden patterns found: {list(bad)}")
|
|
244
|
+
return False
|
|
245
|
+
missing = [p for p in wanted if p not in found]
|
|
246
|
+
if missing:
|
|
247
|
+
_logger.error(f"Missing expected patterns: {missing}")
|
|
248
|
+
return False
|
|
249
|
+
|
|
250
|
+
_logger.debug(f"All expected patterns found: {wanted}")
|
|
251
|
+
return True
|
|
@@ -204,7 +204,7 @@ def _prepare_test_options(config, output_path, model_version=None):
|
|
|
204
204
|
if model_version and "model_version" in config:
|
|
205
205
|
config.update({"model_version": model_version})
|
|
206
206
|
|
|
207
|
-
for key in ["output_path", "
|
|
207
|
+
for key in ["output_path", "pack_for_grid_register"]:
|
|
208
208
|
if key in config:
|
|
209
209
|
config[key] = str(Path(output_path).joinpath(config[key]))
|
|
210
210
|
|
|
@@ -15,7 +15,10 @@ ERROR_PATTERNS = [
|
|
|
15
15
|
re.compile(r"segmentation fault", re.IGNORECASE),
|
|
16
16
|
]
|
|
17
17
|
|
|
18
|
-
IGNORE_PATTERNS = [
|
|
18
|
+
IGNORE_PATTERNS = [
|
|
19
|
+
re.compile(r"Falling back to 'utf-8' with errors='ignore'", re.IGNORECASE),
|
|
20
|
+
re.compile(r"Failed to get user name[^\n]*setting it to UNKNOWN_USER", re.IGNORECASE),
|
|
21
|
+
]
|
|
19
22
|
|
|
20
23
|
|
|
21
24
|
def inspect(log_text):
|
|
@@ -4,9 +4,9 @@ import logging
|
|
|
4
4
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
|
|
7
|
+
from simtools.io.eventio_handler import get_corsika_run_number
|
|
7
8
|
from simtools.simtel.simtel_config_reader import SimtelConfigReader
|
|
8
9
|
from simtools.simtel.simtel_config_writer import sim_telarray_random_seeds
|
|
9
|
-
from simtools.simtel.simtel_io_file_info import get_corsika_run_number
|
|
10
10
|
from simtools.simtel.simtel_io_metadata import (
|
|
11
11
|
get_sim_telarray_telescope_id,
|
|
12
12
|
read_sim_telarray_metadata,
|
|
@@ -13,6 +13,22 @@ from simtools.testing import assertions
|
|
|
13
13
|
|
|
14
14
|
_logger = logging.getLogger(__name__)
|
|
15
15
|
|
|
16
|
+
# Keys to ignore when comparing sim_telarray configuration files
|
|
17
|
+
# (e.g., version numbers, system dependent parameters, CORSIKA options)
|
|
18
|
+
cfg_ignore_keys = [
|
|
19
|
+
"config_release",
|
|
20
|
+
"Label",
|
|
21
|
+
"simtools_version",
|
|
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",
|
|
30
|
+
]
|
|
31
|
+
|
|
16
32
|
|
|
17
33
|
def validate_application_output(
|
|
18
34
|
config, from_command_line=None, from_config_file=None, db_config=None
|
|
@@ -39,6 +55,10 @@ def validate_application_output(
|
|
|
39
55
|
|
|
40
56
|
for integration_test in config["integration_tests"]:
|
|
41
57
|
_logger.info(f"Testing application output: {integration_test}")
|
|
58
|
+
_logger.debug(
|
|
59
|
+
f"Model version from command line: {from_command_line}, "
|
|
60
|
+
f"from config file: {from_config_file}"
|
|
61
|
+
)
|
|
42
62
|
|
|
43
63
|
if from_command_line == from_config_file:
|
|
44
64
|
_validate_output_files(config, integration_test, db_config)
|
|
@@ -117,7 +137,10 @@ def _validate_output_path_and_file(config, integration_file_tests):
|
|
|
117
137
|
except AssertionError as exc:
|
|
118
138
|
raise AssertionError(f"Output file {output_file_path} does not exist. ") from exc
|
|
119
139
|
|
|
120
|
-
|
|
140
|
+
if output_file_path.name.endswith(".simtel.zst"):
|
|
141
|
+
assert assertions.check_output_from_sim_telarray(output_file_path, file_test)
|
|
142
|
+
elif output_file_path.name.endswith(".log_hist.tar.gz"):
|
|
143
|
+
assert assertions.check_simulation_logs(output_file_path, file_test)
|
|
121
144
|
|
|
122
145
|
|
|
123
146
|
def _validate_model_parameter_json_file(config, model_parameter_validation, db_config):
|
|
@@ -350,8 +373,8 @@ def _compare_simtel_cfg_files(reference_file, test_file):
|
|
|
350
373
|
Compare two sim_telarray configuration files.
|
|
351
374
|
|
|
352
375
|
Line-by-line string comparison. Requires similar sequence of
|
|
353
|
-
parameters in the files. Ignore lines
|
|
354
|
-
(
|
|
376
|
+
parameters in the files. Ignore lines listed in cfg_ignore_keys
|
|
377
|
+
(e.g., simtools package versions or hadronic interaction model strings).
|
|
355
378
|
|
|
356
379
|
Parameters
|
|
357
380
|
----------
|
|
@@ -370,16 +393,28 @@ def _compare_simtel_cfg_files(reference_file, test_file):
|
|
|
370
393
|
reference_cfg = [line.rstrip() for line in f1 if line.strip()]
|
|
371
394
|
test_cfg = [line.rstrip() for line in f2 if line.strip()]
|
|
372
395
|
|
|
373
|
-
|
|
396
|
+
def filter_ignored(cfg_lines, file_label):
|
|
397
|
+
filtered = []
|
|
398
|
+
for line in cfg_lines:
|
|
399
|
+
ignored_key = next((ignore for ignore in cfg_ignore_keys if ignore in line), None)
|
|
400
|
+
if ignored_key:
|
|
401
|
+
_logger.debug(f"Ignoring line in {file_label} due to key '{ignored_key}': {line}")
|
|
402
|
+
continue
|
|
403
|
+
filtered.append(line)
|
|
404
|
+
return filtered
|
|
405
|
+
|
|
406
|
+
reference_cfg_filtered = filter_ignored(reference_cfg, "reference file")
|
|
407
|
+
test_cfg_filtered = filter_ignored(test_cfg, "test file")
|
|
408
|
+
|
|
409
|
+
if len(reference_cfg_filtered) != len(test_cfg_filtered):
|
|
374
410
|
_logger.error(
|
|
375
|
-
f"Line counts differ: {reference_file}
|
|
376
|
-
f"
|
|
411
|
+
f"Line counts differ after filtering: {reference_file} "
|
|
412
|
+
f"({len(reference_cfg_filtered)} lines), "
|
|
413
|
+
f"{test_file} ({len(test_cfg_filtered)} lines)."
|
|
377
414
|
)
|
|
378
415
|
return False
|
|
379
416
|
|
|
380
|
-
for ref_line, test_line in zip(
|
|
381
|
-
if any(ignore in ref_line for ignore in ("config_release", "Label", "simtools_version")):
|
|
382
|
-
continue
|
|
417
|
+
for ref_line, test_line in zip(reference_cfg_filtered, test_cfg_filtered):
|
|
383
418
|
if ref_line != test_line:
|
|
384
419
|
_logger.error(
|
|
385
420
|
f"Configuration files {reference_file} and {test_file} do not match: "
|
simtools/utils/names.py
CHANGED
|
@@ -516,6 +516,8 @@ def get_site_from_array_element_name(array_element_name):
|
|
|
516
516
|
Site name(s).
|
|
517
517
|
"""
|
|
518
518
|
try: # e.g. instrument is 'North' as given for the site parameters
|
|
519
|
+
if array_element_name.startswith("OBS"):
|
|
520
|
+
return validate_site_name(array_element_name.split("-")[1])
|
|
519
521
|
return validate_site_name(array_element_name)
|
|
520
522
|
except ValueError: # e.g. instrument is 'LSTN' as given for the array element types
|
|
521
523
|
return array_elements()[get_array_element_type_from_name(array_element_name)]["site"]
|
|
@@ -630,7 +632,6 @@ def get_simulation_software_name_from_parameter_name(
|
|
|
630
632
|
|
|
631
633
|
def simtel_config_file_name(
|
|
632
634
|
site,
|
|
633
|
-
model_version,
|
|
634
635
|
array_name=None,
|
|
635
636
|
telescope_model_name=None,
|
|
636
637
|
label=None,
|
|
@@ -645,8 +646,6 @@ def simtel_config_file_name(
|
|
|
645
646
|
South or North.
|
|
646
647
|
telescope_model_name: str
|
|
647
648
|
LST-1, MST-FlashCam, ...
|
|
648
|
-
model_version: str
|
|
649
|
-
Version of the model.
|
|
650
649
|
label: str
|
|
651
650
|
Instance label.
|
|
652
651
|
extra_label: str
|
|
@@ -661,7 +660,6 @@ def simtel_config_file_name(
|
|
|
661
660
|
name += f"-{array_name}" if array_name is not None else ""
|
|
662
661
|
name += f"-{site}"
|
|
663
662
|
name += f"-{telescope_model_name}" if telescope_model_name is not None else ""
|
|
664
|
-
name += f"-{model_version}"
|
|
665
663
|
name += f"_{label}" if label is not None else ""
|
|
666
664
|
name += f"_{extra_label}" if extra_label is not None else ""
|
|
667
665
|
name += ".cfg"
|
simtools/version.py
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
# which is adapted from https://github.com/astropy/astropy/blob/master/astropy/version.py
|
|
5
5
|
# see https://github.com/astropy/astropy/pull/10774 for a discussion on why this needed.
|
|
6
6
|
|
|
7
|
+
import re
|
|
8
|
+
|
|
7
9
|
from packaging.specifiers import SpecifierSet
|
|
8
10
|
from packaging.version import InvalidVersion, Version
|
|
9
11
|
|
|
@@ -192,6 +194,41 @@ def compare_versions(version_string_1, version_string_2, level=MAJOR_MINOR_PATCH
|
|
|
192
194
|
return (ver1 > ver2) - (ver1 < ver2)
|
|
193
195
|
|
|
194
196
|
|
|
197
|
+
def is_valid_semantic_version(version_string, strict=True):
|
|
198
|
+
"""
|
|
199
|
+
Check if a string is a valid semantic version.
|
|
200
|
+
|
|
201
|
+
Parameters
|
|
202
|
+
----------
|
|
203
|
+
version_string : str
|
|
204
|
+
The version string to validate (e.g., "6.0.2", "1.0.0-alpha").
|
|
205
|
+
strict : bool, optional
|
|
206
|
+
If True, use PEP 440 validation (packaging.version.Version).
|
|
207
|
+
If False, use SemVer 2.0.0 regex pattern (allows more flexible pre-release identifiers).
|
|
208
|
+
|
|
209
|
+
Returns
|
|
210
|
+
-------
|
|
211
|
+
bool
|
|
212
|
+
True if the version string is valid, False otherwise.
|
|
213
|
+
"""
|
|
214
|
+
if not version_string:
|
|
215
|
+
return False
|
|
216
|
+
|
|
217
|
+
if strict:
|
|
218
|
+
try:
|
|
219
|
+
Version(version_string)
|
|
220
|
+
return True
|
|
221
|
+
except InvalidVersion:
|
|
222
|
+
return False
|
|
223
|
+
else:
|
|
224
|
+
semver_regex = (
|
|
225
|
+
r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)" # major.minor.patch
|
|
226
|
+
r"(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?" # pre-release
|
|
227
|
+
r"(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$" # build metadata
|
|
228
|
+
)
|
|
229
|
+
return bool(re.match(semver_regex, version_string))
|
|
230
|
+
|
|
231
|
+
|
|
195
232
|
def check_version_constraint(version_string, constraint):
|
|
196
233
|
"""
|
|
197
234
|
Check if a version satisfies a constraint.
|
|
@@ -11,12 +11,20 @@ Radii are relative to a reference radius (REFERENCE_RADIUS).
|
|
|
11
11
|
"""
|
|
12
12
|
TELESCOPE_CONFIG = {
|
|
13
13
|
"LST": {"color": "darkorange", "radius": 12.5, "shape": "circle", "filled": False},
|
|
14
|
-
"MST": {"color": "dodgerblue", "radius": 9.15, "shape": "circle", "filled":
|
|
15
|
-
"SCT": {"color": "black", "radius": 7.15, "shape": "square", "filled":
|
|
16
|
-
"SST": {"color": "darkgreen", "radius": 3.0, "shape": "circle", "filled":
|
|
14
|
+
"MST": {"color": "dodgerblue", "radius": 9.15, "shape": "circle", "filled": False},
|
|
15
|
+
"SCT": {"color": "black", "radius": 7.15, "shape": "square", "filled": False},
|
|
16
|
+
"SST": {"color": "darkgreen", "radius": 3.0, "shape": "circle", "filled": False},
|
|
17
17
|
"HESS": {"color": "grey", "radius": 6.0, "shape": "hexagon", "filled": True},
|
|
18
18
|
"MAGIC": {"color": "grey", "radius": 8.5, "shape": "hexagon", "filled": True},
|
|
19
19
|
"VERITAS": {"color": "grey", "radius": 6.0, "shape": "hexagon", "filled": True},
|
|
20
|
+
"CEI": {"color": "purple", "radius": 2.0, "shape": "hexagon", "filled": True},
|
|
21
|
+
"RLD": {"color": "brown", "radius": 2.0, "shape": "hexagon", "filled": True},
|
|
22
|
+
"STP": {"color": "olive", "radius": 2.0, "shape": "hexagon", "filled": True},
|
|
23
|
+
"MSP": {"color": "teal", "radius": 2.0, "shape": "hexagon", "filled": True},
|
|
24
|
+
"ILL": {"color": "red", "radius": 2.0, "shape": "hexagon", "filled": False},
|
|
25
|
+
"WST": {"color": "maroon", "radius": 2.0, "shape": "hexagon", "filled": True},
|
|
26
|
+
"ASC": {"color": "cyan", "radius": 2.0, "shape": "hexagon", "filled": True},
|
|
27
|
+
"DUS": {"color": "magenta", "radius": 2.0, "shape": "hexagon", "filled": True},
|
|
20
28
|
}
|
|
21
29
|
|
|
22
30
|
REFERENCE_RADIUS = 12.5
|
|
@@ -30,7 +38,7 @@ def get_telescope_config(telescope_type):
|
|
|
30
38
|
|
|
31
39
|
Parameters
|
|
32
40
|
----------
|
|
33
|
-
telescope_type : str
|
|
41
|
+
telescope_type : str, None
|
|
34
42
|
The type of the telescope (e.g., "LSTN", "MSTS").
|
|
35
43
|
|
|
36
44
|
Returns
|
|
@@ -38,6 +46,8 @@ def get_telescope_config(telescope_type):
|
|
|
38
46
|
dict
|
|
39
47
|
The configuration dictionary for the telescope type.
|
|
40
48
|
"""
|
|
49
|
+
if telescope_type is None:
|
|
50
|
+
return {"color": "blue", "radius": 2.0, "shape": "hexagon", "filled": True}
|
|
41
51
|
config = TELESCOPE_CONFIG.get(telescope_type)
|
|
42
52
|
if not config and len(telescope_type) >= 3:
|
|
43
53
|
config = TELESCOPE_CONFIG.get(telescope_type[:3])
|