gammasimtools 0.9.0__py3-none-any.whl → 0.10.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. {gammasimtools-0.9.0.dist-info → gammasimtools-0.10.0.dist-info}/METADATA +2 -2
  2. {gammasimtools-0.9.0.dist-info → gammasimtools-0.10.0.dist-info}/RECORD +94 -85
  3. {gammasimtools-0.9.0.dist-info → gammasimtools-0.10.0.dist-info}/entry_points.txt +2 -1
  4. simtools/_version.py +2 -2
  5. simtools/applications/calculate_trigger_rate.py +15 -38
  6. simtools/applications/convert_all_model_parameters_from_simtel.py +9 -28
  7. simtools/applications/convert_geo_coordinates_of_array_elements.py +47 -45
  8. simtools/applications/convert_model_parameter_from_simtel.py +2 -2
  9. simtools/applications/db_add_file_to_db.py +1 -2
  10. simtools/applications/db_add_simulation_model_from_repository_to_db.py +110 -0
  11. simtools/applications/db_add_value_from_json_to_db.py +1 -2
  12. simtools/applications/db_development_tools/write_array_elements_positions_to_repository.py +6 -6
  13. simtools/applications/db_get_file_from_db.py +11 -12
  14. simtools/applications/db_get_parameter_from_db.py +44 -32
  15. simtools/applications/derive_photon_electron_spectrum.py +99 -0
  16. simtools/applications/generate_array_config.py +17 -17
  17. simtools/applications/generate_regular_arrays.py +15 -15
  18. simtools/applications/generate_simtel_array_histograms.py +11 -48
  19. simtools/applications/production_generate_simulation_config.py +25 -7
  20. simtools/applications/production_scale_events.py +2 -2
  21. simtools/applications/simulate_prod.py +1 -1
  22. simtools/applications/simulate_prod_htcondor_generator.py +26 -26
  23. simtools/applications/submit_data_from_external.py +12 -4
  24. simtools/applications/submit_model_parameter_from_external.py +8 -6
  25. simtools/applications/validate_camera_efficiency.py +2 -2
  26. simtools/applications/validate_file_using_schema.py +23 -19
  27. simtools/camera/single_photon_electron_spectrum.py +168 -0
  28. simtools/configuration/commandline_parser.py +8 -1
  29. simtools/constants.py +10 -3
  30. simtools/corsika/corsika_config.py +8 -7
  31. simtools/corsika/corsika_histograms.py +1 -1
  32. simtools/data_model/data_reader.py +0 -3
  33. simtools/data_model/metadata_collector.py +3 -4
  34. simtools/data_model/metadata_model.py +8 -124
  35. simtools/data_model/model_data_writer.py +17 -63
  36. simtools/data_model/schema.py +213 -0
  37. simtools/data_model/validate_data.py +9 -44
  38. simtools/db/db_handler.py +323 -495
  39. simtools/db/db_model_upload.py +139 -0
  40. simtools/io_operations/hdf5_handler.py +54 -24
  41. simtools/layout/array_layout.py +33 -28
  42. simtools/model/array_model.py +13 -7
  43. simtools/model/model_parameter.py +22 -54
  44. simtools/model/site_model.py +2 -2
  45. simtools/production_configuration/calculate_statistical_errors_grid_point.py +119 -144
  46. simtools/production_configuration/event_scaler.py +7 -17
  47. simtools/production_configuration/generate_simulation_config.py +5 -32
  48. simtools/production_configuration/interpolation_handler.py +8 -11
  49. simtools/runners/corsika_simtel_runner.py +3 -1
  50. simtools/schemas/input/MST_mirror_2f_measurements.schema.yml +39 -0
  51. simtools/schemas/input/single_pe_spectrum.schema.yml +38 -0
  52. simtools/schemas/integration_tests_config.metaschema.yml +10 -0
  53. simtools/schemas/model_parameter.metaschema.yml +7 -2
  54. simtools/schemas/model_parameter_and_data_schema.metaschema.yml +2 -0
  55. simtools/schemas/model_parameters/array_element_position_utm.schema.yml +1 -1
  56. simtools/schemas/model_parameters/array_window.schema.yml +37 -0
  57. simtools/schemas/model_parameters/asum_clipping.schema.yml +0 -4
  58. simtools/schemas/model_parameters/channels_per_chip.schema.yml +1 -1
  59. simtools/schemas/model_parameters/corsika_iact_io_buffer.schema.yml +2 -2
  60. simtools/schemas/model_parameters/dsum_clipping.schema.yml +0 -2
  61. simtools/schemas/model_parameters/dsum_ignore_below.schema.yml +0 -2
  62. simtools/schemas/model_parameters/dsum_offset.schema.yml +0 -2
  63. simtools/schemas/model_parameters/dsum_pedsub.schema.yml +0 -2
  64. simtools/schemas/model_parameters/dsum_pre_clipping.schema.yml +0 -2
  65. simtools/schemas/model_parameters/dsum_prescale.schema.yml +0 -2
  66. simtools/schemas/model_parameters/dsum_presum_max.schema.yml +0 -2
  67. simtools/schemas/model_parameters/dsum_presum_shift.schema.yml +0 -2
  68. simtools/schemas/model_parameters/dsum_shaping.schema.yml +0 -2
  69. simtools/schemas/model_parameters/dsum_shaping_renormalize.schema.yml +0 -2
  70. simtools/schemas/model_parameters/dsum_threshold.schema.yml +0 -2
  71. simtools/schemas/model_parameters/dsum_zero_clip.schema.yml +0 -2
  72. simtools/schemas/model_parameters/fadc_compensate_pedestal.schema.yml +1 -1
  73. simtools/schemas/model_parameters/fadc_lg_compensate_pedestal.schema.yml +1 -1
  74. simtools/schemas/model_parameters/fadc_noise.schema.yml +3 -3
  75. simtools/schemas/model_parameters/fake_mirror_list.schema.yml +33 -0
  76. simtools/schemas/model_parameters/laser_photons.schema.yml +2 -2
  77. simtools/schemas/model_parameters/secondary_mirror_degraded_reflection.schema.yml +1 -1
  78. simtools/schemas/production_configuration_metrics.schema.yml +68 -0
  79. simtools/schemas/production_tables.schema.yml +41 -0
  80. simtools/simtel/simtel_config_writer.py +5 -6
  81. simtools/simtel/simtel_io_histogram.py +32 -67
  82. simtools/simtel/simtel_io_histograms.py +15 -30
  83. simtools/simtel/simulator_array.py +2 -1
  84. simtools/simtel/simulator_camera_efficiency.py +5 -0
  85. simtools/simtel/simulator_light_emission.py +3 -1
  86. simtools/simtel/simulator_ray_tracing.py +2 -1
  87. simtools/testing/helpers.py +6 -13
  88. simtools/testing/validate_output.py +131 -47
  89. simtools/utils/general.py +102 -12
  90. simtools/utils/names.py +24 -20
  91. simtools/applications/db_add_model_parameters_from_repository_to_db.py +0 -176
  92. simtools/db/db_array_elements.py +0 -130
  93. {gammasimtools-0.9.0.dist-info → gammasimtools-0.10.0.dist-info}/LICENSE +0 -0
  94. {gammasimtools-0.9.0.dist-info → gammasimtools-0.10.0.dist-info}/WHEEL +0 -0
  95. {gammasimtools-0.9.0.dist-info → gammasimtools-0.10.0.dist-info}/top_level.txt +0 -0
  96. /simtools/{camera_efficiency.py → camera/camera_efficiency.py} +0 -0
simtools/db/db_handler.py CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  import logging
4
4
  import re
5
- from importlib.resources import files
6
5
  from pathlib import Path
7
6
  from threading import Lock
8
7
 
@@ -10,11 +9,9 @@ import gridfs
10
9
  import jsonschema
11
10
  from bson.objectid import ObjectId
12
11
  from packaging.version import Version
13
- from pymongo import ASCENDING, MongoClient
14
- from pymongo.errors import BulkWriteError
12
+ from pymongo import MongoClient
15
13
 
16
14
  from simtools.data_model import validate_data
17
- from simtools.db import db_array_elements
18
15
  from simtools.io_operations import io_handler
19
16
  from simtools.utils import names, value_conversion
20
17
 
@@ -66,17 +63,11 @@ class DatabaseHandler:
66
63
  Dictionary with the MongoDB configuration (see jsonschema_db_dict for details).
67
64
  """
68
65
 
69
- DB_CTA_SIMULATION_MODEL_DESCRIPTIONS = "CTA-Simulation-Model-Descriptions"
70
- # DB collection with updates field names
71
- DB_DERIVED_VALUES = "Staging-CTA-Simulation-Model-Derived-Values"
72
-
73
66
  ALLOWED_FILE_EXTENSIONS = [".dat", ".txt", ".lis", ".cfg", ".yml", ".yaml", ".ecsv"]
74
67
 
75
68
  db_client = None
76
- site_parameters_cached = {}
69
+ production_table_cached = {}
77
70
  model_parameters_cached = {}
78
- model_versions_cached = {}
79
- corsika_configuration_parameters_cached = {}
80
71
 
81
72
  def __init__(self, mongo_db_config=None):
82
73
  """Initialize the DatabaseHandler class."""
@@ -150,12 +141,13 @@ class DatabaseHandler:
150
141
 
151
142
  """
152
143
  try:
153
- if not self.mongo_db_config["db_simulation_model"].endswith("LATEST"):
144
+ db_simulation_model = self.mongo_db_config["db_simulation_model"]
145
+ if not db_simulation_model.endswith("LATEST"):
154
146
  return
155
- except TypeError:
147
+ except TypeError: # db_simulation_model is None
156
148
  return
157
149
 
158
- prefix = self.mongo_db_config["db_simulation_model"].replace("LATEST", "")
150
+ prefix = db_simulation_model.replace("LATEST", "")
159
151
  list_of_db_names = self.db_client.list_database_names()
160
152
  filtered_list_of_db_names = [s for s in list_of_db_names if s.startswith(prefix)]
161
153
  versioned_strings = []
@@ -180,69 +172,117 @@ class DatabaseHandler:
180
172
  else:
181
173
  raise ValueError("Found LATEST in the DB name but no matching versions found in DB.")
182
174
 
175
+ def get_model_parameter(
176
+ self,
177
+ parameter,
178
+ parameter_version,
179
+ site,
180
+ array_element_name,
181
+ collection,
182
+ ):
183
+ """
184
+ Get a model parameter using the parameter version.
185
+
186
+ Parameters
187
+ ----------
188
+ parameter: str
189
+ Name of the parameter.
190
+ parameter_version: str
191
+ Version of the parameter.
192
+ site: str
193
+ Site name.
194
+ array_element_name: str
195
+ Name of the array element model (e.g. MSTN, SSTS).
196
+ collection: str
197
+ Collection of array element (e.g. telescopes, calibration_devices).
198
+
199
+ Returns
200
+ -------
201
+ dict containing the parameter
202
+
203
+ """
204
+ query = {
205
+ "parameter_version": parameter_version,
206
+ "parameter": parameter,
207
+ }
208
+ if array_element_name is not None:
209
+ query["instrument"] = array_element_name
210
+ if site is not None:
211
+ query["site"] = site
212
+ return self._read_mongo_db(query=query, collection_name=collection)
213
+
183
214
  def get_model_parameters(
184
215
  self,
185
216
  site,
186
217
  array_element_name,
187
- model_version,
188
- collection="telescope",
189
- only_applicable=False,
218
+ collection,
219
+ model_version=None,
190
220
  ):
191
221
  """
192
- Get parameters from MongoDB or simulation model repository for an array element.
222
+ Get model parameters using the model version.
193
223
 
194
- An array element can be e.g., a telescope or a calibration device.
195
- Read parameters for design and for the specified array element (if necessary). This allows
196
- to overwrite design parameters with specific parameters without having to copy
197
- all model parameters when changing only a few.
224
+ Queries parameters for design and for the specified array element (if necessary).
198
225
 
199
226
  Parameters
200
227
  ----------
201
228
  site: str
202
229
  Site name.
203
230
  array_element_name: str
204
- Name of the array element model (e.g. LSTN-01, MSTS-design)
205
- model_version: str
206
- Version of the model.
231
+ Name of the array element model (e.g. LSTN-01, MSTS-design, ILLN-01).
232
+ model_version: str, list
233
+ Version(s) of the model.
207
234
  collection: str
208
- collection of array element (e.g. telescopes, calibration_devices)
209
- only_applicable: bool
210
- If True, only applicable parameters will be read.
235
+ Collection of array element (e.g. telescopes, calibration_devices).
211
236
 
212
237
  Returns
213
238
  -------
214
239
  dict containing the parameters
215
-
216
240
  """
217
- _site, _array_element_name, _model_version = self._validate_model_input(
218
- site, array_element_name, model_version
219
- )
220
- array_element_list = db_array_elements.get_array_element_list_for_db_query(
221
- _array_element_name, self, _model_version, collection
241
+ model_versions = (
242
+ self.get_model_versions(collection) if model_version is None else [model_version]
222
243
  )
244
+
223
245
  pars = {}
224
- for array_element in array_element_list:
225
- _array_elements_cache_key = self._parameter_cache_key(
226
- site, array_element, model_version, collection
246
+ for _model_version in model_versions:
247
+ production_table = self._read_production_table_from_mongo_db(collection, _model_version)
248
+ array_element_list = self._get_array_element_list(
249
+ array_element_name, site, production_table, collection
227
250
  )
228
- try:
229
- pars.update(DatabaseHandler.model_parameters_cached[_array_elements_cache_key])
230
- except KeyError:
251
+ for array_element in array_element_list:
231
252
  pars.update(
232
- self.read_mongo_db(
233
- self.mongo_db_config.get("db_simulation_model", None),
234
- array_element_name=array_element,
235
- model_version=_model_version,
236
- collection_name=collection,
237
- run_location=None,
238
- write_files=False,
239
- only_applicable=only_applicable,
253
+ self._get_parameter_for_model_version(
254
+ array_element, _model_version, site, collection, production_table
240
255
  )
241
256
  )
242
- DatabaseHandler.model_parameters_cached[_array_elements_cache_key] = pars
243
-
244
257
  return pars
245
258
 
259
+ def _get_parameter_for_model_version(
260
+ self, array_element, model_version, site, collection, production_table
261
+ ):
262
+ cache_key, cache_dict = self._read_cache(
263
+ DatabaseHandler.model_parameters_cached,
264
+ names.validate_site_name(site) if site else None,
265
+ array_element,
266
+ model_version,
267
+ collection,
268
+ )
269
+ if cache_dict:
270
+ self._logger.debug(f"Found {array_element} in cache (key: {cache_key})")
271
+ return cache_dict
272
+ self._logger.debug(f"Did not find {array_element} in cache (key: {cache_key})")
273
+
274
+ try:
275
+ parameter_version_table = production_table["parameters"][array_element]
276
+ except KeyError: # allow missing array elements (parameter dict is checked later)
277
+ return {}
278
+ DatabaseHandler.model_parameters_cached[cache_key] = self._read_mongo_db(
279
+ query=self._get_query_from_parameter_version_table(
280
+ parameter_version_table, array_element, site
281
+ ),
282
+ collection_name=collection,
283
+ )
284
+ return DatabaseHandler.model_parameters_cached[cache_key]
285
+
246
286
  def get_collection(self, db_name, collection_name):
247
287
  """
248
288
  Get a collection from the DB.
@@ -263,99 +303,102 @@ class DatabaseHandler:
263
303
  db_name = self._get_db_name(db_name)
264
304
  return DatabaseHandler.db_client[db_name][collection_name]
265
305
 
266
- def export_file_db(self, db_name, dest, file_name):
306
+ def get_collections(self, db_name=None, model_collections_only=False):
267
307
  """
268
- Get file from the DB and write to disk.
308
+ List of collections in the DB.
269
309
 
270
310
  Parameters
271
311
  ----------
272
312
  db_name: str
273
- Name of the DB to search in.
274
- dest: str or Path
275
- Location where to write the file to.
276
- file_name: str
277
- Name of the file to get.
313
+ Database name.
314
+ model_collections_only: bool
315
+ If True, only return model collections (i.e. exclude fs.files, fs.chunks)
278
316
 
279
317
  Returns
280
318
  -------
281
- file_id: GridOut._id
282
- the database ID the file was assigned when it was inserted to the DB.
283
-
284
- Raises
285
- ------
286
- FileNotFoundError
287
- If the desired file is not found.
319
+ list
320
+ List of collection names
288
321
 
289
322
  """
290
- db_name = self._get_db_name(db_name)
291
-
292
- self._logger.debug(f"Getting {file_name} from {db_name} and writing it to {dest}")
293
- file_path_instance = self._get_file_mongo_db(db_name, file_name)
294
- self._write_file_from_mongo_to_disk(db_name, dest, file_path_instance)
295
- return file_path_instance._id # pylint: disable=protected-access;
323
+ db_name = db_name or self._get_db_name()
324
+ if db_name not in self.list_of_collections:
325
+ self.list_of_collections[db_name] = DatabaseHandler.db_client[
326
+ db_name
327
+ ].list_collection_names()
328
+ collections = self.list_of_collections[db_name]
329
+ if model_collections_only:
330
+ return [collection for collection in collections if not collection.startswith("fs.")]
331
+ return collections
296
332
 
297
- def export_model_files(self, parameters, dest):
333
+ def export_model_files(self, parameters=None, file_names=None, dest=None, db_name=None):
298
334
  """
299
- Export all the files in a model from the DB and write them to disk.
335
+ Export files from the DB to the model directory.
336
+
337
+ The files to be exported can be specified by file_name or retrieved from the database
338
+ using the parameters dictionary.
300
339
 
301
340
  Parameters
302
341
  ----------
303
342
  parameters: dict
304
- Dict of model parameters.
343
+ Dict of model parameters
344
+ file_names: list, str
345
+ List (or string) of file names to export
305
346
  dest: str or Path
306
347
  Location where to write the files to.
307
348
 
308
- Raises
309
- ------
310
- FileNotFoundError
311
- if a file in parameters.values is not found
312
-
349
+ Returns
350
+ -------
351
+ file_id: dict of GridOut._id
352
+ Dict of database IDs of files.
313
353
  """
314
- if self.mongo_db_config:
315
- for info in parameters.values():
316
- if not info or not info.get("file") or info["value"] is None:
317
- continue
318
- if Path(dest).joinpath(info["value"]).exists():
319
- continue
320
- file = self._get_file_mongo_db(self._get_db_name(), info["value"])
321
- self._write_file_from_mongo_to_disk(self._get_db_name(), dest, file)
354
+ db_name = self._get_db_name(db_name)
322
355
 
323
- @staticmethod
324
- def _is_file(value):
325
- """Verify if a parameter value is a file name."""
326
- return any(ext in str(value) for ext in DatabaseHandler.ALLOWED_FILE_EXTENSIONS)
356
+ if file_names:
357
+ file_names = [file_names] if not isinstance(file_names, list) else file_names
358
+ elif parameters:
359
+ file_names = [
360
+ info["value"]
361
+ for info in parameters.values()
362
+ if info and info.get("file") and info["value"] is not None
363
+ ]
327
364
 
328
- def read_mongo_db(
329
- self,
330
- db_name,
331
- array_element_name,
332
- model_version,
333
- run_location,
334
- collection_name,
335
- write_files=True,
336
- only_applicable=False,
365
+ instance_ids = {}
366
+ for file_name in file_names:
367
+ if Path(dest).joinpath(file_name).exists():
368
+ instance_ids[file_name] = "file exists"
369
+ else:
370
+ file_path_instance = self._get_file_mongo_db(self._get_db_name(), file_name)
371
+ self._write_file_from_mongo_to_disk(self._get_db_name(), dest, file_path_instance)
372
+ instance_ids[file_name] = file_path_instance._id # pylint: disable=protected-access
373
+ return instance_ids
374
+
375
+ def _get_query_from_parameter_version_table(
376
+ self, parameter_version_table, array_element_name, site
337
377
  ):
338
- """
339
- Build and execute query to Read the MongoDB for a specific array element.
378
+ """Return query based on parameter version table."""
379
+ query_dict = {
380
+ "$or": [
381
+ {"parameter": param, "parameter_version": version}
382
+ for param, version in parameter_version_table.items()
383
+ ],
384
+ }
385
+ # 'xSTX-design' is a placeholder to ignore 'instrument' field in query.
386
+ if array_element_name and array_element_name != "xSTx-design":
387
+ query_dict["instrument"] = array_element_name
388
+ if site:
389
+ query_dict["site"] = site
390
+ return query_dict
340
391
 
341
- Also writes the files listed in the parameter values into the sim_telarray run location
392
+ def _read_mongo_db(self, query, collection_name):
393
+ """
394
+ Query MongoDB.
342
395
 
343
396
  Parameters
344
397
  ----------
345
- db_name: str
346
- the name of the DB
347
- array_element_name: str
348
- Name of the array element model (e.g. MSTN-design ...)
349
- model_version: str
350
- Version of the model.
351
- run_location: Path or str
352
- The sim_telarray run location to write the tabulated data files into.
398
+ query: dict
399
+ Query to execute.
353
400
  collection_name: str
354
- The name of the collection to read from.
355
- write_files: bool
356
- If true, write the files to the run_location.
357
- only_applicable: bool
358
- If True, only applicable parameters will be read.
401
+ Collection name.
359
402
 
360
403
  Returns
361
404
  -------
@@ -364,154 +407,127 @@ class DatabaseHandler:
364
407
  Raises
365
408
  ------
366
409
  ValueError
367
- if query returned zero results.
368
-
410
+ if query returned no results.
369
411
  """
412
+ db_name = self._get_db_name()
370
413
  collection = self.get_collection(db_name, collection_name)
371
- _parameters = {}
372
-
373
- query = {
374
- "instrument": array_element_name,
375
- "version": self.model_version(model_version, db_name),
376
- }
377
-
378
- if only_applicable:
379
- query["applicable"] = True
380
- if collection.count_documents(query) < 1:
414
+ posts = list(collection.find(query))
415
+ if not posts:
381
416
  raise ValueError(
382
- "The following query returned zero results! Check the input data and rerun.\n",
383
- query,
417
+ f"The following query for {collection_name} returned zero results: {query} "
384
418
  )
385
- for post in collection.find(query).sort("parameter", ASCENDING):
419
+ parameters = {}
420
+ for post in posts:
386
421
  par_now = post["parameter"]
387
- _parameters[par_now] = post
388
- _parameters[par_now].pop("parameter", None)
389
- _parameters[par_now].pop("instrument", None)
390
- _parameters[par_now]["entry_date"] = ObjectId(post["_id"]).generation_time
391
- if _parameters[par_now]["file"] and write_files:
392
- file = self._get_file_mongo_db(db_name, _parameters[par_now]["value"])
393
- self._write_file_from_mongo_to_disk(db_name, run_location, file)
422
+ parameters[par_now] = post
423
+ parameters[par_now]["entry_date"] = ObjectId(post["_id"]).generation_time
424
+ return {k: parameters[k] for k in sorted(parameters)}
394
425
 
395
- return _parameters
396
-
397
- def get_site_parameters(
398
- self,
399
- site,
400
- model_version,
401
- only_applicable=False,
402
- ):
426
+ def _read_production_table_from_mongo_db(self, collection_name, model_version):
403
427
  """
404
- Get parameters from either MongoDB or simulation model repository for a specific site.
428
+ Read production table from MongoDB.
405
429
 
406
430
  Parameters
407
431
  ----------
408
- site: str
409
- Site name.
432
+ collection_name: str
433
+ Name of the collection.
410
434
  model_version: str
411
435
  Version of the model.
412
- only_applicable: bool
413
- If True, only applicable parameters will be read.
414
-
415
- Returns
416
- -------
417
- dict containing the parameters
418
436
 
437
+ Raises
438
+ ------
439
+ ValueError
440
+ if query returned no results.
419
441
  """
420
- _site, _, _model_version = self._validate_model_input(site, None, model_version)
421
- _db_name = self._get_db_name()
422
- _site_cache_key = self._parameter_cache_key(site, None, model_version)
423
442
  try:
424
- return DatabaseHandler.site_parameters_cached[_site_cache_key]
443
+ return DatabaseHandler.production_table_cached[
444
+ self._cache_key(None, None, model_version, collection_name)
445
+ ]
425
446
  except KeyError:
426
447
  pass
427
448
 
428
- DatabaseHandler.site_parameters_cached[_site_cache_key] = (
429
- self._get_site_parameters_mongo_db(
430
- _db_name,
431
- _site,
432
- _model_version,
433
- only_applicable,
434
- )
435
- )
436
- return DatabaseHandler.site_parameters_cached[_site_cache_key]
449
+ query = {"model_version": model_version, "collection": collection_name}
450
+ collection = self.get_collection(self._get_db_name(), "production_tables")
451
+ post = collection.find_one(query)
452
+ if not post:
453
+ raise ValueError(f"The following query returned zero results: {query}")
454
+
455
+ return {
456
+ "collection": post["collection"],
457
+ "model_version": post["model_version"],
458
+ "parameters": post["parameters"],
459
+ "design_model": post.get("design_model", {}),
460
+ "entry_date": ObjectId(post["_id"]).generation_time,
461
+ }
437
462
 
438
- def _get_site_parameters_mongo_db(self, db_name, site, model_version, only_applicable=False):
463
+ def get_model_versions(self, collection_name="telescopes"):
439
464
  """
440
- Get parameters from MongoDB for a specific site.
465
+ Get list of model versions from the DB.
441
466
 
442
467
  Parameters
443
468
  ----------
444
- db_name: str
445
- The name of the DB.
446
- site: str
447
- Site name.
448
- model_version: str
449
- Version of the model.
450
- only_applicable: bool
451
- If True, only applicable parameters will be read.
469
+ collection_name: str
470
+ Name of the collection.
452
471
 
453
472
  Returns
454
473
  -------
455
- dict containing the parameters
456
-
457
- Raises
458
- ------
459
- ValueError
460
- if query returned zero results.
461
-
474
+ list
475
+ List of model versions
462
476
  """
463
- collection = self.get_collection(db_name, "sites")
464
- _parameters = {}
465
-
466
- query = {
467
- "site": site,
468
- "version": model_version,
469
- }
470
- if only_applicable:
471
- query["applicable"] = True
472
- if collection.count_documents(query) < 1:
473
- raise ValueError(
474
- "The following query returned zero results! Check the input data and rerun.\n",
475
- query,
476
- )
477
- for post in collection.find(query).sort("parameter", ASCENDING):
478
- par_now = post["parameter"]
479
- _parameters[par_now] = post
480
- _parameters[par_now].pop("parameter", None)
481
- _parameters[par_now].pop("site", None)
482
- _parameters[par_now]["entry_date"] = ObjectId(post["_id"]).generation_time
483
-
484
- return _parameters
477
+ collection = self.get_collection(self._get_db_name(), "production_tables")
478
+ return sorted(
479
+ [post["model_version"] for post in collection.find({"collection": collection_name})]
480
+ )
485
481
 
486
- def get_derived_values(self, site, array_element_name, model_version):
482
+ def get_array_elements(self, model_version, collection="telescopes"):
487
483
  """
488
- Get all derived values from the DB for a specific array element.
484
+ Get list array elements for a given model version and collection from the DB.
489
485
 
490
486
  Parameters
491
487
  ----------
492
- site: str
493
- Site name.
494
- array_element_name: str
495
- Name of the array element model (e.g. MSTN, SSTS).
496
488
  model_version: str
497
489
  Version of the model.
490
+ collection: str
491
+ Which collection to get the array elements from:
492
+ i.e. telescopes, calibration_devices.
498
493
 
499
494
  Returns
500
495
  -------
501
- dict containing the parameters
496
+ list
497
+ Sorted list of all array elements found in collection
498
+ """
499
+ production_table = self._read_production_table_from_mongo_db(collection, model_version)
500
+ return sorted([entry for entry in production_table["parameters"] if "-design" not in entry])
502
501
 
502
+ def get_array_elements_of_type(self, array_element_type, model_version, collection):
503
503
  """
504
- _, _array_element_name, _model_version = self._validate_model_input(
505
- site, array_element_name, model_version
506
- )
504
+ Get array elements of a certain type (e.g. 'LSTN') for a DB collection.
507
505
 
508
- return self.read_mongo_db(
509
- DatabaseHandler.DB_DERIVED_VALUES,
510
- _array_element_name,
511
- _model_version,
512
- run_location=None,
513
- collection_name="derived_values",
514
- write_files=False,
506
+ Does not return 'design' models.
507
+
508
+ Parameters
509
+ ----------
510
+ array_element_type: str
511
+ Type of the array element (e.g. LSTN, MSTS).
512
+ model_version: str
513
+ Version of the model.
514
+ collection: str
515
+ Which collection to get the array elements from:
516
+ i.e. telescopes, calibration_devices.
517
+
518
+ Returns
519
+ -------
520
+ list
521
+ Sorted list of all array element names found in collection
522
+ """
523
+ production_table = self._read_production_table_from_mongo_db(collection, model_version)
524
+ all_array_elements = production_table["parameters"]
525
+ return sorted(
526
+ [
527
+ entry
528
+ for entry in all_array_elements
529
+ if entry.startswith(array_element_type) and "-design" not in entry
530
+ ]
515
531
  )
516
532
 
517
533
  def get_simulation_configuration_parameters(
@@ -541,63 +557,24 @@ class DatabaseHandler:
541
557
  if simulation_software is not valid.
542
558
  """
543
559
  if simulation_software == "corsika":
544
- return self.get_corsika_configuration_parameters(model_version)
560
+ return self.get_model_parameters(
561
+ None,
562
+ None,
563
+ model_version=model_version,
564
+ collection="configuration_corsika",
565
+ )
545
566
  if simulation_software == "simtel":
546
- if site and array_element_name:
547
- return self.get_model_parameters(
548
- site, array_element_name, model_version, collection="configuration_sim_telarray"
567
+ return (
568
+ self.get_model_parameters(
569
+ site,
570
+ array_element_name,
571
+ model_version=model_version,
572
+ collection="configuration_sim_telarray",
549
573
  )
550
- return {}
551
- raise ValueError(f"Unknown simulation software: {simulation_software}")
552
-
553
- def get_corsika_configuration_parameters(self, model_version):
554
- """
555
- Get CORSIKA configuration parameters from the DB.
556
-
557
- Parameters
558
- ----------
559
- model_version : str
560
- Version of the model.
561
-
562
- Returns
563
- -------
564
- dict
565
- Configuration parameters for CORSIKA
566
- """
567
- _corsika_cache_key = self._parameter_cache_key(None, None, model_version)
568
- try:
569
- return DatabaseHandler.corsika_configuration_parameters_cached[_corsika_cache_key]
570
- except KeyError:
571
- pass
572
- DatabaseHandler.corsika_configuration_parameters_cached[_corsika_cache_key] = (
573
- self.read_mongo_db(
574
- db_name=self._get_db_name(),
575
- array_element_name=None,
576
- model_version=model_version,
577
- run_location=None,
578
- collection_name="configuration_corsika",
579
- write_files=False,
574
+ if site and array_element_name
575
+ else {}
580
576
  )
581
- )
582
- return DatabaseHandler.corsika_configuration_parameters_cached[_corsika_cache_key]
583
-
584
- def _validate_model_input(self, site, array_element_name, model_version):
585
- """
586
- Validate input for model parameter queries.
587
-
588
- site: str
589
- Site name.
590
- array_element_name: str
591
- Name of the array element model (e.g. LSTN-01, MSTS-design)
592
- model_version: str
593
- Version of the model.
594
-
595
- """
596
- return (
597
- names.validate_site_name(site),
598
- names.validate_array_element_name(array_element_name) if array_element_name else None,
599
- self.model_version(model_version),
600
- )
577
+ raise ValueError(f"Unknown simulation software: {simulation_software}")
601
578
 
602
579
  @staticmethod
603
580
  def _get_file_mongo_db(db_name, file_name):
@@ -648,76 +625,22 @@ class DatabaseHandler:
648
625
  with open(Path(path).joinpath(file.filename), "wb") as output_file:
649
626
  fs_output.download_to_stream_by_name(file.filename, output_file)
650
627
 
651
- def copy_array_element(
652
- self,
653
- db_name,
654
- element_to_copy,
655
- version_to_copy,
656
- new_array_element_name,
657
- collection_name="telescopes",
658
- db_to_copy_to=None,
659
- collection_to_copy_to=None,
660
- ):
628
+ def add_production_table(self, db_name, production_table):
661
629
  """
662
- Copy a full array element configuration to a new array element name.
663
-
664
- Only a specific version is copied.
665
- This function should be rarely used and is intended to simplify unit tests.
630
+ Add a production table to the DB.
666
631
 
667
632
  Parameters
668
633
  ----------
669
634
  db_name: str
670
- the name of the DB to copy from
671
- element_to_copy: str
672
- The array element to copy
673
- version_to_copy: str
674
- The version of the configuration to copy
675
- new_array_element_name: str
676
- The name of the new array element
677
- collection_name: str
678
- The name of the collection to copy from.
679
- db_to_copy_to: str
680
- The name of the DB to copy to.
681
- collection_to_copy_to: str
682
- The name of the collection to copy to.
683
-
684
- Raises
685
- ------
686
- BulkWriteError
687
-
635
+ the name of the DB.
636
+ production_table: dict
637
+ The production table to add to the DB.
688
638
  """
689
639
  db_name = self._get_db_name(db_name)
690
- if db_to_copy_to is None:
691
- db_to_copy_to = db_name
692
-
693
- if collection_to_copy_to is None:
694
- collection_to_copy_to = collection_name
695
-
696
- self._logger.info(
697
- f"Copying version {version_to_copy} of {element_to_copy} "
698
- f"to the new array element {new_array_element_name} in the {db_to_copy_to} DB"
699
- )
700
-
701
- collection = self.get_collection(db_name, collection_name)
702
- db_entries = []
703
-
704
- _version_to_copy = self.model_version(version_to_copy)
705
-
706
- query = {
707
- "instrument": element_to_copy,
708
- "version": _version_to_copy,
709
- }
710
- for post in collection.find(query):
711
- post["instrument"] = new_array_element_name
712
- post.pop("_id", None)
713
- db_entries.append(post)
714
-
715
- self._logger.info(f"Creating new array element {new_array_element_name}")
716
- collection = self.get_collection(db_to_copy_to, collection_to_copy_to)
717
- try:
718
- collection.insert_many(db_entries)
719
- except BulkWriteError as exc:
720
- raise BulkWriteError(str(exc.details)) from exc
640
+ collection = self.get_collection(db_name, "production_tables")
641
+ self._logger.info(f"Adding production for {production_table.get('collection')} to to DB")
642
+ collection.insert_one(production_table)
643
+ DatabaseHandler.production_table_cached.clear()
721
644
 
722
645
  def add_new_parameter(
723
646
  self,
@@ -727,7 +650,7 @@ class DatabaseHandler:
727
650
  file_prefix=None,
728
651
  ):
729
652
  """
730
- Add a parameter dictionary for a specific array element to the DB.
653
+ Add a new parameter dictionary to the DB.
731
654
 
732
655
  A new document will be added to the DB, with all fields taken from the input parameters.
733
656
  Parameter dictionaries are validated before submission using the corresponding schema.
@@ -743,13 +666,7 @@ class DatabaseHandler:
743
666
  file_prefix: str or Path
744
667
  where to find files to upload to the DB
745
668
  """
746
- data_validator = validate_data.DataValidator(
747
- schema_file=files("simtools")
748
- / f"schemas/model_parameters/{par_dict['parameter']}.schema.yml",
749
- data_dict=par_dict,
750
- check_exact_data_type=False,
751
- )
752
- par_dict = data_validator.validate_and_transform(is_model_parameter=True)
669
+ par_dict = validate_data.DataValidator.validate_model_parameter(par_dict)
753
670
 
754
671
  db_name = self._get_db_name(db_name)
755
672
  collection = self.get_collection(db_name, collection_name)
@@ -778,7 +695,7 @@ class DatabaseHandler:
778
695
  self._logger.info(f"Will also add the file {file_to_insert_now} to the DB")
779
696
  self.insert_file_to_db(file_to_insert_now, db_name)
780
697
 
781
- self._reset_parameter_cache(par_dict["site"], par_dict["instrument"], par_dict["version"])
698
+ self._reset_parameter_cache()
782
699
 
783
700
  def _get_db_name(self, db_name=None):
784
701
  """
@@ -796,42 +713,6 @@ class DatabaseHandler:
796
713
  """
797
714
  return self.mongo_db_config["db_simulation_model"] if db_name is None else db_name
798
715
 
799
- def model_version(self, version, db_name=None):
800
- """
801
- Return model version and check that it is valid.
802
-
803
- Queries the database for all available model versions and check if the
804
- requested version is valid.
805
-
806
- Parameters
807
- ----------
808
- version : str
809
- Model version.
810
- db_name : str
811
- Database name.
812
-
813
- Returns
814
- -------
815
- str
816
- Model version.
817
-
818
- Raises
819
- ------
820
- ValueError
821
- if version not valid.
822
-
823
- """
824
- _all_versions = self.get_all_versions(db_name=db_name)
825
- if version in _all_versions:
826
- return version
827
- if len(_all_versions) == 0:
828
- return None
829
-
830
- raise ValueError(
831
- f"Invalid model version {version} in DB {self._get_db_name(db_name)} "
832
- f"(allowed are {_all_versions})"
833
- )
834
-
835
716
  def insert_file_to_db(self, file_name, db_name=None, **kwargs):
836
717
  """
837
718
  Insert a file to the DB.
@@ -855,15 +736,11 @@ class DatabaseHandler:
855
736
 
856
737
  """
857
738
  db_name = self._get_db_name(db_name)
858
-
859
739
  db = DatabaseHandler.db_client[db_name]
860
740
  file_system = gridfs.GridFS(db)
861
741
 
862
- if "content_type" not in kwargs:
863
- kwargs["content_type"] = "ascii/dat"
864
-
865
- if "filename" not in kwargs:
866
- kwargs["filename"] = Path(file_name).name
742
+ kwargs.setdefault("content_type", "ascii/dat")
743
+ kwargs.setdefault("filename", Path(file_name).name)
867
744
 
868
745
  if file_system.exists({"filename": kwargs["filename"]}):
869
746
  self._logger.warning(
@@ -872,78 +749,11 @@ class DatabaseHandler:
872
749
  return file_system.find_one( # pylint: disable=protected-access
873
750
  {"filename": kwargs["filename"]}
874
751
  )._id
752
+ self._logger.debug(f"Writing file to DB: {file_name}")
875
753
  with open(file_name, "rb") as data_file:
876
754
  return file_system.put(data_file, **kwargs)
877
755
 
878
- def get_all_versions(
879
- self,
880
- parameter=None,
881
- array_element_name=None,
882
- site=None,
883
- db_name=None,
884
- collection=None,
885
- ):
886
- """
887
- Get all version entries in the DB of collection and/or a specific parameter.
888
-
889
- Parameters
890
- ----------
891
- parameter: str
892
- Which parameter to get the versions of
893
- array_element_name: str
894
- Which array element to get the versions of (in case "collection_name" is not "sites")
895
- site: str
896
- Site name.
897
- db_name: str
898
- Database name.
899
- collection_name: str
900
- The name of the collection in which to update the parameter.
901
-
902
- Returns
903
- -------
904
- all_versions: list
905
- List of all versions found
906
-
907
- Raises
908
- ------
909
- ValueError
910
- If key to collection_name is not valid.
911
-
912
- """
913
- db_name = self._get_db_name() if db_name is None else db_name
914
- if not db_name:
915
- self._logger.warning("No database name defined to determine list of model versions")
916
- return []
917
- _cache_key = f"model_versions_{db_name}-{collection}"
918
-
919
- query = {}
920
- if parameter is not None:
921
- query["parameter"] = parameter
922
- _cache_key = f"{_cache_key}-{parameter}"
923
- if collection in ["telescopes", "calibration_devices"] and array_element_name is not None:
924
- query["instrument"] = names.validate_array_element_name(array_element_name)
925
- _cache_key = f"{_cache_key}-{query['instrument']}"
926
- elif collection == "sites" and site is not None:
927
- query["site"] = names.validate_site_name(site)
928
- _cache_key = f"{_cache_key}-{query['site']}"
929
-
930
- if _cache_key not in DatabaseHandler.model_versions_cached:
931
- all_versions = set()
932
- collections_to_query = (
933
- [collection] if collection else self.get_collections(db_name, True)
934
- )
935
- for collection_name in collections_to_query:
936
- db_collection = self.get_collection(db_name, collection_name)
937
- sorted_posts = db_collection.find(query).sort("version", ASCENDING)
938
- all_versions.update(post["version"] for post in sorted_posts)
939
- DatabaseHandler.model_versions_cached[_cache_key] = list(all_versions)
940
-
941
- if len(DatabaseHandler.model_versions_cached[_cache_key]) == 0:
942
- self._logger.warning(f"The query {query} did not return any results. No versions found")
943
-
944
- return DatabaseHandler.model_versions_cached[_cache_key]
945
-
946
- def _parameter_cache_key(self, site, array_element_name, model_version, collection=None):
756
+ def _cache_key(self, site=None, array_element_name=None, model_version=None, collection=None):
947
757
  """
948
758
  Create a cache key for the parameter cache dictionaries.
949
759
 
@@ -963,61 +773,79 @@ class DatabaseHandler:
963
773
  str
964
774
  Cache key.
965
775
  """
966
- parts = []
967
- if site:
968
- parts.append(site)
969
- if array_element_name:
970
- parts.append(array_element_name)
971
- parts.append(model_version)
972
- if collection:
973
- parts.append(collection)
974
- return "-".join(parts)
776
+ return "-".join(
777
+ part for part in [model_version, collection, site, array_element_name] if part
778
+ )
975
779
 
976
- def _reset_parameter_cache(self, site, array_element_name, model_version):
780
+ def _read_cache(
781
+ self, cache_dict, site=None, array_element_name=None, model_version=None, collection=None
782
+ ):
977
783
  """
978
- Reset the cache for the parameters.
784
+ Read parameters from cache.
979
785
 
980
786
  Parameters
981
787
  ----------
788
+ cache_dict: dict
789
+ Cache dictionary.
982
790
  site: str
983
791
  Site name.
984
792
  array_element_name: str
985
793
  Array element name.
986
794
  model_version: str
987
795
  Model version.
796
+ collection: str
797
+ DB collection name.
798
+
799
+ Returns
800
+ -------
801
+ str
802
+ Cache key.
988
803
  """
989
- self._logger.debug(f"Resetting cache for {site} {array_element_name} {model_version}")
990
- _cache_key = self._parameter_cache_key(site, array_element_name, model_version)
991
- DatabaseHandler.site_parameters_cached.pop(_cache_key, None)
992
- DatabaseHandler.model_parameters_cached.pop(_cache_key, None)
993
- db_array_elements.get_array_elements.cache_clear()
804
+ cache_key = self._cache_key(site, array_element_name, model_version, collection)
805
+ try:
806
+ return cache_key, cache_dict[cache_key]
807
+ except KeyError:
808
+ return cache_key, None
994
809
 
995
- def get_collections(self, db_name=None, model_collections_only=False):
810
+ def _reset_parameter_cache(self):
811
+ """Reset the cache for the parameters."""
812
+ DatabaseHandler.model_parameters_cached.clear()
813
+
814
+ def _get_array_element_list(self, array_element_name, site, production_table, collection):
996
815
  """
997
- List of collections in the DB.
816
+ Return list of array elements for DB queries (add design model if needed).
817
+
818
+ Design model is added if found in the production table.
998
819
 
999
820
  Parameters
1000
821
  ----------
1001
- db_name: str
1002
- Database name.
822
+ array_element_name: str
823
+ Name of the array element.
824
+ site: str
825
+ Site name.
826
+ production_table: dict
827
+ Production table.
828
+ collection: str
829
+ collection of array element (e.g. telescopes, calibration_devices).
1003
830
 
1004
831
  Returns
1005
832
  -------
1006
833
  list
1007
- List of collection names
1008
- model_collections_only: bool
1009
- If True, only return model collections (i.e. exclude fs.files, fs.chunks, metadata)
1010
-
1011
- """
1012
- db_name = self._get_db_name() if db_name is None else db_name
1013
- if db_name not in self.list_of_collections:
1014
- self.list_of_collections[db_name] = DatabaseHandler.db_client[
1015
- db_name
1016
- ].list_collection_names()
1017
- if model_collections_only:
834
+ List of array elements
835
+ """
836
+ if collection == "configuration_corsika":
837
+ return ["xSTx-design"] # placeholder to ignore 'instrument' field in query.
838
+ if collection == "sites":
839
+ return [f"OBS-{site}"]
840
+ if "-design" in array_element_name:
841
+ return [array_element_name]
842
+ try:
843
+ return [
844
+ production_table["design_model"][array_element_name],
845
+ array_element_name,
846
+ ]
847
+ except KeyError:
1018
848
  return [
1019
- collection
1020
- for collection in self.list_of_collections[db_name]
1021
- if not collection.startswith("fs.") and collection != "metadata"
849
+ f"{names.get_array_element_type_from_name(array_element_name)}-design",
850
+ array_element_name,
1022
851
  ]
1023
- return self.list_of_collections[db_name]