gammasimtools 0.9.0__py3-none-any.whl → 0.10.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {gammasimtools-0.9.0.dist-info → gammasimtools-0.10.0.dist-info}/METADATA +2 -2
- {gammasimtools-0.9.0.dist-info → gammasimtools-0.10.0.dist-info}/RECORD +94 -85
- {gammasimtools-0.9.0.dist-info → gammasimtools-0.10.0.dist-info}/entry_points.txt +2 -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 +47 -45
- 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 +1 -2
- simtools/applications/db_development_tools/write_array_elements_positions_to_repository.py +6 -6
- simtools/applications/db_get_file_from_db.py +11 -12
- simtools/applications/db_get_parameter_from_db.py +44 -32
- simtools/applications/derive_photon_electron_spectrum.py +99 -0
- simtools/applications/generate_array_config.py +17 -17
- simtools/applications/generate_regular_arrays.py +15 -15
- simtools/applications/generate_simtel_array_histograms.py +11 -48
- simtools/applications/production_generate_simulation_config.py +25 -7
- simtools/applications/production_scale_events.py +2 -2
- simtools/applications/simulate_prod.py +1 -1
- simtools/applications/simulate_prod_htcondor_generator.py +26 -26
- 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 +2 -2
- simtools/applications/validate_file_using_schema.py +23 -19
- simtools/camera/single_photon_electron_spectrum.py +168 -0
- simtools/configuration/commandline_parser.py +8 -1
- 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 +3 -4
- simtools/data_model/metadata_model.py +8 -124
- simtools/data_model/model_data_writer.py +17 -63
- simtools/data_model/schema.py +213 -0
- simtools/data_model/validate_data.py +9 -44
- simtools/db/db_handler.py +323 -495
- simtools/db/db_model_upload.py +139 -0
- simtools/io_operations/hdf5_handler.py +54 -24
- simtools/layout/array_layout.py +33 -28
- simtools/model/array_model.py +13 -7
- simtools/model/model_parameter.py +22 -54
- simtools/model/site_model.py +2 -2
- 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/runners/corsika_simtel_runner.py +3 -1
- 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 +10 -0
- simtools/schemas/model_parameter.metaschema.yml +7 -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/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/simulator_array.py +2 -1
- simtools/simtel/simulator_camera_efficiency.py +5 -0
- simtools/simtel/simulator_light_emission.py +3 -1
- simtools/simtel/simulator_ray_tracing.py +2 -1
- simtools/testing/helpers.py +6 -13
- simtools/testing/validate_output.py +131 -47
- simtools/utils/general.py +102 -12
- simtools/utils/names.py +24 -20
- simtools/applications/db_add_model_parameters_from_repository_to_db.py +0 -176
- simtools/db/db_array_elements.py +0 -130
- {gammasimtools-0.9.0.dist-info → gammasimtools-0.10.0.dist-info}/LICENSE +0 -0
- {gammasimtools-0.9.0.dist-info → gammasimtools-0.10.0.dist-info}/WHEEL +0 -0
- {gammasimtools-0.9.0.dist-info → gammasimtools-0.10.0.dist-info}/top_level.txt +0 -0
- /simtools/{camera_efficiency.py → camera/camera_efficiency.py} +0 -0
|
@@ -239,7 +239,14 @@ class CommandLineParser(argparse.ArgumentParser):
|
|
|
239
239
|
if "model_version" in model_options:
|
|
240
240
|
_job_group.add_argument(
|
|
241
241
|
"--model_version",
|
|
242
|
-
help="model version",
|
|
242
|
+
help="production model version",
|
|
243
|
+
type=str,
|
|
244
|
+
default=None,
|
|
245
|
+
)
|
|
246
|
+
if "parameter_version" in model_options:
|
|
247
|
+
_job_group.add_argument(
|
|
248
|
+
"--parameter_version",
|
|
249
|
+
help="model parameter version",
|
|
243
250
|
type=str,
|
|
244
251
|
default=None,
|
|
245
252
|
)
|
simtools/constants.py
CHANGED
|
@@ -2,8 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
from importlib.resources import files
|
|
4
4
|
|
|
5
|
+
# Schema path
|
|
6
|
+
SCHEMA_PATH = files("simtools") / "schemas"
|
|
5
7
|
# Path to metadata jsonschema
|
|
6
|
-
METADATA_JSON_SCHEMA =
|
|
7
|
-
|
|
8
|
+
METADATA_JSON_SCHEMA = SCHEMA_PATH / "metadata.metaschema.yml"
|
|
9
|
+
# Path to model parameter metaschema
|
|
10
|
+
MODEL_PARAMETER_METASCHEMA = SCHEMA_PATH / "model_parameter.metaschema.yml"
|
|
11
|
+
# Path to model parameter description metaschema
|
|
12
|
+
MODEL_PARAMETER_DESCRIPTION_METASCHEMA = (
|
|
13
|
+
SCHEMA_PATH / "model_parameter_and_data_schema.metaschema.yml"
|
|
14
|
+
)
|
|
8
15
|
# Path to model parameter schema files
|
|
9
|
-
MODEL_PARAMETER_SCHEMA_PATH =
|
|
16
|
+
MODEL_PARAMETER_SCHEMA_PATH = SCHEMA_PATH / "model_parameters"
|
|
@@ -6,7 +6,6 @@ from pathlib import Path
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
from astropy import units as u
|
|
8
8
|
|
|
9
|
-
import simtools.utils.general as gen
|
|
10
9
|
from simtools.corsika.primary_particle import PrimaryParticle
|
|
11
10
|
from simtools.io_operations import io_handler
|
|
12
11
|
from simtools.model.model_parameter import ModelParameter
|
|
@@ -111,8 +110,6 @@ class CorsikaConfig:
|
|
|
111
110
|
if args_dict is None:
|
|
112
111
|
return {}
|
|
113
112
|
|
|
114
|
-
self._logger.debug("Setting CORSIKA parameters ")
|
|
115
|
-
|
|
116
113
|
self._is_file_updated = False
|
|
117
114
|
self.azimuth_angle = int(args_dict["azimuth_angle"].to("deg").value)
|
|
118
115
|
self.zenith_angle = args_dict["zenith_angle"].to("deg").value
|
|
@@ -243,7 +240,7 @@ class CorsikaConfig:
|
|
|
243
240
|
|
|
244
241
|
def _input_config_corsika_particle_kinetic_energy_cutoff(self, entry):
|
|
245
242
|
"""Return ECUTS parameter CORSIKA format."""
|
|
246
|
-
e_cuts =
|
|
243
|
+
e_cuts = entry["value"]
|
|
247
244
|
return [
|
|
248
245
|
f"{e_cuts[0]*u.Unit(entry['unit']).to('GeV')} "
|
|
249
246
|
f"{e_cuts[1]*u.Unit(entry['unit']).to('GeV')} "
|
|
@@ -280,7 +277,7 @@ class CorsikaConfig:
|
|
|
280
277
|
|
|
281
278
|
def _input_config_corsika_cherenkov_wavelength(self, entry):
|
|
282
279
|
"""Return CWAVLG parameter CORSIKA format."""
|
|
283
|
-
wavelength_range =
|
|
280
|
+
wavelength_range = entry["value"]
|
|
284
281
|
return [
|
|
285
282
|
f"{wavelength_range[0]*u.Unit(entry['unit']).to('nm')}",
|
|
286
283
|
f"{wavelength_range[1]*u.Unit(entry['unit']).to('nm')}",
|
|
@@ -318,8 +315,12 @@ class CorsikaConfig:
|
|
|
318
315
|
}
|
|
319
316
|
|
|
320
317
|
def _input_config_io_buff(self, entry):
|
|
321
|
-
"""Return IO_BUFFER parameter CORSIKA format."""
|
|
322
|
-
|
|
318
|
+
"""Return IO_BUFFER parameter CORSIKA format (Byte or MB required)."""
|
|
319
|
+
value = entry["value"] * u.Unit(entry["unit"]).to("Mbyte")
|
|
320
|
+
# check if value is integer-like
|
|
321
|
+
if value.is_integer():
|
|
322
|
+
return f"{int(value)}MB"
|
|
323
|
+
return f"{int(entry['value'] * u.Unit(entry['unit']).to('byte'))}"
|
|
323
324
|
|
|
324
325
|
def _rotate_azimuth_by_180deg(self, az, correct_for_geomagnetic_field_alignment=True):
|
|
325
326
|
"""
|
|
@@ -678,7 +678,7 @@ class CorsikaHistograms:
|
|
|
678
678
|
----------
|
|
679
679
|
new_individual_telescopes: bool
|
|
680
680
|
if False, the histograms are supposed to be filled for all telescopes.
|
|
681
|
-
if True, one histogram is set for each telescope
|
|
681
|
+
if True, one histogram is set for each telescope separately.
|
|
682
682
|
"""
|
|
683
683
|
if new_individual_telescopes is None:
|
|
684
684
|
self._individual_telescopes = False
|
|
@@ -112,9 +112,6 @@ def read_value_from_file(file_name, schema_file=None, validate=False):
|
|
|
112
112
|
_logger.info("Reading data from %s", file_name)
|
|
113
113
|
|
|
114
114
|
if validate:
|
|
115
|
-
if schema_file is None and "meta_schema_url" in data:
|
|
116
|
-
schema_file = data["meta_schema_url"]
|
|
117
|
-
_logger.debug(f"Using schema from meta_schema_url: {schema_file}")
|
|
118
115
|
if schema_file is None:
|
|
119
116
|
_collector = MetadataCollector(None, metadata_file_name=file_name)
|
|
120
117
|
schema_file = _collector.get_data_model_schema_file_name()
|
|
@@ -10,13 +10,12 @@ import datetime
|
|
|
10
10
|
import getpass
|
|
11
11
|
import logging
|
|
12
12
|
import uuid
|
|
13
|
-
from importlib.resources import files
|
|
14
13
|
from pathlib import Path
|
|
15
14
|
|
|
16
15
|
import simtools.constants
|
|
17
16
|
import simtools.utils.general as gen
|
|
18
17
|
import simtools.version
|
|
19
|
-
from simtools.data_model import metadata_model
|
|
18
|
+
from simtools.data_model import metadata_model, schema
|
|
20
19
|
from simtools.io_operations import io_handler
|
|
21
20
|
from simtools.utils import names
|
|
22
21
|
|
|
@@ -135,7 +134,7 @@ class MetadataCollector:
|
|
|
135
134
|
# from data model name
|
|
136
135
|
if self.data_model_name:
|
|
137
136
|
self._logger.debug(f"Schema file from data model name: {self.data_model_name}")
|
|
138
|
-
return
|
|
137
|
+
return str(schema.get_model_parameter_schema_file(self.data_model_name))
|
|
139
138
|
|
|
140
139
|
# from input metadata
|
|
141
140
|
try:
|
|
@@ -267,7 +266,7 @@ class MetadataCollector:
|
|
|
267
266
|
self._logger.error("Unknown metadata file format: %s", metadata_file_name)
|
|
268
267
|
raise gen.InvalidConfigDataError
|
|
269
268
|
|
|
270
|
-
|
|
269
|
+
schema.validate_dict_using_schema(_input_metadata, None)
|
|
271
270
|
|
|
272
271
|
return gen.change_dict_keys_case(
|
|
273
272
|
self._process_metadata_from_file(_input_metadata),
|
|
@@ -9,48 +9,12 @@ Follows CTAO top-level data model definition.
|
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
import logging
|
|
12
|
-
from importlib.resources import files
|
|
13
12
|
|
|
14
|
-
import
|
|
15
|
-
|
|
16
|
-
import simtools.constants
|
|
17
|
-
import simtools.utils.general as gen
|
|
18
|
-
from simtools.data_model import format_checkers
|
|
19
|
-
from simtools.utils import names
|
|
13
|
+
import simtools.data_model.schema
|
|
20
14
|
|
|
21
15
|
_logger = logging.getLogger(__name__)
|
|
22
16
|
|
|
23
17
|
|
|
24
|
-
def validate_schema(data, schema_file):
|
|
25
|
-
"""
|
|
26
|
-
Validate dictionary against schema.
|
|
27
|
-
|
|
28
|
-
Parameters
|
|
29
|
-
----------
|
|
30
|
-
data
|
|
31
|
-
dictionary to be validated
|
|
32
|
-
schema_file (dict)
|
|
33
|
-
schema used for validation
|
|
34
|
-
|
|
35
|
-
Raises
|
|
36
|
-
------
|
|
37
|
-
jsonschema.exceptions.ValidationError
|
|
38
|
-
if validation fails
|
|
39
|
-
|
|
40
|
-
"""
|
|
41
|
-
schema, schema_file = _load_schema(
|
|
42
|
-
schema_file,
|
|
43
|
-
data.get("schema_version", "0.1.0"), # default version to ensure backward compatibility
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
try:
|
|
47
|
-
jsonschema.validate(data, schema=schema, format_checker=format_checkers.format_checker)
|
|
48
|
-
except jsonschema.exceptions.ValidationError:
|
|
49
|
-
_logger.error(f"Failed using {schema}")
|
|
50
|
-
raise
|
|
51
|
-
_logger.debug(f"Successful validation of data using schema from {schema_file}")
|
|
52
|
-
|
|
53
|
-
|
|
54
18
|
def get_default_metadata_dict(schema_file=None, observatory="CTA"):
|
|
55
19
|
"""
|
|
56
20
|
Return metadata schema with default values.
|
|
@@ -71,90 +35,10 @@ def get_default_metadata_dict(schema_file=None, observatory="CTA"):
|
|
|
71
35
|
|
|
72
36
|
|
|
73
37
|
"""
|
|
74
|
-
schema
|
|
38
|
+
schema = simtools.data_model.schema.load_schema(schema_file)
|
|
75
39
|
return _fill_defaults(schema["definitions"], observatory)
|
|
76
40
|
|
|
77
41
|
|
|
78
|
-
def _load_schema(schema_file=None, schema_version=None):
|
|
79
|
-
"""
|
|
80
|
-
Load parameter schema from file from simpipe metadata schema.
|
|
81
|
-
|
|
82
|
-
Returns
|
|
83
|
-
-------
|
|
84
|
-
schema_file: str
|
|
85
|
-
File name schema is loaded from. If schema_file is not given,
|
|
86
|
-
the default schema file name is returned.
|
|
87
|
-
schema_version: str
|
|
88
|
-
Schema version.
|
|
89
|
-
|
|
90
|
-
Raises
|
|
91
|
-
------
|
|
92
|
-
FileNotFoundError
|
|
93
|
-
if schema file is not found
|
|
94
|
-
|
|
95
|
-
"""
|
|
96
|
-
if schema_file is None:
|
|
97
|
-
schema_file = files("simtools").joinpath(simtools.constants.METADATA_JSON_SCHEMA)
|
|
98
|
-
|
|
99
|
-
try:
|
|
100
|
-
schema = gen.collect_data_from_file(file_name=schema_file)
|
|
101
|
-
except FileNotFoundError:
|
|
102
|
-
schema_file = files("simtools").joinpath("schemas") / schema_file
|
|
103
|
-
schema = gen.collect_data_from_file(file_name=schema_file)
|
|
104
|
-
|
|
105
|
-
if isinstance(schema, list): # schema file with several schemas defined
|
|
106
|
-
if schema_version is None:
|
|
107
|
-
raise ValueError(f"Schema version not given in {schema_file}.")
|
|
108
|
-
schema = next((doc for doc in schema if doc.get("version") == schema_version), None)
|
|
109
|
-
if schema is None:
|
|
110
|
-
raise ValueError(f"Schema version {schema_version} not found in {schema_file}.")
|
|
111
|
-
elif schema_version is not None and schema_version != schema.get("version"):
|
|
112
|
-
_logger.warning(f"Schema version {schema_version} does not match {schema.get('version')}")
|
|
113
|
-
|
|
114
|
-
_logger.debug(f"Loading schema from {schema_file}")
|
|
115
|
-
_add_array_elements("InstrumentTypeElement", schema)
|
|
116
|
-
|
|
117
|
-
return schema, schema_file
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
def _add_array_elements(key, schema):
|
|
121
|
-
"""
|
|
122
|
-
Add list of array elements to schema.
|
|
123
|
-
|
|
124
|
-
This assumes an element [key]['enum'] is a list of elements.
|
|
125
|
-
|
|
126
|
-
Parameters
|
|
127
|
-
----------
|
|
128
|
-
key: str
|
|
129
|
-
Key in schema dictionary
|
|
130
|
-
schema: dict
|
|
131
|
-
Schema dictionary
|
|
132
|
-
|
|
133
|
-
Returns
|
|
134
|
-
-------
|
|
135
|
-
dict
|
|
136
|
-
Schema dictionary with added array elements.
|
|
137
|
-
|
|
138
|
-
"""
|
|
139
|
-
_list_of_array_elements = sorted(names.array_elements().keys())
|
|
140
|
-
|
|
141
|
-
def recursive_search(sub_schema, key):
|
|
142
|
-
if key in sub_schema:
|
|
143
|
-
if "enum" in sub_schema[key] and isinstance(sub_schema[key]["enum"], list):
|
|
144
|
-
sub_schema[key]["enum"] = list(
|
|
145
|
-
set(sub_schema[key]["enum"] + _list_of_array_elements)
|
|
146
|
-
)
|
|
147
|
-
else:
|
|
148
|
-
sub_schema[key]["enum"] = _list_of_array_elements
|
|
149
|
-
else:
|
|
150
|
-
for _, v in sub_schema.items():
|
|
151
|
-
if isinstance(v, dict):
|
|
152
|
-
recursive_search(v, key)
|
|
153
|
-
|
|
154
|
-
recursive_search(schema, key)
|
|
155
|
-
return schema
|
|
156
|
-
|
|
157
|
-
|
|
158
42
|
def _resolve_references(yaml_data, observatory="CTA"):
|
|
159
43
|
"""
|
|
160
44
|
Resolve references in yaml data and expand the received dictionary accordingly.
|
|
@@ -227,21 +111,21 @@ def _fill_defaults(schema, observatory="CTA"):
|
|
|
227
111
|
return defaults
|
|
228
112
|
|
|
229
113
|
|
|
230
|
-
def _fill_defaults_recursive(
|
|
114
|
+
def _fill_defaults_recursive(sub_schema, current_dict):
|
|
231
115
|
"""
|
|
232
|
-
Recursively fill default values from the
|
|
116
|
+
Recursively fill default values from the sub_schema into the current dictionary.
|
|
233
117
|
|
|
234
118
|
Parameters
|
|
235
119
|
----------
|
|
236
|
-
|
|
237
|
-
|
|
120
|
+
sub_schema: dict
|
|
121
|
+
Sub schema describing part of the input data.
|
|
238
122
|
current_dict: dict
|
|
239
123
|
Current dictionary to fill with default values.
|
|
240
124
|
"""
|
|
241
|
-
if "properties" not in
|
|
125
|
+
if "properties" not in sub_schema:
|
|
242
126
|
_raise_missing_properties_error()
|
|
243
127
|
|
|
244
|
-
for prop, prop_schema in
|
|
128
|
+
for prop, prop_schema in sub_schema["properties"].items():
|
|
245
129
|
_process_property(prop, prop_schema, current_dict)
|
|
246
130
|
|
|
247
131
|
|
|
@@ -10,8 +10,7 @@ import yaml
|
|
|
10
10
|
from astropy.io.registry.base import IORegistryError
|
|
11
11
|
|
|
12
12
|
import simtools.utils.general as gen
|
|
13
|
-
from simtools.
|
|
14
|
-
from simtools.data_model import validate_data
|
|
13
|
+
from simtools.data_model import schema, validate_data
|
|
15
14
|
from simtools.data_model.metadata_collector import MetadataCollector
|
|
16
15
|
from simtools.io_operations import io_handler
|
|
17
16
|
from simtools.utils import names, value_conversion
|
|
@@ -122,7 +121,7 @@ class ModelDataWriter:
|
|
|
122
121
|
parameter_name,
|
|
123
122
|
value,
|
|
124
123
|
instrument,
|
|
125
|
-
|
|
124
|
+
parameter_version,
|
|
126
125
|
output_file,
|
|
127
126
|
output_path=None,
|
|
128
127
|
use_plain_output_path=False,
|
|
@@ -139,8 +138,8 @@ class ModelDataWriter:
|
|
|
139
138
|
Value of the parameter.
|
|
140
139
|
instrument: str
|
|
141
140
|
Name of the instrument.
|
|
142
|
-
|
|
143
|
-
Version of the
|
|
141
|
+
parameter_version: str
|
|
142
|
+
Version of the parameter.
|
|
144
143
|
output_file: str
|
|
145
144
|
Name of output file.
|
|
146
145
|
output_path: str or Path
|
|
@@ -163,7 +162,7 @@ class ModelDataWriter:
|
|
|
163
162
|
use_plain_output_path=use_plain_output_path,
|
|
164
163
|
)
|
|
165
164
|
_json_dict = writer.get_validated_parameter_dict(
|
|
166
|
-
parameter_name, value, instrument,
|
|
165
|
+
parameter_name, value, instrument, parameter_version
|
|
167
166
|
)
|
|
168
167
|
writer.write_dict_to_model_parameter_json(output_file, _json_dict)
|
|
169
168
|
if metadata_input_dict is not None:
|
|
@@ -175,7 +174,9 @@ class ModelDataWriter:
|
|
|
175
174
|
)
|
|
176
175
|
return _json_dict
|
|
177
176
|
|
|
178
|
-
def get_validated_parameter_dict(
|
|
177
|
+
def get_validated_parameter_dict(
|
|
178
|
+
self, parameter_name, value, instrument, parameter_version, schema_version=None
|
|
179
|
+
):
|
|
179
180
|
"""
|
|
180
181
|
Get validated parameter dictionary.
|
|
181
182
|
|
|
@@ -187,8 +188,10 @@ class ModelDataWriter:
|
|
|
187
188
|
Value of the parameter.
|
|
188
189
|
instrument: str
|
|
189
190
|
Name of the instrument.
|
|
190
|
-
|
|
191
|
-
Version of the
|
|
191
|
+
parameter_version: str
|
|
192
|
+
Version of the parameter.
|
|
193
|
+
schema_version: str
|
|
194
|
+
Version of the schema.
|
|
192
195
|
|
|
193
196
|
Returns
|
|
194
197
|
-------
|
|
@@ -196,29 +199,26 @@ class ModelDataWriter:
|
|
|
196
199
|
Validated parameter dictionary.
|
|
197
200
|
"""
|
|
198
201
|
self._logger.debug(f"Getting validated parameter dictionary for {instrument}")
|
|
199
|
-
schema_file =
|
|
202
|
+
schema_file = schema.get_model_parameter_schema_file(parameter_name)
|
|
203
|
+
self.schema_dict = gen.collect_data_from_file(schema_file)
|
|
200
204
|
|
|
201
205
|
try: # e.g. instrument is 'North"
|
|
202
206
|
site = names.validate_site_name(instrument)
|
|
203
207
|
except ValueError: # e.g. instrument is 'LSTN-01'
|
|
204
208
|
site = names.get_site_from_array_element_name(instrument)
|
|
205
209
|
|
|
206
|
-
try:
|
|
207
|
-
applicable = self._get_parameter_applicability(instrument)
|
|
208
|
-
except ValueError:
|
|
209
|
-
applicable = True # Default to True (expect that this field goes in future)
|
|
210
|
-
|
|
211
210
|
value, unit = value_conversion.split_value_and_unit(value)
|
|
212
211
|
|
|
213
212
|
data_dict = {
|
|
213
|
+
"schema_version": schema.get_model_parameter_schema_version(schema_version),
|
|
214
214
|
"parameter": parameter_name,
|
|
215
215
|
"instrument": instrument,
|
|
216
216
|
"site": site,
|
|
217
|
-
"
|
|
217
|
+
"parameter_version": parameter_version,
|
|
218
|
+
"unique_id": None,
|
|
218
219
|
"value": value,
|
|
219
220
|
"unit": unit,
|
|
220
221
|
"type": self._get_parameter_type(),
|
|
221
|
-
"applicable": applicable,
|
|
222
222
|
"file": self._parameter_is_a_file(),
|
|
223
223
|
}
|
|
224
224
|
return self.validate_and_transform(
|
|
@@ -227,22 +227,6 @@ class ModelDataWriter:
|
|
|
227
227
|
is_model_parameter=True,
|
|
228
228
|
)
|
|
229
229
|
|
|
230
|
-
def _read_model_parameter_schema(self, parameter_name):
|
|
231
|
-
"""
|
|
232
|
-
Read model parameter schema.
|
|
233
|
-
|
|
234
|
-
Parameters
|
|
235
|
-
----------
|
|
236
|
-
parameter_name: str
|
|
237
|
-
Name of the parameter.
|
|
238
|
-
"""
|
|
239
|
-
schema_file = MODEL_PARAMETER_SCHEMA_PATH / f"{parameter_name}.schema.yml"
|
|
240
|
-
try:
|
|
241
|
-
self.schema_dict = gen.collect_data_from_file(file_name=schema_file)
|
|
242
|
-
except FileNotFoundError as exc:
|
|
243
|
-
raise FileNotFoundError(f"Schema file not found: {schema_file}") from exc
|
|
244
|
-
return schema_file
|
|
245
|
-
|
|
246
230
|
def _get_parameter_type(self):
|
|
247
231
|
"""
|
|
248
232
|
Return parameter type from schema.
|
|
@@ -273,36 +257,6 @@ class ModelDataWriter:
|
|
|
273
257
|
pass
|
|
274
258
|
return False
|
|
275
259
|
|
|
276
|
-
def _get_parameter_applicability(self, telescope_name):
|
|
277
|
-
"""
|
|
278
|
-
Check if a parameter is applicable for a given telescope using schema files.
|
|
279
|
-
|
|
280
|
-
First check for exact telescope name (e.g., LSTN-01), if not listed in the schema
|
|
281
|
-
use telescope type (LSTN).
|
|
282
|
-
|
|
283
|
-
Parameters
|
|
284
|
-
----------
|
|
285
|
-
telescope_name: str
|
|
286
|
-
Telescope name (e.g., LSTN-01)
|
|
287
|
-
|
|
288
|
-
Returns
|
|
289
|
-
-------
|
|
290
|
-
bool
|
|
291
|
-
True if parameter is applicable to telescope.
|
|
292
|
-
|
|
293
|
-
"""
|
|
294
|
-
try:
|
|
295
|
-
if telescope_name in self.schema_dict["instrument"]["type"]:
|
|
296
|
-
return True
|
|
297
|
-
except KeyError as exc:
|
|
298
|
-
self._logger.error("Schema file does not contain 'instrument:type' key.")
|
|
299
|
-
raise exc
|
|
300
|
-
|
|
301
|
-
return (
|
|
302
|
-
names.get_array_element_type_from_name(telescope_name)
|
|
303
|
-
in self.schema_dict["instrument"]["type"]
|
|
304
|
-
)
|
|
305
|
-
|
|
306
260
|
def _get_unit_from_schema(self):
|
|
307
261
|
"""
|
|
308
262
|
Return unit(s) from schema dict.
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""Module providing functionality to read and validate dictionaries using schema."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import jsonschema
|
|
7
|
+
|
|
8
|
+
import simtools.utils.general as gen
|
|
9
|
+
from simtools.constants import (
|
|
10
|
+
METADATA_JSON_SCHEMA,
|
|
11
|
+
MODEL_PARAMETER_METASCHEMA,
|
|
12
|
+
MODEL_PARAMETER_SCHEMA_PATH,
|
|
13
|
+
SCHEMA_PATH,
|
|
14
|
+
)
|
|
15
|
+
from simtools.data_model import format_checkers
|
|
16
|
+
from simtools.utils import names
|
|
17
|
+
|
|
18
|
+
_logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_get_model_parameter_schema_files(schema_directory=MODEL_PARAMETER_SCHEMA_PATH):
|
|
22
|
+
"""
|
|
23
|
+
Return list of parameters and schema files located in schema file directory.
|
|
24
|
+
|
|
25
|
+
Returns
|
|
26
|
+
-------
|
|
27
|
+
list
|
|
28
|
+
List of parameters found in schema file directory.
|
|
29
|
+
list
|
|
30
|
+
List of schema files found in schema file directory.
|
|
31
|
+
|
|
32
|
+
"""
|
|
33
|
+
schema_files = sorted(Path(schema_directory).rglob("*.schema.yml"))
|
|
34
|
+
if not schema_files:
|
|
35
|
+
raise FileNotFoundError(f"No schema files found in {schema_directory}")
|
|
36
|
+
parameters = []
|
|
37
|
+
for schema_file in schema_files:
|
|
38
|
+
schema_dict = gen.collect_data_from_file(file_name=schema_file)
|
|
39
|
+
parameters.append(schema_dict.get("name"))
|
|
40
|
+
return parameters, schema_files
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_model_parameter_schema_file(parameter):
|
|
44
|
+
"""
|
|
45
|
+
Return schema file path for a given model parameter.
|
|
46
|
+
|
|
47
|
+
Parameters
|
|
48
|
+
----------
|
|
49
|
+
parameter: str
|
|
50
|
+
Model parameter name.
|
|
51
|
+
|
|
52
|
+
Returns
|
|
53
|
+
-------
|
|
54
|
+
Path
|
|
55
|
+
Schema file path.
|
|
56
|
+
|
|
57
|
+
"""
|
|
58
|
+
schema_file = MODEL_PARAMETER_SCHEMA_PATH / f"{parameter}.schema.yml"
|
|
59
|
+
if not schema_file.exists():
|
|
60
|
+
raise FileNotFoundError(f"Schema file not found: {schema_file}")
|
|
61
|
+
return schema_file
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def get_model_parameter_schema_version(schema_version=None):
|
|
65
|
+
"""
|
|
66
|
+
Validate and return schema versions.
|
|
67
|
+
|
|
68
|
+
If no schema_version is given, the most recent version is provided.
|
|
69
|
+
|
|
70
|
+
Parameters
|
|
71
|
+
----------
|
|
72
|
+
schema_version: str
|
|
73
|
+
Schema version.
|
|
74
|
+
|
|
75
|
+
Returns
|
|
76
|
+
-------
|
|
77
|
+
str
|
|
78
|
+
Schema version.
|
|
79
|
+
|
|
80
|
+
"""
|
|
81
|
+
schemas = gen.collect_data_from_file(MODEL_PARAMETER_METASCHEMA)
|
|
82
|
+
|
|
83
|
+
if schema_version is None and schemas:
|
|
84
|
+
return schemas[0].get("version")
|
|
85
|
+
|
|
86
|
+
if any(schema.get("version") == schema_version for schema in schemas):
|
|
87
|
+
return schema_version
|
|
88
|
+
|
|
89
|
+
raise ValueError(f"Schema version {schema_version} not found in {MODEL_PARAMETER_METASCHEMA}.")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def validate_dict_using_schema(data, schema_file=None, json_schema=None):
|
|
93
|
+
"""
|
|
94
|
+
Validate a data dictionary against a schema.
|
|
95
|
+
|
|
96
|
+
Parameters
|
|
97
|
+
----------
|
|
98
|
+
data
|
|
99
|
+
dictionary to be validated
|
|
100
|
+
schema_file (dict)
|
|
101
|
+
schema used for validation
|
|
102
|
+
|
|
103
|
+
Raises
|
|
104
|
+
------
|
|
105
|
+
jsonschema.exceptions.ValidationError
|
|
106
|
+
if validation fails
|
|
107
|
+
|
|
108
|
+
"""
|
|
109
|
+
if json_schema is None and schema_file is None:
|
|
110
|
+
_logger.warning(f"No schema provided for validation of {data}")
|
|
111
|
+
return
|
|
112
|
+
if json_schema is None:
|
|
113
|
+
json_schema = load_schema(
|
|
114
|
+
schema_file,
|
|
115
|
+
data.get("schema_version", "0.1.0"), # default version to ensure backward compatibility
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
jsonschema.validate(data, schema=json_schema, format_checker=format_checkers.format_checker)
|
|
120
|
+
except jsonschema.exceptions.ValidationError as exc:
|
|
121
|
+
_logger.error(f"Validation failed using schema: {json_schema}")
|
|
122
|
+
raise exc
|
|
123
|
+
if data.get("meta_schema_url") and not gen.url_exists(data["meta_schema_url"]):
|
|
124
|
+
raise FileNotFoundError(f"Meta schema URL does not exist: {data['meta_schema_url']}")
|
|
125
|
+
|
|
126
|
+
_logger.debug(f"Successful validation of data using schema ({json_schema.get('name')})")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def load_schema(schema_file=None, schema_version=None):
|
|
130
|
+
"""
|
|
131
|
+
Load parameter schema from file.
|
|
132
|
+
|
|
133
|
+
Parameters
|
|
134
|
+
----------
|
|
135
|
+
schema_file: str
|
|
136
|
+
Path to schema file.
|
|
137
|
+
schema_version: str
|
|
138
|
+
Schema version.
|
|
139
|
+
|
|
140
|
+
Returns
|
|
141
|
+
-------
|
|
142
|
+
schema: dict
|
|
143
|
+
Schema dictionary.
|
|
144
|
+
|
|
145
|
+
Raises
|
|
146
|
+
------
|
|
147
|
+
FileNotFoundError
|
|
148
|
+
if schema file is not found
|
|
149
|
+
|
|
150
|
+
"""
|
|
151
|
+
schema_file = schema_file or METADATA_JSON_SCHEMA
|
|
152
|
+
|
|
153
|
+
for path in (schema_file, SCHEMA_PATH / schema_file):
|
|
154
|
+
try:
|
|
155
|
+
schema = gen.collect_data_from_file(file_name=path)
|
|
156
|
+
break
|
|
157
|
+
except FileNotFoundError:
|
|
158
|
+
continue
|
|
159
|
+
else:
|
|
160
|
+
raise FileNotFoundError(f"Schema file not found: {schema_file}")
|
|
161
|
+
|
|
162
|
+
if isinstance(schema, list): # schema file with several schemas defined
|
|
163
|
+
if schema_version is None:
|
|
164
|
+
raise ValueError(f"Schema version not given in {schema_file}.")
|
|
165
|
+
schema = next((doc for doc in schema if doc.get("version") == schema_version), None)
|
|
166
|
+
if schema is None:
|
|
167
|
+
raise ValueError(f"Schema version {schema_version} not found in {schema_file}.")
|
|
168
|
+
elif schema_version is not None and schema_version != schema.get("version"):
|
|
169
|
+
_logger.warning(f"Schema version {schema_version} does not match {schema.get('version')}")
|
|
170
|
+
|
|
171
|
+
_logger.debug(f"Loading schema from {schema_file}")
|
|
172
|
+
_add_array_elements("InstrumentTypeElement", schema)
|
|
173
|
+
|
|
174
|
+
return schema
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _add_array_elements(key, schema):
|
|
178
|
+
"""
|
|
179
|
+
Add list of array elements to schema.
|
|
180
|
+
|
|
181
|
+
Avoids having to list all array elements in multiple schema.
|
|
182
|
+
Assumes an element [key]['enum'] is a list of elements.
|
|
183
|
+
|
|
184
|
+
Parameters
|
|
185
|
+
----------
|
|
186
|
+
key: str
|
|
187
|
+
Key in schema dictionary
|
|
188
|
+
schema: dict
|
|
189
|
+
Schema dictionary
|
|
190
|
+
|
|
191
|
+
Returns
|
|
192
|
+
-------
|
|
193
|
+
dict
|
|
194
|
+
Schema dictionary with added array elements.
|
|
195
|
+
|
|
196
|
+
"""
|
|
197
|
+
_list_of_array_elements = sorted(names.array_elements().keys())
|
|
198
|
+
|
|
199
|
+
def recursive_search(sub_schema, key):
|
|
200
|
+
if key in sub_schema:
|
|
201
|
+
if "enum" in sub_schema[key] and isinstance(sub_schema[key]["enum"], list):
|
|
202
|
+
sub_schema[key]["enum"] = list(
|
|
203
|
+
set(sub_schema[key]["enum"] + _list_of_array_elements)
|
|
204
|
+
)
|
|
205
|
+
else:
|
|
206
|
+
sub_schema[key]["enum"] = _list_of_array_elements
|
|
207
|
+
else:
|
|
208
|
+
for _, v in sub_schema.items():
|
|
209
|
+
if isinstance(v, dict):
|
|
210
|
+
recursive_search(v, key)
|
|
211
|
+
|
|
212
|
+
recursive_search(schema, key)
|
|
213
|
+
return schema
|