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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. {gammasimtools-0.9.0.dist-info → gammasimtools-0.11.0.dist-info}/METADATA +4 -2
  2. {gammasimtools-0.9.0.dist-info → gammasimtools-0.11.0.dist-info}/RECORD +133 -117
  3. {gammasimtools-0.9.0.dist-info → gammasimtools-0.11.0.dist-info}/WHEEL +1 -1
  4. {gammasimtools-0.9.0.dist-info → gammasimtools-0.11.0.dist-info}/entry_points.txt +6 -1
  5. simtools/_version.py +9 -4
  6. simtools/applications/calculate_trigger_rate.py +15 -38
  7. simtools/applications/convert_all_model_parameters_from_simtel.py +9 -29
  8. simtools/applications/convert_geo_coordinates_of_array_elements.py +47 -45
  9. simtools/applications/convert_model_parameter_from_simtel.py +2 -3
  10. simtools/applications/db_add_file_to_db.py +1 -3
  11. simtools/applications/db_add_simulation_model_from_repository_to_db.py +110 -0
  12. simtools/applications/db_add_value_from_json_to_db.py +1 -2
  13. simtools/applications/db_development_tools/write_array_elements_positions_to_repository.py +6 -6
  14. simtools/applications/db_get_file_from_db.py +11 -12
  15. simtools/applications/db_get_parameter_from_db.py +26 -35
  16. simtools/applications/derive_mirror_rnda.py +1 -2
  17. simtools/applications/derive_photon_electron_spectrum.py +99 -0
  18. simtools/applications/derive_psf_parameters.py +1 -0
  19. simtools/applications/docs_produce_array_element_report.py +71 -0
  20. simtools/applications/docs_produce_model_parameter_reports.py +63 -0
  21. simtools/applications/generate_array_config.py +17 -17
  22. simtools/applications/generate_corsika_histograms.py +2 -2
  23. simtools/applications/generate_regular_arrays.py +19 -17
  24. simtools/applications/generate_simtel_array_histograms.py +11 -48
  25. simtools/applications/production_derive_limits.py +95 -0
  26. simtools/applications/production_generate_simulation_config.py +37 -33
  27. simtools/applications/production_scale_events.py +4 -9
  28. simtools/applications/run_application.py +165 -0
  29. simtools/applications/simulate_light_emission.py +0 -4
  30. simtools/applications/simulate_prod.py +1 -1
  31. simtools/applications/simulate_prod_htcondor_generator.py +26 -26
  32. simtools/applications/submit_data_from_external.py +12 -4
  33. simtools/applications/submit_model_parameter_from_external.py +18 -11
  34. simtools/applications/validate_camera_efficiency.py +2 -2
  35. simtools/applications/validate_file_using_schema.py +26 -22
  36. simtools/camera/single_photon_electron_spectrum.py +168 -0
  37. simtools/configuration/commandline_parser.py +37 -1
  38. simtools/configuration/configurator.py +8 -10
  39. simtools/constants.py +10 -3
  40. simtools/corsika/corsika_config.py +19 -17
  41. simtools/corsika/corsika_histograms.py +5 -7
  42. simtools/corsika/corsika_histograms_visualize.py +2 -4
  43. simtools/data_model/data_reader.py +0 -3
  44. simtools/data_model/metadata_collector.py +20 -12
  45. simtools/data_model/metadata_model.py +8 -124
  46. simtools/data_model/model_data_writer.py +81 -75
  47. simtools/data_model/schema.py +220 -0
  48. simtools/data_model/validate_data.py +79 -68
  49. simtools/db/db_handler.py +350 -492
  50. simtools/db/db_model_upload.py +139 -0
  51. simtools/dependencies.py +112 -0
  52. simtools/io_operations/hdf5_handler.py +54 -24
  53. simtools/layout/array_layout.py +38 -32
  54. simtools/model/array_model.py +13 -7
  55. simtools/model/model_parameter.py +55 -54
  56. simtools/model/site_model.py +2 -2
  57. simtools/production_configuration/calculate_statistical_errors_grid_point.py +119 -145
  58. simtools/production_configuration/event_scaler.py +9 -35
  59. simtools/production_configuration/generate_simulation_config.py +9 -44
  60. simtools/production_configuration/interpolation_handler.py +9 -15
  61. simtools/production_configuration/limits_calculation.py +202 -0
  62. simtools/reporting/docs_read_parameters.py +310 -0
  63. simtools/runners/corsika_simtel_runner.py +4 -4
  64. simtools/schemas/{integration_tests_config.metaschema.yml → application_workflow.metaschema.yml} +61 -27
  65. simtools/schemas/array_elements.yml +8 -0
  66. simtools/schemas/input/MST_mirror_2f_measurements.schema.yml +39 -0
  67. simtools/schemas/input/single_pe_spectrum.schema.yml +38 -0
  68. simtools/schemas/model_parameter.metaschema.yml +103 -2
  69. simtools/schemas/model_parameter_and_data_schema.metaschema.yml +4 -1
  70. simtools/schemas/model_parameters/array_element_position_utm.schema.yml +1 -1
  71. simtools/schemas/model_parameters/array_window.schema.yml +37 -0
  72. simtools/schemas/model_parameters/asum_clipping.schema.yml +0 -4
  73. simtools/schemas/model_parameters/channels_per_chip.schema.yml +1 -1
  74. simtools/schemas/model_parameters/correct_nsb_spectrum_to_telescope_altitude.schema.yml +1 -1
  75. simtools/schemas/model_parameters/corsika_cherenkov_photon_bunch_size.schema.yml +2 -0
  76. simtools/schemas/model_parameters/corsika_cherenkov_photon_wavelength_range.schema.yml +2 -0
  77. simtools/schemas/model_parameters/corsika_first_interaction_height.schema.yml +2 -0
  78. simtools/schemas/model_parameters/corsika_iact_io_buffer.schema.yml +4 -2
  79. simtools/schemas/model_parameters/corsika_iact_max_bunches.schema.yml +2 -0
  80. simtools/schemas/model_parameters/corsika_iact_split_auto.schema.yml +2 -0
  81. simtools/schemas/model_parameters/corsika_longitudinal_shower_development.schema.yml +2 -0
  82. simtools/schemas/model_parameters/corsika_particle_kinetic_energy_cutoff.schema.yml +2 -0
  83. simtools/schemas/model_parameters/corsika_starting_grammage.schema.yml +2 -0
  84. simtools/schemas/model_parameters/dsum_clipping.schema.yml +0 -2
  85. simtools/schemas/model_parameters/dsum_ignore_below.schema.yml +0 -2
  86. simtools/schemas/model_parameters/dsum_offset.schema.yml +0 -2
  87. simtools/schemas/model_parameters/dsum_pedsub.schema.yml +0 -2
  88. simtools/schemas/model_parameters/dsum_pre_clipping.schema.yml +0 -2
  89. simtools/schemas/model_parameters/dsum_prescale.schema.yml +0 -2
  90. simtools/schemas/model_parameters/dsum_presum_max.schema.yml +0 -2
  91. simtools/schemas/model_parameters/dsum_presum_shift.schema.yml +0 -2
  92. simtools/schemas/model_parameters/dsum_shaping.schema.yml +0 -2
  93. simtools/schemas/model_parameters/dsum_shaping_renormalize.schema.yml +0 -2
  94. simtools/schemas/model_parameters/dsum_threshold.schema.yml +0 -2
  95. simtools/schemas/model_parameters/dsum_zero_clip.schema.yml +0 -2
  96. simtools/schemas/model_parameters/fadc_compensate_pedestal.schema.yml +1 -1
  97. simtools/schemas/model_parameters/fadc_lg_compensate_pedestal.schema.yml +1 -1
  98. simtools/schemas/model_parameters/fadc_noise.schema.yml +3 -3
  99. simtools/schemas/model_parameters/fake_mirror_list.schema.yml +33 -0
  100. simtools/schemas/model_parameters/iobuf_maximum.schema.yml +1 -1
  101. simtools/schemas/model_parameters/iobuf_output_maximum.schema.yml +1 -1
  102. simtools/schemas/model_parameters/laser_photons.schema.yml +2 -2
  103. simtools/schemas/model_parameters/lightguide_efficiency_vs_incidence_angle.schema.yml +1 -1
  104. simtools/schemas/model_parameters/lightguide_efficiency_vs_wavelength.schema.yml +1 -1
  105. simtools/schemas/model_parameters/min_photoelectrons.schema.yml +1 -1
  106. simtools/schemas/model_parameters/min_photons.schema.yml +1 -1
  107. simtools/schemas/model_parameters/random_generator.schema.yml +1 -1
  108. simtools/schemas/model_parameters/sampled_output.schema.yml +1 -1
  109. simtools/schemas/model_parameters/save_pe_with_amplitude.schema.yml +1 -1
  110. simtools/schemas/model_parameters/secondary_mirror_degraded_reflection.schema.yml +1 -1
  111. simtools/schemas/model_parameters/store_photoelectrons.schema.yml +1 -1
  112. simtools/schemas/model_parameters/tailcut_scale.schema.yml +1 -1
  113. simtools/schemas/production_configuration_metrics.schema.yml +68 -0
  114. simtools/schemas/production_tables.schema.yml +41 -0
  115. simtools/simtel/simtel_config_reader.py +1 -2
  116. simtools/simtel/simtel_config_writer.py +6 -8
  117. simtools/simtel/simtel_io_histogram.py +32 -68
  118. simtools/simtel/simtel_io_histograms.py +17 -34
  119. simtools/simtel/simulator_array.py +2 -1
  120. simtools/simtel/simulator_camera_efficiency.py +6 -3
  121. simtools/simtel/simulator_light_emission.py +5 -6
  122. simtools/simtel/simulator_ray_tracing.py +3 -4
  123. simtools/testing/configuration.py +2 -1
  124. simtools/testing/helpers.py +6 -13
  125. simtools/testing/validate_output.py +141 -47
  126. simtools/utils/general.py +114 -14
  127. simtools/utils/names.py +299 -157
  128. simtools/utils/value_conversion.py +17 -13
  129. simtools/version.py +2 -2
  130. simtools/visualization/legend_handlers.py +2 -0
  131. simtools/applications/db_add_model_parameters_from_repository_to_db.py +0 -176
  132. simtools/db/db_array_elements.py +0 -130
  133. {gammasimtools-0.9.0.dist-info → gammasimtools-0.11.0.dist-info}/LICENSE +0 -0
  134. {gammasimtools-0.9.0.dist-info → gammasimtools-0.11.0.dist-info}/top_level.txt +0 -0
  135. /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,116 @@ 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
+ ):
182
+ """
183
+ Get a model parameter using the parameter version.
184
+
185
+ Parameters
186
+ ----------
187
+ parameter: str
188
+ Name of the parameter.
189
+ parameter_version: str
190
+ Version of the parameter.
191
+ site: str
192
+ Site name.
193
+ array_element_name: str
194
+ Name of the array element model (e.g. MSTN, SSTS).
195
+
196
+ Returns
197
+ -------
198
+ dict containing the parameter
199
+
200
+ """
201
+ query = {
202
+ "parameter_version": parameter_version,
203
+ "parameter": parameter,
204
+ }
205
+ if array_element_name is not None:
206
+ query["instrument"] = array_element_name
207
+ if site is not None:
208
+ query["site"] = site
209
+ return self._read_mongo_db(
210
+ query=query, collection_name=names.get_collection_name_from_parameter_name(parameter)
211
+ )
212
+
183
213
  def get_model_parameters(
184
214
  self,
185
215
  site,
186
216
  array_element_name,
187
- model_version,
188
- collection="telescope",
189
- only_applicable=False,
217
+ collection,
218
+ model_version=None,
190
219
  ):
191
220
  """
192
- Get parameters from MongoDB or simulation model repository for an array element.
221
+ Get model parameters using the model version.
193
222
 
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.
223
+ Queries parameters for design and for the specified array element (if necessary).
198
224
 
199
225
  Parameters
200
226
  ----------
201
227
  site: str
202
228
  Site name.
203
229
  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.
230
+ Name of the array element model (e.g. LSTN-01, MSTx-FlashCam, ILLN-01).
231
+ model_version: str, list
232
+ Version(s) of the model.
207
233
  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.
234
+ Collection of array element (e.g. telescopes, calibration_devices).
211
235
 
212
236
  Returns
213
237
  -------
214
238
  dict containing the parameters
215
-
216
239
  """
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
240
+ model_versions = (
241
+ self.get_model_versions(collection) if model_version is None else [model_version]
222
242
  )
243
+
223
244
  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
245
+ for _model_version in model_versions:
246
+ production_table = self._read_production_table_from_mongo_db(collection, _model_version)
247
+ array_element_list = self._get_array_element_list(
248
+ array_element_name, site, production_table, collection
227
249
  )
228
- try:
229
- pars.update(DatabaseHandler.model_parameters_cached[_array_elements_cache_key])
230
- except KeyError:
250
+ for array_element in array_element_list:
231
251
  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,
252
+ self._get_parameter_for_model_version(
253
+ array_element, _model_version, site, collection, production_table
240
254
  )
241
255
  )
242
- DatabaseHandler.model_parameters_cached[_array_elements_cache_key] = pars
243
-
244
256
  return pars
245
257
 
258
+ def _get_parameter_for_model_version(
259
+ self, array_element, model_version, site, collection, production_table
260
+ ):
261
+ cache_key, cache_dict = self._read_cache(
262
+ DatabaseHandler.model_parameters_cached,
263
+ names.validate_site_name(site) if site else None,
264
+ array_element,
265
+ model_version,
266
+ collection,
267
+ )
268
+ if cache_dict:
269
+ self._logger.debug(f"Found {array_element} in cache (key: {cache_key})")
270
+ return cache_dict
271
+ self._logger.debug(f"Did not find {array_element} in cache (key: {cache_key})")
272
+
273
+ try:
274
+ parameter_version_table = production_table["parameters"][array_element]
275
+ except KeyError: # allow missing array elements (parameter dict is checked later)
276
+ return {}
277
+ DatabaseHandler.model_parameters_cached[cache_key] = self._read_mongo_db(
278
+ query=self._get_query_from_parameter_version_table(
279
+ parameter_version_table, array_element, site
280
+ ),
281
+ collection_name=collection,
282
+ )
283
+ return DatabaseHandler.model_parameters_cached[cache_key]
284
+
246
285
  def get_collection(self, db_name, collection_name):
247
286
  """
248
287
  Get a collection from the DB.
@@ -263,99 +302,102 @@ class DatabaseHandler:
263
302
  db_name = self._get_db_name(db_name)
264
303
  return DatabaseHandler.db_client[db_name][collection_name]
265
304
 
266
- def export_file_db(self, db_name, dest, file_name):
305
+ def get_collections(self, db_name=None, model_collections_only=False):
267
306
  """
268
- Get file from the DB and write to disk.
307
+ List of collections in the DB.
269
308
 
270
309
  Parameters
271
310
  ----------
272
311
  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.
312
+ Database name.
313
+ model_collections_only: bool
314
+ If True, only return model collections (i.e. exclude fs.files, fs.chunks)
278
315
 
279
316
  Returns
280
317
  -------
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.
318
+ list
319
+ List of collection names
288
320
 
289
321
  """
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;
322
+ db_name = db_name or self._get_db_name()
323
+ if db_name not in self.list_of_collections:
324
+ self.list_of_collections[db_name] = DatabaseHandler.db_client[
325
+ db_name
326
+ ].list_collection_names()
327
+ collections = self.list_of_collections[db_name]
328
+ if model_collections_only:
329
+ return [collection for collection in collections if not collection.startswith("fs.")]
330
+ return collections
296
331
 
297
- def export_model_files(self, parameters, dest):
332
+ def export_model_files(self, parameters=None, file_names=None, dest=None, db_name=None):
298
333
  """
299
- Export all the files in a model from the DB and write them to disk.
334
+ Export files from the DB to the model directory.
335
+
336
+ The files to be exported can be specified by file_name or retrieved from the database
337
+ using the parameters dictionary.
300
338
 
301
339
  Parameters
302
340
  ----------
303
341
  parameters: dict
304
- Dict of model parameters.
342
+ Dict of model parameters
343
+ file_names: list, str
344
+ List (or string) of file names to export
305
345
  dest: str or Path
306
346
  Location where to write the files to.
307
347
 
308
- Raises
309
- ------
310
- FileNotFoundError
311
- if a file in parameters.values is not found
312
-
348
+ Returns
349
+ -------
350
+ file_id: dict of GridOut._id
351
+ Dict of database IDs of files.
313
352
  """
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)
353
+ db_name = self._get_db_name(db_name)
322
354
 
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)
355
+ if file_names:
356
+ file_names = [file_names] if not isinstance(file_names, list) else file_names
357
+ elif parameters:
358
+ file_names = [
359
+ info["value"]
360
+ for info in parameters.values()
361
+ if info and info.get("file") and info["value"] is not None
362
+ ]
327
363
 
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,
364
+ instance_ids = {}
365
+ for file_name in file_names:
366
+ if Path(dest).joinpath(file_name).exists():
367
+ instance_ids[file_name] = "file exists"
368
+ else:
369
+ file_path_instance = self._get_file_mongo_db(self._get_db_name(), file_name)
370
+ self._write_file_from_mongo_to_disk(self._get_db_name(), dest, file_path_instance)
371
+ instance_ids[file_name] = file_path_instance._id # pylint: disable=protected-access
372
+ return instance_ids
373
+
374
+ def _get_query_from_parameter_version_table(
375
+ self, parameter_version_table, array_element_name, site
337
376
  ):
338
- """
339
- Build and execute query to Read the MongoDB for a specific array element.
377
+ """Return query based on parameter version table."""
378
+ query_dict = {
379
+ "$or": [
380
+ {"parameter": param, "parameter_version": version}
381
+ for param, version in parameter_version_table.items()
382
+ ],
383
+ }
384
+ # 'xSTX-design' is a placeholder to ignore 'instrument' field in query.
385
+ if array_element_name and array_element_name != "xSTx-design":
386
+ query_dict["instrument"] = array_element_name
387
+ if site:
388
+ query_dict["site"] = site
389
+ return query_dict
340
390
 
341
- Also writes the files listed in the parameter values into the sim_telarray run location
391
+ def _read_mongo_db(self, query, collection_name):
392
+ """
393
+ Query MongoDB.
342
394
 
343
395
  Parameters
344
396
  ----------
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.
397
+ query: dict
398
+ Query to execute.
353
399
  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.
400
+ Collection name.
359
401
 
360
402
  Returns
361
403
  -------
@@ -364,154 +406,153 @@ class DatabaseHandler:
364
406
  Raises
365
407
  ------
366
408
  ValueError
367
- if query returned zero results.
368
-
409
+ if query returned no results.
369
410
  """
411
+ db_name = self._get_db_name()
370
412
  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:
413
+ posts = list(collection.find(query))
414
+ if not posts:
381
415
  raise ValueError(
382
- "The following query returned zero results! Check the input data and rerun.\n",
383
- query,
416
+ f"The following query for {collection_name} returned zero results: {query} "
384
417
  )
385
- for post in collection.find(query).sort("parameter", ASCENDING):
418
+ parameters = {}
419
+ for post in posts:
386
420
  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)
394
-
395
- return _parameters
421
+ parameters[par_now] = post
422
+ parameters[par_now]["entry_date"] = ObjectId(post["_id"]).generation_time
423
+ return {k: parameters[k] for k in sorted(parameters)}
396
424
 
397
- def get_site_parameters(
398
- self,
399
- site,
400
- model_version,
401
- only_applicable=False,
402
- ):
425
+ def _read_production_table_from_mongo_db(self, collection_name, model_version):
403
426
  """
404
- Get parameters from either MongoDB or simulation model repository for a specific site.
427
+ Read production table from MongoDB.
405
428
 
406
429
  Parameters
407
430
  ----------
408
- site: str
409
- Site name.
431
+ collection_name: str
432
+ Name of the collection.
410
433
  model_version: str
411
434
  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
435
 
436
+ Raises
437
+ ------
438
+ ValueError
439
+ if query returned no results.
419
440
  """
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
441
  try:
424
- return DatabaseHandler.site_parameters_cached[_site_cache_key]
442
+ return DatabaseHandler.production_table_cached[
443
+ self._cache_key(None, None, model_version, collection_name)
444
+ ]
425
445
  except KeyError:
426
446
  pass
427
447
 
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]
448
+ query = {"model_version": model_version, "collection": collection_name}
449
+ collection = self.get_collection(self._get_db_name(), "production_tables")
450
+ post = collection.find_one(query)
451
+ if not post:
452
+ raise ValueError(f"The following query returned zero results: {query}")
453
+
454
+ return {
455
+ "collection": post["collection"],
456
+ "model_version": post["model_version"],
457
+ "parameters": post["parameters"],
458
+ "design_model": post.get("design_model", {}),
459
+ "entry_date": ObjectId(post["_id"]).generation_time,
460
+ }
437
461
 
438
- def _get_site_parameters_mongo_db(self, db_name, site, model_version, only_applicable=False):
462
+ def get_model_versions(self, collection_name="telescopes"):
439
463
  """
440
- Get parameters from MongoDB for a specific site.
464
+ Get list of model versions from the DB.
441
465
 
442
466
  Parameters
443
467
  ----------
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.
468
+ collection_name: str
469
+ Name of the collection.
452
470
 
453
471
  Returns
454
472
  -------
455
- dict containing the parameters
456
-
457
- Raises
458
- ------
459
- ValueError
460
- if query returned zero results.
473
+ list
474
+ List of model versions
475
+ """
476
+ collection = self.get_collection(self._get_db_name(), "production_tables")
477
+ return sorted(
478
+ {post["model_version"] for post in collection.find({"collection": collection_name})}
479
+ )
461
480
 
481
+ def get_array_elements(self, model_version, collection="telescopes"):
462
482
  """
463
- collection = self.get_collection(db_name, "sites")
464
- _parameters = {}
483
+ Get list array elements for a given model version and collection from the DB.
465
484
 
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
485
+ Parameters
486
+ ----------
487
+ model_version: str
488
+ Version of the model.
489
+ collection: str
490
+ Which collection to get the array elements from:
491
+ i.e. telescopes, calibration_devices.
483
492
 
484
- return _parameters
493
+ Returns
494
+ -------
495
+ list
496
+ Sorted list of all array elements found in collection
497
+ """
498
+ production_table = self._read_production_table_from_mongo_db(collection, model_version)
499
+ return sorted([entry for entry in production_table["parameters"] if "-design" not in entry])
485
500
 
486
- def get_derived_values(self, site, array_element_name, model_version):
501
+ def get_design_model(self, model_version, array_element_name, collection="telescopes"):
487
502
  """
488
- Get all derived values from the DB for a specific array element.
503
+ Get the design model used for a given array element and a given model version.
489
504
 
490
505
  Parameters
491
506
  ----------
492
- site: str
493
- Site name.
494
- array_element_name: str
495
- Name of the array element model (e.g. MSTN, SSTS).
496
507
  model_version: str
497
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.
498
514
 
499
515
  Returns
500
516
  -------
501
- dict containing the parameters
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
502
526
 
527
+ def get_array_elements_of_type(self, array_element_type, model_version, collection):
503
528
  """
504
- _, _array_element_name, _model_version = self._validate_model_input(
505
- site, array_element_name, model_version
506
- )
529
+ Get array elements of a certain type (e.g. 'LSTN') for a DB collection.
530
+
531
+ Does not return 'design' models.
507
532
 
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,
533
+ Parameters
534
+ ----------
535
+ array_element_type: str
536
+ Type of the array element (e.g. LSTN, MSTS).
537
+ model_version: str
538
+ Version of the model.
539
+ collection: str
540
+ Which collection to get the array elements from:
541
+ i.e. telescopes, calibration_devices.
542
+
543
+ Returns
544
+ -------
545
+ list
546
+ Sorted list of all array element names found in collection
547
+ """
548
+ production_table = self._read_production_table_from_mongo_db(collection, model_version)
549
+ all_array_elements = production_table["parameters"]
550
+ return sorted(
551
+ [
552
+ entry
553
+ for entry in all_array_elements
554
+ if entry.startswith(array_element_type) and "-design" not in entry
555
+ ]
515
556
  )
516
557
 
517
558
  def get_simulation_configuration_parameters(
@@ -541,63 +582,24 @@ class DatabaseHandler:
541
582
  if simulation_software is not valid.
542
583
  """
543
584
  if simulation_software == "corsika":
544
- return self.get_corsika_configuration_parameters(model_version)
585
+ return self.get_model_parameters(
586
+ None,
587
+ None,
588
+ model_version=model_version,
589
+ collection="configuration_corsika",
590
+ )
545
591
  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"
592
+ return (
593
+ self.get_model_parameters(
594
+ site,
595
+ array_element_name,
596
+ model_version=model_version,
597
+ collection="configuration_sim_telarray",
549
598
  )
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,
599
+ if site and array_element_name
600
+ else {}
580
601
  )
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
- )
602
+ raise ValueError(f"Unknown simulation software: {simulation_software}")
601
603
 
602
604
  @staticmethod
603
605
  def _get_file_mongo_db(db_name, file_name):
@@ -648,76 +650,22 @@ class DatabaseHandler:
648
650
  with open(Path(path).joinpath(file.filename), "wb") as output_file:
649
651
  fs_output.download_to_stream_by_name(file.filename, output_file)
650
652
 
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
- ):
653
+ def add_production_table(self, db_name, production_table):
661
654
  """
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.
655
+ Add a production table to the DB.
666
656
 
667
657
  Parameters
668
658
  ----------
669
659
  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
-
660
+ the name of the DB.
661
+ production_table: dict
662
+ The production table to add to the DB.
688
663
  """
689
664
  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
665
+ collection = self.get_collection(db_name, "production_tables")
666
+ self._logger.info(f"Adding production for {production_table.get('collection')} to to DB")
667
+ collection.insert_one(production_table)
668
+ DatabaseHandler.production_table_cached.clear()
721
669
 
722
670
  def add_new_parameter(
723
671
  self,
@@ -727,7 +675,7 @@ class DatabaseHandler:
727
675
  file_prefix=None,
728
676
  ):
729
677
  """
730
- Add a parameter dictionary for a specific array element to the DB.
678
+ Add a new parameter dictionary to the DB.
731
679
 
732
680
  A new document will be added to the DB, with all fields taken from the input parameters.
733
681
  Parameter dictionaries are validated before submission using the corresponding schema.
@@ -743,13 +691,7 @@ class DatabaseHandler:
743
691
  file_prefix: str or Path
744
692
  where to find files to upload to the DB
745
693
  """
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)
694
+ par_dict = validate_data.DataValidator.validate_model_parameter(par_dict)
753
695
 
754
696
  db_name = self._get_db_name(db_name)
755
697
  collection = self.get_collection(db_name, collection_name)
@@ -778,7 +720,7 @@ class DatabaseHandler:
778
720
  self._logger.info(f"Will also add the file {file_to_insert_now} to the DB")
779
721
  self.insert_file_to_db(file_to_insert_now, db_name)
780
722
 
781
- self._reset_parameter_cache(par_dict["site"], par_dict["instrument"], par_dict["version"])
723
+ self._reset_parameter_cache()
782
724
 
783
725
  def _get_db_name(self, db_name=None):
784
726
  """
@@ -796,42 +738,6 @@ class DatabaseHandler:
796
738
  """
797
739
  return self.mongo_db_config["db_simulation_model"] if db_name is None else db_name
798
740
 
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
741
  def insert_file_to_db(self, file_name, db_name=None, **kwargs):
836
742
  """
837
743
  Insert a file to the DB.
@@ -855,15 +761,11 @@ class DatabaseHandler:
855
761
 
856
762
  """
857
763
  db_name = self._get_db_name(db_name)
858
-
859
764
  db = DatabaseHandler.db_client[db_name]
860
765
  file_system = gridfs.GridFS(db)
861
766
 
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
767
+ kwargs.setdefault("content_type", "ascii/dat")
768
+ kwargs.setdefault("filename", Path(file_name).name)
867
769
 
868
770
  if file_system.exists({"filename": kwargs["filename"]}):
869
771
  self._logger.warning(
@@ -872,78 +774,11 @@ class DatabaseHandler:
872
774
  return file_system.find_one( # pylint: disable=protected-access
873
775
  {"filename": kwargs["filename"]}
874
776
  )._id
777
+ self._logger.debug(f"Writing file to DB: {file_name}")
875
778
  with open(file_name, "rb") as data_file:
876
779
  return file_system.put(data_file, **kwargs)
877
780
 
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):
781
+ def _cache_key(self, site=None, array_element_name=None, model_version=None, collection=None):
947
782
  """
948
783
  Create a cache key for the parameter cache dictionaries.
949
784
 
@@ -963,61 +798,84 @@ class DatabaseHandler:
963
798
  str
964
799
  Cache key.
965
800
  """
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)
801
+ return "-".join(
802
+ part for part in [model_version, collection, site, array_element_name] if part
803
+ )
975
804
 
976
- def _reset_parameter_cache(self, site, array_element_name, model_version):
805
+ def _read_cache(
806
+ self, cache_dict, site=None, array_element_name=None, model_version=None, collection=None
807
+ ):
977
808
  """
978
- Reset the cache for the parameters.
809
+ Read parameters from cache.
979
810
 
980
811
  Parameters
981
812
  ----------
813
+ cache_dict: dict
814
+ Cache dictionary.
982
815
  site: str
983
816
  Site name.
984
817
  array_element_name: str
985
818
  Array element name.
986
819
  model_version: str
987
820
  Model version.
821
+ collection: str
822
+ DB collection name.
823
+
824
+ Returns
825
+ -------
826
+ str
827
+ Cache key.
988
828
  """
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()
829
+ cache_key = self._cache_key(site, array_element_name, model_version, collection)
830
+ try:
831
+ return cache_key, cache_dict[cache_key]
832
+ except KeyError:
833
+ return cache_key, None
994
834
 
995
- def get_collections(self, db_name=None, model_collections_only=False):
835
+ def _reset_parameter_cache(self):
836
+ """Reset the cache for the parameters."""
837
+ DatabaseHandler.model_parameters_cached.clear()
838
+
839
+ def _get_array_element_list(self, array_element_name, site, production_table, collection):
996
840
  """
997
- List of collections in the DB.
841
+ Return list of array elements for DB queries (add design model if needed).
842
+
843
+ Design model is added if found in the production table.
998
844
 
999
845
  Parameters
1000
846
  ----------
1001
- db_name: str
1002
- Database name.
847
+ array_element_name: str
848
+ Name of the array element.
849
+ site: str
850
+ Site name.
851
+ production_table: dict
852
+ Production table.
853
+ collection: str
854
+ collection of array element (e.g. telescopes, calibration_devices).
1003
855
 
1004
856
  Returns
1005
857
  -------
1006
858
  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:
859
+ List of array elements
860
+ """
861
+ if collection == "configuration_corsika":
862
+ return ["xSTx-design"] # placeholder to ignore 'instrument' field in query.
863
+ if collection == "sites":
864
+ return [f"OBS-{site}"]
865
+ if names.is_design_type(array_element_name):
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
+ )
873
+ try:
1018
874
  return [
1019
- collection
1020
- for collection in self.list_of_collections[db_name]
1021
- if not collection.startswith("fs.") and collection != "metadata"
875
+ production_table["design_model"][array_element_name],
876
+ array_element_name,
1022
877
  ]
1023
- return self.list_of_collections[db_name]
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