gammasimtools 0.10.0__py3-none-any.whl → 0.12.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.10.0.dist-info → gammasimtools-0.12.0.dist-info}/METADATA +3 -1
- {gammasimtools-0.10.0.dist-info → gammasimtools-0.12.0.dist-info}/RECORD +84 -77
- {gammasimtools-0.10.0.dist-info → gammasimtools-0.12.0.dist-info}/WHEEL +1 -1
- {gammasimtools-0.10.0.dist-info → gammasimtools-0.12.0.dist-info}/entry_points.txt +4 -0
- simtools/_version.py +9 -4
- simtools/applications/convert_all_model_parameters_from_simtel.py +0 -1
- simtools/applications/convert_model_parameter_from_simtel.py +0 -1
- simtools/applications/db_add_file_to_db.py +0 -1
- simtools/applications/db_get_parameter_from_db.py +7 -28
- simtools/applications/derive_mirror_rnda.py +1 -2
- 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_corsika_histograms.py +2 -2
- simtools/applications/generate_regular_arrays.py +4 -2
- simtools/applications/production_derive_limits.py +95 -0
- simtools/applications/production_generate_simulation_config.py +15 -29
- simtools/applications/production_scale_events.py +2 -7
- simtools/applications/run_application.py +165 -0
- simtools/applications/simulate_light_emission.py +0 -4
- simtools/applications/submit_model_parameter_from_external.py +11 -6
- simtools/applications/validate_file_using_schema.py +3 -3
- simtools/configuration/commandline_parser.py +30 -1
- simtools/configuration/configurator.py +8 -10
- simtools/corsika/corsika_config.py +11 -10
- simtools/corsika/corsika_histograms.py +4 -6
- simtools/corsika/corsika_histograms_visualize.py +2 -4
- simtools/data_model/metadata_collector.py +18 -9
- simtools/data_model/model_data_writer.py +67 -15
- simtools/data_model/schema.py +10 -3
- simtools/data_model/validate_data.py +70 -24
- simtools/db/db_handler.py +46 -14
- simtools/dependencies.py +112 -0
- simtools/layout/array_layout.py +5 -4
- simtools/model/model_parameter.py +35 -2
- simtools/production_configuration/calculate_statistical_errors_grid_point.py +5 -6
- simtools/production_configuration/event_scaler.py +3 -19
- simtools/production_configuration/generate_simulation_config.py +4 -12
- simtools/production_configuration/interpolation_handler.py +2 -5
- simtools/production_configuration/limits_calculation.py +202 -0
- simtools/reporting/docs_read_parameters.py +310 -0
- simtools/runners/corsika_simtel_runner.py +1 -3
- simtools/schemas/{integration_tests_config.metaschema.yml → application_workflow.metaschema.yml} +51 -27
- simtools/schemas/array_elements.yml +8 -0
- simtools/schemas/model_parameter.metaschema.yml +96 -0
- simtools/schemas/model_parameter_and_data_schema.metaschema.yml +2 -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 +2 -0
- 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/iobuf_maximum.schema.yml +1 -1
- simtools/schemas/model_parameters/iobuf_output_maximum.schema.yml +1 -1
- 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/store_photoelectrons.schema.yml +1 -1
- simtools/schemas/model_parameters/tailcut_scale.schema.yml +1 -1
- simtools/schemas/production_tables.schema.yml +1 -1
- simtools/simtel/simtel_config_reader.py +1 -2
- simtools/simtel/simtel_config_writer.py +1 -2
- simtools/simtel/simtel_io_histogram.py +0 -1
- simtools/simtel/simtel_io_histograms.py +2 -4
- simtools/simtel/simulator_camera_efficiency.py +1 -3
- simtools/simtel/simulator_light_emission.py +2 -5
- simtools/simtel/simulator_ray_tracing.py +1 -3
- simtools/testing/configuration.py +2 -1
- simtools/testing/validate_output.py +23 -13
- simtools/utils/general.py +12 -2
- simtools/utils/names.py +290 -152
- simtools/utils/value_conversion.py +20 -14
- simtools/version.py +2 -2
- simtools/visualization/legend_handlers.py +2 -0
- {gammasimtools-0.10.0.dist-info → gammasimtools-0.12.0.dist-info}/LICENSE +0 -0
- {gammasimtools-0.10.0.dist-info → gammasimtools-0.12.0.dist-info}/top_level.txt +0 -0
|
@@ -944,10 +944,10 @@ class CorsikaHistograms:
|
|
|
944
944
|
|
|
945
945
|
hist_2d_values_list, x_position_list, y_position_list = self.get_2d_photon_position_distr()
|
|
946
946
|
|
|
947
|
-
for i_hist,
|
|
947
|
+
for i_hist, x_pos in enumerate(x_position_list):
|
|
948
948
|
hist_1d, bin_edges_1d = convert_2d_to_radial_distr(
|
|
949
949
|
hist_2d_values_list[i_hist],
|
|
950
|
-
|
|
950
|
+
x_pos,
|
|
951
951
|
y_position_list[i_hist],
|
|
952
952
|
bins=bins,
|
|
953
953
|
max_dist=max_dist,
|
|
@@ -1248,8 +1248,7 @@ class CorsikaHistograms:
|
|
|
1248
1248
|
meta_data=self._meta_dict,
|
|
1249
1249
|
)
|
|
1250
1250
|
self._logger.info(
|
|
1251
|
-
f"Writing 1D histogram with name {hdf5_table_name} to "
|
|
1252
|
-
f"{self.hdf5_file_name}."
|
|
1251
|
+
f"Writing 1D histogram with name {hdf5_table_name} to {self.hdf5_file_name}."
|
|
1253
1252
|
)
|
|
1254
1253
|
# overwrite takes precedence over append
|
|
1255
1254
|
if overwrite is True:
|
|
@@ -1382,8 +1381,7 @@ class CorsikaHistograms:
|
|
|
1382
1381
|
)
|
|
1383
1382
|
|
|
1384
1383
|
self._logger.info(
|
|
1385
|
-
f"Writing 2D histogram with name {hdf5_table_name} to "
|
|
1386
|
-
f"{self.hdf5_file_name}."
|
|
1384
|
+
f"Writing 2D histogram with name {hdf5_table_name} to {self.hdf5_file_name}."
|
|
1387
1385
|
)
|
|
1388
1386
|
# Always appending to table due to the file previously created
|
|
1389
1387
|
# by self._export_1d_histograms.
|
|
@@ -93,8 +93,7 @@ def _kernel_plot_2d_photons(histograms_instance, property_name, log_z=False):
|
|
|
93
93
|
all_figs.append(fig)
|
|
94
94
|
if histograms_instance.individual_telescopes is False:
|
|
95
95
|
ax.set_title(
|
|
96
|
-
f"{histograms_instance.dict_2d_distributions[property_name]['file name']}"
|
|
97
|
-
"_all_tels"
|
|
96
|
+
f"{histograms_instance.dict_2d_distributions[property_name]['file name']}_all_tels"
|
|
98
97
|
)
|
|
99
98
|
else:
|
|
100
99
|
ax.text(
|
|
@@ -285,8 +284,7 @@ def _kernel_plot_1d_photons(histograms_instance, property_name, log_y=True):
|
|
|
285
284
|
ax.set_yscale("log")
|
|
286
285
|
if histograms_instance.individual_telescopes is False:
|
|
287
286
|
ax.set_title(
|
|
288
|
-
f"{histograms_instance.dict_1d_distributions[property_name]['file name']}"
|
|
289
|
-
"_all_tels"
|
|
287
|
+
f"{histograms_instance.dict_1d_distributions[property_name]['file name']}_all_tels"
|
|
290
288
|
)
|
|
291
289
|
else:
|
|
292
290
|
ax.set_title(
|
|
@@ -6,7 +6,6 @@ implementation of the observatory metadata model.
|
|
|
6
6
|
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
import datetime
|
|
10
9
|
import getpass
|
|
11
10
|
import logging
|
|
12
11
|
import uuid
|
|
@@ -15,6 +14,7 @@ from pathlib import Path
|
|
|
15
14
|
import simtools.constants
|
|
16
15
|
import simtools.utils.general as gen
|
|
17
16
|
import simtools.version
|
|
17
|
+
from simtools.constants import METADATA_JSON_SCHEMA
|
|
18
18
|
from simtools.data_model import metadata_model, schema
|
|
19
19
|
from simtools.io_operations import io_handler
|
|
20
20
|
from simtools.utils import names
|
|
@@ -96,9 +96,9 @@ class MetadataCollector:
|
|
|
96
96
|
|
|
97
97
|
"""
|
|
98
98
|
try:
|
|
99
|
-
self.top_level_meta[self.observatory]["activity"][
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
self.top_level_meta[self.observatory]["activity"]["end"] = (
|
|
100
|
+
gen.now_date_time_in_isoformat()
|
|
101
|
+
)
|
|
102
102
|
except KeyError:
|
|
103
103
|
pass
|
|
104
104
|
return self.top_level_meta
|
|
@@ -199,8 +199,17 @@ class MetadataCollector:
|
|
|
199
199
|
contact_dict: dict
|
|
200
200
|
Dictionary for contact metadata fields.
|
|
201
201
|
"""
|
|
202
|
-
|
|
202
|
+
contact_dict["name"] = contact_dict.get("name") or self.args_dict.get("user_name")
|
|
203
|
+
if contact_dict["name"] is None:
|
|
204
|
+
self._logger.warning("No user name provided, take user info from system level.")
|
|
203
205
|
contact_dict["name"] = getpass.getuser()
|
|
206
|
+
meta_dict = {
|
|
207
|
+
"email": "user_mail",
|
|
208
|
+
"orcid": "user_orcid",
|
|
209
|
+
"organization": "user_organization",
|
|
210
|
+
}
|
|
211
|
+
for key, value in meta_dict.items():
|
|
212
|
+
contact_dict[key] = contact_dict.get(key) or self.args_dict.get(value)
|
|
204
213
|
|
|
205
214
|
def _fill_context_meta(self, context_dict):
|
|
206
215
|
"""
|
|
@@ -266,7 +275,7 @@ class MetadataCollector:
|
|
|
266
275
|
self._logger.error("Unknown metadata file format: %s", metadata_file_name)
|
|
267
276
|
raise gen.InvalidConfigDataError
|
|
268
277
|
|
|
269
|
-
schema.validate_dict_using_schema(_input_metadata,
|
|
278
|
+
schema.validate_dict_using_schema(_input_metadata, schema_file=METADATA_JSON_SCHEMA)
|
|
270
279
|
|
|
271
280
|
return gen.change_dict_keys_case(
|
|
272
281
|
self._process_metadata_from_file(_input_metadata),
|
|
@@ -326,7 +335,7 @@ class MetadataCollector:
|
|
|
326
335
|
self.schema_dict = self.get_data_model_schema_dict()
|
|
327
336
|
|
|
328
337
|
product_dict["id"] = str(uuid.uuid4())
|
|
329
|
-
product_dict["creation_time"] =
|
|
338
|
+
product_dict["creation_time"] = gen.now_date_time_in_isoformat()
|
|
330
339
|
product_dict["description"] = self.schema_dict.get("description", None)
|
|
331
340
|
|
|
332
341
|
# DATA:CATEGORY
|
|
@@ -366,7 +375,7 @@ class MetadataCollector:
|
|
|
366
375
|
)
|
|
367
376
|
if instrument_dict["ID"]:
|
|
368
377
|
instrument_dict["class"] = names.get_collection_name_from_array_element_name(
|
|
369
|
-
instrument_dict["ID"]
|
|
378
|
+
instrument_dict["ID"], False
|
|
370
379
|
)
|
|
371
380
|
|
|
372
381
|
def _fill_process_meta(self, process_dict):
|
|
@@ -394,7 +403,7 @@ class MetadataCollector:
|
|
|
394
403
|
activity_dict["name"] = self.args_dict.get("label", None)
|
|
395
404
|
activity_dict["type"] = "software"
|
|
396
405
|
activity_dict["id"] = self.args_dict.get("activity_id", "UNDEFINED_ACTIVITY_ID")
|
|
397
|
-
activity_dict["start"] =
|
|
406
|
+
activity_dict["start"] = gen.now_date_time_in_isoformat()
|
|
398
407
|
activity_dict["end"] = activity_dict["start"]
|
|
399
408
|
activity_dict["software"]["name"] = "simtools"
|
|
400
409
|
activity_dict["software"]["version"] = simtools.version.__version__
|
|
@@ -12,6 +12,7 @@ from astropy.io.registry.base import IORegistryError
|
|
|
12
12
|
import simtools.utils.general as gen
|
|
13
13
|
from simtools.data_model import schema, validate_data
|
|
14
14
|
from simtools.data_model.metadata_collector import MetadataCollector
|
|
15
|
+
from simtools.db import db_handler
|
|
15
16
|
from simtools.io_operations import io_handler
|
|
16
17
|
from simtools.utils import names, value_conversion
|
|
17
18
|
|
|
@@ -126,6 +127,7 @@ class ModelDataWriter:
|
|
|
126
127
|
output_path=None,
|
|
127
128
|
use_plain_output_path=False,
|
|
128
129
|
metadata_input_dict=None,
|
|
130
|
+
db_config=None,
|
|
129
131
|
):
|
|
130
132
|
"""
|
|
131
133
|
Generate DB-style model parameter dict and write it to json file.
|
|
@@ -148,6 +150,8 @@ class ModelDataWriter:
|
|
|
148
150
|
Use plain output path.
|
|
149
151
|
metadata_input_dict: dict
|
|
150
152
|
Input to metadata collector.
|
|
153
|
+
db_config: dict
|
|
154
|
+
Database configuration. If not None, check if parameter with the same version exists.
|
|
151
155
|
|
|
152
156
|
Returns
|
|
153
157
|
-------
|
|
@@ -161,21 +165,72 @@ class ModelDataWriter:
|
|
|
161
165
|
output_path=output_path,
|
|
162
166
|
use_plain_output_path=use_plain_output_path,
|
|
163
167
|
)
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
|
168
174
|
if metadata_input_dict is not None:
|
|
169
175
|
metadata_input_dict["output_file"] = output_file
|
|
170
176
|
metadata_input_dict["output_file_format"] = Path(output_file).suffix.lstrip(".")
|
|
177
|
+
metadata = MetadataCollector(args_dict=metadata_input_dict).get_top_level_metadata()
|
|
171
178
|
writer.write_metadata_to_yml(
|
|
172
|
-
metadata=
|
|
173
|
-
yml_file=output_path / f"{Path(output_file).stem}",
|
|
179
|
+
metadata=metadata, yml_file=output_path / f"{Path(output_file).stem}"
|
|
174
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)
|
|
175
187
|
return _json_dict
|
|
176
188
|
|
|
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
|
+
|
|
177
226
|
def get_validated_parameter_dict(
|
|
178
|
-
self,
|
|
227
|
+
self,
|
|
228
|
+
parameter_name,
|
|
229
|
+
value,
|
|
230
|
+
instrument,
|
|
231
|
+
parameter_version,
|
|
232
|
+
unique_id=None,
|
|
233
|
+
schema_version=None,
|
|
179
234
|
):
|
|
180
235
|
"""
|
|
181
236
|
Get validated parameter dictionary.
|
|
@@ -202,20 +257,15 @@ class ModelDataWriter:
|
|
|
202
257
|
schema_file = schema.get_model_parameter_schema_file(parameter_name)
|
|
203
258
|
self.schema_dict = gen.collect_data_from_file(schema_file)
|
|
204
259
|
|
|
205
|
-
try: # e.g. instrument is 'North"
|
|
206
|
-
site = names.validate_site_name(instrument)
|
|
207
|
-
except ValueError: # e.g. instrument is 'LSTN-01'
|
|
208
|
-
site = names.get_site_from_array_element_name(instrument)
|
|
209
|
-
|
|
210
260
|
value, unit = value_conversion.split_value_and_unit(value)
|
|
211
261
|
|
|
212
262
|
data_dict = {
|
|
213
263
|
"schema_version": schema.get_model_parameter_schema_version(schema_version),
|
|
214
264
|
"parameter": parameter_name,
|
|
215
265
|
"instrument": instrument,
|
|
216
|
-
"site":
|
|
266
|
+
"site": names.get_site_from_array_element_name(instrument),
|
|
217
267
|
"parameter_version": parameter_version,
|
|
218
|
-
"unique_id":
|
|
268
|
+
"unique_id": unique_id,
|
|
219
269
|
"value": value,
|
|
220
270
|
"unit": unit,
|
|
221
271
|
"type": self._get_parameter_type(),
|
|
@@ -425,7 +475,9 @@ class ModelDataWriter:
|
|
|
425
475
|
If yml_file is not defined.
|
|
426
476
|
"""
|
|
427
477
|
try:
|
|
428
|
-
yml_file =
|
|
478
|
+
yml_file = names.file_name_with_version(
|
|
479
|
+
yml_file or self.product_data_file, ".metadata.yml"
|
|
480
|
+
)
|
|
429
481
|
with open(yml_file, "w", encoding="UTF-8") as file:
|
|
430
482
|
yaml.safe_dump(
|
|
431
483
|
gen.change_dict_keys_case(metadata, keys_lower_case),
|
simtools/data_model/schema.py
CHANGED
|
@@ -112,15 +112,22 @@ def validate_dict_using_schema(data, schema_file=None, json_schema=None):
|
|
|
112
112
|
if json_schema is None:
|
|
113
113
|
json_schema = load_schema(
|
|
114
114
|
schema_file,
|
|
115
|
-
data.get("schema_version"
|
|
115
|
+
data.get("schema_version")
|
|
116
|
+
or data.get(
|
|
117
|
+
"SCHEMA_VERSION", "0.1.0"
|
|
118
|
+
), # default version to ensure backward compatibility
|
|
116
119
|
)
|
|
117
120
|
|
|
118
121
|
try:
|
|
119
122
|
jsonschema.validate(data, schema=json_schema, format_checker=format_checkers.format_checker)
|
|
120
123
|
except jsonschema.exceptions.ValidationError as exc:
|
|
121
|
-
_logger.error(f"Validation failed using schema: {json_schema}")
|
|
124
|
+
_logger.error(f"Validation failed using schema: {json_schema} for data: {data}")
|
|
122
125
|
raise exc
|
|
123
|
-
if
|
|
126
|
+
if (
|
|
127
|
+
isinstance(data, dict)
|
|
128
|
+
and data.get("meta_schema_url")
|
|
129
|
+
and not gen.url_exists(data["meta_schema_url"])
|
|
130
|
+
):
|
|
124
131
|
raise FileNotFoundError(f"Meta schema URL does not exist: {data['meta_schema_url']}")
|
|
125
132
|
|
|
126
133
|
_logger.debug(f"Successful validation of data using schema ({json_schema.get('name')})")
|
|
@@ -12,7 +12,7 @@ from astropy.utils.diff import report_diff_values
|
|
|
12
12
|
|
|
13
13
|
import simtools.utils.general as gen
|
|
14
14
|
from simtools.data_model import schema
|
|
15
|
-
from simtools.utils import value_conversion
|
|
15
|
+
from simtools.utils import names, value_conversion
|
|
16
16
|
|
|
17
17
|
__all__ = ["DataValidator"]
|
|
18
18
|
|
|
@@ -79,7 +79,7 @@ class DataValidator:
|
|
|
79
79
|
|
|
80
80
|
"""
|
|
81
81
|
if self.data_file_name:
|
|
82
|
-
self.validate_data_file()
|
|
82
|
+
self.validate_data_file(is_model_parameter)
|
|
83
83
|
if isinstance(self.data_dict, dict):
|
|
84
84
|
return self._validate_data_dict(is_model_parameter, lists_as_strings)
|
|
85
85
|
if isinstance(self.data_table, Table):
|
|
@@ -87,11 +87,16 @@ class DataValidator:
|
|
|
87
87
|
self._logger.error("No data or data table to validate")
|
|
88
88
|
raise TypeError
|
|
89
89
|
|
|
90
|
-
def validate_data_file(self):
|
|
90
|
+
def validate_data_file(self, is_model_parameter=None):
|
|
91
91
|
"""
|
|
92
92
|
Open data file and read data from file.
|
|
93
93
|
|
|
94
94
|
Doing this successfully is understood as file validation.
|
|
95
|
+
|
|
96
|
+
Parameters
|
|
97
|
+
----------
|
|
98
|
+
is_model_parameter: bool
|
|
99
|
+
This is a model parameter file.
|
|
95
100
|
"""
|
|
96
101
|
try:
|
|
97
102
|
if Path(self.data_file_name).suffix in (".yml", ".yaml", ".json"):
|
|
@@ -102,14 +107,30 @@ class DataValidator:
|
|
|
102
107
|
self._logger.info(f"Validating tabled data from: {self.data_file_name}")
|
|
103
108
|
except (AttributeError, TypeError):
|
|
104
109
|
pass
|
|
110
|
+
if is_model_parameter:
|
|
111
|
+
self.validate_parameter_and_file_name()
|
|
105
112
|
|
|
106
113
|
def validate_parameter_and_file_name(self):
|
|
107
|
-
"""
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
114
|
+
"""
|
|
115
|
+
Validate model parameter file name.
|
|
116
|
+
|
|
117
|
+
Expect that the following convention is used:
|
|
118
|
+
|
|
119
|
+
- file name starts with the parameter name
|
|
120
|
+
- file name ends with parameter version string
|
|
121
|
+
|
|
122
|
+
"""
|
|
123
|
+
file_stem = Path(self.data_file_name).stem
|
|
124
|
+
param = self.data_dict.get("parameter")
|
|
125
|
+
param_version = self.data_dict.get("parameter_version")
|
|
126
|
+
if not file_stem.startswith(param):
|
|
127
|
+
raise ValueError(f"Mismatch: parameter '{param}' vs. file '{file_stem}'.")
|
|
128
|
+
|
|
129
|
+
if param_version and not file_stem.endswith(param_version):
|
|
130
|
+
raise ValueError(f"Mismatch: version '{param_version}' vs. file '{file_stem}'.")
|
|
131
|
+
|
|
132
|
+
if param_version is None:
|
|
133
|
+
self._logger.warning(f"File '{file_stem}' has no parameter version defined.")
|
|
113
134
|
|
|
114
135
|
@staticmethod
|
|
115
136
|
def validate_model_parameter(par_dict):
|
|
@@ -170,7 +191,17 @@ class DataValidator:
|
|
|
170
191
|
else:
|
|
171
192
|
self.data_dict["value"], self.data_dict["unit"] = value_as_list, unit_as_list
|
|
172
193
|
|
|
173
|
-
self.
|
|
194
|
+
if self.data_dict.get("instrument"):
|
|
195
|
+
if self.data_dict["instrument"] == self.data_dict["site"]: # site parameters
|
|
196
|
+
names.validate_site_name(self.data_dict["site"])
|
|
197
|
+
else:
|
|
198
|
+
names.validate_array_element_name(self.data_dict["instrument"])
|
|
199
|
+
self._check_site_and_array_element_consistency(
|
|
200
|
+
self.data_dict.get("instrument"), self.data_dict.get("site")
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
for version_string in ("version", "parameter_version", "model_version"):
|
|
204
|
+
self._check_version_string(self.data_dict.get(version_string))
|
|
174
205
|
|
|
175
206
|
if lists_as_strings:
|
|
176
207
|
self._convert_results_to_model_format()
|
|
@@ -766,20 +797,14 @@ class DataValidator:
|
|
|
766
797
|
Converts strings to numerical values or lists of values, if required.
|
|
767
798
|
|
|
768
799
|
"""
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
self.data_dict["value"] = float(value) if _is_float else int(value)
|
|
778
|
-
else:
|
|
779
|
-
self.data_dict["value"] = gen.convert_string_to_list(value, is_float=_is_float)
|
|
780
|
-
|
|
781
|
-
if self.data_dict["unit"] is not None:
|
|
782
|
-
self.data_dict["unit"] = gen.convert_string_to_list(self.data_dict["unit"])
|
|
800
|
+
self.data_dict["value"], _ = value_conversion.split_value_and_unit(
|
|
801
|
+
self.data_dict["value"],
|
|
802
|
+
"int" in self.data_dict.get("type", "float"),
|
|
803
|
+
)
|
|
804
|
+
if isinstance(self.data_dict["unit"], str):
|
|
805
|
+
self.data_dict["unit"] = gen.convert_string_to_list(
|
|
806
|
+
self.data_dict["unit"], force_comma_separation=True
|
|
807
|
+
)
|
|
783
808
|
|
|
784
809
|
def _convert_results_to_model_format(self):
|
|
785
810
|
"""
|
|
@@ -814,3 +839,24 @@ class DataValidator:
|
|
|
814
839
|
if not re.match(semver_regex, version):
|
|
815
840
|
raise ValueError(f"Invalid version string '{version}'")
|
|
816
841
|
self._logger.debug(f"Valid version string '{version}'")
|
|
842
|
+
|
|
843
|
+
def _check_site_and_array_element_consistency(self, instrument, site):
|
|
844
|
+
"""
|
|
845
|
+
Check that site and array element names are consistent.
|
|
846
|
+
|
|
847
|
+
An example for an inconsistency is 'LSTN' at site 'South'
|
|
848
|
+
"""
|
|
849
|
+
if not all([instrument, site]) or "OBS" in instrument:
|
|
850
|
+
return
|
|
851
|
+
|
|
852
|
+
def to_sorted_list(value):
|
|
853
|
+
"""Return value as sorted list."""
|
|
854
|
+
return [value] if isinstance(value, str) else sorted(value)
|
|
855
|
+
|
|
856
|
+
instrument_site = to_sorted_list(names.get_site_from_array_element_name(instrument))
|
|
857
|
+
site = to_sorted_list(site)
|
|
858
|
+
|
|
859
|
+
if instrument_site != site:
|
|
860
|
+
raise ValueError(
|
|
861
|
+
f"Site '{site}' and instrument site '{instrument_site}' are inconsistent."
|
|
862
|
+
)
|
simtools/db/db_handler.py
CHANGED
|
@@ -40,7 +40,7 @@ jsonschema_db_dict = {
|
|
|
40
40
|
"db_api_user": {"type": "string", "description": "API username"},
|
|
41
41
|
"db_api_pw": {"type": "string", "description": "Password for the API user"},
|
|
42
42
|
"db_api_authentication_database": {
|
|
43
|
-
"type": "string",
|
|
43
|
+
"type": ["string", "null"],
|
|
44
44
|
"default": "admin",
|
|
45
45
|
"description": "DB with user info (optional)",
|
|
46
46
|
},
|
|
@@ -119,7 +119,9 @@ class DatabaseHandler:
|
|
|
119
119
|
port=self.mongo_db_config["db_api_port"],
|
|
120
120
|
username=self.mongo_db_config["db_api_user"],
|
|
121
121
|
password=self.mongo_db_config["db_api_pw"],
|
|
122
|
-
authSource=self.mongo_db_config.get("db_api_authentication_database"
|
|
122
|
+
authSource=self.mongo_db_config.get("db_api_authentication_database")
|
|
123
|
+
if self.mongo_db_config.get("db_api_authentication_database")
|
|
124
|
+
else "admin",
|
|
123
125
|
directConnection=direct_connection,
|
|
124
126
|
ssl=not direct_connection,
|
|
125
127
|
tlsallowinvalidhostnames=True,
|
|
@@ -178,7 +180,6 @@ class DatabaseHandler:
|
|
|
178
180
|
parameter_version,
|
|
179
181
|
site,
|
|
180
182
|
array_element_name,
|
|
181
|
-
collection,
|
|
182
183
|
):
|
|
183
184
|
"""
|
|
184
185
|
Get a model parameter using the parameter version.
|
|
@@ -193,8 +194,6 @@ class DatabaseHandler:
|
|
|
193
194
|
Site name.
|
|
194
195
|
array_element_name: str
|
|
195
196
|
Name of the array element model (e.g. MSTN, SSTS).
|
|
196
|
-
collection: str
|
|
197
|
-
Collection of array element (e.g. telescopes, calibration_devices).
|
|
198
197
|
|
|
199
198
|
Returns
|
|
200
199
|
-------
|
|
@@ -209,7 +208,9 @@ class DatabaseHandler:
|
|
|
209
208
|
query["instrument"] = array_element_name
|
|
210
209
|
if site is not None:
|
|
211
210
|
query["site"] = site
|
|
212
|
-
return self._read_mongo_db(
|
|
211
|
+
return self._read_mongo_db(
|
|
212
|
+
query=query, collection_name=names.get_collection_name_from_parameter_name(parameter)
|
|
213
|
+
)
|
|
213
214
|
|
|
214
215
|
def get_model_parameters(
|
|
215
216
|
self,
|
|
@@ -228,7 +229,7 @@ class DatabaseHandler:
|
|
|
228
229
|
site: str
|
|
229
230
|
Site name.
|
|
230
231
|
array_element_name: str
|
|
231
|
-
Name of the array element model (e.g. LSTN-01,
|
|
232
|
+
Name of the array element model (e.g. LSTN-01, MSTx-FlashCam, ILLN-01).
|
|
232
233
|
model_version: str, list
|
|
233
234
|
Version(s) of the model.
|
|
234
235
|
collection: str
|
|
@@ -476,7 +477,7 @@ class DatabaseHandler:
|
|
|
476
477
|
"""
|
|
477
478
|
collection = self.get_collection(self._get_db_name(), "production_tables")
|
|
478
479
|
return sorted(
|
|
479
|
-
|
|
480
|
+
{post["model_version"] for post in collection.find({"collection": collection_name})}
|
|
480
481
|
)
|
|
481
482
|
|
|
482
483
|
def get_array_elements(self, model_version, collection="telescopes"):
|
|
@@ -499,6 +500,32 @@ class DatabaseHandler:
|
|
|
499
500
|
production_table = self._read_production_table_from_mongo_db(collection, model_version)
|
|
500
501
|
return sorted([entry for entry in production_table["parameters"] if "-design" not in entry])
|
|
501
502
|
|
|
503
|
+
def get_design_model(self, model_version, array_element_name, collection="telescopes"):
|
|
504
|
+
"""
|
|
505
|
+
Get the design model used for a given array element and a given model version.
|
|
506
|
+
|
|
507
|
+
Parameters
|
|
508
|
+
----------
|
|
509
|
+
model_version: str
|
|
510
|
+
Version of the model.
|
|
511
|
+
array_element_name: str
|
|
512
|
+
Name of the array element model (e.g. MSTN, SSTS).
|
|
513
|
+
collection: str
|
|
514
|
+
Which collection to get the array elements from:
|
|
515
|
+
i.e. telescopes, calibration_devices.
|
|
516
|
+
|
|
517
|
+
Returns
|
|
518
|
+
-------
|
|
519
|
+
str
|
|
520
|
+
Design model for a given array element.
|
|
521
|
+
"""
|
|
522
|
+
production_table = self._read_production_table_from_mongo_db(collection, model_version)
|
|
523
|
+
try:
|
|
524
|
+
return production_table["design_model"][array_element_name]
|
|
525
|
+
except KeyError:
|
|
526
|
+
# for eg. array_element_name == 'LSTN-design' returns 'LSTN-design'
|
|
527
|
+
return array_element_name
|
|
528
|
+
|
|
502
529
|
def get_array_elements_of_type(self, array_element_type, model_version, collection):
|
|
503
530
|
"""
|
|
504
531
|
Get array elements of a certain type (e.g. 'LSTN') for a DB collection.
|
|
@@ -837,15 +864,20 @@ class DatabaseHandler:
|
|
|
837
864
|
return ["xSTx-design"] # placeholder to ignore 'instrument' field in query.
|
|
838
865
|
if collection == "sites":
|
|
839
866
|
return [f"OBS-{site}"]
|
|
840
|
-
if
|
|
867
|
+
if names.is_design_type(array_element_name):
|
|
841
868
|
return [array_element_name]
|
|
869
|
+
if collection == "configuration_sim_telarray":
|
|
870
|
+
# get design model from 'telescope' or 'calibration_device' production tables
|
|
871
|
+
production_table = self._read_production_table_from_mongo_db(
|
|
872
|
+
names.get_collection_name_from_array_element_name(array_element_name),
|
|
873
|
+
production_table["model_version"],
|
|
874
|
+
)
|
|
842
875
|
try:
|
|
843
876
|
return [
|
|
844
877
|
production_table["design_model"][array_element_name],
|
|
845
878
|
array_element_name,
|
|
846
879
|
]
|
|
847
|
-
except KeyError:
|
|
848
|
-
|
|
849
|
-
f"{
|
|
850
|
-
|
|
851
|
-
]
|
|
880
|
+
except KeyError as exc:
|
|
881
|
+
raise KeyError(
|
|
882
|
+
f"Failed generated array element list for db query for {array_element_name}"
|
|
883
|
+
) from exc
|