gammasimtools 0.23.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.23.0.dist-info → gammasimtools-0.25.0.dist-info}/METADATA +1 -1
- {gammasimtools-0.23.0.dist-info → gammasimtools-0.25.0.dist-info}/RECORD +89 -85
- {gammasimtools-0.23.0.dist-info → gammasimtools-0.25.0.dist-info}/entry_points.txt +1 -0
- simtools/_version.py +2 -2
- simtools/application_control.py +54 -4
- simtools/applications/convert_geo_coordinates_of_array_elements.py +1 -1
- simtools/applications/db_add_file_to_db.py +2 -2
- simtools/applications/db_add_simulation_model_from_repository_to_db.py +1 -1
- simtools/applications/db_add_value_from_json_to_db.py +2 -2
- simtools/applications/db_development_tools/write_array_elements_positions_to_repository.py +1 -1
- simtools/applications/db_generate_compound_indexes.py +1 -1
- simtools/applications/db_get_array_layouts_from_db.py +2 -2
- 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 +4 -2
- simtools/applications/db_upload_model_repository.py +1 -1
- simtools/applications/derive_ctao_array_layouts.py +1 -1
- simtools/applications/derive_psf_parameters.py +5 -0
- simtools/applications/derive_pulse_shape_parameters.py +195 -0
- simtools/applications/generate_array_config.py +1 -1
- simtools/applications/maintain_simulation_model_add_production.py +11 -21
- simtools/applications/plot_array_layout.py +63 -1
- simtools/applications/production_generate_grid.py +1 -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 +7 -5
- simtools/applications/validate_camera_fov.py +1 -1
- simtools/applications/validate_cumulative_psf.py +2 -2
- simtools/applications/validate_file_using_schema.py +49 -123
- simtools/applications/validate_optics.py +1 -1
- simtools/configuration/commandline_parser.py +15 -15
- simtools/configuration/configurator.py +1 -1
- simtools/corsika/corsika_config.py +199 -91
- simtools/data_model/model_data_writer.py +15 -3
- simtools/data_model/schema.py +145 -36
- simtools/data_model/validate_data.py +82 -48
- simtools/db/db_handler.py +61 -294
- simtools/db/db_model_upload.py +3 -2
- simtools/db/mongo_db.py +626 -0
- simtools/dependencies.py +38 -17
- simtools/io/eventio_handler.py +128 -0
- simtools/job_execution/htcondor_script_generator.py +0 -2
- simtools/layout/array_layout.py +7 -7
- simtools/layout/array_layout_utils.py +4 -4
- simtools/model/array_model.py +72 -72
- simtools/model/calibration_model.py +12 -9
- simtools/model/model_parameter.py +196 -160
- simtools/model/model_repository.py +176 -39
- simtools/model/model_utils.py +3 -3
- simtools/model/site_model.py +59 -27
- simtools/model/telescope_model.py +21 -13
- simtools/ray_tracing/mirror_panel_psf.py +4 -4
- simtools/ray_tracing/psf_analysis.py +11 -8
- simtools/ray_tracing/psf_parameter_optimisation.py +823 -680
- simtools/reporting/docs_auto_report_generator.py +1 -1
- simtools/reporting/docs_read_parameters.py +72 -11
- 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 +4 -1
- simtools/simtel/pulse_shapes.py +268 -0
- simtools/simtel/simtel_config_writer.py +179 -21
- 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 -346
- simtools/testing/assertions.py +110 -10
- 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 +46 -15
- simtools/utils/names.py +2 -4
- simtools/utils/value_conversion.py +10 -5
- simtools/version.py +61 -0
- simtools/visualization/legend_handlers.py +14 -4
- simtools/visualization/plot_array_layout.py +229 -33
- simtools/visualization/plot_mirrors.py +837 -0
- simtools/visualization/plot_pixels.py +1 -1
- simtools/visualization/plot_psf.py +1 -1
- simtools/visualization/plot_tables.py +1 -1
- simtools/simtel/simtel_io_file_info.py +0 -62
- {gammasimtools-0.23.0.dist-info → gammasimtools-0.25.0.dist-info}/WHEEL +0 -0
- {gammasimtools-0.23.0.dist-info → gammasimtools-0.25.0.dist-info}/licenses/LICENSE +0 -0
- {gammasimtools-0.23.0.dist-info → gammasimtools-0.25.0.dist-info}/top_level.txt +0 -0
simtools/testing/assertions.py
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
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
|
|
|
8
10
|
import numpy as np
|
|
9
11
|
import yaml
|
|
10
12
|
|
|
13
|
+
from simtools.simtel.simtel_io_metadata import read_sim_telarray_metadata
|
|
14
|
+
|
|
11
15
|
_logger = logging.getLogger(__name__)
|
|
12
16
|
|
|
13
17
|
|
|
@@ -124,7 +128,35 @@ def assert_expected_output(file, expected_output):
|
|
|
124
128
|
return True
|
|
125
129
|
|
|
126
130
|
|
|
127
|
-
def
|
|
131
|
+
def assert_expected_simtel_metadata(file, expected_metadata):
|
|
132
|
+
"""
|
|
133
|
+
Assert that expected metadata is present in the sim_telarray file.
|
|
134
|
+
|
|
135
|
+
Parameters
|
|
136
|
+
----------
|
|
137
|
+
file: Path
|
|
138
|
+
Path to the sim_telarray file.
|
|
139
|
+
expected_metadata: dict
|
|
140
|
+
Expected metadata values.
|
|
141
|
+
|
|
142
|
+
"""
|
|
143
|
+
global_meta, telescope_meta = read_sim_telarray_metadata(file)
|
|
144
|
+
|
|
145
|
+
for key, value in expected_metadata.items():
|
|
146
|
+
if key not in global_meta and key not in telescope_meta:
|
|
147
|
+
_logger.error(f"Metadata key {key} not found in sim_telarray file {file}")
|
|
148
|
+
return False
|
|
149
|
+
if key in global_meta and global_meta[key] != value:
|
|
150
|
+
_logger.error(
|
|
151
|
+
f"Metadata key {key} has value {global_meta[key]} instead of expected {value}"
|
|
152
|
+
)
|
|
153
|
+
return False
|
|
154
|
+
_logger.debug(f"Metadata key {key} matches expected value {value}")
|
|
155
|
+
|
|
156
|
+
return True
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def check_output_from_sim_telarray(file, file_test):
|
|
128
160
|
"""
|
|
129
161
|
Check that the sim_telarray simulation result is reasonable and matches the expected output.
|
|
130
162
|
|
|
@@ -132,20 +164,88 @@ def check_output_from_sim_telarray(file, expected_output):
|
|
|
132
164
|
----------
|
|
133
165
|
file: Path
|
|
134
166
|
Path to the sim_telarray file.
|
|
135
|
-
|
|
136
|
-
|
|
167
|
+
file_test: dict
|
|
168
|
+
File test description including expected output and metadata.
|
|
137
169
|
|
|
138
170
|
Raises
|
|
139
171
|
------
|
|
140
172
|
ValueError
|
|
141
173
|
If the file is not a zstd compressed file.
|
|
142
174
|
"""
|
|
143
|
-
if
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
175
|
+
if "expected_output" not in file_test and "expected_simtel_metadata" not in file_test:
|
|
176
|
+
_logger.debug(f"No expected output or metadata provided, skipping checks {file_test}")
|
|
177
|
+
return True
|
|
178
|
+
|
|
179
|
+
assert_output = assert_metadata = True
|
|
180
|
+
|
|
181
|
+
if "expected_output" in file_test:
|
|
182
|
+
assert_output = assert_expected_output(
|
|
183
|
+
file=file, expected_output=file_test["expected_output"]
|
|
147
184
|
)
|
|
148
185
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
186
|
+
if "expected_simtel_metadata" in file_test:
|
|
187
|
+
assert_metadata = assert_expected_simtel_metadata(
|
|
188
|
+
file=file, expected_metadata=file_test["expected_simtel_metadata"]
|
|
189
|
+
)
|
|
190
|
+
|
|
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,11 +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
|
-
if "
|
|
121
|
-
assert assertions.check_output_from_sim_telarray(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
)
|
|
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)
|
|
125
144
|
|
|
126
145
|
|
|
127
146
|
def _validate_model_parameter_json_file(config, model_parameter_validation, db_config):
|
|
@@ -139,7 +158,7 @@ def _validate_model_parameter_json_file(config, model_parameter_validation, db_c
|
|
|
139
158
|
|
|
140
159
|
"""
|
|
141
160
|
_logger.info(f"Checking model parameter json file: {model_parameter_validation}")
|
|
142
|
-
db = db_handler.DatabaseHandler(
|
|
161
|
+
db = db_handler.DatabaseHandler(db_config=db_config)
|
|
143
162
|
|
|
144
163
|
reference_parameter_name = model_parameter_validation.get("reference_parameter_name")
|
|
145
164
|
|
|
@@ -346,7 +365,7 @@ def _validate_simtel_cfg_files(config, simtel_cfg_file):
|
|
|
346
365
|
f"Comparing simtel cfg files: {reference_file} and {test_file} "
|
|
347
366
|
f"for model version {config['configuration']['model_version']}"
|
|
348
367
|
)
|
|
349
|
-
|
|
368
|
+
assert _compare_simtel_cfg_files(reference_file, test_file)
|
|
350
369
|
|
|
351
370
|
|
|
352
371
|
def _compare_simtel_cfg_files(reference_file, test_file):
|
|
@@ -354,8 +373,8 @@ def _compare_simtel_cfg_files(reference_file, test_file):
|
|
|
354
373
|
Compare two sim_telarray configuration files.
|
|
355
374
|
|
|
356
375
|
Line-by-line string comparison. Requires similar sequence of
|
|
357
|
-
parameters in the files. Ignore lines
|
|
358
|
-
(
|
|
376
|
+
parameters in the files. Ignore lines listed in cfg_ignore_keys
|
|
377
|
+
(e.g., simtools package versions or hadronic interaction model strings).
|
|
359
378
|
|
|
360
379
|
Parameters
|
|
361
380
|
----------
|
|
@@ -374,16 +393,28 @@ def _compare_simtel_cfg_files(reference_file, test_file):
|
|
|
374
393
|
reference_cfg = [line.rstrip() for line in f1 if line.strip()]
|
|
375
394
|
test_cfg = [line.rstrip() for line in f2 if line.strip()]
|
|
376
395
|
|
|
377
|
-
|
|
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):
|
|
378
410
|
_logger.error(
|
|
379
|
-
f"Line counts differ: {reference_file}
|
|
380
|
-
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)."
|
|
381
414
|
)
|
|
382
415
|
return False
|
|
383
416
|
|
|
384
|
-
for ref_line, test_line in zip(
|
|
385
|
-
if any(ignore in ref_line for ignore in ("config_release", "Label")):
|
|
386
|
-
continue
|
|
417
|
+
for ref_line, test_line in zip(reference_cfg_filtered, test_cfg_filtered):
|
|
387
418
|
if ref_line != test_line:
|
|
388
419
|
_logger.error(
|
|
389
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"
|
|
@@ -170,16 +170,21 @@ def get_value_as_quantity(value, unit):
|
|
|
170
170
|
|
|
171
171
|
Raises
|
|
172
172
|
------
|
|
173
|
-
|
|
173
|
+
ValueError
|
|
174
174
|
If the value cannot be converted to the given unit.
|
|
175
175
|
"""
|
|
176
176
|
if isinstance(value, u.Quantity):
|
|
177
177
|
try:
|
|
178
178
|
return value.to(unit)
|
|
179
|
-
except u.UnitConversionError:
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
179
|
+
except u.UnitConversionError as exc:
|
|
180
|
+
raise ValueError(f"Cannot convert {value} with unit {value.unit} to {unit}.") from exc
|
|
181
|
+
elif not isinstance(value, int | float):
|
|
182
|
+
return value
|
|
183
|
+
|
|
184
|
+
if unit is None or unit == "null":
|
|
185
|
+
return value * u.dimensionless_unscaled
|
|
186
|
+
|
|
187
|
+
return value * u.Unit(unit)
|
|
183
188
|
|
|
184
189
|
|
|
185
190
|
def _unit_as_string(unit):
|
simtools/version.py
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
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
|
+
|
|
9
|
+
from packaging.specifiers import SpecifierSet
|
|
7
10
|
from packaging.version import InvalidVersion, Version
|
|
8
11
|
|
|
9
12
|
MAJOR_MINOR_PATCH = "major.minor.patch"
|
|
@@ -189,3 +192,61 @@ def compare_versions(version_string_1, version_string_2, level=MAJOR_MINOR_PATCH
|
|
|
189
192
|
raise ValueError(f"Unknown level: {level}")
|
|
190
193
|
|
|
191
194
|
return (ver1 > ver2) - (ver1 < ver2)
|
|
195
|
+
|
|
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
|
+
|
|
232
|
+
def check_version_constraint(version_string, constraint):
|
|
233
|
+
"""
|
|
234
|
+
Check if a version satisfies a constraint.
|
|
235
|
+
|
|
236
|
+
Parameters
|
|
237
|
+
----------
|
|
238
|
+
version_string : str
|
|
239
|
+
The version string to check (e.g., "6.0.2").
|
|
240
|
+
constraint : str
|
|
241
|
+
The version constraint to check against (e.g., ">=6.0.0").
|
|
242
|
+
|
|
243
|
+
Returns
|
|
244
|
+
-------
|
|
245
|
+
bool
|
|
246
|
+
True if the version satisfies the constraint, False otherwise.
|
|
247
|
+
"""
|
|
248
|
+
spec = SpecifierSet(constraint.strip(), prereleases=True)
|
|
249
|
+
ver = Version(version_string)
|
|
250
|
+
if ver in spec:
|
|
251
|
+
return True
|
|
252
|
+
return False
|
|
@@ -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])
|