gammasimtools 0.24.0__py3-none-any.whl → 0.26.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.24.0.dist-info → gammasimtools-0.26.0.dist-info}/METADATA +2 -1
- {gammasimtools-0.24.0.dist-info → gammasimtools-0.26.0.dist-info}/RECORD +134 -130
- {gammasimtools-0.24.0.dist-info → gammasimtools-0.26.0.dist-info}/entry_points.txt +3 -1
- {gammasimtools-0.24.0.dist-info → gammasimtools-0.26.0.dist-info}/licenses/LICENSE +1 -1
- simtools/_version.py +2 -2
- simtools/application_control.py +78 -0
- simtools/applications/calculate_incident_angles.py +0 -2
- simtools/applications/convert_geo_coordinates_of_array_elements.py +1 -2
- simtools/applications/db_add_file_to_db.py +1 -1
- simtools/applications/db_add_simulation_model_from_repository_to_db.py +1 -1
- simtools/applications/db_add_value_from_json_to_db.py +1 -1
- simtools/applications/db_generate_compound_indexes.py +1 -1
- simtools/applications/db_get_array_layouts_from_db.py +2 -6
- simtools/applications/db_get_file_from_db.py +1 -1
- simtools/applications/db_get_parameter_from_db.py +1 -1
- simtools/applications/db_inspect_databases.py +1 -1
- simtools/applications/db_upload_model_repository.py +1 -1
- simtools/applications/derive_ctao_array_layouts.py +1 -2
- simtools/applications/derive_mirror_rnda.py +1 -3
- simtools/applications/derive_psf_parameters.py +5 -1
- simtools/applications/derive_pulse_shape_parameters.py +194 -0
- simtools/applications/derive_trigger_rates.py +1 -1
- simtools/applications/docs_produce_array_element_report.py +2 -8
- simtools/applications/docs_produce_calibration_reports.py +1 -3
- simtools/applications/docs_produce_model_parameter_reports.py +0 -2
- simtools/applications/docs_produce_simulation_configuration_report.py +1 -3
- simtools/applications/generate_array_config.py +0 -1
- simtools/applications/generate_corsika_histograms.py +48 -235
- simtools/applications/generate_regular_arrays.py +5 -35
- simtools/applications/generate_simtel_event_data.py +2 -2
- simtools/applications/maintain_simulation_model_add_production.py +2 -2
- simtools/applications/maintain_simulation_model_write_array_element_positions.py +87 -0
- simtools/applications/plot_array_layout.py +64 -108
- simtools/applications/plot_simulated_event_distributions.py +57 -0
- simtools/applications/plot_tabular_data.py +0 -1
- simtools/applications/plot_tabular_data_for_model_parameter.py +1 -6
- simtools/applications/production_derive_corsika_limits.py +1 -1
- simtools/applications/production_generate_grid.py +0 -1
- simtools/applications/run_application.py +1 -1
- simtools/applications/simulate_flasher.py +3 -4
- simtools/applications/simulate_illuminator.py +0 -1
- simtools/applications/simulate_pedestals.py +2 -6
- simtools/applications/simulate_prod.py +9 -28
- simtools/applications/simulate_prod_htcondor_generator.py +8 -1
- simtools/applications/submit_array_layouts.py +7 -7
- simtools/applications/submit_model_parameter_from_external.py +1 -3
- simtools/applications/validate_camera_efficiency.py +0 -1
- simtools/applications/validate_camera_fov.py +0 -1
- simtools/applications/validate_cumulative_psf.py +0 -2
- simtools/applications/validate_file_using_schema.py +49 -123
- simtools/applications/validate_optics.py +0 -13
- simtools/camera/camera_efficiency.py +1 -6
- simtools/camera/single_photon_electron_spectrum.py +2 -1
- simtools/configuration/commandline_parser.py +43 -8
- simtools/configuration/configurator.py +6 -11
- simtools/corsika/corsika_config.py +204 -99
- simtools/corsika/corsika_histograms.py +411 -1735
- simtools/corsika/primary_particle.py +1 -1
- simtools/data_model/metadata_collector.py +5 -2
- simtools/data_model/metadata_model.py +0 -4
- simtools/data_model/model_data_writer.py +27 -17
- simtools/data_model/schema.py +112 -5
- simtools/data_model/validate_data.py +80 -48
- simtools/db/db_handler.py +19 -8
- simtools/db/db_model_upload.py +2 -1
- simtools/db/mongo_db.py +133 -42
- simtools/dependencies.py +83 -44
- simtools/io/ascii_handler.py +4 -2
- simtools/io/table_handler.py +1 -1
- simtools/job_execution/htcondor_script_generator.py +0 -2
- simtools/layout/array_layout.py +4 -12
- simtools/layout/array_layout_utils.py +227 -58
- simtools/model/array_model.py +37 -18
- simtools/model/calibration_model.py +0 -4
- simtools/model/legacy_model_parameter.py +134 -0
- simtools/model/model_parameter.py +24 -14
- simtools/model/model_repository.py +18 -5
- simtools/model/model_utils.py +1 -6
- simtools/model/site_model.py +0 -4
- simtools/model/telescope_model.py +6 -11
- simtools/production_configuration/derive_corsika_limits.py +6 -11
- simtools/production_configuration/interpolation_handler.py +16 -16
- simtools/ray_tracing/incident_angles.py +5 -11
- simtools/ray_tracing/mirror_panel_psf.py +3 -7
- simtools/ray_tracing/psf_analysis.py +29 -27
- simtools/ray_tracing/psf_parameter_optimisation.py +822 -680
- simtools/ray_tracing/ray_tracing.py +6 -15
- simtools/reporting/docs_auto_report_generator.py +8 -13
- simtools/reporting/docs_read_parameters.py +70 -16
- simtools/runners/corsika_runner.py +15 -10
- simtools/runners/corsika_simtel_runner.py +9 -8
- simtools/runners/runner_services.py +17 -7
- simtools/runners/simtel_runner.py +11 -58
- simtools/runners/simtools_runner.py +2 -4
- simtools/schemas/model_parameters/flasher_pulse_exp_decay.schema.yml +2 -0
- simtools/schemas/model_parameters/flasher_pulse_shape.schema.yml +50 -0
- simtools/schemas/model_parameters/flasher_pulse_width.schema.yml +2 -0
- simtools/schemas/simulation_models_info.schema.yml +2 -0
- simtools/settings.py +154 -0
- simtools/sim_events/file_info.py +128 -0
- simtools/{simtel/simtel_io_event_histograms.py → sim_events/histograms.py} +25 -15
- simtools/{simtel/simtel_io_event_reader.py → sim_events/reader.py} +20 -17
- simtools/{simtel/simtel_io_event_writer.py → sim_events/writer.py} +84 -25
- simtools/simtel/pulse_shapes.py +273 -0
- simtools/simtel/simtel_config_writer.py +146 -22
- simtools/simtel/simtel_table_reader.py +6 -4
- simtools/simtel/simulator_array.py +62 -23
- simtools/simtel/simulator_camera_efficiency.py +4 -6
- simtools/simtel/simulator_light_emission.py +101 -19
- simtools/simtel/simulator_ray_tracing.py +4 -10
- simtools/simulator.py +360 -353
- simtools/telescope_trigger_rates.py +3 -4
- simtools/testing/assertions.py +115 -8
- simtools/testing/configuration.py +2 -3
- simtools/testing/helpers.py +2 -3
- simtools/testing/log_inspector.py +5 -1
- simtools/testing/sim_telarray_metadata.py +1 -1
- simtools/testing/validate_output.py +69 -23
- simtools/utils/general.py +37 -0
- simtools/utils/geometry.py +0 -77
- simtools/utils/names.py +7 -9
- simtools/version.py +37 -0
- simtools/visualization/legend_handlers.py +21 -10
- simtools/visualization/plot_array_layout.py +312 -41
- simtools/visualization/plot_corsika_histograms.py +143 -605
- simtools/visualization/plot_mirrors.py +834 -0
- simtools/visualization/plot_pixels.py +2 -4
- simtools/visualization/plot_psf.py +0 -1
- simtools/visualization/plot_simtel_event_histograms.py +4 -4
- simtools/visualization/plot_simtel_events.py +6 -11
- simtools/visualization/plot_tables.py +8 -19
- simtools/visualization/visualize.py +22 -2
- simtools/applications/db_development_tools/write_array_elements_positions_to_repository.py +0 -160
- simtools/applications/print_version.py +0 -53
- simtools/io/hdf5_handler.py +0 -139
- simtools/simtel/simtel_io_file_info.py +0 -62
- {gammasimtools-0.24.0.dist-info → gammasimtools-0.26.0.dist-info}/WHEEL +0 -0
- {gammasimtools-0.24.0.dist-info → gammasimtools-0.26.0.dist-info}/top_level.txt +0 -0
|
@@ -185,7 +185,7 @@ class PrimaryParticle:
|
|
|
185
185
|
"silicon": {"corsika7_id": 2814},
|
|
186
186
|
"iron": {"corsika7_id": 5626},
|
|
187
187
|
}
|
|
188
|
-
for
|
|
188
|
+
for ids in particles.values():
|
|
189
189
|
ids["pdg_id"] = Corsika7ID(ids["corsika7_id"]).to_pdgid().numerator
|
|
190
190
|
ids["pdg_name"] = Particle.findall(pdgid=ids["pdg_id"])[0].name
|
|
191
191
|
|
|
@@ -360,6 +360,9 @@ class MetadataCollector:
|
|
|
360
360
|
"Metadata extraction from sim_telarray files is not supported yet."
|
|
361
361
|
)
|
|
362
362
|
continue
|
|
363
|
+
elif Path(metadata_file).name.endswith((".corsika.zst", ".corsika")):
|
|
364
|
+
self._logger.warning("Metadata extraction from CORSIKA files is not supported yet.")
|
|
365
|
+
continue
|
|
363
366
|
else:
|
|
364
367
|
raise ValueError(f"Unknown metadata file format: {metadata_file}")
|
|
365
368
|
|
|
@@ -422,7 +425,7 @@ class MetadataCollector:
|
|
|
422
425
|
product_dict["creation_time"] = gen.now_date_time_in_isoformat()
|
|
423
426
|
product_dict["description"] = self.schema_dict.get("description", None)
|
|
424
427
|
|
|
425
|
-
# DATA:CATEGORY
|
|
428
|
+
# metadata DATA:CATEGORY
|
|
426
429
|
product_dict["data"]["category"] = "SIM"
|
|
427
430
|
product_dict["data"]["level"] = "R1"
|
|
428
431
|
product_dict["data"]["type"] = "Service"
|
|
@@ -431,7 +434,7 @@ class MetadataCollector:
|
|
|
431
434
|
except KeyError:
|
|
432
435
|
pass
|
|
433
436
|
|
|
434
|
-
# DATA:MODEL
|
|
437
|
+
# metadata DATA:MODEL
|
|
435
438
|
product_dict["data"]["model"]["name"] = (
|
|
436
439
|
self.schema_dict.get("name")
|
|
437
440
|
or self.args_dict.get("metadata_product_data_name")
|
|
@@ -8,13 +8,9 @@ Follows CTAO top-level data model definition.
|
|
|
8
8
|
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
-
import logging
|
|
12
|
-
|
|
13
11
|
import simtools.data_model.schema
|
|
14
12
|
import simtools.utils.general as gen
|
|
15
13
|
|
|
16
|
-
_logger = logging.getLogger(__name__)
|
|
17
|
-
|
|
18
14
|
|
|
19
15
|
def get_default_metadata_dict(
|
|
20
16
|
schema_file=None, observatory="CTA", schema_version="latest", lower_case=True
|
|
@@ -98,9 +98,10 @@ class ModelDataWriter:
|
|
|
98
98
|
output_file,
|
|
99
99
|
output_path=None,
|
|
100
100
|
metadata_input_dict=None,
|
|
101
|
-
db_config=None,
|
|
102
101
|
unit=None,
|
|
103
102
|
meta_parameter=False,
|
|
103
|
+
model_parameter_schema_version=None,
|
|
104
|
+
check_db_for_existing_parameter=True,
|
|
104
105
|
):
|
|
105
106
|
"""
|
|
106
107
|
Generate DB-style model parameter dict and write it to json file.
|
|
@@ -121,10 +122,14 @@ class ModelDataWriter:
|
|
|
121
122
|
Path to output file.
|
|
122
123
|
metadata_input_dict: dict
|
|
123
124
|
Input to metadata collector.
|
|
124
|
-
db_config: dict
|
|
125
|
-
Database configuration. If not None, check if parameter with the same version exists.
|
|
126
125
|
unit: str
|
|
127
126
|
Unit of the parameter value (if applicable and value is not of type astropy Quantity).
|
|
127
|
+
meta_parameter: bool
|
|
128
|
+
Setting for meta parameter flag.
|
|
129
|
+
model_parameter_schema_version: str, None
|
|
130
|
+
Version of the model parameter schema (if None, use schema version from schema dict).
|
|
131
|
+
check_db_for_existing_parameter: bool
|
|
132
|
+
If True, check if parameter with same version exists in DB before writing.
|
|
128
133
|
|
|
129
134
|
Returns
|
|
130
135
|
-------
|
|
@@ -137,10 +142,8 @@ class ModelDataWriter:
|
|
|
137
142
|
args_dict=None,
|
|
138
143
|
output_path=output_path,
|
|
139
144
|
)
|
|
140
|
-
if
|
|
141
|
-
writer.check_db_for_existing_parameter(
|
|
142
|
-
parameter_name, instrument, parameter_version, db_config
|
|
143
|
-
)
|
|
145
|
+
if check_db_for_existing_parameter:
|
|
146
|
+
writer.check_db_for_existing_parameter(parameter_name, instrument, parameter_version)
|
|
144
147
|
|
|
145
148
|
unique_id = None
|
|
146
149
|
if metadata_input_dict is not None:
|
|
@@ -158,15 +161,14 @@ class ModelDataWriter:
|
|
|
158
161
|
instrument,
|
|
159
162
|
parameter_version,
|
|
160
163
|
unique_id,
|
|
164
|
+
model_parameter_schema_version=model_parameter_schema_version,
|
|
161
165
|
unit=unit,
|
|
162
166
|
meta_parameter=meta_parameter,
|
|
163
167
|
)
|
|
164
168
|
writer.write_dict_to_model_parameter_json(output_file, _json_dict)
|
|
165
169
|
return _json_dict
|
|
166
170
|
|
|
167
|
-
def check_db_for_existing_parameter(
|
|
168
|
-
self, parameter_name, instrument, parameter_version, db_config
|
|
169
|
-
):
|
|
171
|
+
def check_db_for_existing_parameter(self, parameter_name, instrument, parameter_version):
|
|
170
172
|
"""
|
|
171
173
|
Check if a parameter with the same version exists in the simulation model database.
|
|
172
174
|
|
|
@@ -178,15 +180,15 @@ class ModelDataWriter:
|
|
|
178
180
|
Name of the instrument.
|
|
179
181
|
parameter_version: str
|
|
180
182
|
Version of the parameter.
|
|
181
|
-
db_config: dict
|
|
182
|
-
Database configuration.
|
|
183
183
|
|
|
184
184
|
Raises
|
|
185
185
|
------
|
|
186
186
|
ValueError
|
|
187
187
|
If parameter with the same version exists in the database.
|
|
188
188
|
"""
|
|
189
|
-
db = db_handler.DatabaseHandler(
|
|
189
|
+
db = db_handler.DatabaseHandler()
|
|
190
|
+
if not db.is_configured():
|
|
191
|
+
return
|
|
190
192
|
try:
|
|
191
193
|
db.get_model_parameter(
|
|
192
194
|
parameter=parameter_name,
|
|
@@ -211,6 +213,7 @@ class ModelDataWriter:
|
|
|
211
213
|
schema_version=None,
|
|
212
214
|
unit=None,
|
|
213
215
|
meta_parameter=False,
|
|
216
|
+
model_parameter_schema_version=None,
|
|
214
217
|
):
|
|
215
218
|
"""
|
|
216
219
|
Get validated parameter dictionary.
|
|
@@ -233,6 +236,8 @@ class ModelDataWriter:
|
|
|
233
236
|
Unit of the parameter value (if applicable and value is not an astropy Quantity).
|
|
234
237
|
meta_parameter: bool
|
|
235
238
|
Setting for meta parameter flag.
|
|
239
|
+
model_parameter_schema_version: str, None
|
|
240
|
+
Version of the model parameter schema (if None, use schema version from schema dict).
|
|
236
241
|
|
|
237
242
|
Returns
|
|
238
243
|
-------
|
|
@@ -240,7 +245,9 @@ class ModelDataWriter:
|
|
|
240
245
|
Validated parameter dictionary.
|
|
241
246
|
"""
|
|
242
247
|
self._logger.debug(f"Getting validated parameter dictionary for {instrument}")
|
|
243
|
-
self.schema_dict, schema_file = self._read_schema_dict(
|
|
248
|
+
self.schema_dict, schema_file = self._read_schema_dict(
|
|
249
|
+
parameter_name, model_parameter_schema_version
|
|
250
|
+
)
|
|
244
251
|
|
|
245
252
|
if unit is None:
|
|
246
253
|
value, unit = value_conversion.split_value_and_unit(value)
|
|
@@ -257,7 +264,8 @@ class ModelDataWriter:
|
|
|
257
264
|
"type": self._get_parameter_type(),
|
|
258
265
|
"file": self._parameter_is_a_file(),
|
|
259
266
|
"meta_parameter": meta_parameter,
|
|
260
|
-
"model_parameter_schema_version":
|
|
267
|
+
"model_parameter_schema_version": model_parameter_schema_version
|
|
268
|
+
or self.schema_dict.get("schema_version", "0.1.0"),
|
|
261
269
|
}
|
|
262
270
|
return self.validate_and_transform(
|
|
263
271
|
product_data_dict=data_dict,
|
|
@@ -361,8 +369,10 @@ class ModelDataWriter:
|
|
|
361
369
|
"""
|
|
362
370
|
try:
|
|
363
371
|
unit_list = []
|
|
364
|
-
|
|
365
|
-
|
|
372
|
+
unit_list = [
|
|
373
|
+
data["unit"] if data["unit"] != "dimensionless" else None
|
|
374
|
+
for data in self.schema_dict["data"]
|
|
375
|
+
]
|
|
366
376
|
return unit_list if len(unit_list) > 1 else unit_list[0]
|
|
367
377
|
except (KeyError, IndexError):
|
|
368
378
|
pass
|
simtools/data_model/schema.py
CHANGED
|
@@ -146,11 +146,10 @@ def validate_dict_using_schema(
|
|
|
146
146
|
|
|
147
147
|
def _validate_meta_schema_url(data):
|
|
148
148
|
"""Validate meta_schema_url if present in data."""
|
|
149
|
-
if (
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
):
|
|
149
|
+
if not isinstance(data, dict):
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
if data.get("meta_schema_url") is not None and not gen.url_exists(data["meta_schema_url"]):
|
|
154
153
|
raise FileNotFoundError(f"Meta schema URL does not exist: {data['meta_schema_url']}")
|
|
155
154
|
|
|
156
155
|
|
|
@@ -355,3 +354,111 @@ def validate_deprecation_and_version(data, software_name=None, ignore_software_v
|
|
|
355
354
|
_logger.warning(f"{msg}, but version check is ignored.")
|
|
356
355
|
else:
|
|
357
356
|
raise ValueError(msg)
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def validate_schema_from_files(
|
|
360
|
+
file_directory, file_name=None, schema_file=None, ignore_software_version=False
|
|
361
|
+
):
|
|
362
|
+
"""
|
|
363
|
+
Validate a schema file or several files in a directory.
|
|
364
|
+
|
|
365
|
+
Files to be validated are taken from file_directory and file_name pattern.
|
|
366
|
+
The schema is either given as command line argument, read from the meta_schema_url or from
|
|
367
|
+
the metadata section of the data dictionary.
|
|
368
|
+
|
|
369
|
+
Parameters
|
|
370
|
+
----------
|
|
371
|
+
file_directory : str or Path, optional
|
|
372
|
+
Directory with files to be validated.
|
|
373
|
+
file_name : str or Path, optional
|
|
374
|
+
File name pattern to be validated.
|
|
375
|
+
schema_file : str, optional
|
|
376
|
+
Schema file name provided directly.
|
|
377
|
+
ignore_software_version : bool
|
|
378
|
+
If True, ignore software version check.
|
|
379
|
+
"""
|
|
380
|
+
if file_directory and file_name:
|
|
381
|
+
file_list = sorted(Path(file_directory).rglob(file_name))
|
|
382
|
+
else:
|
|
383
|
+
file_list = [Path(file_name)] if file_name else []
|
|
384
|
+
|
|
385
|
+
for _file_name in file_list:
|
|
386
|
+
try:
|
|
387
|
+
data = ascii_handler.collect_data_from_file(file_name=_file_name)
|
|
388
|
+
except FileNotFoundError as exc:
|
|
389
|
+
raise FileNotFoundError(f"Error reading schema file from {_file_name}") from exc
|
|
390
|
+
data = data if isinstance(data, list) else [data]
|
|
391
|
+
try:
|
|
392
|
+
for data_dict in data:
|
|
393
|
+
validate_dict_using_schema(
|
|
394
|
+
data_dict,
|
|
395
|
+
_get_schema_file_name(schema_file, _file_name, data_dict),
|
|
396
|
+
ignore_software_version=ignore_software_version,
|
|
397
|
+
)
|
|
398
|
+
except Exception as exc:
|
|
399
|
+
raise ValueError(f"Validation of file {_file_name} failed") from exc
|
|
400
|
+
_logger.info(f"Successful validation of file {_file_name}")
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def _get_schema_file_name(schema_file=None, file_name=None, data_dict=None):
|
|
404
|
+
"""
|
|
405
|
+
Get schema file name from metadata, data dict, or from file.
|
|
406
|
+
|
|
407
|
+
Parameters
|
|
408
|
+
----------
|
|
409
|
+
schema_file : str, optional
|
|
410
|
+
Schema file name provided directly.
|
|
411
|
+
file_name : str or Path, optional
|
|
412
|
+
File name to extract schema information from.
|
|
413
|
+
data_dict : dict, optional
|
|
414
|
+
Dictionary with metaschema information.
|
|
415
|
+
|
|
416
|
+
Returns
|
|
417
|
+
-------
|
|
418
|
+
str or None
|
|
419
|
+
Schema file name.
|
|
420
|
+
"""
|
|
421
|
+
if schema_file is not None:
|
|
422
|
+
return schema_file
|
|
423
|
+
|
|
424
|
+
if data_dict and (url := data_dict.get("meta_schema_url")):
|
|
425
|
+
return url
|
|
426
|
+
|
|
427
|
+
if file_name:
|
|
428
|
+
return _extract_schema_from_file(file_name)
|
|
429
|
+
|
|
430
|
+
return None
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def _extract_schema_url_from_metadata_dict(metadata, observatory="cta"):
|
|
434
|
+
"""Extract schema URL from metadata dictionary."""
|
|
435
|
+
for key in (observatory, observatory.lower()):
|
|
436
|
+
url = metadata.get(key, {}).get("product", {}).get("data", {}).get("model", {}).get("url")
|
|
437
|
+
if url:
|
|
438
|
+
return url
|
|
439
|
+
return None
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def _extract_schema_from_file(file_name, observatory="cta"):
|
|
443
|
+
"""
|
|
444
|
+
Extract schema file name from a metadata or data file.
|
|
445
|
+
|
|
446
|
+
Parameters
|
|
447
|
+
----------
|
|
448
|
+
file_name : str or Path
|
|
449
|
+
File name to extract schema information from.
|
|
450
|
+
observatory : str
|
|
451
|
+
Observatory name (default: "cta").
|
|
452
|
+
|
|
453
|
+
Returns
|
|
454
|
+
-------
|
|
455
|
+
str or None
|
|
456
|
+
Schema file name or None if not found.
|
|
457
|
+
|
|
458
|
+
"""
|
|
459
|
+
try:
|
|
460
|
+
metadata = ascii_handler.collect_data_from_file(file_name=file_name, yaml_document=0)
|
|
461
|
+
except FileNotFoundError:
|
|
462
|
+
return None
|
|
463
|
+
|
|
464
|
+
return _extract_schema_url_from_metadata_dict(metadata, observatory)
|
|
@@ -14,6 +14,7 @@ import simtools.utils.general as gen
|
|
|
14
14
|
from simtools.data_model import schema
|
|
15
15
|
from simtools.io import ascii_handler
|
|
16
16
|
from simtools.utils import names, value_conversion
|
|
17
|
+
from simtools.version import is_valid_semantic_version
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
class DataValidator:
|
|
@@ -46,7 +47,7 @@ class DataValidator:
|
|
|
46
47
|
check_exact_data_type=True,
|
|
47
48
|
):
|
|
48
49
|
"""Initialize validation class and read required reference data columns."""
|
|
49
|
-
self.
|
|
50
|
+
self.logger = logging.getLogger(__name__)
|
|
50
51
|
|
|
51
52
|
self.data_file_name = data_file
|
|
52
53
|
self.schema_file_name = schema_file
|
|
@@ -83,7 +84,7 @@ class DataValidator:
|
|
|
83
84
|
return self._validate_data_dict(is_model_parameter, lists_as_strings)
|
|
84
85
|
if isinstance(self.data_table, Table):
|
|
85
86
|
return self._validate_data_table()
|
|
86
|
-
self.
|
|
87
|
+
self.logger.error("No data or data table to validate")
|
|
87
88
|
raise TypeError
|
|
88
89
|
|
|
89
90
|
def validate_data_file(self, is_model_parameter=None):
|
|
@@ -100,10 +101,10 @@ class DataValidator:
|
|
|
100
101
|
try:
|
|
101
102
|
if Path(self.data_file_name).suffix in (".yml", ".yaml", ".json"):
|
|
102
103
|
self.data_dict = ascii_handler.collect_data_from_file(self.data_file_name)
|
|
103
|
-
self.
|
|
104
|
+
self.logger.info(f"Validating data from: {self.data_file_name}")
|
|
104
105
|
else:
|
|
105
106
|
self.data_table = Table.read(self.data_file_name, guess=True, delimiter=r"\s")
|
|
106
|
-
self.
|
|
107
|
+
self.logger.info(f"Validating tabled data from: {self.data_file_name}")
|
|
107
108
|
except (AttributeError, TypeError):
|
|
108
109
|
pass
|
|
109
110
|
if is_model_parameter:
|
|
@@ -129,7 +130,7 @@ class DataValidator:
|
|
|
129
130
|
raise ValueError(f"Mismatch: version '{param_version}' vs. file '{file_stem}'.")
|
|
130
131
|
|
|
131
132
|
if param_version is None:
|
|
132
|
-
self.
|
|
133
|
+
self.logger.warning(f"File '{file_stem}' has no parameter version defined.")
|
|
133
134
|
|
|
134
135
|
@staticmethod
|
|
135
136
|
def validate_model_parameter(par_dict):
|
|
@@ -153,6 +154,46 @@ class DataValidator:
|
|
|
153
154
|
)
|
|
154
155
|
return data_validator.validate_and_transform(is_model_parameter=True)
|
|
155
156
|
|
|
157
|
+
@staticmethod
|
|
158
|
+
def validate_data_files(
|
|
159
|
+
file_directory=None,
|
|
160
|
+
file_name=None,
|
|
161
|
+
is_model_parameter=True,
|
|
162
|
+
check_exact_data_type=False,
|
|
163
|
+
schema_file=None,
|
|
164
|
+
):
|
|
165
|
+
"""
|
|
166
|
+
Validate data or model parameters in files in a directory or a single file.
|
|
167
|
+
|
|
168
|
+
Parameters
|
|
169
|
+
----------
|
|
170
|
+
file_directory: str or Path
|
|
171
|
+
Directory with files to be validated.
|
|
172
|
+
file_name: str or Path
|
|
173
|
+
Name of the file to be validated.
|
|
174
|
+
is_model_parameter: bool
|
|
175
|
+
This is a model parameter (add some data preparation).
|
|
176
|
+
check_exact_data_type: bool
|
|
177
|
+
Require exact data type for validation.
|
|
178
|
+
"""
|
|
179
|
+
if file_directory:
|
|
180
|
+
file_list = sorted(Path(file_directory).rglob("*.json"))
|
|
181
|
+
elif file_name:
|
|
182
|
+
file_list = [Path(file_name)]
|
|
183
|
+
else:
|
|
184
|
+
return
|
|
185
|
+
|
|
186
|
+
for data_file in file_list:
|
|
187
|
+
parameter_name = re.sub(r"-\d+\.\d+\.\d+", "", data_file.stem)
|
|
188
|
+
schema_path = schema_file or schema.get_model_parameter_schema_file(f"{parameter_name}")
|
|
189
|
+
data_validator = DataValidator(
|
|
190
|
+
schema_file=schema_path,
|
|
191
|
+
data_file=data_file,
|
|
192
|
+
check_exact_data_type=check_exact_data_type,
|
|
193
|
+
)
|
|
194
|
+
data_validator.validate_and_transform(is_model_parameter)
|
|
195
|
+
data_validator.logger.info(f"Validated data file {data_file} with schema {schema_path}")
|
|
196
|
+
|
|
156
197
|
def _validate_data_dict(self, is_model_parameter=False, lists_as_strings=False):
|
|
157
198
|
"""
|
|
158
199
|
Validate values in a dictionary.
|
|
@@ -208,8 +249,10 @@ class DataValidator:
|
|
|
208
249
|
self.data_dict.get("instrument"), self.data_dict.get("site")
|
|
209
250
|
)
|
|
210
251
|
|
|
211
|
-
for
|
|
212
|
-
self.
|
|
252
|
+
for version_type in ("version", "parameter_version", "model_version"):
|
|
253
|
+
version_string = self.data_dict.get(version_type, "0.0.0")
|
|
254
|
+
if not is_valid_semantic_version(version_string):
|
|
255
|
+
raise ValueError(f"Invalid version string '{version_string}'")
|
|
213
256
|
|
|
214
257
|
if lists_as_strings:
|
|
215
258
|
self._convert_results_to_model_format()
|
|
@@ -278,7 +321,7 @@ class DataValidator:
|
|
|
278
321
|
"table_columns", None
|
|
279
322
|
)
|
|
280
323
|
except IndexError:
|
|
281
|
-
self.
|
|
324
|
+
self.logger.error(f"Error reading validation schema from {self.schema_file_name}")
|
|
282
325
|
raise
|
|
283
326
|
|
|
284
327
|
if self._data_description is not None:
|
|
@@ -327,7 +370,7 @@ class DataValidator:
|
|
|
327
370
|
for entry in self._data_description:
|
|
328
371
|
if entry.get("required", False):
|
|
329
372
|
if entry["name"] in self.data_table.columns:
|
|
330
|
-
self.
|
|
373
|
+
self.logger.debug(f"Found required data column {entry['name']}")
|
|
331
374
|
else:
|
|
332
375
|
raise KeyError(f"Missing required column {entry['name']}")
|
|
333
376
|
|
|
@@ -353,18 +396,18 @@ class DataValidator:
|
|
|
353
396
|
_columns_by_which_to_reverse_sort.append(entry["name"])
|
|
354
397
|
|
|
355
398
|
if len(_columns_by_which_to_sort) > 0:
|
|
356
|
-
self.
|
|
399
|
+
self.logger.debug(f"Sorting data columns: {_columns_by_which_to_sort}")
|
|
357
400
|
try:
|
|
358
401
|
self.data_table.sort(_columns_by_which_to_sort)
|
|
359
402
|
except AttributeError:
|
|
360
|
-
self.
|
|
403
|
+
self.logger.error("No data table defined for sorting")
|
|
361
404
|
raise
|
|
362
405
|
elif len(_columns_by_which_to_reverse_sort) > 0:
|
|
363
|
-
self.
|
|
406
|
+
self.logger.debug(f"Reverse sorting data columns: {_columns_by_which_to_reverse_sort}")
|
|
364
407
|
try:
|
|
365
408
|
self.data_table.sort(_columns_by_which_to_reverse_sort, reverse=True)
|
|
366
409
|
except AttributeError:
|
|
367
|
-
self.
|
|
410
|
+
self.logger.error("No data table defined for reverse sorting")
|
|
368
411
|
raise
|
|
369
412
|
|
|
370
413
|
def _check_data_for_duplicates(self):
|
|
@@ -379,7 +422,7 @@ class DataValidator:
|
|
|
379
422
|
"""
|
|
380
423
|
_column_with_unique_requirement = self._get_unique_column_requirement()
|
|
381
424
|
if len(_column_with_unique_requirement) == 0:
|
|
382
|
-
self.
|
|
425
|
+
self.logger.debug("No data columns with unique value requirement")
|
|
383
426
|
return
|
|
384
427
|
_data_table_unique_for_key_column = unique(
|
|
385
428
|
self.data_table, keys=_column_with_unique_requirement
|
|
@@ -412,10 +455,10 @@ class DataValidator:
|
|
|
412
455
|
|
|
413
456
|
for entry in self._data_description:
|
|
414
457
|
if "input_processing" in entry and "remove_duplicates" in entry["input_processing"]:
|
|
415
|
-
self.
|
|
458
|
+
self.logger.debug(f"Removing duplicates for column {entry['name']}")
|
|
416
459
|
_unique_required_column.append(entry["name"])
|
|
417
460
|
|
|
418
|
-
self.
|
|
461
|
+
self.logger.debug(f"Unique required columns: {_unique_required_column}")
|
|
419
462
|
return _unique_required_column
|
|
420
463
|
|
|
421
464
|
def _get_reference_unit(self, column_name):
|
|
@@ -470,7 +513,7 @@ class DataValidator:
|
|
|
470
513
|
dtype=dtype,
|
|
471
514
|
allow_subtypes=(not self.check_exact_data_type),
|
|
472
515
|
):
|
|
473
|
-
self.
|
|
516
|
+
self.logger.error(
|
|
474
517
|
f"Invalid data type in column '{column_name}'. "
|
|
475
518
|
f"Expected type '{reference_dtype}', found '{dtype}' "
|
|
476
519
|
f"(exact type: {self.check_exact_data_type})"
|
|
@@ -505,9 +548,9 @@ class DataValidator:
|
|
|
505
548
|
data = np.array(data)
|
|
506
549
|
|
|
507
550
|
if np.isnan(data).any():
|
|
508
|
-
self.
|
|
551
|
+
self.logger.info(f"Column {col_name} contains NaN.")
|
|
509
552
|
if np.isinf(data).any():
|
|
510
|
-
self.
|
|
553
|
+
self.logger.info(f"Column {col_name} contains infinite value.")
|
|
511
554
|
|
|
512
555
|
entry = self._get_data_description(col_name)
|
|
513
556
|
if "allow_nan" in entry.get("input_processing", {}):
|
|
@@ -593,7 +636,7 @@ class DataValidator:
|
|
|
593
636
|
# ensure that the data type is preserved (e.g., integers)
|
|
594
637
|
return (type(data)(u.Unit(column_unit).to(reference_unit) * data), reference_unit)
|
|
595
638
|
except (u.core.UnitConversionError, ValueError) as exc:
|
|
596
|
-
self.
|
|
639
|
+
self.logger.error(
|
|
597
640
|
f"Invalid unit in data column '{col_name}'. "
|
|
598
641
|
f"Expected type '{reference_unit}', found '{column_unit}'"
|
|
599
642
|
)
|
|
@@ -696,9 +739,9 @@ class DataValidator:
|
|
|
696
739
|
try:
|
|
697
740
|
col_index = int(col_name)
|
|
698
741
|
if col_index < max_logs:
|
|
699
|
-
self.
|
|
742
|
+
self.logger.debug(message)
|
|
700
743
|
except (ValueError, TypeError):
|
|
701
|
-
self.
|
|
744
|
+
self.logger.debug(message)
|
|
702
745
|
|
|
703
746
|
@staticmethod
|
|
704
747
|
def _interval_check(data, axis_range, range_type):
|
|
@@ -817,7 +860,7 @@ class DataValidator:
|
|
|
817
860
|
except IndexError as exc:
|
|
818
861
|
if len(self._data_description) == 1: # all columns are described by the same schema
|
|
819
862
|
return self._data_description[0]
|
|
820
|
-
self.
|
|
863
|
+
self.logger.error(
|
|
821
864
|
f"Data column '{column_name}' not found in reference column definition"
|
|
822
865
|
)
|
|
823
866
|
raise exc
|
|
@@ -835,7 +878,7 @@ class DataValidator:
|
|
|
835
878
|
try:
|
|
836
879
|
return _entry[_index]
|
|
837
880
|
except IndexError:
|
|
838
|
-
self.
|
|
881
|
+
self.logger.error(
|
|
839
882
|
f"Data column '{column_name}' not found in reference column definition"
|
|
840
883
|
)
|
|
841
884
|
raise
|
|
@@ -868,42 +911,31 @@ class DataValidator:
|
|
|
868
911
|
if isinstance(self.data_dict["unit"], list):
|
|
869
912
|
self.data_dict["unit"] = gen.convert_list_to_string(self.data_dict["unit"])
|
|
870
913
|
|
|
871
|
-
def _check_version_string(self, version):
|
|
872
|
-
"""
|
|
873
|
-
Check that version string follows semantic versioning.
|
|
874
|
-
|
|
875
|
-
Parameters
|
|
876
|
-
----------
|
|
877
|
-
version: str
|
|
878
|
-
version string
|
|
879
|
-
|
|
880
|
-
Raises
|
|
881
|
-
------
|
|
882
|
-
ValueError
|
|
883
|
-
if version string does not follow semantic versioning
|
|
884
|
-
|
|
885
|
-
"""
|
|
886
|
-
if version is None:
|
|
887
|
-
return
|
|
888
|
-
semver_regex = r"^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$"
|
|
889
|
-
if not re.match(semver_regex, version):
|
|
890
|
-
raise ValueError(f"Invalid version string '{version}'")
|
|
891
|
-
self._logger.debug(f"Valid version string '{version}'")
|
|
892
|
-
|
|
893
914
|
def _check_site_and_array_element_consistency(self, instrument, site):
|
|
894
915
|
"""
|
|
895
916
|
Check that site and array element names are consistent.
|
|
896
917
|
|
|
897
918
|
An example for an inconsistency is 'LSTN' at site 'South'
|
|
898
919
|
"""
|
|
899
|
-
if not
|
|
920
|
+
if not (instrument and site):
|
|
921
|
+
return
|
|
922
|
+
|
|
923
|
+
instruments = [instrument] if isinstance(instrument, str) else instrument
|
|
924
|
+
if any(inst.startswith("OBS") for inst in instruments):
|
|
900
925
|
return
|
|
901
926
|
|
|
902
927
|
def to_sorted_list(value):
|
|
903
928
|
"""Return value as sorted list."""
|
|
904
929
|
return [value] if isinstance(value, str) else sorted(value)
|
|
905
930
|
|
|
906
|
-
|
|
931
|
+
instrument_sites = [names.get_site_from_array_element_name(inst) for inst in instruments]
|
|
932
|
+
# names.get_site_from_array_element_name might return a list
|
|
933
|
+
flat_sites = [
|
|
934
|
+
s
|
|
935
|
+
for sublist in instrument_sites
|
|
936
|
+
for s in (sublist if isinstance(sublist, list) else [sublist])
|
|
937
|
+
]
|
|
938
|
+
instrument_site = to_sorted_list(set(flat_sites))
|
|
907
939
|
site = to_sorted_list(site)
|
|
908
940
|
|
|
909
941
|
if instrument_site != site:
|
simtools/db/db_handler.py
CHANGED
|
@@ -4,6 +4,7 @@ import logging
|
|
|
4
4
|
from collections import defaultdict
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
|
+
from simtools import settings
|
|
7
8
|
from simtools.data_model import validate_data
|
|
8
9
|
from simtools.db.mongo_db import MongoDBHandler
|
|
9
10
|
from simtools.io import io_handler
|
|
@@ -20,11 +21,6 @@ class DatabaseHandler:
|
|
|
20
21
|
|
|
21
22
|
- db_simulation_model_version (from db_config): version of the simulation model database
|
|
22
23
|
- model_version (from production_tables): version of the model contained in the database
|
|
23
|
-
|
|
24
|
-
Parameters
|
|
25
|
-
----------
|
|
26
|
-
db_config: dict
|
|
27
|
-
Dictionary with the DB configuration.
|
|
28
24
|
"""
|
|
29
25
|
|
|
30
26
|
ALLOWED_FILE_EXTENSIONS = [".dat", ".txt", ".lis", ".cfg", ".yml", ".yaml", ".ecsv"]
|
|
@@ -33,13 +29,17 @@ class DatabaseHandler:
|
|
|
33
29
|
model_parameters_cached = {}
|
|
34
30
|
model_versions_cached = {}
|
|
35
31
|
|
|
36
|
-
def __init__(self
|
|
32
|
+
def __init__(self):
|
|
37
33
|
"""Initialize the DatabaseHandler class."""
|
|
38
34
|
self._logger = logging.getLogger(__name__)
|
|
39
35
|
|
|
40
|
-
self.db_config =
|
|
36
|
+
self.db_config = (
|
|
37
|
+
MongoDBHandler.validate_db_config(dict(settings.config.db_config))
|
|
38
|
+
if settings.config.db_config
|
|
39
|
+
else None
|
|
40
|
+
)
|
|
41
41
|
self.io_handler = io_handler.IOHandler()
|
|
42
|
-
self.mongo_db_handler = MongoDBHandler(db_config) if self.db_config else None
|
|
42
|
+
self.mongo_db_handler = MongoDBHandler(self.db_config) if self.db_config else None
|
|
43
43
|
|
|
44
44
|
self.db_name = (
|
|
45
45
|
MongoDBHandler.get_db_name(
|
|
@@ -50,6 +50,17 @@ class DatabaseHandler:
|
|
|
50
50
|
else None
|
|
51
51
|
)
|
|
52
52
|
|
|
53
|
+
def is_configured(self):
|
|
54
|
+
"""
|
|
55
|
+
Check if the DatabaseHandler is configured.
|
|
56
|
+
|
|
57
|
+
Returns
|
|
58
|
+
-------
|
|
59
|
+
bool
|
|
60
|
+
True if the DatabaseHandler is configured, False otherwise.
|
|
61
|
+
"""
|
|
62
|
+
return self.mongo_db_handler is not None
|
|
63
|
+
|
|
53
64
|
def get_db_name(self, db_name=None, db_simulation_model_version=None, model_name=None):
|
|
54
65
|
"""Build DB name from configuration."""
|
|
55
66
|
if db_name:
|
simtools/db/db_model_upload.py
CHANGED
|
@@ -186,7 +186,8 @@ def _read_production_tables(model_path):
|
|
|
186
186
|
models = [model_path.name]
|
|
187
187
|
if (model_path / "info.yml").exists():
|
|
188
188
|
info = ascii_handler.collect_data_from_file(file_name=model_path / "info.yml")
|
|
189
|
-
|
|
189
|
+
if info.get("model_update") == "patch_update":
|
|
190
|
+
models.extend(info.get("model_version_history", []))
|
|
190
191
|
# sort oldest --> newest
|
|
191
192
|
models = sorted(set(models), key=Version, reverse=False)
|
|
192
193
|
for model in models:
|