gammasimtools 0.9.0__py3-none-any.whl → 0.11.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.11.0.dist-info}/METADATA +4 -2
- {gammasimtools-0.9.0.dist-info → gammasimtools-0.11.0.dist-info}/RECORD +133 -117
- {gammasimtools-0.9.0.dist-info → gammasimtools-0.11.0.dist-info}/WHEEL +1 -1
- {gammasimtools-0.9.0.dist-info → gammasimtools-0.11.0.dist-info}/entry_points.txt +6 -1
- simtools/_version.py +9 -4
- simtools/applications/calculate_trigger_rate.py +15 -38
- simtools/applications/convert_all_model_parameters_from_simtel.py +9 -29
- simtools/applications/convert_geo_coordinates_of_array_elements.py +47 -45
- simtools/applications/convert_model_parameter_from_simtel.py +2 -3
- simtools/applications/db_add_file_to_db.py +1 -3
- 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 +26 -35
- simtools/applications/derive_mirror_rnda.py +1 -2
- simtools/applications/derive_photon_electron_spectrum.py +99 -0
- simtools/applications/derive_psf_parameters.py +1 -0
- simtools/applications/docs_produce_array_element_report.py +71 -0
- simtools/applications/docs_produce_model_parameter_reports.py +63 -0
- simtools/applications/generate_array_config.py +17 -17
- simtools/applications/generate_corsika_histograms.py +2 -2
- simtools/applications/generate_regular_arrays.py +19 -17
- simtools/applications/generate_simtel_array_histograms.py +11 -48
- simtools/applications/production_derive_limits.py +95 -0
- simtools/applications/production_generate_simulation_config.py +37 -33
- simtools/applications/production_scale_events.py +4 -9
- simtools/applications/run_application.py +165 -0
- simtools/applications/simulate_light_emission.py +0 -4
- 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 +18 -11
- simtools/applications/validate_camera_efficiency.py +2 -2
- simtools/applications/validate_file_using_schema.py +26 -22
- simtools/camera/single_photon_electron_spectrum.py +168 -0
- simtools/configuration/commandline_parser.py +37 -1
- simtools/configuration/configurator.py +8 -10
- simtools/constants.py +10 -3
- simtools/corsika/corsika_config.py +19 -17
- simtools/corsika/corsika_histograms.py +5 -7
- simtools/corsika/corsika_histograms_visualize.py +2 -4
- simtools/data_model/data_reader.py +0 -3
- simtools/data_model/metadata_collector.py +20 -12
- simtools/data_model/metadata_model.py +8 -124
- simtools/data_model/model_data_writer.py +81 -75
- simtools/data_model/schema.py +220 -0
- simtools/data_model/validate_data.py +79 -68
- simtools/db/db_handler.py +350 -492
- simtools/db/db_model_upload.py +139 -0
- simtools/dependencies.py +112 -0
- simtools/io_operations/hdf5_handler.py +54 -24
- simtools/layout/array_layout.py +38 -32
- simtools/model/array_model.py +13 -7
- simtools/model/model_parameter.py +55 -54
- simtools/model/site_model.py +2 -2
- simtools/production_configuration/calculate_statistical_errors_grid_point.py +119 -145
- simtools/production_configuration/event_scaler.py +9 -35
- simtools/production_configuration/generate_simulation_config.py +9 -44
- simtools/production_configuration/interpolation_handler.py +9 -15
- simtools/production_configuration/limits_calculation.py +202 -0
- simtools/reporting/docs_read_parameters.py +310 -0
- simtools/runners/corsika_simtel_runner.py +4 -4
- simtools/schemas/{integration_tests_config.metaschema.yml → application_workflow.metaschema.yml} +61 -27
- simtools/schemas/array_elements.yml +8 -0
- simtools/schemas/input/MST_mirror_2f_measurements.schema.yml +39 -0
- simtools/schemas/input/single_pe_spectrum.schema.yml +38 -0
- simtools/schemas/model_parameter.metaschema.yml +103 -2
- simtools/schemas/model_parameter_and_data_schema.metaschema.yml +4 -1
- 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/correct_nsb_spectrum_to_telescope_altitude.schema.yml +1 -1
- simtools/schemas/model_parameters/corsika_cherenkov_photon_bunch_size.schema.yml +2 -0
- simtools/schemas/model_parameters/corsika_cherenkov_photon_wavelength_range.schema.yml +2 -0
- simtools/schemas/model_parameters/corsika_first_interaction_height.schema.yml +2 -0
- simtools/schemas/model_parameters/corsika_iact_io_buffer.schema.yml +4 -2
- simtools/schemas/model_parameters/corsika_iact_max_bunches.schema.yml +2 -0
- simtools/schemas/model_parameters/corsika_iact_split_auto.schema.yml +2 -0
- simtools/schemas/model_parameters/corsika_longitudinal_shower_development.schema.yml +2 -0
- simtools/schemas/model_parameters/corsika_particle_kinetic_energy_cutoff.schema.yml +2 -0
- simtools/schemas/model_parameters/corsika_starting_grammage.schema.yml +2 -0
- 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/iobuf_maximum.schema.yml +1 -1
- simtools/schemas/model_parameters/iobuf_output_maximum.schema.yml +1 -1
- simtools/schemas/model_parameters/laser_photons.schema.yml +2 -2
- simtools/schemas/model_parameters/lightguide_efficiency_vs_incidence_angle.schema.yml +1 -1
- simtools/schemas/model_parameters/lightguide_efficiency_vs_wavelength.schema.yml +1 -1
- simtools/schemas/model_parameters/min_photoelectrons.schema.yml +1 -1
- simtools/schemas/model_parameters/min_photons.schema.yml +1 -1
- simtools/schemas/model_parameters/random_generator.schema.yml +1 -1
- simtools/schemas/model_parameters/sampled_output.schema.yml +1 -1
- simtools/schemas/model_parameters/save_pe_with_amplitude.schema.yml +1 -1
- simtools/schemas/model_parameters/secondary_mirror_degraded_reflection.schema.yml +1 -1
- simtools/schemas/model_parameters/store_photoelectrons.schema.yml +1 -1
- simtools/schemas/model_parameters/tailcut_scale.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_reader.py +1 -2
- simtools/simtel/simtel_config_writer.py +6 -8
- simtools/simtel/simtel_io_histogram.py +32 -68
- simtools/simtel/simtel_io_histograms.py +17 -34
- simtools/simtel/simulator_array.py +2 -1
- simtools/simtel/simulator_camera_efficiency.py +6 -3
- simtools/simtel/simulator_light_emission.py +5 -6
- simtools/simtel/simulator_ray_tracing.py +3 -4
- simtools/testing/configuration.py +2 -1
- simtools/testing/helpers.py +6 -13
- simtools/testing/validate_output.py +141 -47
- simtools/utils/general.py +114 -14
- simtools/utils/names.py +299 -157
- simtools/utils/value_conversion.py +17 -13
- simtools/version.py +2 -2
- simtools/visualization/legend_handlers.py +2 -0
- 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.11.0.dist-info}/LICENSE +0 -0
- {gammasimtools-0.9.0.dist-info → gammasimtools-0.11.0.dist-info}/top_level.txt +0 -0
- /simtools/{camera_efficiency.py → camera/camera_efficiency.py} +0 -0
|
@@ -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,9 +10,9 @@ 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
|
|
15
|
+
from simtools.db import db_handler
|
|
16
16
|
from simtools.io_operations import io_handler
|
|
17
17
|
from simtools.utils import names, value_conversion
|
|
18
18
|
|
|
@@ -122,11 +122,12 @@ class ModelDataWriter:
|
|
|
122
122
|
parameter_name,
|
|
123
123
|
value,
|
|
124
124
|
instrument,
|
|
125
|
-
|
|
125
|
+
parameter_version,
|
|
126
126
|
output_file,
|
|
127
127
|
output_path=None,
|
|
128
128
|
use_plain_output_path=False,
|
|
129
129
|
metadata_input_dict=None,
|
|
130
|
+
db_config=None,
|
|
130
131
|
):
|
|
131
132
|
"""
|
|
132
133
|
Generate DB-style model parameter dict and write it to json file.
|
|
@@ -139,8 +140,8 @@ class ModelDataWriter:
|
|
|
139
140
|
Value of the parameter.
|
|
140
141
|
instrument: str
|
|
141
142
|
Name of the instrument.
|
|
142
|
-
|
|
143
|
-
Version of the
|
|
143
|
+
parameter_version: str
|
|
144
|
+
Version of the parameter.
|
|
144
145
|
output_file: str
|
|
145
146
|
Name of output file.
|
|
146
147
|
output_path: str or Path
|
|
@@ -149,6 +150,8 @@ class ModelDataWriter:
|
|
|
149
150
|
Use plain output path.
|
|
150
151
|
metadata_input_dict: dict
|
|
151
152
|
Input to metadata collector.
|
|
153
|
+
db_config: dict
|
|
154
|
+
Database configuration. If not None, check if parameter with the same version exists.
|
|
152
155
|
|
|
153
156
|
Returns
|
|
154
157
|
-------
|
|
@@ -162,20 +165,73 @@ class ModelDataWriter:
|
|
|
162
165
|
output_path=output_path,
|
|
163
166
|
use_plain_output_path=use_plain_output_path,
|
|
164
167
|
)
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
168
|
+
if db_config is not None:
|
|
169
|
+
writer.check_db_for_existing_parameter(
|
|
170
|
+
parameter_name, instrument, parameter_version, db_config
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
unique_id = None
|
|
169
174
|
if metadata_input_dict is not None:
|
|
170
175
|
metadata_input_dict["output_file"] = output_file
|
|
171
176
|
metadata_input_dict["output_file_format"] = Path(output_file).suffix.lstrip(".")
|
|
177
|
+
metadata = MetadataCollector(args_dict=metadata_input_dict).get_top_level_metadata()
|
|
172
178
|
writer.write_metadata_to_yml(
|
|
173
|
-
metadata=
|
|
174
|
-
yml_file=output_path / f"{Path(output_file).stem}",
|
|
179
|
+
metadata=metadata, yml_file=output_path / f"{Path(output_file).stem}"
|
|
175
180
|
)
|
|
181
|
+
unique_id = metadata.get("cta", {}).get("product", {}).get("id")
|
|
182
|
+
|
|
183
|
+
_json_dict = writer.get_validated_parameter_dict(
|
|
184
|
+
parameter_name, value, instrument, parameter_version, unique_id
|
|
185
|
+
)
|
|
186
|
+
writer.write_dict_to_model_parameter_json(output_file, _json_dict)
|
|
176
187
|
return _json_dict
|
|
177
188
|
|
|
178
|
-
def
|
|
189
|
+
def check_db_for_existing_parameter(
|
|
190
|
+
self, parameter_name, instrument, parameter_version, db_config
|
|
191
|
+
):
|
|
192
|
+
"""
|
|
193
|
+
Check if a parameter with the same version exists in the simulation model database.
|
|
194
|
+
|
|
195
|
+
Parameters
|
|
196
|
+
----------
|
|
197
|
+
parameter_name: str
|
|
198
|
+
Name of the parameter.
|
|
199
|
+
instrument: str
|
|
200
|
+
Name of the instrument.
|
|
201
|
+
parameter_version: str
|
|
202
|
+
Version of the parameter.
|
|
203
|
+
db_config: dict
|
|
204
|
+
Database configuration.
|
|
205
|
+
|
|
206
|
+
Raises
|
|
207
|
+
------
|
|
208
|
+
ValueError
|
|
209
|
+
If parameter with the same version exists in the database.
|
|
210
|
+
"""
|
|
211
|
+
db = db_handler.DatabaseHandler(mongo_db_config=db_config)
|
|
212
|
+
try:
|
|
213
|
+
db.get_model_parameter(
|
|
214
|
+
parameter=parameter_name,
|
|
215
|
+
parameter_version=parameter_version,
|
|
216
|
+
site=names.get_site_from_array_element_name(instrument),
|
|
217
|
+
array_element_name=instrument,
|
|
218
|
+
)
|
|
219
|
+
except ValueError:
|
|
220
|
+
pass # parameter does not exist - expected behavior
|
|
221
|
+
else:
|
|
222
|
+
raise ValueError(
|
|
223
|
+
f"Parameter {parameter_name} with version {parameter_version} already exists."
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
def get_validated_parameter_dict(
|
|
227
|
+
self,
|
|
228
|
+
parameter_name,
|
|
229
|
+
value,
|
|
230
|
+
instrument,
|
|
231
|
+
parameter_version,
|
|
232
|
+
unique_id=None,
|
|
233
|
+
schema_version=None,
|
|
234
|
+
):
|
|
179
235
|
"""
|
|
180
236
|
Get validated parameter dictionary.
|
|
181
237
|
|
|
@@ -187,8 +243,10 @@ class ModelDataWriter:
|
|
|
187
243
|
Value of the parameter.
|
|
188
244
|
instrument: str
|
|
189
245
|
Name of the instrument.
|
|
190
|
-
|
|
191
|
-
Version of the
|
|
246
|
+
parameter_version: str
|
|
247
|
+
Version of the parameter.
|
|
248
|
+
schema_version: str
|
|
249
|
+
Version of the schema.
|
|
192
250
|
|
|
193
251
|
Returns
|
|
194
252
|
-------
|
|
@@ -196,29 +254,21 @@ class ModelDataWriter:
|
|
|
196
254
|
Validated parameter dictionary.
|
|
197
255
|
"""
|
|
198
256
|
self._logger.debug(f"Getting validated parameter dictionary for {instrument}")
|
|
199
|
-
schema_file =
|
|
200
|
-
|
|
201
|
-
try: # e.g. instrument is 'North"
|
|
202
|
-
site = names.validate_site_name(instrument)
|
|
203
|
-
except ValueError: # e.g. instrument is 'LSTN-01'
|
|
204
|
-
site = names.get_site_from_array_element_name(instrument)
|
|
205
|
-
|
|
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)
|
|
257
|
+
schema_file = schema.get_model_parameter_schema_file(parameter_name)
|
|
258
|
+
self.schema_dict = gen.collect_data_from_file(schema_file)
|
|
210
259
|
|
|
211
260
|
value, unit = value_conversion.split_value_and_unit(value)
|
|
212
261
|
|
|
213
262
|
data_dict = {
|
|
263
|
+
"schema_version": schema.get_model_parameter_schema_version(schema_version),
|
|
214
264
|
"parameter": parameter_name,
|
|
215
265
|
"instrument": instrument,
|
|
216
|
-
"site":
|
|
217
|
-
"
|
|
266
|
+
"site": names.get_site_from_array_element_name(instrument),
|
|
267
|
+
"parameter_version": parameter_version,
|
|
268
|
+
"unique_id": unique_id,
|
|
218
269
|
"value": value,
|
|
219
270
|
"unit": unit,
|
|
220
271
|
"type": self._get_parameter_type(),
|
|
221
|
-
"applicable": applicable,
|
|
222
272
|
"file": self._parameter_is_a_file(),
|
|
223
273
|
}
|
|
224
274
|
return self.validate_and_transform(
|
|
@@ -227,22 +277,6 @@ class ModelDataWriter:
|
|
|
227
277
|
is_model_parameter=True,
|
|
228
278
|
)
|
|
229
279
|
|
|
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
280
|
def _get_parameter_type(self):
|
|
247
281
|
"""
|
|
248
282
|
Return parameter type from schema.
|
|
@@ -273,36 +307,6 @@ class ModelDataWriter:
|
|
|
273
307
|
pass
|
|
274
308
|
return False
|
|
275
309
|
|
|
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
310
|
def _get_unit_from_schema(self):
|
|
307
311
|
"""
|
|
308
312
|
Return unit(s) from schema dict.
|
|
@@ -471,7 +475,9 @@ class ModelDataWriter:
|
|
|
471
475
|
If yml_file is not defined.
|
|
472
476
|
"""
|
|
473
477
|
try:
|
|
474
|
-
yml_file =
|
|
478
|
+
yml_file = names.file_name_with_version(
|
|
479
|
+
yml_file or self.product_data_file, ".metadata.yml"
|
|
480
|
+
)
|
|
475
481
|
with open(yml_file, "w", encoding="UTF-8") as file:
|
|
476
482
|
yaml.safe_dump(
|
|
477
483
|
gen.change_dict_keys_case(metadata, keys_lower_case),
|
|
@@ -0,0 +1,220 @@
|
|
|
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")
|
|
116
|
+
or data.get(
|
|
117
|
+
"SCHEMA_VERSION", "0.1.0"
|
|
118
|
+
), # default version to ensure backward compatibility
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
try:
|
|
122
|
+
jsonschema.validate(data, schema=json_schema, format_checker=format_checkers.format_checker)
|
|
123
|
+
except jsonschema.exceptions.ValidationError as exc:
|
|
124
|
+
_logger.error(f"Validation failed using schema: {json_schema} for data: {data}")
|
|
125
|
+
raise exc
|
|
126
|
+
if (
|
|
127
|
+
isinstance(data, dict)
|
|
128
|
+
and data.get("meta_schema_url")
|
|
129
|
+
and not gen.url_exists(data["meta_schema_url"])
|
|
130
|
+
):
|
|
131
|
+
raise FileNotFoundError(f"Meta schema URL does not exist: {data['meta_schema_url']}")
|
|
132
|
+
|
|
133
|
+
_logger.debug(f"Successful validation of data using schema ({json_schema.get('name')})")
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def load_schema(schema_file=None, schema_version=None):
|
|
137
|
+
"""
|
|
138
|
+
Load parameter schema from file.
|
|
139
|
+
|
|
140
|
+
Parameters
|
|
141
|
+
----------
|
|
142
|
+
schema_file: str
|
|
143
|
+
Path to schema file.
|
|
144
|
+
schema_version: str
|
|
145
|
+
Schema version.
|
|
146
|
+
|
|
147
|
+
Returns
|
|
148
|
+
-------
|
|
149
|
+
schema: dict
|
|
150
|
+
Schema dictionary.
|
|
151
|
+
|
|
152
|
+
Raises
|
|
153
|
+
------
|
|
154
|
+
FileNotFoundError
|
|
155
|
+
if schema file is not found
|
|
156
|
+
|
|
157
|
+
"""
|
|
158
|
+
schema_file = schema_file or METADATA_JSON_SCHEMA
|
|
159
|
+
|
|
160
|
+
for path in (schema_file, SCHEMA_PATH / schema_file):
|
|
161
|
+
try:
|
|
162
|
+
schema = gen.collect_data_from_file(file_name=path)
|
|
163
|
+
break
|
|
164
|
+
except FileNotFoundError:
|
|
165
|
+
continue
|
|
166
|
+
else:
|
|
167
|
+
raise FileNotFoundError(f"Schema file not found: {schema_file}")
|
|
168
|
+
|
|
169
|
+
if isinstance(schema, list): # schema file with several schemas defined
|
|
170
|
+
if schema_version is None:
|
|
171
|
+
raise ValueError(f"Schema version not given in {schema_file}.")
|
|
172
|
+
schema = next((doc for doc in schema if doc.get("version") == schema_version), None)
|
|
173
|
+
if schema is None:
|
|
174
|
+
raise ValueError(f"Schema version {schema_version} not found in {schema_file}.")
|
|
175
|
+
elif schema_version is not None and schema_version != schema.get("version"):
|
|
176
|
+
_logger.warning(f"Schema version {schema_version} does not match {schema.get('version')}")
|
|
177
|
+
|
|
178
|
+
_logger.debug(f"Loading schema from {schema_file}")
|
|
179
|
+
_add_array_elements("InstrumentTypeElement", schema)
|
|
180
|
+
|
|
181
|
+
return schema
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _add_array_elements(key, schema):
|
|
185
|
+
"""
|
|
186
|
+
Add list of array elements to schema.
|
|
187
|
+
|
|
188
|
+
Avoids having to list all array elements in multiple schema.
|
|
189
|
+
Assumes an element [key]['enum'] is a list of elements.
|
|
190
|
+
|
|
191
|
+
Parameters
|
|
192
|
+
----------
|
|
193
|
+
key: str
|
|
194
|
+
Key in schema dictionary
|
|
195
|
+
schema: dict
|
|
196
|
+
Schema dictionary
|
|
197
|
+
|
|
198
|
+
Returns
|
|
199
|
+
-------
|
|
200
|
+
dict
|
|
201
|
+
Schema dictionary with added array elements.
|
|
202
|
+
|
|
203
|
+
"""
|
|
204
|
+
_list_of_array_elements = sorted(names.array_elements().keys())
|
|
205
|
+
|
|
206
|
+
def recursive_search(sub_schema, key):
|
|
207
|
+
if key in sub_schema:
|
|
208
|
+
if "enum" in sub_schema[key] and isinstance(sub_schema[key]["enum"], list):
|
|
209
|
+
sub_schema[key]["enum"] = list(
|
|
210
|
+
set(sub_schema[key]["enum"] + _list_of_array_elements)
|
|
211
|
+
)
|
|
212
|
+
else:
|
|
213
|
+
sub_schema[key]["enum"] = _list_of_array_elements
|
|
214
|
+
else:
|
|
215
|
+
for _, v in sub_schema.items():
|
|
216
|
+
if isinstance(v, dict):
|
|
217
|
+
recursive_search(v, key)
|
|
218
|
+
|
|
219
|
+
recursive_search(schema, key)
|
|
220
|
+
return schema
|