gammasimtools 0.8.2__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 (122) hide show
  1. {gammasimtools-0.8.2.dist-info → gammasimtools-0.10.0.dist-info}/METADATA +4 -4
  2. {gammasimtools-0.8.2.dist-info → gammasimtools-0.10.0.dist-info}/RECORD +119 -105
  3. {gammasimtools-0.8.2.dist-info → gammasimtools-0.10.0.dist-info}/WHEEL +1 -1
  4. {gammasimtools-0.8.2.dist-info → gammasimtools-0.10.0.dist-info}/entry_points.txt +4 -1
  5. simtools/_version.py +2 -2
  6. simtools/applications/calculate_trigger_rate.py +15 -38
  7. simtools/applications/convert_all_model_parameters_from_simtel.py +9 -28
  8. simtools/applications/convert_geo_coordinates_of_array_elements.py +54 -53
  9. simtools/applications/convert_model_parameter_from_simtel.py +2 -2
  10. simtools/applications/db_add_file_to_db.py +1 -2
  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 +2 -11
  13. simtools/applications/db_development_tools/write_array_elements_positions_to_repository.py +6 -6
  14. simtools/applications/db_get_array_layouts_from_db.py +3 -1
  15. simtools/applications/db_get_file_from_db.py +11 -12
  16. simtools/applications/db_get_parameter_from_db.py +44 -32
  17. simtools/applications/derive_mirror_rnda.py +10 -1
  18. simtools/applications/derive_photon_electron_spectrum.py +99 -0
  19. simtools/applications/derive_psf_parameters.py +1 -1
  20. simtools/applications/generate_array_config.py +18 -22
  21. simtools/applications/generate_regular_arrays.py +24 -21
  22. simtools/applications/generate_simtel_array_histograms.py +11 -48
  23. simtools/applications/plot_array_layout.py +3 -1
  24. simtools/applications/plot_tabular_data.py +84 -0
  25. simtools/applications/production_generate_simulation_config.py +25 -7
  26. simtools/applications/production_scale_events.py +3 -4
  27. simtools/applications/simulate_light_emission.py +2 -2
  28. simtools/applications/simulate_prod.py +25 -60
  29. simtools/applications/simulate_prod_htcondor_generator.py +95 -0
  30. simtools/applications/submit_data_from_external.py +12 -4
  31. simtools/applications/submit_model_parameter_from_external.py +8 -6
  32. simtools/applications/validate_camera_efficiency.py +3 -3
  33. simtools/applications/validate_camera_fov.py +3 -7
  34. simtools/applications/validate_cumulative_psf.py +3 -7
  35. simtools/applications/validate_file_using_schema.py +38 -24
  36. simtools/applications/validate_optics.py +3 -4
  37. simtools/{camera_efficiency.py → camera/camera_efficiency.py} +1 -4
  38. simtools/camera/single_photon_electron_spectrum.py +168 -0
  39. simtools/configuration/commandline_parser.py +14 -13
  40. simtools/configuration/configurator.py +6 -19
  41. simtools/constants.py +10 -3
  42. simtools/corsika/corsika_config.py +8 -7
  43. simtools/corsika/corsika_histograms.py +1 -1
  44. simtools/data_model/data_reader.py +0 -3
  45. simtools/data_model/metadata_collector.py +21 -4
  46. simtools/data_model/metadata_model.py +8 -111
  47. simtools/data_model/model_data_writer.py +18 -64
  48. simtools/data_model/schema.py +213 -0
  49. simtools/data_model/validate_data.py +73 -51
  50. simtools/db/db_handler.py +395 -790
  51. simtools/db/db_model_upload.py +139 -0
  52. simtools/io_operations/hdf5_handler.py +54 -24
  53. simtools/io_operations/legacy_data_handler.py +61 -0
  54. simtools/job_execution/htcondor_script_generator.py +133 -0
  55. simtools/job_execution/job_manager.py +77 -50
  56. simtools/layout/array_layout.py +33 -28
  57. simtools/model/array_model.py +13 -7
  58. simtools/model/camera.py +4 -2
  59. simtools/model/model_parameter.py +61 -63
  60. simtools/model/site_model.py +3 -3
  61. simtools/production_configuration/calculate_statistical_errors_grid_point.py +119 -144
  62. simtools/production_configuration/event_scaler.py +7 -17
  63. simtools/production_configuration/generate_simulation_config.py +5 -32
  64. simtools/production_configuration/interpolation_handler.py +8 -11
  65. simtools/ray_tracing/mirror_panel_psf.py +47 -27
  66. simtools/runners/corsika_runner.py +14 -3
  67. simtools/runners/corsika_simtel_runner.py +3 -1
  68. simtools/runners/runner_services.py +3 -3
  69. simtools/runners/simtel_runner.py +27 -8
  70. simtools/schemas/input/MST_mirror_2f_measurements.schema.yml +39 -0
  71. simtools/schemas/input/single_pe_spectrum.schema.yml +38 -0
  72. simtools/schemas/integration_tests_config.metaschema.yml +23 -3
  73. simtools/schemas/model_parameter.metaschema.yml +95 -2
  74. simtools/schemas/model_parameter_and_data_schema.metaschema.yml +2 -0
  75. simtools/schemas/model_parameters/array_element_position_utm.schema.yml +1 -1
  76. simtools/schemas/model_parameters/array_window.schema.yml +37 -0
  77. simtools/schemas/model_parameters/asum_clipping.schema.yml +0 -4
  78. simtools/schemas/model_parameters/channels_per_chip.schema.yml +1 -1
  79. simtools/schemas/model_parameters/corsika_iact_io_buffer.schema.yml +2 -2
  80. simtools/schemas/model_parameters/dsum_clipping.schema.yml +0 -2
  81. simtools/schemas/model_parameters/dsum_ignore_below.schema.yml +0 -2
  82. simtools/schemas/model_parameters/dsum_offset.schema.yml +0 -2
  83. simtools/schemas/model_parameters/dsum_pedsub.schema.yml +0 -2
  84. simtools/schemas/model_parameters/dsum_pre_clipping.schema.yml +0 -2
  85. simtools/schemas/model_parameters/dsum_prescale.schema.yml +0 -2
  86. simtools/schemas/model_parameters/dsum_presum_max.schema.yml +0 -2
  87. simtools/schemas/model_parameters/dsum_presum_shift.schema.yml +0 -2
  88. simtools/schemas/model_parameters/dsum_shaping.schema.yml +0 -2
  89. simtools/schemas/model_parameters/dsum_shaping_renormalize.schema.yml +0 -2
  90. simtools/schemas/model_parameters/dsum_threshold.schema.yml +0 -2
  91. simtools/schemas/model_parameters/dsum_zero_clip.schema.yml +0 -2
  92. simtools/schemas/model_parameters/effective_focal_length.schema.yml +31 -1
  93. simtools/schemas/model_parameters/fadc_compensate_pedestal.schema.yml +1 -1
  94. simtools/schemas/model_parameters/fadc_lg_compensate_pedestal.schema.yml +1 -1
  95. simtools/schemas/model_parameters/fadc_noise.schema.yml +3 -3
  96. simtools/schemas/model_parameters/fake_mirror_list.schema.yml +33 -0
  97. simtools/schemas/model_parameters/laser_photons.schema.yml +2 -2
  98. simtools/schemas/model_parameters/secondary_mirror_degraded_reflection.schema.yml +1 -1
  99. simtools/schemas/production_configuration_metrics.schema.yml +68 -0
  100. simtools/schemas/production_tables.schema.yml +41 -0
  101. simtools/simtel/simtel_config_writer.py +5 -6
  102. simtools/simtel/simtel_io_histogram.py +32 -67
  103. simtools/simtel/simtel_io_histograms.py +15 -30
  104. simtools/simtel/simtel_table_reader.py +410 -0
  105. simtools/simtel/simulator_array.py +2 -1
  106. simtools/simtel/simulator_camera_efficiency.py +11 -4
  107. simtools/simtel/simulator_light_emission.py +5 -3
  108. simtools/simtel/simulator_ray_tracing.py +2 -2
  109. simtools/simulator.py +80 -33
  110. simtools/testing/configuration.py +12 -8
  111. simtools/testing/helpers.py +9 -16
  112. simtools/testing/validate_output.py +152 -68
  113. simtools/utils/general.py +149 -12
  114. simtools/utils/names.py +25 -21
  115. simtools/utils/value_conversion.py +9 -1
  116. simtools/visualization/plot_tables.py +106 -0
  117. simtools/visualization/visualize.py +43 -5
  118. simtools/applications/db_add_model_parameters_from_repository_to_db.py +0 -184
  119. simtools/db/db_array_elements.py +0 -130
  120. simtools/db/db_from_repo_handler.py +0 -106
  121. {gammasimtools-0.8.2.dist-info → gammasimtools-0.10.0.dist-info}/LICENSE +0 -0
  122. {gammasimtools-0.8.2.dist-info → gammasimtools-0.10.0.dist-info}/top_level.txt +0 -0
simtools/db/db_handler.py CHANGED
@@ -6,12 +6,12 @@ from pathlib import Path
6
6
  from threading import Lock
7
7
 
8
8
  import gridfs
9
+ import jsonschema
9
10
  from bson.objectid import ObjectId
10
11
  from packaging.version import Version
11
- from pymongo import ASCENDING, MongoClient
12
- from pymongo.errors import BulkWriteError
12
+ from pymongo import MongoClient
13
13
 
14
- from simtools.db import db_array_elements, db_from_repo_handler
14
+ from simtools.data_model import validate_data
15
15
  from simtools.io_operations import io_handler
16
16
  from simtools.utils import names, value_conversion
17
17
 
@@ -24,6 +24,35 @@ logging.getLogger("pymongo").setLevel(logging.WARNING)
24
24
  # The above comment is because pylint does not know that DatabaseHandler.db_client is subscriptable
25
25
 
26
26
 
27
+ jsonschema_db_dict = {
28
+ "$schema": "https://json-schema.org/draft/2020-12/schema#",
29
+ "type": "object",
30
+ "description": "MongoDB configuration",
31
+ "properties": {
32
+ "db_server": {"type": "string", "description": "DB server address"},
33
+ "db_api_port": {
34
+ "type": "integer",
35
+ "minimum": 1,
36
+ "maximum": 65535,
37
+ "default": 27017,
38
+ "description": "Port to use",
39
+ },
40
+ "db_api_user": {"type": "string", "description": "API username"},
41
+ "db_api_pw": {"type": "string", "description": "Password for the API user"},
42
+ "db_api_authentication_database": {
43
+ "type": "string",
44
+ "default": "admin",
45
+ "description": "DB with user info (optional)",
46
+ },
47
+ "db_simulation_model": {
48
+ "type": "string",
49
+ "description": "Name of simulation model database",
50
+ },
51
+ },
52
+ "required": ["db_server", "db_api_port", "db_api_user", "db_api_pw", "db_simulation_model"],
53
+ }
54
+
55
+
27
56
  class DatabaseHandler:
28
57
  """
29
58
  DatabaseHandler provides the interface to the DB.
@@ -31,32 +60,20 @@ class DatabaseHandler:
31
60
  Parameters
32
61
  ----------
33
62
  mongo_db_config: dict
34
- Dictionary with the MongoDB configuration with the following entries:
35
- "db_server" - DB server address
36
- "db_api_port" - Port to use
37
- "db_api_user" - API username
38
- "db_api_pw" - Password for the API user
39
- "db_api_authentication_database" - DB with user info (optional, default is "admin")
40
- "db_simulation_model" - Name of simulation model database
63
+ Dictionary with the MongoDB configuration (see jsonschema_db_dict for details).
41
64
  """
42
65
 
43
- DB_CTA_SIMULATION_MODEL_DESCRIPTIONS = "CTA-Simulation-Model-Descriptions"
44
- # DB collection with updates field names
45
- DB_DERIVED_VALUES = "Staging-CTA-Simulation-Model-Derived-Values"
46
-
47
66
  ALLOWED_FILE_EXTENSIONS = [".dat", ".txt", ".lis", ".cfg", ".yml", ".yaml", ".ecsv"]
48
67
 
49
68
  db_client = None
50
- site_parameters_cached = {}
69
+ production_table_cached = {}
51
70
  model_parameters_cached = {}
52
- model_versions_cached = {}
53
- corsika_configuration_parameters_cached = {}
54
71
 
55
72
  def __init__(self, mongo_db_config=None):
56
73
  """Initialize the DatabaseHandler class."""
57
74
  self._logger = logging.getLogger(__name__)
58
75
 
59
- self.mongo_db_config = mongo_db_config
76
+ self.mongo_db_config = self._validate_mongo_db_config(mongo_db_config)
60
77
  self.io_handler = io_handler.IOHandler()
61
78
  self.list_of_collections = {}
62
79
 
@@ -70,6 +87,16 @@ class DatabaseHandler:
70
87
  with lock:
71
88
  DatabaseHandler.db_client = self._open_mongo_db()
72
89
 
90
+ def _validate_mongo_db_config(self, mongo_db_config):
91
+ """Validate the MongoDB configuration."""
92
+ if mongo_db_config is None or all(value is None for value in mongo_db_config.values()):
93
+ return None
94
+ try:
95
+ jsonschema.validate(instance=mongo_db_config, schema=jsonschema_db_dict)
96
+ return mongo_db_config
97
+ except jsonschema.exceptions.ValidationError as err:
98
+ raise ValueError("Invalid MongoDB configuration") from err
99
+
73
100
  def _open_mongo_db(self):
74
101
  """
75
102
  Open a connection to MongoDB and return the client to read/write to the DB with.
@@ -83,25 +110,21 @@ class DatabaseHandler:
83
110
  KeyError
84
111
  If the DB configuration is invalid
85
112
  """
86
- try:
87
- direct_connection = self.mongo_db_config["db_server"] in (
88
- "localhost",
89
- "simtools-mongodb",
90
- )
91
- return MongoClient(
92
- self.mongo_db_config["db_server"],
93
- port=self.mongo_db_config["db_api_port"],
94
- username=self.mongo_db_config["db_api_user"],
95
- password=self.mongo_db_config["db_api_pw"],
96
- authSource=self.mongo_db_config.get("db_api_authentication_database", "admin"),
97
- directConnection=direct_connection,
98
- ssl=not direct_connection,
99
- tlsallowinvalidhostnames=True,
100
- tlsallowinvalidcertificates=True,
101
- )
102
- except KeyError:
103
- self._logger.error("Invalid setting of DB configuration")
104
- raise
113
+ direct_connection = self.mongo_db_config["db_server"] in (
114
+ "localhost",
115
+ "simtools-mongodb",
116
+ )
117
+ return MongoClient(
118
+ self.mongo_db_config["db_server"],
119
+ port=self.mongo_db_config["db_api_port"],
120
+ username=self.mongo_db_config["db_api_user"],
121
+ password=self.mongo_db_config["db_api_pw"],
122
+ authSource=self.mongo_db_config.get("db_api_authentication_database", "admin"),
123
+ directConnection=direct_connection,
124
+ ssl=not direct_connection,
125
+ tlsallowinvalidhostnames=True,
126
+ tlsallowinvalidcertificates=True,
127
+ )
105
128
 
106
129
  def _find_latest_simulation_model_db(self):
107
130
  """
@@ -118,12 +141,13 @@ class DatabaseHandler:
118
141
 
119
142
  """
120
143
  try:
121
- 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"):
122
146
  return
123
- except TypeError:
147
+ except TypeError: # db_simulation_model is None
124
148
  return
125
149
 
126
- prefix = self.mongo_db_config["db_simulation_model"].replace("LATEST", "")
150
+ prefix = db_simulation_model.replace("LATEST", "")
127
151
  list_of_db_names = self.db_client.list_database_names()
128
152
  filtered_list_of_db_names = [s for s in list_of_db_names if s.startswith(prefix)]
129
153
  versioned_strings = []
@@ -148,78 +172,117 @@ class DatabaseHandler:
148
172
  else:
149
173
  raise ValueError("Found LATEST in the DB name but no matching versions found in DB.")
150
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
+
151
214
  def get_model_parameters(
152
215
  self,
153
216
  site,
154
217
  array_element_name,
155
- model_version,
156
- collection="telescope",
157
- only_applicable=False,
218
+ collection,
219
+ model_version=None,
158
220
  ):
159
221
  """
160
- Get parameters from MongoDB or simulation model repository for an array element.
222
+ Get model parameters using the model version.
161
223
 
162
- An array element can be e.g., a telescope or a calibration device.
163
- Read parameters for design and for the specified array element (if necessary). This allows
164
- to overwrite design parameters with specific parameters without having to copy
165
- all model parameters when changing only a few.
224
+ Queries parameters for design and for the specified array element (if necessary).
166
225
 
167
226
  Parameters
168
227
  ----------
169
228
  site: str
170
229
  Site name.
171
230
  array_element_name: str
172
- Name of the array element model (e.g. LSTN-01, MSTS-design)
173
- model_version: str
174
- 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.
175
234
  collection: str
176
- collection of array element (e.g. telescopes, calibration_devices)
177
- only_applicable: bool
178
- If True, only applicable parameters will be read.
235
+ Collection of array element (e.g. telescopes, calibration_devices).
179
236
 
180
237
  Returns
181
238
  -------
182
239
  dict containing the parameters
183
-
184
240
  """
185
- _site, _array_element_name, _model_version = self._validate_model_input(
186
- site, array_element_name, model_version
187
- )
188
- array_element_list = db_array_elements.get_array_element_list_for_db_query(
189
- _array_element_name, self, _model_version, collection
241
+ model_versions = (
242
+ self.get_model_versions(collection) if model_version is None else [model_version]
190
243
  )
244
+
191
245
  pars = {}
192
- for array_element in array_element_list:
193
- _array_elements_cache_key = self._parameter_cache_key(
194
- 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
195
250
  )
196
- try:
197
- pars.update(DatabaseHandler.model_parameters_cached[_array_elements_cache_key])
198
- except KeyError:
251
+ for array_element in array_element_list:
199
252
  pars.update(
200
- self.read_mongo_db(
201
- self.mongo_db_config.get("db_simulation_model", None),
202
- array_element_name=array_element,
203
- model_version=_model_version,
204
- collection_name=collection,
205
- run_location=None,
206
- write_files=False,
207
- only_applicable=only_applicable,
253
+ self._get_parameter_for_model_version(
254
+ array_element, _model_version, site, collection, production_table
208
255
  )
209
256
  )
210
- if self.mongo_db_config.get("db_simulation_model_url", None) is not None:
211
- pars = db_from_repo_handler.update_model_parameters_from_repo(
212
- parameters=pars,
213
- site=_site,
214
- parameter_collection=collection,
215
- array_element_name=array_element,
216
- model_version=_model_version,
217
- db_simulation_model_url=self.mongo_db_config.get("db_simulation_model_url"),
218
- )
219
- DatabaseHandler.model_parameters_cached[_array_elements_cache_key] = pars
220
-
221
257
  return pars
222
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
+
223
286
  def get_collection(self, db_name, collection_name):
224
287
  """
225
288
  Get a collection from the DB.
@@ -240,103 +303,102 @@ class DatabaseHandler:
240
303
  db_name = self._get_db_name(db_name)
241
304
  return DatabaseHandler.db_client[db_name][collection_name]
242
305
 
243
- def export_file_db(self, db_name, dest, file_name):
306
+ def get_collections(self, db_name=None, model_collections_only=False):
244
307
  """
245
- Get file from the DB and write to disk.
308
+ List of collections in the DB.
246
309
 
247
310
  Parameters
248
311
  ----------
249
312
  db_name: str
250
- Name of the DB to search in.
251
- dest: str or Path
252
- Location where to write the file to.
253
- file_name: str
254
- 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)
255
316
 
256
317
  Returns
257
318
  -------
258
- file_id: GridOut._id
259
- the database ID the file was assigned when it was inserted to the DB.
260
-
261
- Raises
262
- ------
263
- FileNotFoundError
264
- If the desired file is not found.
319
+ list
320
+ List of collection names
265
321
 
266
322
  """
267
- db_name = self._get_db_name(db_name)
268
-
269
- self._logger.debug(f"Getting {file_name} from {db_name} and writing it to {dest}")
270
- file_path_instance = self._get_file_mongo_db(db_name, file_name)
271
- self._write_file_from_mongo_to_disk(db_name, dest, file_path_instance)
272
- 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
273
332
 
274
- def export_model_files(self, parameters, dest):
333
+ def export_model_files(self, parameters=None, file_names=None, dest=None, db_name=None):
275
334
  """
276
- 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.
277
339
 
278
340
  Parameters
279
341
  ----------
280
342
  parameters: dict
281
343
  Dict of model parameters
344
+ file_names: list, str
345
+ List (or string) of file names to export
282
346
  dest: str or Path
283
347
  Location where to write the files to.
284
348
 
285
- Raises
286
- ------
287
- FileNotFoundError
288
- if a file in parameters.values is not found
289
-
349
+ Returns
350
+ -------
351
+ file_id: dict of GridOut._id
352
+ Dict of database IDs of files.
290
353
  """
291
- if self.mongo_db_config:
292
- for info in parameters.values():
293
- if not info or not info.get("file") or info["value"] is None:
294
- continue
295
- if Path(dest).joinpath(info["value"]).exists():
296
- continue
297
- file = self._get_file_mongo_db(self._get_db_name(), info["value"])
298
- self._write_file_from_mongo_to_disk(self._get_db_name(), dest, file)
299
- if self.mongo_db_config.get("db_simulation_model_url", None) is not None:
300
- self._logger.warning(
301
- "Exporting model files from simulation model repository not yet implemented"
302
- )
354
+ db_name = self._get_db_name(db_name)
303
355
 
304
- @staticmethod
305
- def _is_file(value):
306
- """Verify if a parameter value is a file name."""
307
- 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
+ ]
308
364
 
309
- def read_mongo_db(
310
- self,
311
- db_name,
312
- array_element_name,
313
- model_version,
314
- run_location,
315
- collection_name,
316
- write_files=True,
317
- 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
318
377
  ):
319
- """
320
- 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
321
391
 
322
- 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.
323
395
 
324
396
  Parameters
325
397
  ----------
326
- db_name: str
327
- the name of the DB
328
- array_element_name: str
329
- Name of the array element model (e.g. MSTN-design ...)
330
- model_version: str
331
- Version of the model.
332
- run_location: Path or str
333
- The sim_telarray run location to write the tabulated data files into.
398
+ query: dict
399
+ Query to execute.
334
400
  collection_name: str
335
- The name of the collection to read from.
336
- write_files: bool
337
- If true, write the files to the run_location.
338
- only_applicable: bool
339
- If True, only applicable parameters will be read.
401
+ Collection name.
340
402
 
341
403
  Returns
342
404
  -------
@@ -345,164 +407,127 @@ class DatabaseHandler:
345
407
  Raises
346
408
  ------
347
409
  ValueError
348
- if query returned zero results.
349
-
410
+ if query returned no results.
350
411
  """
412
+ db_name = self._get_db_name()
351
413
  collection = self.get_collection(db_name, collection_name)
352
- _parameters = {}
353
-
354
- query = {
355
- "instrument": array_element_name,
356
- "version": self.model_version(model_version, db_name),
357
- }
358
-
359
- if only_applicable:
360
- query["applicable"] = True
361
- if collection.count_documents(query) < 1:
414
+ posts = list(collection.find(query))
415
+ if not posts:
362
416
  raise ValueError(
363
- "The following query returned zero results! Check the input data and rerun.\n",
364
- query,
417
+ f"The following query for {collection_name} returned zero results: {query} "
365
418
  )
366
- for post in collection.find(query).sort("parameter", ASCENDING):
419
+ parameters = {}
420
+ for post in posts:
367
421
  par_now = post["parameter"]
368
- _parameters[par_now] = post
369
- _parameters[par_now].pop("parameter", None)
370
- _parameters[par_now].pop("instrument", None)
371
- _parameters[par_now]["entry_date"] = ObjectId(post["_id"]).generation_time
372
- if _parameters[par_now]["file"] and write_files:
373
- file = self._get_file_mongo_db(db_name, _parameters[par_now]["value"])
374
- self._write_file_from_mongo_to_disk(db_name, run_location, file)
375
-
376
- return _parameters
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)}
377
425
 
378
- def get_site_parameters(
379
- self,
380
- site,
381
- model_version,
382
- only_applicable=False,
383
- ):
426
+ def _read_production_table_from_mongo_db(self, collection_name, model_version):
384
427
  """
385
- Get parameters from either MongoDB or simulation model repository for a specific site.
428
+ Read production table from MongoDB.
386
429
 
387
430
  Parameters
388
431
  ----------
389
- site: str
390
- Site name.
432
+ collection_name: str
433
+ Name of the collection.
391
434
  model_version: str
392
435
  Version of the model.
393
- only_applicable: bool
394
- If True, only applicable parameters will be read.
395
-
396
- Returns
397
- -------
398
- dict containing the parameters
399
436
 
437
+ Raises
438
+ ------
439
+ ValueError
440
+ if query returned no results.
400
441
  """
401
- _site, _, _model_version = self._validate_model_input(site, None, model_version)
402
- _db_name = self._get_db_name()
403
- _site_cache_key = self._parameter_cache_key(site, None, model_version)
404
442
  try:
405
- 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
+ ]
406
446
  except KeyError:
407
447
  pass
408
448
 
409
- _pars = self._get_site_parameters_mongo_db(
410
- _db_name,
411
- _site,
412
- _model_version,
413
- only_applicable,
414
- )
415
- # update simulation model using repository
416
- if self.mongo_db_config.get("db_simulation_model_url", None) is not None:
417
- _pars = db_from_repo_handler.update_model_parameters_from_repo(
418
- parameters=_pars,
419
- site=_site,
420
- array_element_name=None,
421
- parameter_collection="site",
422
- model_version=_model_version,
423
- db_simulation_model_url=self.mongo_db_config.get("db_simulation_model_url", None),
424
- )
425
-
426
- DatabaseHandler.site_parameters_cached[_site_cache_key] = _pars
427
- 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
+ }
428
462
 
429
- def _get_site_parameters_mongo_db(self, db_name, site, model_version, only_applicable=False):
463
+ def get_model_versions(self, collection_name="telescopes"):
430
464
  """
431
- Get parameters from MongoDB for a specific site.
465
+ Get list of model versions from the DB.
432
466
 
433
467
  Parameters
434
468
  ----------
435
- db_name: str
436
- The name of the DB.
437
- site: str
438
- Site name.
439
- model_version: str
440
- Version of the model.
441
- only_applicable: bool
442
- If True, only applicable parameters will be read.
469
+ collection_name: str
470
+ Name of the collection.
443
471
 
444
472
  Returns
445
473
  -------
446
- dict containing the parameters
447
-
448
- Raises
449
- ------
450
- ValueError
451
- if query returned zero results.
452
-
474
+ list
475
+ List of model versions
453
476
  """
454
- collection = self.get_collection(db_name, "sites")
455
- _parameters = {}
456
-
457
- query = {
458
- "site": site,
459
- "version": model_version,
460
- }
461
- if only_applicable:
462
- query["applicable"] = True
463
- if collection.count_documents(query) < 1:
464
- raise ValueError(
465
- "The following query returned zero results! Check the input data and rerun.\n",
466
- query,
467
- )
468
- for post in collection.find(query).sort("parameter", ASCENDING):
469
- par_now = post["parameter"]
470
- _parameters[par_now] = post
471
- _parameters[par_now].pop("parameter", None)
472
- _parameters[par_now].pop("site", None)
473
- _parameters[par_now]["entry_date"] = ObjectId(post["_id"]).generation_time
474
-
475
- 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
+ )
476
481
 
477
- def get_derived_values(self, site, array_element_name, model_version):
482
+ def get_array_elements(self, model_version, collection="telescopes"):
478
483
  """
479
- 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.
480
485
 
481
486
  Parameters
482
487
  ----------
483
- site: str
484
- Site name.
485
- array_element_name: str
486
- Name of the array element model (e.g. MSTN, SSTS).
487
488
  model_version: str
488
489
  Version of the model.
490
+ collection: str
491
+ Which collection to get the array elements from:
492
+ i.e. telescopes, calibration_devices.
489
493
 
490
494
  Returns
491
495
  -------
492
- 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])
493
501
 
502
+ def get_array_elements_of_type(self, array_element_type, model_version, collection):
494
503
  """
495
- _, _array_element_name, _model_version = self._validate_model_input(
496
- site, array_element_name, model_version
497
- )
504
+ Get array elements of a certain type (e.g. 'LSTN') for a DB collection.
498
505
 
499
- return self.read_mongo_db(
500
- DatabaseHandler.DB_DERIVED_VALUES,
501
- _array_element_name,
502
- _model_version,
503
- run_location=None,
504
- collection_name="derived_values",
505
- 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
+ ]
506
531
  )
507
532
 
508
533
  def get_simulation_configuration_parameters(
@@ -532,63 +557,24 @@ class DatabaseHandler:
532
557
  if simulation_software is not valid.
533
558
  """
534
559
  if simulation_software == "corsika":
535
- 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
+ )
536
566
  if simulation_software == "simtel":
537
- if site and array_element_name:
538
- return self.get_model_parameters(
539
- 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",
540
573
  )
541
- return {}
542
- raise ValueError(f"Unknown simulation software: {simulation_software}")
543
-
544
- def get_corsika_configuration_parameters(self, model_version):
545
- """
546
- Get CORSIKA configuration parameters from the DB.
547
-
548
- Parameters
549
- ----------
550
- model_version : str
551
- Version of the model.
552
-
553
- Returns
554
- -------
555
- dict
556
- Configuration parameters for CORSIKA
557
- """
558
- _corsika_cache_key = self._parameter_cache_key(None, None, model_version)
559
- try:
560
- return DatabaseHandler.corsika_configuration_parameters_cached[_corsika_cache_key]
561
- except KeyError:
562
- pass
563
- DatabaseHandler.corsika_configuration_parameters_cached[_corsika_cache_key] = (
564
- self.read_mongo_db(
565
- db_name=self._get_db_name(),
566
- array_element_name=None,
567
- model_version=model_version,
568
- run_location=None,
569
- collection_name="configuration_corsika",
570
- write_files=False,
574
+ if site and array_element_name
575
+ else {}
571
576
  )
572
- )
573
- return DatabaseHandler.corsika_configuration_parameters_cached[_corsika_cache_key]
574
-
575
- def _validate_model_input(self, site, array_element_name, model_version):
576
- """
577
- Validate input for model parameter queries.
578
-
579
- site: str
580
- Site name.
581
- array_element_name: str
582
- Name of the array element model (e.g. LSTN-01, MSTS-design)
583
- model_version: str
584
- Version of the model.
585
-
586
- """
587
- return (
588
- names.validate_site_name(site),
589
- names.validate_array_element_name(array_element_name) if array_element_name else None,
590
- self.model_version(model_version),
591
- )
577
+ raise ValueError(f"Unknown simulation software: {simulation_software}")
592
578
 
593
579
  @staticmethod
594
580
  def _get_file_mongo_db(db_name, file_name):
@@ -639,369 +625,77 @@ class DatabaseHandler:
639
625
  with open(Path(path).joinpath(file.filename), "wb") as output_file:
640
626
  fs_output.download_to_stream_by_name(file.filename, output_file)
641
627
 
642
- def copy_array_element(
643
- self,
644
- db_name,
645
- element_to_copy,
646
- version_to_copy,
647
- new_array_element_name,
648
- collection_name="telescopes",
649
- db_to_copy_to=None,
650
- collection_to_copy_to=None,
651
- ):
628
+ def add_production_table(self, db_name, production_table):
652
629
  """
653
- Copy a full array element configuration to a new array element name.
654
-
655
- Only a specific version is copied.
656
- (This function should be rarely used, probably only during "construction".)
630
+ Add a production table to the DB.
657
631
 
658
632
  Parameters
659
633
  ----------
660
634
  db_name: str
661
- the name of the DB to copy from
662
- element_to_copy: str
663
- The array element to copy
664
- version_to_copy: str
665
- The version of the configuration to copy
666
- new_array_element_name: str
667
- The name of the new array element
668
- collection_name: str
669
- The name of the collection to copy from.
670
- db_to_copy_to: str
671
- The name of the DB to copy to.
672
- collection_to_copy_to: str
673
- The name of the collection to copy to.
674
-
675
- Raises
676
- ------
677
- BulkWriteError
678
-
679
- """
680
- db_name = self._get_db_name(db_name)
681
- if db_to_copy_to is None:
682
- db_to_copy_to = db_name
683
-
684
- if collection_to_copy_to is None:
685
- collection_to_copy_to = collection_name
686
-
687
- self._logger.info(
688
- f"Copying version {version_to_copy} of {element_to_copy} "
689
- f"to the new array element {new_array_element_name} in the {db_to_copy_to} DB"
690
- )
691
-
692
- collection = self.get_collection(db_name, collection_name)
693
- db_entries = []
694
-
695
- _version_to_copy = self.model_version(version_to_copy)
696
-
697
- query = {
698
- "instrument": element_to_copy,
699
- "version": _version_to_copy,
700
- }
701
- for post in collection.find(query):
702
- post["instrument"] = new_array_element_name
703
- post.pop("_id", None)
704
- db_entries.append(post)
705
-
706
- self._logger.info(f"Creating new array element {new_array_element_name}")
707
- collection = self.get_collection(db_to_copy_to, collection_to_copy_to)
708
- try:
709
- collection.insert_many(db_entries)
710
- except BulkWriteError as exc:
711
- raise BulkWriteError(str(exc.details)) from exc
712
-
713
- def copy_documents(self, db_name, collection, query, db_to_copy_to, collection_to_copy_to=None):
714
- """
715
- Copy the documents matching to "query" to the DB "db_to_copy_to".
716
-
717
- The documents are copied to the same collection as in "db_name".
718
- (This function should be rarely used, probably only during "construction".)
719
-
720
- Parameters
721
- ----------
722
- db_name: str
723
- the name of the DB to copy from
724
- collection: str
725
- the name of the collection to copy from
726
- query: dict
727
- A dictionary with a query to search for documents to copy.
728
- For example, the query below would copy all entries of version 6.0.0
729
- from telescope LSTN-01 to "db_to_copy_to".
730
-
731
- .. code-block:: python
732
-
733
- query = {
734
- "instrument": "LSTN-01",
735
- "version": "6.0.0",
736
- }
737
- db_to_copy_to: str
738
- The name of the DB to copy to.
739
-
740
- Raises
741
- ------
742
- BulkWriteError
743
-
635
+ the name of the DB.
636
+ production_table: dict
637
+ The production table to add to the DB.
744
638
  """
745
639
  db_name = self._get_db_name(db_name)
746
-
747
- _collection = self.get_collection(db_name, collection)
748
- if collection_to_copy_to is None:
749
- collection_to_copy_to = collection
750
- db_entries = []
751
-
752
- for post in _collection.find(query):
753
- post.pop("_id", None)
754
- db_entries.append(post)
755
-
756
- self._logger.info(
757
- f"Copying documents matching the following query {query}\nto {db_to_copy_to}"
758
- )
759
- _collection = self.get_collection(db_to_copy_to, collection_to_copy_to)
760
- try:
761
- _collection.insert_many(db_entries)
762
- except BulkWriteError as exc:
763
- raise BulkWriteError(str(exc.details)) from exc
764
-
765
- def delete_query(self, db_name, collection, query):
766
- """
767
- Delete all entries from the DB which correspond to the provided query.
768
-
769
- (This function should be rarely used, if at all.)
770
-
771
- Parameters
772
- ----------
773
- db_name: str
774
- the name of the DB
775
- collection: str
776
- the name of the collection to copy from
777
- query: dict
778
- A dictionary listing the fields/values to delete.
779
- For example, the query below would delete the entire version 6.0.0
780
- from telescope LSTN-01.
781
-
782
- .. code-block:: python
783
-
784
- query = {
785
- "instrument": "LSTN-01",
786
- "version": "6.0.0",
787
- }
788
-
789
- """
790
- _collection = self.get_collection(db_name, collection)
791
-
792
- if "version" in query:
793
- query["version"] = self.model_version(query["version"])
794
-
795
- self._logger.info(f"Deleting {_collection.count_documents(query)} entries from {db_name}")
796
-
797
- _collection.delete_many(query)
798
-
799
- def update_parameter_field(
800
- self,
801
- db_name,
802
- model_version,
803
- parameter,
804
- field,
805
- new_value,
806
- array_element_name=None,
807
- site=None,
808
- collection_name="telescopes",
809
- ):
810
- """
811
- Update a parameter field value for a specific array element/version.
812
-
813
- This function only modifies the value of one of the following
814
- DB entries: Applicable, units, Type, items, minimum, maximum.
815
- These type of changes should be very rare. However they can
816
- be done without changing the Object ID of the entry since
817
- they are generally "harmless".
818
-
819
- Parameters
820
- ----------
821
- db_name: str
822
- the name of the DB
823
- model_version: str
824
- Which model version to update
825
- parameter: str
826
- Which parameter to update
827
- field: str
828
- Field to update (only options are Applicable, units, Type, items, minimum, maximum).
829
- If the field does not exist, it will be added.
830
- new_value: type identical to the original field type
831
- The new value to set to the field given in "field".
832
- array_element_name: str
833
- Which array element to update, if None then update a site parameter
834
- site: str, North or South
835
- Update a site parameter (the array_element_name argument must be None)
836
- collection_name: str
837
- The name of the collection in which to update the parameter.
838
-
839
- Raises
840
- ------
841
- ValueError
842
- if field not in allowed fields
843
-
844
- """
845
- db_name = self._get_db_name(db_name)
846
- allowed_fields = ["applicable", "unit", "type", "items", "minimum", "maximum"]
847
- if field not in allowed_fields:
848
- raise ValueError(f"The field {field} must be one of {', '.join(allowed_fields)}")
849
-
850
- collection = self.get_collection(db_name, collection_name)
851
- _model_version = self.model_version(model_version, db_name)
852
-
853
- query = {
854
- "version": _model_version,
855
- "parameter": parameter,
856
- }
857
- if array_element_name is not None:
858
- query["instrument"] = array_element_name
859
- logger_info = f"instrument {array_element_name}"
860
- elif site is not None and site in names.site_names():
861
- query["site"] = site
862
- logger_info = f"site {site}"
863
- else:
864
- raise ValueError("You need to specify an array element or a site.")
865
-
866
- par_entry = collection.find_one(query)
867
- if par_entry is None:
868
- self._logger.warning(
869
- f"The query {query} did not return any results. I will not make any changes."
870
- )
871
- return
872
-
873
- if field in par_entry:
874
- old_field_value = par_entry[field]
875
-
876
- if old_field_value == new_value:
877
- self._logger.warning(
878
- f"The value of the field {field} is already {new_value}. No changes necessary"
879
- )
880
- return
881
-
882
- self._logger.info(
883
- f"For {logger_info}, version {_model_version}, parameter {parameter}, "
884
- f"replacing field {field} value from {old_field_value} to {new_value}"
885
- )
886
- else:
887
- self._logger.info(
888
- f"For {logger_info}, version {_model_version}, parameter {parameter}, "
889
- f"the field {field} does not exist, adding it"
890
- )
891
-
892
- query_update = {"$set": {field: new_value}}
893
- collection.update_one(query, query_update)
894
-
895
- self._reset_parameter_cache(
896
- site=(
897
- site
898
- if site is not None
899
- else names.get_site_from_array_element_name(array_element_name)
900
- ),
901
- array_element_name=array_element_name,
902
- model_version=_model_version,
903
- )
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()
904
644
 
905
645
  def add_new_parameter(
906
646
  self,
907
647
  db_name,
908
- version,
909
- parameter,
910
- value,
911
- array_element_name=None,
912
- site=None,
648
+ par_dict,
913
649
  collection_name="telescopes",
914
650
  file_prefix=None,
915
- **kwargs,
916
651
  ):
917
652
  """
918
- Add a parameter value for a specific array element.
653
+ Add a new parameter dictionary to the DB.
919
654
 
920
- A new document will be added to the DB,
921
- with all fields taken from the input parameters.
655
+ A new document will be added to the DB, with all fields taken from the input parameters.
656
+ Parameter dictionaries are validated before submission using the corresponding schema.
922
657
 
923
658
  Parameters
924
659
  ----------
925
660
  db_name: str
926
661
  the name of the DB
927
- version: str
928
- The version of the new parameter value
929
- parameter: str
930
- Which parameter to add
931
- value: can be any type, preferably given in kwargs
932
- The value to set for the new parameter
933
- array_element_name: str
934
- The name of the array element to add a parameter to
935
- (only used if collection_name is not "sites").
936
- site: str
937
- Site name; ignored if collection_name is "telescopes".
662
+ par_dict: dict
663
+ dictionary with parameter data
938
664
  collection_name: str
939
665
  The name of the collection to add a parameter to.
940
666
  file_prefix: str or Path
941
667
  where to find files to upload to the DB
942
- kwargs: dict
943
- Any additional fields to add to the parameter
944
-
945
- Raises
946
- ------
947
- ValueError
948
- If key to collection_name is not valid.
949
-
950
668
  """
669
+ par_dict = validate_data.DataValidator.validate_model_parameter(par_dict)
670
+
951
671
  db_name = self._get_db_name(db_name)
952
672
  collection = self.get_collection(db_name, collection_name)
953
673
 
954
- db_entry = {}
955
- if any(
956
- key in collection_name
957
- for key in ["telescopes", "calibration_devices", "configuration_sim_telarray"]
958
- ):
959
- db_entry["instrument"] = names.validate_array_element_name(array_element_name)
960
- elif "sites" in collection_name:
961
- db_entry["instrument"] = names.validate_site_name(site)
962
- elif "configuration_corsika" in collection_name:
963
- db_entry["instrument"] = None
964
- else:
965
- raise ValueError(f"Cannot add parameter to collection {collection_name}")
966
-
967
- db_entry["version"] = version
968
- db_entry["parameter"] = parameter
969
- if site is not None:
970
- db_entry["site"] = names.validate_site_name(site)
971
-
972
- _base_value, _base_unit, _base_type = value_conversion.get_value_unit_type(
973
- value=value, unit_str=kwargs.get("unit", None)
674
+ par_dict["value"], _base_unit, _ = value_conversion.get_value_unit_type(
675
+ value=par_dict["value"], unit_str=par_dict.get("unit", None)
974
676
  )
975
- db_entry["value"] = _base_value
976
- if _base_unit is not None:
977
- db_entry["unit"] = _base_unit
978
- db_entry["type"] = kwargs["type"] if "type" in kwargs else _base_type
677
+ par_dict["unit"] = _base_unit if _base_unit else None
979
678
 
980
679
  files_to_add_to_db = set()
981
- db_entry["file"] = False
982
- if self._is_file(value):
983
- db_entry["file"] = True
680
+ if par_dict["file"] and par_dict["value"]:
984
681
  if file_prefix is None:
985
682
  raise FileNotFoundError(
986
683
  "The location of the file to upload, "
987
- f"corresponding to the {parameter} parameter, must be provided."
684
+ f"corresponding to the {par_dict['parameter']} parameter, must be provided."
988
685
  )
989
- file_path = Path(file_prefix).joinpath(value)
686
+ file_path = Path(file_prefix).joinpath(par_dict["value"])
990
687
  files_to_add_to_db.add(f"{file_path}")
991
688
 
992
- kwargs.pop("type", None)
993
- db_entry.update(kwargs)
994
-
995
689
  self._logger.info(
996
- f"Will add the following entry to DB {db_name} and collection {db_name}:\n{db_entry}"
690
+ f"Adding a new entry to DB {db_name} and collection {db_name}:\n{par_dict}"
997
691
  )
692
+ collection.insert_one(par_dict)
998
693
 
999
- collection.insert_one(db_entry)
1000
694
  for file_to_insert_now in files_to_add_to_db:
1001
695
  self._logger.info(f"Will also add the file {file_to_insert_now} to the DB")
1002
696
  self.insert_file_to_db(file_to_insert_now, db_name)
1003
697
 
1004
- self._reset_parameter_cache(site, array_element_name, version)
698
+ self._reset_parameter_cache()
1005
699
 
1006
700
  def _get_db_name(self, db_name=None):
1007
701
  """
@@ -1019,42 +713,6 @@ class DatabaseHandler:
1019
713
  """
1020
714
  return self.mongo_db_config["db_simulation_model"] if db_name is None else db_name
1021
715
 
1022
- def model_version(self, version, db_name=None):
1023
- """
1024
- Return model version and check that it is valid.
1025
-
1026
- Queries the database for all available model versions and check if the
1027
- requested version is valid.
1028
-
1029
- Parameters
1030
- ----------
1031
- version : str
1032
- Model version.
1033
- db_name : str
1034
- Database name.
1035
-
1036
- Returns
1037
- -------
1038
- str
1039
- Model version.
1040
-
1041
- Raises
1042
- ------
1043
- ValueError
1044
- if version not valid.
1045
-
1046
- """
1047
- _all_versions = self.get_all_versions(db_name=db_name)
1048
- if version in _all_versions:
1049
- return version
1050
- if len(_all_versions) == 0:
1051
- return None
1052
-
1053
- raise ValueError(
1054
- f"Invalid model version {version} in DB {self._get_db_name(db_name)} "
1055
- f"(allowed are {_all_versions})"
1056
- )
1057
-
1058
716
  def insert_file_to_db(self, file_name, db_name=None, **kwargs):
1059
717
  """
1060
718
  Insert a file to the DB.
@@ -1078,15 +736,11 @@ class DatabaseHandler:
1078
736
 
1079
737
  """
1080
738
  db_name = self._get_db_name(db_name)
1081
-
1082
739
  db = DatabaseHandler.db_client[db_name]
1083
740
  file_system = gridfs.GridFS(db)
1084
741
 
1085
- if "content_type" not in kwargs:
1086
- kwargs["content_type"] = "ascii/dat"
1087
-
1088
- if "filename" not in kwargs:
1089
- kwargs["filename"] = Path(file_name).name
742
+ kwargs.setdefault("content_type", "ascii/dat")
743
+ kwargs.setdefault("filename", Path(file_name).name)
1090
744
 
1091
745
  if file_system.exists({"filename": kwargs["filename"]}):
1092
746
  self._logger.warning(
@@ -1095,78 +749,11 @@ class DatabaseHandler:
1095
749
  return file_system.find_one( # pylint: disable=protected-access
1096
750
  {"filename": kwargs["filename"]}
1097
751
  )._id
752
+ self._logger.debug(f"Writing file to DB: {file_name}")
1098
753
  with open(file_name, "rb") as data_file:
1099
754
  return file_system.put(data_file, **kwargs)
1100
755
 
1101
- def get_all_versions(
1102
- self,
1103
- parameter=None,
1104
- array_element_name=None,
1105
- site=None,
1106
- db_name=None,
1107
- collection=None,
1108
- ):
1109
- """
1110
- Get all version entries in the DB of collection and/or a specific parameter.
1111
-
1112
- Parameters
1113
- ----------
1114
- parameter: str
1115
- Which parameter to get the versions of
1116
- array_element_name: str
1117
- Which array element to get the versions of (in case "collection_name" is not "sites")
1118
- site: str
1119
- Site name.
1120
- db_name: str
1121
- Database name.
1122
- collection_name: str
1123
- The name of the collection in which to update the parameter.
1124
-
1125
- Returns
1126
- -------
1127
- all_versions: list
1128
- List of all versions found
1129
-
1130
- Raises
1131
- ------
1132
- ValueError
1133
- If key to collection_name is not valid.
1134
-
1135
- """
1136
- db_name = self._get_db_name() if db_name is None else db_name
1137
- if not db_name:
1138
- self._logger.warning("No database name defined to determine list of model versions")
1139
- return []
1140
- _cache_key = f"model_versions_{db_name}-{collection}"
1141
-
1142
- query = {}
1143
- if parameter is not None:
1144
- query["parameter"] = parameter
1145
- _cache_key = f"{_cache_key}-{parameter}"
1146
- if collection in ["telescopes", "calibration_devices"] and array_element_name is not None:
1147
- query["instrument"] = names.validate_array_element_name(array_element_name)
1148
- _cache_key = f"{_cache_key}-{query['instrument']}"
1149
- elif collection == "sites" and site is not None:
1150
- query["site"] = names.validate_site_name(site)
1151
- _cache_key = f"{_cache_key}-{query['site']}"
1152
-
1153
- if _cache_key not in DatabaseHandler.model_versions_cached:
1154
- all_versions = set()
1155
- collections_to_query = (
1156
- [collection] if collection else self.get_collections(db_name, True)
1157
- )
1158
- for collection_name in collections_to_query:
1159
- db_collection = self.get_collection(db_name, collection_name)
1160
- sorted_posts = db_collection.find(query).sort("version", ASCENDING)
1161
- all_versions.update(post["version"] for post in sorted_posts)
1162
- DatabaseHandler.model_versions_cached[_cache_key] = list(all_versions)
1163
-
1164
- if len(DatabaseHandler.model_versions_cached[_cache_key]) == 0:
1165
- self._logger.warning(f"The query {query} did not return any results. No versions found")
1166
-
1167
- return DatabaseHandler.model_versions_cached[_cache_key]
1168
-
1169
- 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):
1170
757
  """
1171
758
  Create a cache key for the parameter cache dictionaries.
1172
759
 
@@ -1186,61 +773,79 @@ class DatabaseHandler:
1186
773
  str
1187
774
  Cache key.
1188
775
  """
1189
- parts = []
1190
- if site:
1191
- parts.append(site)
1192
- if array_element_name:
1193
- parts.append(array_element_name)
1194
- parts.append(model_version)
1195
- if collection:
1196
- parts.append(collection)
1197
- return "-".join(parts)
1198
-
1199
- def _reset_parameter_cache(self, site, array_element_name, model_version):
776
+ return "-".join(
777
+ part for part in [model_version, collection, site, array_element_name] if part
778
+ )
779
+
780
+ def _read_cache(
781
+ self, cache_dict, site=None, array_element_name=None, model_version=None, collection=None
782
+ ):
1200
783
  """
1201
- Reset the cache for the parameters.
784
+ Read parameters from cache.
1202
785
 
1203
786
  Parameters
1204
787
  ----------
788
+ cache_dict: dict
789
+ Cache dictionary.
1205
790
  site: str
1206
791
  Site name.
1207
792
  array_element_name: str
1208
793
  Array element name.
1209
794
  model_version: str
1210
795
  Model version.
796
+ collection: str
797
+ DB collection name.
798
+
799
+ Returns
800
+ -------
801
+ str
802
+ Cache key.
1211
803
  """
1212
- self._logger.debug(f"Resetting cache for {site} {array_element_name} {model_version}")
1213
- _cache_key = self._parameter_cache_key(site, array_element_name, model_version)
1214
- DatabaseHandler.site_parameters_cached.pop(_cache_key, None)
1215
- DatabaseHandler.model_parameters_cached.pop(_cache_key, None)
1216
- 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
1217
809
 
1218
- 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):
1219
815
  """
1220
- 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.
1221
819
 
1222
820
  Parameters
1223
821
  ----------
1224
- db_name: str
1225
- 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).
1226
830
 
1227
831
  Returns
1228
832
  -------
1229
833
  list
1230
- List of collection names
1231
- model_collections_only: bool
1232
- If True, only return model collections (i.e. exclude fs.files, fs.chunks, metadata)
1233
-
1234
- """
1235
- db_name = self._get_db_name() if db_name is None else db_name
1236
- if db_name not in self.list_of_collections:
1237
- self.list_of_collections[db_name] = DatabaseHandler.db_client[
1238
- db_name
1239
- ].list_collection_names()
1240
- 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:
1241
848
  return [
1242
- collection
1243
- for collection in self.list_of_collections[db_name]
1244
- 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,
1245
851
  ]
1246
- return self.list_of_collections[db_name]