gammasimtools 0.19.0__py3-none-any.whl → 0.21.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 (59) hide show
  1. {gammasimtools-0.19.0.dist-info → gammasimtools-0.21.0.dist-info}/METADATA +1 -3
  2. {gammasimtools-0.19.0.dist-info → gammasimtools-0.21.0.dist-info}/RECORD +54 -51
  3. {gammasimtools-0.19.0.dist-info → gammasimtools-0.21.0.dist-info}/entry_points.txt +3 -3
  4. simtools/_version.py +2 -2
  5. simtools/applications/calculate_incident_angles.py +182 -0
  6. simtools/applications/db_add_simulation_model_from_repository_to_db.py +17 -14
  7. simtools/applications/db_add_value_from_json_to_db.py +6 -9
  8. simtools/applications/db_generate_compound_indexes.py +7 -3
  9. simtools/applications/db_get_file_from_db.py +11 -23
  10. simtools/applications/derive_psf_parameters.py +58 -39
  11. simtools/applications/derive_trigger_rates.py +91 -0
  12. simtools/applications/generate_corsika_histograms.py +7 -184
  13. simtools/applications/maintain_simulation_model_add_production.py +105 -0
  14. simtools/applications/plot_simtel_events.py +5 -189
  15. simtools/applications/print_version.py +8 -7
  16. simtools/applications/validate_file_using_schema.py +7 -4
  17. simtools/configuration/commandline_parser.py +17 -11
  18. simtools/corsika/corsika_histograms.py +81 -0
  19. simtools/data_model/validate_data.py +8 -3
  20. simtools/db/db_handler.py +122 -31
  21. simtools/db/db_model_upload.py +51 -30
  22. simtools/dependencies.py +10 -5
  23. simtools/layout/array_layout_utils.py +37 -5
  24. simtools/model/array_model.py +18 -1
  25. simtools/model/model_repository.py +118 -63
  26. simtools/model/site_model.py +25 -0
  27. simtools/production_configuration/derive_corsika_limits.py +9 -34
  28. simtools/ray_tracing/incident_angles.py +706 -0
  29. simtools/ray_tracing/psf_parameter_optimisation.py +999 -565
  30. simtools/schemas/model_parameter_and_data_schema.metaschema.yml +2 -2
  31. simtools/schemas/model_parameters/nsb_reference_spectrum.schema.yml +1 -1
  32. simtools/schemas/model_parameters/nsb_spectrum.schema.yml +22 -29
  33. simtools/schemas/model_parameters/stars.schema.yml +1 -1
  34. simtools/schemas/production_tables.schema.yml +5 -0
  35. simtools/simtel/simtel_config_writer.py +18 -20
  36. simtools/simtel/simtel_io_event_histograms.py +253 -516
  37. simtools/simtel/simtel_io_event_reader.py +51 -2
  38. simtools/simtel/simtel_io_event_writer.py +31 -11
  39. simtools/simtel/simtel_io_metadata.py +1 -1
  40. simtools/simtel/simtel_table_reader.py +3 -3
  41. simtools/simulator.py +1 -4
  42. simtools/telescope_trigger_rates.py +119 -0
  43. simtools/testing/log_inspector.py +13 -11
  44. simtools/utils/geometry.py +20 -0
  45. simtools/version.py +89 -0
  46. simtools/{corsika/corsika_histograms_visualize.py → visualization/plot_corsika_histograms.py} +109 -0
  47. simtools/visualization/plot_incident_angles.py +431 -0
  48. simtools/visualization/plot_psf.py +673 -0
  49. simtools/visualization/plot_simtel_event_histograms.py +376 -0
  50. simtools/visualization/{simtel_event_plots.py → plot_simtel_events.py} +284 -87
  51. simtools/visualization/visualize.py +1 -3
  52. simtools/applications/calculate_trigger_rate.py +0 -187
  53. simtools/applications/generate_sim_telarray_histograms.py +0 -196
  54. simtools/applications/maintain_simulation_model_add_production_table.py +0 -71
  55. simtools/simtel/simtel_io_histogram.py +0 -623
  56. simtools/simtel/simtel_io_histograms.py +0 -556
  57. {gammasimtools-0.19.0.dist-info → gammasimtools-0.21.0.dist-info}/WHEEL +0 -0
  58. {gammasimtools-0.19.0.dist-info → gammasimtools-0.21.0.dist-info}/licenses/LICENSE +0 -0
  59. {gammasimtools-0.19.0.dist-info → gammasimtools-0.21.0.dist-info}/top_level.txt +0 -0
simtools/db/db_handler.py CHANGED
@@ -1,5 +1,6 @@
1
1
  """Module to handle interaction with DB."""
2
2
 
3
+ import io
3
4
  import logging
4
5
  import re
5
6
  from collections import defaultdict
@@ -8,6 +9,7 @@ from threading import Lock
8
9
 
9
10
  import gridfs
10
11
  import jsonschema
12
+ from astropy.table import Table
11
13
  from bson.objectid import ObjectId
12
14
  from packaging.version import Version
13
15
  from pymongo import MongoClient
@@ -16,6 +18,7 @@ from simtools.data_model import validate_data
16
18
  from simtools.io import ascii_handler, io_handler
17
19
  from simtools.simtel import simtel_table_reader
18
20
  from simtools.utils import names, value_conversion
21
+ from simtools.version import resolve_version_to_latest_patch
19
22
 
20
23
  __all__ = ["DatabaseHandler"]
21
24
 
@@ -50,8 +53,19 @@ jsonschema_db_dict = {
50
53
  "type": "string",
51
54
  "description": "Name of simulation model database",
52
55
  },
56
+ "db_simulation_model_version": {
57
+ "type": "string",
58
+ "description": "Version of simulation model database",
59
+ },
53
60
  },
54
- "required": ["db_server", "db_api_port", "db_api_user", "db_api_pw", "db_simulation_model"],
61
+ "required": [
62
+ "db_server",
63
+ "db_api_port",
64
+ "db_api_user",
65
+ "db_api_pw",
66
+ "db_simulation_model",
67
+ "db_simulation_model_version",
68
+ ],
55
69
  }
56
70
 
57
71
 
@@ -59,6 +73,11 @@ class DatabaseHandler:
59
73
  """
60
74
  DatabaseHandler provides the interface to the DB.
61
75
 
76
+ Note the two types of version variables used in this class:
77
+
78
+ - db_simulation_model_version (from mongo_db_config): version of the simulation model database
79
+ - model_version (from production_tables): version of the model contained in the database
80
+
62
81
  Parameters
63
82
  ----------
64
83
  mongo_db_config: dict
@@ -70,6 +89,7 @@ class DatabaseHandler:
70
89
  db_client = None
71
90
  production_table_cached = {}
72
91
  model_parameters_cached = {}
92
+ model_versions_cached = {}
73
93
 
74
94
  def __init__(self, mongo_db_config=None):
75
95
  """Initialize the DatabaseHandler class."""
@@ -82,7 +102,12 @@ class DatabaseHandler:
82
102
  self._set_up_connection()
83
103
  self._find_latest_simulation_model_db()
84
104
  self.db_name = (
85
- self.mongo_db_config.get("db_simulation_model", None) if self.mongo_db_config else None
105
+ self.get_db_name(
106
+ db_simulation_model_version=self.mongo_db_config.get("db_simulation_model_version"),
107
+ model_name=self.mongo_db_config.get("db_simulation_model"),
108
+ )
109
+ if self.mongo_db_config
110
+ else None
86
111
  )
87
112
 
88
113
  def _set_up_connection(self):
@@ -92,6 +117,16 @@ class DatabaseHandler:
92
117
  with lock:
93
118
  DatabaseHandler.db_client = self._open_mongo_db()
94
119
 
120
+ def get_db_name(self, db_name=None, db_simulation_model_version=None, model_name=None):
121
+ """Build DB name from configuration."""
122
+ if db_name:
123
+ return db_name
124
+ if db_simulation_model_version and model_name:
125
+ return f"{model_name}-{db_simulation_model_version.replace('.', '-')}"
126
+ if db_simulation_model_version or model_name:
127
+ return None
128
+ return None if (db_simulation_model_version or model_name) else self.db_name
129
+
95
130
  def _validate_mongo_db_config(self, mongo_db_config):
96
131
  """Validate the MongoDB configuration."""
97
132
  if mongo_db_config is None or all(value is None for value in mongo_db_config.values()):
@@ -140,8 +175,7 @@ class DatabaseHandler:
140
175
  """
141
176
  Find the latest released version of the simulation model and update the DB config.
142
177
 
143
- This is indicated by adding "LATEST" to the name of the simulation model database
144
- (field "db_simulation_model" in the database configuration dictionary).
178
+ This is indicated by "LATEST" to the simulation model data base version.
145
179
  Only released versions are considered, pre-releases are ignored.
146
180
 
147
181
  Raises
@@ -151,27 +185,27 @@ class DatabaseHandler:
151
185
 
152
186
  """
153
187
  try:
188
+ db_simulation_model_version = self.mongo_db_config["db_simulation_model_version"]
154
189
  db_simulation_model = self.mongo_db_config["db_simulation_model"]
155
- if not db_simulation_model.endswith("LATEST"):
190
+ if db_simulation_model_version != "LATEST":
156
191
  return
157
- except TypeError: # db_simulation_model is None
192
+ except TypeError: # db_simulation_model_version is None
158
193
  return
159
194
 
160
- prefix = db_simulation_model.replace("LATEST", "")
161
195
  list_of_db_names = self.db_client.list_database_names()
162
- filtered_list_of_db_names = [s for s in list_of_db_names if s.startswith(prefix)]
163
- versioned_strings = []
164
- version_pattern = re.compile(
165
- rf"{re.escape(prefix)}v?(\d+)-(\d+)-(\d+)(?:-([a-zA-Z0-9_.]+))?"
166
- )
196
+ filtered_list_of_db_names = [
197
+ s for s in list_of_db_names if s.startswith(db_simulation_model)
198
+ ]
199
+ pattern = re.compile(rf"{re.escape(db_simulation_model)}-v(\d+)-(\d+)-(\d+)(?:-(.+))?$")
167
200
 
201
+ versioned_strings = []
168
202
  for s in filtered_list_of_db_names:
169
- match = version_pattern.search(s)
170
- # A version is considered a pre-release if it contains a '-' character (re group 4)
171
- if match and match.group(4) is None:
172
- version_str = match.group(1) + "." + match.group(2) + "." + match.group(3)
173
- version = Version(version_str)
174
- versioned_strings.append((s, version))
203
+ m = pattern.match(s)
204
+ if m:
205
+ # skip pre-releases (have suffix)
206
+ if m.group(4) is None:
207
+ version_str = f"{m.group(1)}.{m.group(2)}.{m.group(3)}"
208
+ versioned_strings.append((s, Version(version_str)))
175
209
 
176
210
  if versioned_strings:
177
211
  latest_string, _ = max(versioned_strings, key=lambda x: x[1])
@@ -180,7 +214,7 @@ class DatabaseHandler:
180
214
  f"Updated the DB simulation model to the latest version {latest_string}"
181
215
  )
182
216
  else:
183
- raise ValueError("Found LATEST in the DB name but no matching versions found in DB.")
217
+ raise ValueError("LATEST requested but no released versions found in DB.")
184
218
 
185
219
  def generate_compound_indexes(self, db_name=None):
186
220
  """
@@ -242,6 +276,9 @@ class DatabaseHandler:
242
276
  raise ValueError(
243
277
  "Only one model version can be passed to get_model_parameter, not a list."
244
278
  )
279
+ model_version = resolve_version_to_latest_patch(
280
+ model_version, self.get_model_versions(collection_name)
281
+ )
245
282
  production_table = self.read_production_table_from_mongo_db(
246
283
  collection_name, model_version
247
284
  )
@@ -288,6 +325,9 @@ class DatabaseHandler:
288
325
  dict containing the parameters
289
326
  """
290
327
  pars = {}
328
+ model_version = resolve_version_to_latest_patch(
329
+ model_version, self.get_model_versions(collection)
330
+ )
291
331
  production_table = self.read_production_table_from_mongo_db(collection, model_version)
292
332
  array_element_list = self._get_array_element_list(
293
333
  array_element_name, site, production_table, collection
@@ -334,6 +374,11 @@ class DatabaseHandler:
334
374
  def _get_parameter_for_model_version(
335
375
  self, array_element, model_version, site, collection, production_table
336
376
  ):
377
+ """
378
+ Get parameters for a specific model version and array element.
379
+
380
+ Uses caching wherever possible.
381
+ """
337
382
  cache_key, cache_dict = self._read_cache(
338
383
  DatabaseHandler.model_parameters_cached,
339
384
  names.validate_site_name(site) if site else None,
@@ -562,6 +607,9 @@ class DatabaseHandler:
562
607
  ValueError
563
608
  if query returned no results.
564
609
  """
610
+ model_version = resolve_version_to_latest_patch(
611
+ model_version, self.get_model_versions(collection_name)
612
+ )
565
613
  try:
566
614
  return DatabaseHandler.production_table_cached[
567
615
  self._cache_key(None, None, model_version, collection_name)
@@ -585,7 +633,7 @@ class DatabaseHandler:
585
633
 
586
634
  def get_model_versions(self, collection_name="telescopes"):
587
635
  """
588
- Get list of model versions from the DB.
636
+ Get list of model versions from the DB with caching.
589
637
 
590
638
  Parameters
591
639
  ----------
@@ -597,10 +645,12 @@ class DatabaseHandler:
597
645
  list
598
646
  List of model versions
599
647
  """
600
- collection = self.get_collection("production_tables", db_name=self.db_name)
601
- return sorted(
602
- {post["model_version"] for post in collection.find({"collection": collection_name})}
603
- )
648
+ if collection_name not in DatabaseHandler.model_versions_cached:
649
+ collection = self.get_collection("production_tables", db_name=self.db_name)
650
+ DatabaseHandler.model_versions_cached[collection_name] = sorted(
651
+ {post["model_version"] for post in collection.find({"collection": collection_name})}
652
+ )
653
+ return DatabaseHandler.model_versions_cached[collection_name]
604
654
 
605
655
  def get_array_elements(self, model_version, collection="telescopes"):
606
656
  """
@@ -619,6 +669,9 @@ class DatabaseHandler:
619
669
  list
620
670
  Sorted list of all array elements found in collection
621
671
  """
672
+ model_version = resolve_version_to_latest_patch(
673
+ model_version, self.get_model_versions(collection)
674
+ )
622
675
  production_table = self.read_production_table_from_mongo_db(collection, model_version)
623
676
  return sorted([entry for entry in production_table["parameters"] if "-design" not in entry])
624
677
 
@@ -641,6 +694,9 @@ class DatabaseHandler:
641
694
  str
642
695
  Design model for a given array element.
643
696
  """
697
+ model_version = resolve_version_to_latest_patch(
698
+ model_version, self.get_model_versions(collection)
699
+ )
644
700
  production_table = self.read_production_table_from_mongo_db(collection, model_version)
645
701
  try:
646
702
  return production_table["design_model"][array_element_name]
@@ -669,6 +725,9 @@ class DatabaseHandler:
669
725
  list
670
726
  Sorted list of all array element names found in collection
671
727
  """
728
+ model_version = resolve_version_to_latest_patch(
729
+ model_version, self.get_model_versions(collection)
730
+ )
672
731
  production_table = self.read_production_table_from_mongo_db(collection, model_version)
673
732
  all_array_elements = production_table["parameters"]
674
733
  return sorted(
@@ -774,26 +833,57 @@ class DatabaseHandler:
774
833
  with open(Path(path).joinpath(file.filename), "wb") as output_file:
775
834
  fs_output.download_to_stream_by_name(file.filename, output_file)
776
835
 
777
- def add_production_table(self, db_name, production_table):
836
+ def get_ecsv_file_as_astropy_table(self, file_name, db_name=None):
778
837
  """
779
- Add a production table to the DB.
838
+ Read contents of an ECSV file from the database and return it as an Astropy Table.
839
+
840
+ Files are not written to disk.
780
841
 
781
842
  Parameters
782
843
  ----------
844
+ file_name: str
845
+ The name of the ECSV file.
783
846
  db_name: str
784
- the name of the DB.
847
+ The name of the database.
848
+
849
+ Returns
850
+ -------
851
+ astropy.table.Table
852
+ The contents of the ECSV file as an Astropy Table.
853
+ """
854
+ db = DatabaseHandler.db_client[db_name or self.db_name]
855
+ fs = gridfs.GridFSBucket(db)
856
+
857
+ buf = io.BytesIO()
858
+ try:
859
+ fs.download_to_stream_by_name(file_name, buf)
860
+ except gridfs.errors.NoFile as exc:
861
+ raise FileNotFoundError(f"ECSV file '{file_name}' not found in DB.") from exc
862
+ buf.seek(0)
863
+ return Table.read(buf.getvalue().decode("utf-8"), format="ascii.ecsv")
864
+
865
+ def add_production_table(self, production_table, db_name=None):
866
+ """
867
+ Add a production table to the DB.
868
+
869
+ Parameters
870
+ ----------
785
871
  production_table: dict
786
872
  The production table to add to the DB.
873
+ db_name: str
874
+ the name of the DB.
787
875
  """
876
+ db_name = db_name or self.db_name
788
877
  collection = self.get_collection("production_tables", db_name=db_name or self.db_name)
789
878
  self._logger.debug(f"Adding production for {production_table.get('collection')} to to DB")
790
879
  collection.insert_one(production_table)
791
880
  DatabaseHandler.production_table_cached.clear()
881
+ DatabaseHandler.model_versions_cached.clear()
792
882
 
793
883
  def add_new_parameter(
794
884
  self,
795
- db_name,
796
885
  par_dict,
886
+ db_name=None,
797
887
  collection_name="telescopes",
798
888
  file_prefix=None,
799
889
  ):
@@ -805,10 +895,10 @@ class DatabaseHandler:
805
895
 
806
896
  Parameters
807
897
  ----------
808
- db_name: str
809
- the name of the DB
810
898
  par_dict: dict
811
899
  dictionary with parameter data
900
+ db_name: str
901
+ the name of the DB
812
902
  collection_name: str
813
903
  The name of the collection to add a parameter to.
814
904
  file_prefix: str or Path
@@ -859,7 +949,7 @@ class DatabaseHandler:
859
949
  the name of the DB
860
950
  **kwargs (optional): keyword arguments for file creation.
861
951
  The full list of arguments can be found in, \
862
- https://docs.mongodb.com/manual/core/gridfs/#the-files-collection
952
+ https://www.mongodb.com/docs/manual/core/gridfs/
863
953
  mostly these are unnecessary though.
864
954
 
865
955
  Returns
@@ -944,6 +1034,7 @@ class DatabaseHandler:
944
1034
  def _reset_parameter_cache(self):
945
1035
  """Reset the cache for the parameters."""
946
1036
  DatabaseHandler.model_parameters_cached.clear()
1037
+ DatabaseHandler.model_versions_cached.clear()
947
1038
 
948
1039
  def _get_array_element_list(self, array_element_name, site, production_table, collection):
949
1040
  """
@@ -3,26 +3,26 @@
3
3
  import logging
4
4
  from pathlib import Path
5
5
 
6
+ from packaging.version import Version
7
+
6
8
  from simtools.io import ascii_handler
7
9
  from simtools.utils import names
8
10
 
9
11
  logger = logging.getLogger(__name__)
10
12
 
11
13
 
12
- def add_values_from_json_to_db(file, collection, db, db_name, file_prefix):
14
+ def add_values_from_json_to_db(file, collection, db, file_prefix):
13
15
  """
14
16
  Upload new model parameter from json files to db.
15
17
 
16
18
  Parameters
17
19
  ----------
18
20
  file : list
19
- Json file to be uploaded to the DB.
21
+ JSON file to be uploaded to the DB.
20
22
  collection : str
21
23
  The DB collection to which to add the file.
22
24
  db : DatabaseHandler
23
25
  Database handler object.
24
- db_name : str
25
- Name of the database to be created.
26
26
  file_prefix : str
27
27
  Path to location of all additional files to be uploaded.
28
28
  """
@@ -30,32 +30,30 @@ def add_values_from_json_to_db(file, collection, db, db_name, file_prefix):
30
30
  logger.debug(
31
31
  f"Adding the following parameter to the DB: {par_dict['parameter']} "
32
32
  f"version {par_dict['parameter_version']} "
33
- f"(collection {collection} in database {db_name})"
33
+ f"(collection {collection} in database {db.get_db_name()})"
34
34
  )
35
35
 
36
36
  db.add_new_parameter(
37
- db_name=db_name,
38
37
  par_dict=par_dict,
39
38
  collection_name=collection,
40
39
  file_prefix=file_prefix,
41
40
  )
42
41
 
43
42
 
44
- def add_model_parameters_to_db(args_dict, db):
43
+ def add_model_parameters_to_db(input_path, db):
45
44
  """
46
45
  Read model parameters from a directory and upload them to the database.
47
46
 
48
47
  Parameters
49
48
  ----------
50
- args_dict : dict
51
- Command line arguments.
49
+ input_path : Path, str
50
+ Path to the directory containing the model parameters.
52
51
  db : DatabaseHandler
53
52
  Database handler object.
54
53
  """
55
- input_path = Path(args_dict["input_path"])
54
+ input_path = Path(input_path)
56
55
  logger.info(f"Reading model parameters from repository path {input_path}")
57
- array_elements = [d for d in input_path.iterdir() if d.is_dir()]
58
- for element in array_elements:
56
+ for element in filter(Path.is_dir, input_path.iterdir()):
59
57
  collection = names.get_collection_name_from_array_element_name(element.name, False)
60
58
  if collection == "Files":
61
59
  logger.info("Files (tables) are uploaded with the corresponding model parameters")
@@ -67,12 +65,11 @@ def add_model_parameters_to_db(args_dict, db):
67
65
  file=file,
68
66
  collection=collection,
69
67
  db=db,
70
- db_name=args_dict["db_name"],
71
68
  file_prefix=input_path / "Files",
72
69
  )
73
70
 
74
71
 
75
- def add_production_tables_to_db(args_dict, db):
72
+ def add_production_tables_to_db(input_path, db):
76
73
  """
77
74
  Read production tables from a directory and upload them to the database.
78
75
 
@@ -81,29 +78,50 @@ def add_production_tables_to_db(args_dict, db):
81
78
 
82
79
  Parameters
83
80
  ----------
84
- args_dict : dict
85
- Command line arguments.
81
+ input_path : Path, str
82
+ Path to the directory containing the production tables.
86
83
  db : DatabaseHandler
87
84
  Database handler object.
88
85
  """
89
- input_path = Path(args_dict["input_path"])
86
+ input_path = Path(input_path)
90
87
  logger.info(f"Reading production tables from repository path {input_path}")
91
88
 
92
89
  for model in filter(Path.is_dir, input_path.iterdir()):
93
90
  logger.info(f"Reading production tables for model version {model.name}")
94
- model_dict = {}
95
- for file in sorted(model.rglob("*json")):
96
- _read_production_table(model_dict, file, model.name)
91
+ model_dict = _read_production_tables(model)
97
92
 
98
93
  for collection, data in model_dict.items():
99
- if not data["parameters"]:
94
+ if data["parameters"]:
95
+ logger.info(f"Adding production table for {collection} to the database")
96
+ db.add_production_table(production_table=data)
97
+ else:
100
98
  logger.info(f"No production table for {collection} in model version {model.name}")
101
- continue
102
- logger.info(f"Adding production table for {collection} to the database")
103
- db.add_production_table(
104
- db_name=args_dict["db_name"],
105
- production_table=data,
106
- )
99
+
100
+
101
+ def _read_production_tables(model_path):
102
+ """
103
+ Read production tables from a directory.
104
+
105
+ Take into account that some productions include patch updates only. Read in this cases
106
+ the base models first.
107
+
108
+ Parameters
109
+ ----------
110
+ model_path : Path
111
+ Path to the directory containing the production tables for a specific model version.
112
+ """
113
+ model_dict = {}
114
+ models = [model_path.name]
115
+ if (model_path / "info.yml").exists():
116
+ info = ascii_handler.collect_data_from_file(file_name=model_path / "info.yml")
117
+ models.extend(info.get("model_version_history", []))
118
+ # sort oldest --> newest
119
+ models = sorted(set(models), key=Version, reverse=False)
120
+ for model in models:
121
+ for file in sorted((model_path.parent / model).rglob("*json")):
122
+ _read_production_table(model_dict, file, model)
123
+
124
+ return model_dict
107
125
 
108
126
 
109
127
  def _read_production_table(model_dict, file, model_name):
@@ -125,9 +143,10 @@ def _read_production_table(model_dict, file, model_name):
125
143
  if array_element in ("configuration_corsika", "configuration_sim_telarray"):
126
144
  model_dict[collection]["parameters"] = parameter_dict["parameters"]
127
145
  else:
128
- model_dict[collection]["parameters"][array_element] = parameter_dict["parameters"][
129
- array_element
130
- ]
146
+ model_dict[collection]["parameters"].setdefault(array_element, {}).update(
147
+ parameter_dict["parameters"][array_element]
148
+ )
149
+
131
150
  except KeyError as exc:
132
151
  logger.error(f"KeyError: {exc}")
133
152
  raise
@@ -137,3 +156,5 @@ def _read_production_table(model_dict, file, model_name):
137
156
  ]
138
157
  except KeyError:
139
158
  pass
159
+
160
+ model_dict[collection]["model_version"] = model_name
simtools/dependencies.py CHANGED
@@ -40,7 +40,8 @@ def get_version_string(db_config=None, run_time=None):
40
40
 
41
41
  """
42
42
  return (
43
- f"Database version: {get_database_version(db_config)}\n"
43
+ f"Database name: {get_database_version_or_name(db_config, version=False)}\n"
44
+ f"Database version: {get_database_version_or_name(db_config, version=True)}\n"
44
45
  f"sim_telarray version: {get_sim_telarray_version(run_time)}\n"
45
46
  f"CORSIKA version: {get_corsika_version(run_time)}\n"
46
47
  f"Build options: {get_build_options(run_time)}\n"
@@ -48,25 +49,29 @@ def get_version_string(db_config=None, run_time=None):
48
49
  )
49
50
 
50
51
 
51
- def get_database_version(db_config):
52
+ def get_database_version_or_name(db_config, version=True):
52
53
  """
53
- Get the version of the simulation model data base used.
54
+ Get the version or name of the simulation model data base used.
54
55
 
55
56
  Parameters
56
57
  ----------
57
58
  db_config : dict
58
59
  Dictionary containing the database configuration.
60
+ version : bool
61
+ If True, return the version of the database. If False, return the name.
59
62
 
60
63
  Returns
61
64
  -------
62
65
  str
63
- Version of the simulation model data base used.
66
+ Version or name of the simulation model data base used.
64
67
 
65
68
  """
66
69
  if db_config is None:
67
70
  return None
68
71
  db = DatabaseHandler(db_config)
69
- return db.mongo_db_config.get("db_simulation_model")
72
+ return db.mongo_db_config.get(
73
+ "db_simulation_model_version" if version else "db_simulation_model"
74
+ )
70
75
 
71
76
 
72
77
  def get_sim_telarray_version(run_time):
@@ -282,11 +282,7 @@ def get_array_layouts_from_db(
282
282
  """
283
283
  layout_names = []
284
284
  if layout_name:
285
- layout_names.append(
286
- layout_name[0]
287
- if isinstance(layout_name, list) and len(layout_name) == 1
288
- else layout_name
289
- )
285
+ layout_names = gen.ensure_iterable(layout_name)
290
286
  else:
291
287
  site_model = SiteModel(site=site, model_version=model_version, mongo_db_config=db_config)
292
288
  layout_names = site_model.get_list_of_array_layouts()
@@ -394,3 +390,39 @@ def _get_array_layout_dict(
394
390
  coordinate_system=coordinate_system
395
391
  ),
396
392
  }
393
+
394
+
395
+ def get_array_elements_from_db_for_layouts(layouts, site, model_version, db_config):
396
+ """
397
+ Get list of array elements from the database for given list of layout names.
398
+
399
+ Structure of the returned dictionary::
400
+
401
+ {
402
+ "layout_name_1": [telescope_id_1, telescope_id_2, ...],
403
+ "layout_name_2": [telescope_id_3, telescope_id_4, ...],
404
+ ...
405
+ }
406
+
407
+ Parameters
408
+ ----------
409
+ layouts : list[str]
410
+ List of layout names to read. If "all", read all available layouts.
411
+ site : str
412
+ Site name for the array layouts.
413
+ model_version : str
414
+ Model version for the array layouts.
415
+ db_config : dict
416
+ Database configuration dictionary.
417
+
418
+ Returns
419
+ -------
420
+ dict
421
+ Dictionary mapping layout names to telescope IDs.
422
+ """
423
+ site_model = SiteModel(site=site, model_version=model_version, mongo_db_config=db_config)
424
+ layout_names = site_model.get_list_of_array_layouts() if layouts == ["all"] else layouts
425
+ layout_dict = {}
426
+ for layout_name in layout_names:
427
+ layout_dict[layout_name] = site_model.get_array_elements_for_layout(layout_name)
428
+ return layout_dict
@@ -272,7 +272,7 @@ class ArrayModel:
272
272
  config_file_path=self.config_file_path,
273
273
  telescope_model=self.telescope_model,
274
274
  site_model=self.site_model,
275
- sim_telarray_seeds=self.sim_telarray_seeds,
275
+ additional_metadata=self._get_additional_simtel_metadata(),
276
276
  )
277
277
  self._array_model_file_exported = True
278
278
 
@@ -484,3 +484,20 @@ class ArrayModel:
484
484
 
485
485
  table.sort("telescope_name")
486
486
  return table
487
+
488
+ def _get_additional_simtel_metadata(self):
489
+ """
490
+ Collect additional metadata to be included in sim_telarray output.
491
+
492
+ Returns
493
+ -------
494
+ dict
495
+ Dictionary with additional metadata.
496
+ """
497
+ metadata = {}
498
+ if self.sim_telarray_seeds is not None:
499
+ metadata.update(self.sim_telarray_seeds)
500
+
501
+ metadata["nsb_integrated_flux"] = self.site_model.get_nsb_integrated_flux()
502
+
503
+ return metadata