gammasimtools 0.10.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.10.0.dist-info → gammasimtools-0.11.0.dist-info}/METADATA +3 -1
- {gammasimtools-0.10.0.dist-info → gammasimtools-0.11.0.dist-info}/RECORD +84 -77
- {gammasimtools-0.10.0.dist-info → gammasimtools-0.11.0.dist-info}/WHEEL +1 -1
- {gammasimtools-0.10.0.dist-info → gammasimtools-0.11.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 +29 -0
- 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 +42 -12
- 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 +17 -13
- simtools/version.py +2 -2
- simtools/visualization/legend_handlers.py +2 -0
- {gammasimtools-0.10.0.dist-info → gammasimtools-0.11.0.dist-info}/LICENSE +0 -0
- {gammasimtools-0.10.0.dist-info → gammasimtools-0.11.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
|
@@ -178,7 +178,6 @@ class DatabaseHandler:
|
|
|
178
178
|
parameter_version,
|
|
179
179
|
site,
|
|
180
180
|
array_element_name,
|
|
181
|
-
collection,
|
|
182
181
|
):
|
|
183
182
|
"""
|
|
184
183
|
Get a model parameter using the parameter version.
|
|
@@ -193,8 +192,6 @@ class DatabaseHandler:
|
|
|
193
192
|
Site name.
|
|
194
193
|
array_element_name: str
|
|
195
194
|
Name of the array element model (e.g. MSTN, SSTS).
|
|
196
|
-
collection: str
|
|
197
|
-
Collection of array element (e.g. telescopes, calibration_devices).
|
|
198
195
|
|
|
199
196
|
Returns
|
|
200
197
|
-------
|
|
@@ -209,7 +206,9 @@ class DatabaseHandler:
|
|
|
209
206
|
query["instrument"] = array_element_name
|
|
210
207
|
if site is not None:
|
|
211
208
|
query["site"] = site
|
|
212
|
-
return self._read_mongo_db(
|
|
209
|
+
return self._read_mongo_db(
|
|
210
|
+
query=query, collection_name=names.get_collection_name_from_parameter_name(parameter)
|
|
211
|
+
)
|
|
213
212
|
|
|
214
213
|
def get_model_parameters(
|
|
215
214
|
self,
|
|
@@ -228,7 +227,7 @@ class DatabaseHandler:
|
|
|
228
227
|
site: str
|
|
229
228
|
Site name.
|
|
230
229
|
array_element_name: str
|
|
231
|
-
Name of the array element model (e.g. LSTN-01,
|
|
230
|
+
Name of the array element model (e.g. LSTN-01, MSTx-FlashCam, ILLN-01).
|
|
232
231
|
model_version: str, list
|
|
233
232
|
Version(s) of the model.
|
|
234
233
|
collection: str
|
|
@@ -476,7 +475,7 @@ class DatabaseHandler:
|
|
|
476
475
|
"""
|
|
477
476
|
collection = self.get_collection(self._get_db_name(), "production_tables")
|
|
478
477
|
return sorted(
|
|
479
|
-
|
|
478
|
+
{post["model_version"] for post in collection.find({"collection": collection_name})}
|
|
480
479
|
)
|
|
481
480
|
|
|
482
481
|
def get_array_elements(self, model_version, collection="telescopes"):
|
|
@@ -499,6 +498,32 @@ class DatabaseHandler:
|
|
|
499
498
|
production_table = self._read_production_table_from_mongo_db(collection, model_version)
|
|
500
499
|
return sorted([entry for entry in production_table["parameters"] if "-design" not in entry])
|
|
501
500
|
|
|
501
|
+
def get_design_model(self, model_version, array_element_name, collection="telescopes"):
|
|
502
|
+
"""
|
|
503
|
+
Get the design model used for a given array element and a given model version.
|
|
504
|
+
|
|
505
|
+
Parameters
|
|
506
|
+
----------
|
|
507
|
+
model_version: str
|
|
508
|
+
Version of the model.
|
|
509
|
+
array_element_name: str
|
|
510
|
+
Name of the array element model (e.g. MSTN, SSTS).
|
|
511
|
+
collection: str
|
|
512
|
+
Which collection to get the array elements from:
|
|
513
|
+
i.e. telescopes, calibration_devices.
|
|
514
|
+
|
|
515
|
+
Returns
|
|
516
|
+
-------
|
|
517
|
+
str
|
|
518
|
+
Design model for a given array element.
|
|
519
|
+
"""
|
|
520
|
+
production_table = self._read_production_table_from_mongo_db(collection, model_version)
|
|
521
|
+
try:
|
|
522
|
+
return production_table["design_model"][array_element_name]
|
|
523
|
+
except KeyError:
|
|
524
|
+
# for eg. array_element_name == 'LSTN-design' returns 'LSTN-design'
|
|
525
|
+
return array_element_name
|
|
526
|
+
|
|
502
527
|
def get_array_elements_of_type(self, array_element_type, model_version, collection):
|
|
503
528
|
"""
|
|
504
529
|
Get array elements of a certain type (e.g. 'LSTN') for a DB collection.
|
|
@@ -837,15 +862,20 @@ class DatabaseHandler:
|
|
|
837
862
|
return ["xSTx-design"] # placeholder to ignore 'instrument' field in query.
|
|
838
863
|
if collection == "sites":
|
|
839
864
|
return [f"OBS-{site}"]
|
|
840
|
-
if
|
|
865
|
+
if names.is_design_type(array_element_name):
|
|
841
866
|
return [array_element_name]
|
|
867
|
+
if collection == "configuration_sim_telarray":
|
|
868
|
+
# get design model from 'telescope' or 'calibration_device' production tables
|
|
869
|
+
production_table = self._read_production_table_from_mongo_db(
|
|
870
|
+
names.get_collection_name_from_array_element_name(array_element_name),
|
|
871
|
+
production_table["model_version"],
|
|
872
|
+
)
|
|
842
873
|
try:
|
|
843
874
|
return [
|
|
844
875
|
production_table["design_model"][array_element_name],
|
|
845
876
|
array_element_name,
|
|
846
877
|
]
|
|
847
|
-
except KeyError:
|
|
848
|
-
|
|
849
|
-
f"{
|
|
850
|
-
|
|
851
|
-
]
|
|
878
|
+
except KeyError as exc:
|
|
879
|
+
raise KeyError(
|
|
880
|
+
f"Failed generated array element list for db query for {array_element_name}"
|
|
881
|
+
) from exc
|
simtools/dependencies.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Simtools dependencies version management.
|
|
3
|
+
|
|
4
|
+
This modules provides two main functionalities:
|
|
5
|
+
|
|
6
|
+
- retrieve the versions of simtools dependencies (e.g., databases, sim_telarray, CORSIKA)
|
|
7
|
+
- provide space for future implementations of version management
|
|
8
|
+
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
import os
|
|
13
|
+
import re
|
|
14
|
+
import subprocess
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
import simtools.utils.general as gen
|
|
18
|
+
from simtools.db.db_handler import DatabaseHandler
|
|
19
|
+
|
|
20
|
+
_logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_version_string(db_config=None):
|
|
24
|
+
"""Print the versions of the dependencies."""
|
|
25
|
+
return (
|
|
26
|
+
f"Database version: {get_database_version(db_config)}\n"
|
|
27
|
+
f"sim_telarray version: {get_sim_telarray_version()}\n"
|
|
28
|
+
f"CORSIKA version: {get_corsika_version()}\n"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_database_version(db_config):
|
|
33
|
+
"""
|
|
34
|
+
Get the version of the simulation model data base used.
|
|
35
|
+
|
|
36
|
+
Parameters
|
|
37
|
+
----------
|
|
38
|
+
db_config : dict
|
|
39
|
+
Dictionary containing the database configuration.
|
|
40
|
+
|
|
41
|
+
Returns
|
|
42
|
+
-------
|
|
43
|
+
str
|
|
44
|
+
Version of the simulation model data base used.
|
|
45
|
+
|
|
46
|
+
"""
|
|
47
|
+
if db_config is None:
|
|
48
|
+
return None
|
|
49
|
+
db = DatabaseHandler(db_config)
|
|
50
|
+
return db.mongo_db_config.get("db_simulation_model")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_sim_telarray_version():
|
|
54
|
+
"""
|
|
55
|
+
Get the version of the sim_telarray package using 'sim_telarray --version'.
|
|
56
|
+
|
|
57
|
+
Returns
|
|
58
|
+
-------
|
|
59
|
+
str
|
|
60
|
+
Version of the sim_telarray package.
|
|
61
|
+
"""
|
|
62
|
+
sim_telarray_path = os.getenv("SIMTOOLS_SIMTEL_PATH")
|
|
63
|
+
if sim_telarray_path is None:
|
|
64
|
+
_logger.warning("Environment variable SIMTOOLS_SIMTEL_PATH is not set.")
|
|
65
|
+
return None
|
|
66
|
+
sim_telarray_path = Path(sim_telarray_path) / "sim_telarray" / "bin" / "sim_telarray"
|
|
67
|
+
|
|
68
|
+
# expect stdout with e.g. a line 'Release: 2024.271.0 from 2024-09-27'
|
|
69
|
+
result = subprocess.run(
|
|
70
|
+
[sim_telarray_path, "--version"],
|
|
71
|
+
capture_output=True,
|
|
72
|
+
text=True,
|
|
73
|
+
check=False,
|
|
74
|
+
)
|
|
75
|
+
match = re.search(r"^Release:\s+(.+)", result.stdout, re.MULTILINE)
|
|
76
|
+
|
|
77
|
+
if match:
|
|
78
|
+
return match.group(1).split()[0]
|
|
79
|
+
raise ValueError(f"sim_telarray release not found in {result.stdout}")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def get_corsika_version():
|
|
83
|
+
"""
|
|
84
|
+
Get the version of the corsika package.
|
|
85
|
+
|
|
86
|
+
Returns
|
|
87
|
+
-------
|
|
88
|
+
str
|
|
89
|
+
Version of the corsika package.
|
|
90
|
+
"""
|
|
91
|
+
try:
|
|
92
|
+
build_opts = get_build_options()
|
|
93
|
+
except (FileNotFoundError, TypeError):
|
|
94
|
+
_logger.warning("CORSIKA version not implemented yet.")
|
|
95
|
+
return None
|
|
96
|
+
return build_opts.get("corsika_version")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def get_build_options():
|
|
100
|
+
"""
|
|
101
|
+
Return CORSIKA / sim_telarray build options.
|
|
102
|
+
|
|
103
|
+
Expects a build_opts.yml file in the sim_telarray directory.
|
|
104
|
+
"""
|
|
105
|
+
try:
|
|
106
|
+
return gen.collect_data_from_file(
|
|
107
|
+
Path(os.getenv("SIMTOOLS_SIMTEL_PATH")) / "build_opts.yml"
|
|
108
|
+
)
|
|
109
|
+
except FileNotFoundError as exc:
|
|
110
|
+
raise FileNotFoundError("No build_opts.yml file found.") from exc
|
|
111
|
+
except TypeError as exc:
|
|
112
|
+
raise TypeError("SIMTOOLS_SIMTEL_PATH not defined.") from exc
|