gammasimtools 0.25.0__py3-none-any.whl → 0.27.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 (138) hide show
  1. {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/METADATA +6 -1
  2. {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/RECORD +135 -130
  3. {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/WHEEL +1 -1
  4. {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/entry_points.txt +3 -2
  5. {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/licenses/LICENSE +1 -1
  6. simtools/_version.py +2 -2
  7. simtools/application_control.py +35 -7
  8. simtools/applications/convert_geo_coordinates_of_array_elements.py +3 -3
  9. simtools/applications/db_add_file_to_db.py +1 -1
  10. simtools/applications/db_add_simulation_model_from_repository_to_db.py +1 -1
  11. simtools/applications/db_add_value_from_json_to_db.py +1 -1
  12. simtools/applications/db_generate_compound_indexes.py +1 -1
  13. simtools/applications/db_get_array_layouts_from_db.py +3 -7
  14. simtools/applications/db_get_file_from_db.py +1 -1
  15. simtools/applications/db_get_parameter_from_db.py +1 -1
  16. simtools/applications/db_inspect_databases.py +1 -1
  17. simtools/applications/db_upload_model_repository.py +1 -1
  18. simtools/applications/derive_ctao_array_layouts.py +1 -2
  19. simtools/applications/{calculate_incident_angles.py → derive_incident_angle.py} +16 -18
  20. simtools/applications/derive_mirror_rnda.py +112 -180
  21. simtools/applications/derive_psf_parameters.py +0 -1
  22. simtools/applications/derive_pulse_shape_parameters.py +0 -1
  23. simtools/applications/derive_trigger_rates.py +1 -1
  24. simtools/applications/docs_produce_array_element_report.py +2 -8
  25. simtools/applications/docs_produce_calibration_reports.py +1 -3
  26. simtools/applications/docs_produce_model_parameter_reports.py +0 -2
  27. simtools/applications/docs_produce_simulation_configuration_report.py +1 -3
  28. simtools/applications/generate_array_config.py +0 -1
  29. simtools/applications/generate_corsika_histograms.py +79 -229
  30. simtools/applications/generate_regular_arrays.py +76 -69
  31. simtools/applications/generate_simtel_event_data.py +2 -2
  32. simtools/applications/maintain_simulation_model_add_production.py +2 -2
  33. simtools/applications/maintain_simulation_model_write_array_element_positions.py +87 -0
  34. simtools/applications/plot_array_layout.py +5 -111
  35. simtools/applications/plot_simulated_event_distributions.py +57 -0
  36. simtools/applications/plot_tabular_data.py +0 -1
  37. simtools/applications/plot_tabular_data_for_model_parameter.py +1 -6
  38. simtools/applications/production_derive_corsika_limits.py +1 -1
  39. simtools/applications/production_generate_grid.py +0 -1
  40. simtools/applications/run_application.py +1 -1
  41. simtools/applications/simulate_flasher.py +3 -15
  42. simtools/applications/simulate_illuminator.py +2 -11
  43. simtools/applications/simulate_pedestals.py +1 -5
  44. simtools/applications/simulate_prod.py +8 -11
  45. simtools/applications/simulate_prod_htcondor_generator.py +1 -1
  46. simtools/applications/submit_array_layouts.py +2 -4
  47. simtools/applications/submit_data_from_external.py +2 -1
  48. simtools/applications/submit_model_parameter_from_external.py +1 -3
  49. simtools/applications/validate_camera_efficiency.py +28 -28
  50. simtools/applications/validate_camera_fov.py +0 -1
  51. simtools/applications/validate_cumulative_psf.py +1 -5
  52. simtools/applications/validate_optics.py +2 -14
  53. simtools/atmosphere.py +83 -0
  54. simtools/camera/camera_efficiency.py +171 -53
  55. simtools/camera/single_photon_electron_spectrum.py +8 -7
  56. simtools/configuration/commandline_parser.py +82 -11
  57. simtools/configuration/configurator.py +6 -11
  58. simtools/constants.py +5 -0
  59. simtools/corsika/corsika_config.py +100 -202
  60. simtools/corsika/corsika_histograms.py +561 -1708
  61. simtools/corsika/primary_particle.py +1 -1
  62. simtools/data_model/metadata_collector.py +5 -2
  63. simtools/data_model/metadata_model.py +0 -4
  64. simtools/data_model/model_data_writer.py +59 -64
  65. simtools/data_model/schema.py +2 -0
  66. simtools/data_model/validate_data.py +1 -3
  67. simtools/db/db_handler.py +23 -10
  68. simtools/db/mongo_db.py +2 -2
  69. simtools/dependencies.py +81 -38
  70. simtools/io/ascii_handler.py +55 -5
  71. simtools/io/io_handler.py +23 -12
  72. simtools/io/table_handler.py +1 -1
  73. simtools/job_execution/job_manager.py +154 -79
  74. simtools/job_execution/process_pool.py +137 -0
  75. simtools/layout/array_layout.py +4 -13
  76. simtools/layout/array_layout_utils.py +348 -57
  77. simtools/model/array_model.py +23 -63
  78. simtools/model/calibration_model.py +4 -8
  79. simtools/model/legacy_model_parameter.py +134 -0
  80. simtools/model/model_parameter.py +147 -86
  81. simtools/model/model_utils.py +40 -6
  82. simtools/model/site_model.py +4 -8
  83. simtools/model/telescope_model.py +10 -16
  84. simtools/production_configuration/derive_corsika_limits.py +6 -11
  85. simtools/production_configuration/interpolation_handler.py +16 -16
  86. simtools/ray_tracing/incident_angles.py +92 -17
  87. simtools/ray_tracing/mirror_panel_psf.py +338 -222
  88. simtools/ray_tracing/psf_analysis.py +62 -48
  89. simtools/ray_tracing/psf_parameter_optimisation.py +3 -3
  90. simtools/ray_tracing/ray_tracing.py +43 -25
  91. simtools/reporting/docs_auto_report_generator.py +8 -13
  92. simtools/reporting/docs_read_parameters.py +2 -8
  93. simtools/runners/corsika_runner.py +52 -195
  94. simtools/runners/corsika_simtel_runner.py +77 -108
  95. simtools/runners/runner_services.py +214 -213
  96. simtools/runners/simtel_runner.py +27 -160
  97. simtools/runners/simtools_runner.py +11 -73
  98. simtools/schemas/application_workflow.metaschema.yml +8 -0
  99. simtools/settings.py +173 -0
  100. simtools/{io/eventio_handler.py → sim_events/file_info.py} +3 -3
  101. simtools/{simtel/simtel_io_event_histograms.py → sim_events/histograms.py} +25 -15
  102. simtools/{simtel/simtel_io_event_reader.py → sim_events/reader.py} +20 -17
  103. simtools/{simtel/simtel_io_event_writer.py → sim_events/writer.py} +84 -25
  104. simtools/simtel/pulse_shapes.py +7 -2
  105. simtools/simtel/simtel_config_writer.py +79 -91
  106. simtools/simtel/simtel_seeds.py +184 -0
  107. simtools/simtel/simtel_table_reader.py +6 -4
  108. simtools/simtel/simulator_array.py +114 -109
  109. simtools/simtel/simulator_camera_efficiency.py +68 -46
  110. simtools/simtel/simulator_light_emission.py +164 -132
  111. simtools/simtel/simulator_ray_tracing.py +80 -71
  112. simtools/simulator.py +137 -355
  113. simtools/telescope_trigger_rates.py +3 -4
  114. simtools/testing/assertions.py +84 -33
  115. simtools/testing/configuration.py +1 -2
  116. simtools/testing/helpers.py +2 -3
  117. simtools/testing/log_inspector.py +1 -0
  118. simtools/testing/sim_telarray_metadata.py +14 -12
  119. simtools/testing/validate_output.py +121 -42
  120. simtools/utils/general.py +43 -17
  121. simtools/utils/geometry.py +0 -77
  122. simtools/utils/names.py +5 -5
  123. simtools/utils/random.py +36 -0
  124. simtools/visualization/legend_handlers.py +7 -6
  125. simtools/visualization/plot_array_layout.py +91 -16
  126. simtools/visualization/plot_corsika_histograms.py +145 -605
  127. simtools/visualization/plot_incident_angles.py +48 -1
  128. simtools/visualization/plot_mirrors.py +1 -4
  129. simtools/visualization/plot_pixels.py +2 -4
  130. simtools/visualization/plot_psf.py +160 -19
  131. simtools/visualization/plot_simtel_event_histograms.py +4 -4
  132. simtools/visualization/plot_simtel_events.py +6 -11
  133. simtools/visualization/plot_tables.py +8 -19
  134. simtools/visualization/visualize.py +22 -2
  135. simtools/applications/db_development_tools/write_array_elements_positions_to_repository.py +0 -160
  136. simtools/applications/print_version.py +0 -53
  137. simtools/io/hdf5_handler.py +0 -139
  138. {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/top_level.txt +0 -0
@@ -185,7 +185,7 @@ class PrimaryParticle:
185
185
  "silicon": {"corsika7_id": 2814},
186
186
  "iron": {"corsika7_id": 5626},
187
187
  }
188
- for _, ids in particles.items():
188
+ for ids in particles.values():
189
189
  ids["pdg_id"] = Corsika7ID(ids["corsika7_id"]).to_pdgid().numerator
190
190
  ids["pdg_name"] = Particle.findall(pdgid=ids["pdg_id"])[0].name
191
191
 
@@ -360,6 +360,9 @@ class MetadataCollector:
360
360
  "Metadata extraction from sim_telarray files is not supported yet."
361
361
  )
362
362
  continue
363
+ elif Path(metadata_file).name.endswith((".corsika.zst", ".corsika")):
364
+ self._logger.warning("Metadata extraction from CORSIKA files is not supported yet.")
365
+ continue
363
366
  else:
364
367
  raise ValueError(f"Unknown metadata file format: {metadata_file}")
365
368
 
@@ -422,7 +425,7 @@ class MetadataCollector:
422
425
  product_dict["creation_time"] = gen.now_date_time_in_isoformat()
423
426
  product_dict["description"] = self.schema_dict.get("description", None)
424
427
 
425
- # DATA:CATEGORY
428
+ # metadata DATA:CATEGORY
426
429
  product_dict["data"]["category"] = "SIM"
427
430
  product_dict["data"]["level"] = "R1"
428
431
  product_dict["data"]["type"] = "Service"
@@ -431,7 +434,7 @@ class MetadataCollector:
431
434
  except KeyError:
432
435
  pass
433
436
 
434
- # DATA:MODEL
437
+ # metadata DATA:MODEL
435
438
  product_dict["data"]["model"]["name"] = (
436
439
  self.schema_dict.get("name")
437
440
  or self.args_dict.get("metadata_product_data_name")
@@ -8,13 +8,9 @@ Follows CTAO top-level data model definition.
8
8
 
9
9
  """
10
10
 
11
- import logging
12
-
13
11
  import simtools.data_model.schema
14
12
  import simtools.utils.general as gen
15
13
 
16
- _logger = logging.getLogger(__name__)
17
-
18
14
 
19
15
  def get_default_metadata_dict(
20
16
  schema_file=None, observatory="CTA", schema_version="latest", lower_case=True
@@ -7,6 +7,7 @@ import packaging.version
7
7
  from astropy.io.registry.base import IORegistryError
8
8
 
9
9
  import simtools.utils.general as gen
10
+ from simtools import settings
10
11
  from simtools.data_model import schema, validate_data
11
12
  from simtools.data_model.metadata_collector import MetadataCollector
12
13
  from simtools.db import db_handler
@@ -20,69 +21,62 @@ class ModelDataWriter:
20
21
 
21
22
  Parameters
22
23
  ----------
23
- product_data_file: str
24
+ output_file: str
24
25
  Name of output file.
25
- product_data_format: str
26
+ output_file_format: str
26
27
  Format of output file.
27
- args_dict: Dictionary
28
- Dictionary with configuration parameters.
29
28
  output_path: str or Path
30
29
  Path to output file.
31
- args_dict: dict
32
- Dictionary with configuration parameters.
33
-
34
30
  """
35
31
 
36
- def __init__(
37
- self,
38
- product_data_file=None,
39
- product_data_format=None,
40
- output_path=None,
41
- args_dict=None,
42
- ):
32
+ def __init__(self, output_file=None, output_file_format=None, output_path=None):
43
33
  """Initialize model data writer."""
44
34
  self._logger = logging.getLogger(__name__)
45
35
  self.io_handler = io_handler.IOHandler()
46
36
  self.schema_dict = {}
47
- if args_dict is not None:
48
- output_path = args_dict.get("output_path", output_path)
49
- if output_path is not None:
50
- self.io_handler.set_paths(output_path=output_path)
37
+ self.output_label = "model_data_writer"
38
+ self.io_handler.set_paths(
39
+ output_path=output_path or settings.config.args.get("output_path"),
40
+ output_path_label=self.output_label,
41
+ )
51
42
  try:
52
- self.product_data_file = self.io_handler.get_output_file(file_name=product_data_file)
43
+ self.output_file = self.io_handler.get_output_file(
44
+ file_name=output_file, output_path_label=self.output_label
45
+ )
53
46
  except TypeError:
54
- self.product_data_file = None
55
- self.product_data_format = self._astropy_data_format(product_data_format)
47
+ self.output_file = None
48
+ self.output_file_format = self._derive_data_format(output_file_format, self.output_file)
56
49
 
57
50
  @staticmethod
58
51
  def dump(
59
- args_dict, output_file=None, metadata=None, product_data=None, validate_schema_file=None
52
+ output_file=None,
53
+ metadata=None,
54
+ product_data=None,
55
+ output_file_format="ascii.ecsv",
56
+ validate_schema_file=None,
60
57
  ):
61
58
  """
62
59
  Write model data and metadata (as static method).
63
60
 
64
61
  Parameters
65
62
  ----------
66
- args_dict: dict
67
- Dictionary with configuration parameters (including output file name and path).
68
63
  output_file: string or Path
69
64
  Name of output file (args["output_file"] is used if this parameter is not set).
70
65
  metadata: MetadataCollector object
71
66
  Metadata to be written.
72
67
  product_data: astropy Table
73
68
  Model data to be written
69
+ output_file_format: str
70
+ Format of output file.
74
71
  validate_schema_file: str
75
72
  Schema file used in validation of output data.
76
-
77
73
  """
78
74
  writer = ModelDataWriter(
79
- product_data_file=(
80
- args_dict.get("output_file", None) if output_file is None else output_file
81
- ),
82
- product_data_format=args_dict.get("output_file_format", "ascii.ecsv"),
83
- args_dict=args_dict,
75
+ output_file=output_file,
76
+ output_file_format=output_file_format,
84
77
  )
85
- if validate_schema_file is not None and not args_dict.get("skip_output_validation", True):
78
+ skip_output_validation = settings.config.args.get("skip_output_validation", True)
79
+ if validate_schema_file is not None and not skip_output_validation:
86
80
  product_data = writer.validate_and_transform(
87
81
  product_data_table=product_data,
88
82
  validate_schema_file=validate_schema_file,
@@ -98,10 +92,10 @@ class ModelDataWriter:
98
92
  output_file,
99
93
  output_path=None,
100
94
  metadata_input_dict=None,
101
- db_config=None,
102
95
  unit=None,
103
96
  meta_parameter=False,
104
97
  model_parameter_schema_version=None,
98
+ check_db_for_existing_parameter=True,
105
99
  ):
106
100
  """
107
101
  Generate DB-style model parameter dict and write it to json file.
@@ -122,14 +116,14 @@ class ModelDataWriter:
122
116
  Path to output file.
123
117
  metadata_input_dict: dict
124
118
  Input to metadata collector.
125
- db_config: dict
126
- Database configuration. If not None, check if parameter with the same version exists.
127
119
  unit: str
128
120
  Unit of the parameter value (if applicable and value is not of type astropy Quantity).
129
121
  meta_parameter: bool
130
122
  Setting for meta parameter flag.
131
123
  model_parameter_schema_version: str, None
132
124
  Version of the model parameter schema (if None, use schema version from schema dict).
125
+ check_db_for_existing_parameter: bool
126
+ If True, check if parameter with same version exists in DB before writing.
133
127
 
134
128
  Returns
135
129
  -------
@@ -137,15 +131,12 @@ class ModelDataWriter:
137
131
  Validated parameter dictionary.
138
132
  """
139
133
  writer = ModelDataWriter(
140
- product_data_file=output_file,
141
- product_data_format="json",
142
- args_dict=None,
134
+ output_file=output_file,
135
+ output_file_format="json",
143
136
  output_path=output_path,
144
137
  )
145
- if db_config is not None:
146
- writer.check_db_for_existing_parameter(
147
- parameter_name, instrument, parameter_version, db_config
148
- )
138
+ if check_db_for_existing_parameter:
139
+ writer.check_db_for_existing_parameter(parameter_name, instrument, parameter_version)
149
140
 
150
141
  unique_id = None
151
142
  if metadata_input_dict is not None:
@@ -170,9 +161,7 @@ class ModelDataWriter:
170
161
  writer.write_dict_to_model_parameter_json(output_file, _json_dict)
171
162
  return _json_dict
172
163
 
173
- def check_db_for_existing_parameter(
174
- self, parameter_name, instrument, parameter_version, db_config
175
- ):
164
+ def check_db_for_existing_parameter(self, parameter_name, instrument, parameter_version):
176
165
  """
177
166
  Check if a parameter with the same version exists in the simulation model database.
178
167
 
@@ -184,15 +173,15 @@ class ModelDataWriter:
184
173
  Name of the instrument.
185
174
  parameter_version: str
186
175
  Version of the parameter.
187
- db_config: dict
188
- Database configuration.
189
176
 
190
177
  Raises
191
178
  ------
192
179
  ValueError
193
180
  If parameter with the same version exists in the database.
194
181
  """
195
- db = db_handler.DatabaseHandler(db_config=db_config)
182
+ db = db_handler.DatabaseHandler()
183
+ if not db.is_configured():
184
+ return
196
185
  try:
197
186
  db.get_model_parameter(
198
187
  parameter=parameter_name,
@@ -373,8 +362,10 @@ class ModelDataWriter:
373
362
  """
374
363
  try:
375
364
  unit_list = []
376
- for data in self.schema_dict["data"]:
377
- unit_list.append(data["unit"] if data["unit"] != "dimensionless" else None)
365
+ unit_list = [
366
+ data["unit"] if data["unit"] != "dimensionless" else None
367
+ for data in self.schema_dict["data"]
368
+ ]
378
369
  return unit_list if len(unit_list) > 1 else unit_list[0]
379
370
  except (KeyError, IndexError):
380
371
  pass
@@ -436,19 +427,17 @@ class ModelDataWriter:
436
427
  gen.change_dict_keys_case(metadata.get_top_level_metadata(), True)
437
428
  )
438
429
 
439
- self._logger.info(f"Writing data to {self.product_data_file}")
440
- if isinstance(product_data, dict) and Path(self.product_data_file).suffix == ".json":
441
- self.write_dict_to_model_parameter_json(self.product_data_file, product_data)
430
+ self._logger.info(f"Writing data to {self.output_file}")
431
+ if isinstance(product_data, dict) and Path(self.output_file).suffix == ".json":
432
+ self.write_dict_to_model_parameter_json(self.output_file, product_data)
442
433
  return
443
434
  try:
444
- product_data.write(
445
- self.product_data_file, format=self.product_data_format, overwrite=True
446
- )
435
+ product_data.write(self.output_file, format=self.output_file_format, overwrite=True)
447
436
  except IORegistryError:
448
- self._logger.error(f"Error writing model data to {self.product_data_file}.")
437
+ self._logger.error(f"Error writing model data to {self.output_file}.")
449
438
  raise
450
439
  if metadata is not None:
451
- metadata.write(self.product_data_file, add_activity_name=True)
440
+ metadata.write(self.output_file, add_activity_name=True)
452
441
 
453
442
  def write_dict_to_model_parameter_json(self, file_name, data_dict):
454
443
  """
@@ -467,10 +456,13 @@ class ModelDataWriter:
467
456
  if data writing was not successful.
468
457
  """
469
458
  data_dict = ModelDataWriter.prepare_data_dict_for_writing(data_dict)
470
- self._logger.info(f"Writing data to {self.io_handler.get_output_file(file_name)}")
459
+ output_file = self.io_handler.get_output_file(
460
+ file_name, output_path_label=self.output_label
461
+ )
462
+ self._logger.info(f"Writing data to {output_file}")
471
463
  ascii_handler.write_data_to_file(
472
464
  data=data_dict,
473
- output_file=self.io_handler.get_output_file(file_name),
465
+ output_file=output_file,
474
466
  sort_keys=True,
475
467
  numpy_types=True,
476
468
  )
@@ -510,9 +502,12 @@ class ModelDataWriter:
510
502
  return data_dict
511
503
 
512
504
  @staticmethod
513
- def _astropy_data_format(product_data_format):
505
+ def _derive_data_format(product_data_format, output_file=None):
514
506
  """
515
- Ensure conformance with astropy data format naming.
507
+ Derive data format and ensure conformance with astropy data format naming.
508
+
509
+ If product_data_format is None and output_file is given, derive format
510
+ from output_file suffix.
516
511
 
517
512
  Parameters
518
513
  ----------
@@ -520,6 +515,6 @@ class ModelDataWriter:
520
515
  format identifier
521
516
 
522
517
  """
523
- if product_data_format == "ecsv":
524
- product_data_format = "ascii.ecsv"
525
- return product_data_format
518
+ if product_data_format is None and output_file is not None:
519
+ product_data_format = Path(output_file).suffix.lstrip(".")
520
+ return "ascii.ecsv" if product_data_format == "ecsv" else product_data_format
@@ -1,6 +1,7 @@
1
1
  """Module providing functionality to read and validate dictionaries using schema."""
2
2
 
3
3
  import logging
4
+ from functools import lru_cache
4
5
  from pathlib import Path
5
6
 
6
7
  import jsonschema
@@ -153,6 +154,7 @@ def _validate_meta_schema_url(data):
153
154
  raise FileNotFoundError(f"Meta schema URL does not exist: {data['meta_schema_url']}")
154
155
 
155
156
 
157
+ @lru_cache
156
158
  def _retrieve_yaml_schema_from_uri(uri):
157
159
  """Load schema from a file URI."""
158
160
  path = SCHEMA_PATH / Path(uri.removeprefix("file:/"))
@@ -928,9 +928,7 @@ class DataValidator:
928
928
  """Return value as sorted list."""
929
929
  return [value] if isinstance(value, str) else sorted(value)
930
930
 
931
- instrument_sites = []
932
- for inst in instruments:
933
- instrument_sites.append(names.get_site_from_array_element_name(inst))
931
+ instrument_sites = [names.get_site_from_array_element_name(inst) for inst in instruments]
934
932
  # names.get_site_from_array_element_name might return a list
935
933
  flat_sites = [
936
934
  s
simtools/db/db_handler.py CHANGED
@@ -4,6 +4,7 @@ import logging
4
4
  from collections import defaultdict
5
5
  from pathlib import Path
6
6
 
7
+ from simtools import settings
7
8
  from simtools.data_model import validate_data
8
9
  from simtools.db.mongo_db import MongoDBHandler
9
10
  from simtools.io import io_handler
@@ -20,11 +21,6 @@ class DatabaseHandler:
20
21
 
21
22
  - db_simulation_model_version (from db_config): version of the simulation model database
22
23
  - model_version (from production_tables): version of the model contained in the database
23
-
24
- Parameters
25
- ----------
26
- db_config: dict
27
- Dictionary with the DB configuration.
28
24
  """
29
25
 
30
26
  ALLOWED_FILE_EXTENSIONS = [".dat", ".txt", ".lis", ".cfg", ".yml", ".yaml", ".ecsv"]
@@ -33,13 +29,17 @@ class DatabaseHandler:
33
29
  model_parameters_cached = {}
34
30
  model_versions_cached = {}
35
31
 
36
- def __init__(self, db_config=None):
32
+ def __init__(self):
37
33
  """Initialize the DatabaseHandler class."""
38
34
  self._logger = logging.getLogger(__name__)
39
35
 
40
- self.db_config = MongoDBHandler.validate_db_config(db_config)
36
+ self.db_config = (
37
+ MongoDBHandler.validate_db_config(dict(settings.config.db_config))
38
+ if settings.config.db_config
39
+ else None
40
+ )
41
41
  self.io_handler = io_handler.IOHandler()
42
- self.mongo_db_handler = MongoDBHandler(db_config) if self.db_config else None
42
+ self.mongo_db_handler = MongoDBHandler(self.db_config) if self.db_config else None
43
43
 
44
44
  self.db_name = (
45
45
  MongoDBHandler.get_db_name(
@@ -50,6 +50,17 @@ class DatabaseHandler:
50
50
  else None
51
51
  )
52
52
 
53
+ def is_configured(self):
54
+ """
55
+ Check if the DatabaseHandler is configured.
56
+
57
+ Returns
58
+ -------
59
+ bool
60
+ True if the DatabaseHandler is configured, False otherwise.
61
+ """
62
+ return self.mongo_db_handler is not None
63
+
53
64
  def get_db_name(self, db_name=None, db_simulation_model_version=None, model_name=None):
54
65
  """Build DB name from configuration."""
55
66
  if db_name:
@@ -249,9 +260,7 @@ class DatabaseHandler:
249
260
  collection,
250
261
  )
251
262
  if cache_dict:
252
- self._logger.debug(f"Found {array_element} in cache (key: {cache_key})")
253
263
  return cache_dict
254
- self._logger.debug(f"Did not find {array_element} in cache (key: {cache_key})")
255
264
 
256
265
  try:
257
266
  parameter_version_table = production_table["parameters"][array_element]
@@ -862,6 +871,10 @@ class DatabaseHandler:
862
871
  array_element_name,
863
872
  ]
864
873
  except KeyError as exc:
874
+ # simplified model definitions when e.g. adding new telescopes without design model
875
+ if settings.config.args.get("ignore_missing_design_model", False):
876
+ element_type = names.get_array_element_type_from_name(array_element_name)
877
+ return [array_element_name, f"{element_type}-01", f"{element_type}-design"]
865
878
  raise KeyError(
866
879
  f"Failed generated array element list for db query for {array_element_name}"
867
880
  ) from exc
simtools/db/mongo_db.py CHANGED
@@ -4,7 +4,7 @@ import io
4
4
  import logging
5
5
  import re
6
6
  from pathlib import Path
7
- from threading import Lock
7
+ from threading import Lock as _Lock
8
8
 
9
9
  import gridfs
10
10
  import jsonschema
@@ -126,7 +126,7 @@ class MongoDBHandler: # pylint: disable=unsubscriptable-object
126
126
  """
127
127
 
128
128
  db_client: MongoClient = None
129
- _lock = Lock()
129
+ _lock = _Lock()
130
130
  _logger = logging.getLogger(__name__)
131
131
 
132
132
  def __init__(self, db_config=None):
simtools/dependencies.py CHANGED
@@ -9,27 +9,26 @@ This modules provides two main functionalities:
9
9
  """
10
10
 
11
11
  import logging
12
- import os
13
12
  import re
14
13
  import subprocess
15
14
  from pathlib import Path
16
15
 
17
16
  import yaml
18
17
 
18
+ from simtools import settings
19
19
  from simtools.io import ascii_handler
20
+ from simtools.utils import general as gen
20
21
  from simtools.version import __version__
21
22
 
22
23
  _logger = logging.getLogger(__name__)
23
24
 
24
25
 
25
- def get_version_string(db_config=None, run_time=None):
26
+ def get_version_string(run_time=None):
26
27
  """
27
28
  Print the versions of the dependencies.
28
29
 
29
30
  Parameters
30
31
  ----------
31
- db_config : dict, optional
32
- Database configuration dictionary.
33
32
  run_time : list, optional
34
33
  Runtime environment command (e.g., Docker).
35
34
 
@@ -40,10 +39,13 @@ def get_version_string(db_config=None, run_time=None):
40
39
 
41
40
  """
42
41
  return (
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"
42
+ f"Database name: {get_database_version_or_name(version=False)}\n"
43
+ f"Database version: {get_database_version_or_name(version=True)}\n"
45
44
  f"sim_telarray version: {get_sim_telarray_version(run_time)}\n"
45
+ "sim_telarray exe: "
46
+ f"{settings.config.sim_telarray_exe if settings.config.sim_telarray_exe else 'None'}\n"
46
47
  f"CORSIKA version: {get_corsika_version(run_time)}\n"
48
+ f"CORSIKA exe: {settings.config.corsika_exe if settings.config.corsika_exe else 'None'}\n"
47
49
  f"Build options: {get_build_options(run_time)}\n"
48
50
  f"Runtime environment: {run_time if run_time else 'None'}\n"
49
51
  )
@@ -73,14 +75,12 @@ def get_software_version(software):
73
75
  raise ValueError(f"Unknown software: {software}") from exc
74
76
 
75
77
 
76
- def get_database_version_or_name(db_config, version=True):
78
+ def get_database_version_or_name(version=True):
77
79
  """
78
80
  Get the version or name of the simulation model data base used.
79
81
 
80
82
  Parameters
81
83
  ----------
82
- db_config : dict
83
- Dictionary containing the database configuration.
84
84
  version : bool
85
85
  If True, return the version of the database. If False, return the name.
86
86
 
@@ -91,8 +91,10 @@ def get_database_version_or_name(db_config, version=True):
91
91
 
92
92
  """
93
93
  if version:
94
- return db_config and db_config.get("db_simulation_model_version")
95
- return db_config and db_config.get("db_simulation_model")
94
+ return settings.config.db_config and settings.config.db_config.get(
95
+ "db_simulation_model_version"
96
+ )
97
+ return settings.config.db_config and settings.config.db_config.get("db_simulation_model")
96
98
 
97
99
 
98
100
  def get_sim_telarray_version(run_time=None):
@@ -111,16 +113,13 @@ def get_sim_telarray_version(run_time=None):
111
113
  str
112
114
  Version of the sim_telarray package.
113
115
  """
114
- sim_telarray_path = os.getenv("SIMTOOLS_SIMTEL_PATH")
115
- if sim_telarray_path is None:
116
- _logger.warning("Environment variable SIMTOOLS_SIMTEL_PATH is not set.")
116
+ if settings.config.sim_telarray_exe is None:
117
+ _logger.warning("sim_telarray environment not configured.")
117
118
  return None
118
- sim_telarray_path = Path(sim_telarray_path) / "sim_telarray" / "bin" / "sim_telarray"
119
-
120
119
  if run_time is None:
121
- command = [str(sim_telarray_path), "--version"]
120
+ command = [str(settings.config.sim_telarray_exe), "--version"]
122
121
  else:
123
- command = [*run_time, str(sim_telarray_path), "--version"]
122
+ command = [*run_time, str(settings.config.sim_telarray_exe), "--version"]
124
123
 
125
124
  _logger.debug(f"Running command: {command}")
126
125
  result = subprocess.run(command, capture_output=True, text=True, check=False)
@@ -151,19 +150,15 @@ def get_corsika_version(run_time=None):
151
150
  str
152
151
  Version of the CORSIKA package.
153
152
  """
154
- corsika_path = os.getenv("SIMTOOLS_SIMTEL_PATH")
155
- if corsika_path is None:
156
- _logger.warning("Environment variable SIMTOOLS_SIMTEL_PATH is not set.")
153
+ if settings.config.corsika_exe is None:
154
+ _logger.warning("CORSIKA environment not configured.")
157
155
  return None
158
- corsika_command = Path(corsika_path) / "corsika-run" / "corsika"
159
156
 
160
157
  if run_time is None:
161
- command = [str(corsika_command)]
158
+ command = [str(settings.config.corsika_exe)]
162
159
  else:
163
- command = [*run_time, str(corsika_command)]
160
+ command = [*run_time, str(settings.config.corsika_exe)]
164
161
 
165
- # Below I do not use the standard context manager because
166
- # it makes mocking in the tests significantly more difficult
167
162
  process = subprocess.Popen( # pylint: disable=consider-using-with
168
163
  command,
169
164
  stdout=subprocess.PIPE,
@@ -197,9 +192,14 @@ def get_corsika_version(run_time=None):
197
192
 
198
193
  def get_build_options(run_time=None):
199
194
  """
200
- Return CORSIKA / sim_telarray build options.
195
+ Return CORSIKA / sim_telarray config and build options.
196
+
197
+ For CORSIKA / sim_telarray build for simtools version >0.25.0:
198
+ expects build_opts.yml file in each CORSIKA and sim_telarray
199
+ directories.
201
200
 
202
- Expects a build_opts.yml file in the sim_telarray directory.
201
+ For CORSIKA / sim_telarray build for simtools version <=0.25.0:
202
+ expects a build_opts.yml file in the sim_telarray directory.
203
203
 
204
204
  Parameters
205
205
  ----------
@@ -209,28 +209,71 @@ def get_build_options(run_time=None):
209
209
  Returns
210
210
  -------
211
211
  dict
212
- Build options from build_opts.yml file.
212
+ CORSIKA / sim_telarray build options.
213
213
  """
214
- sim_telarray_path = os.getenv("SIMTOOLS_SIMTEL_PATH")
215
- if sim_telarray_path is None:
216
- raise ValueError("SIMTOOLS_SIMTEL_PATH not defined.")
217
-
218
- build_opts_path = Path(sim_telarray_path) / "build_opts.yml"
219
-
214
+ build_opts = {}
215
+ for package in ["corsika", "sim_telarray"]:
216
+ path = _get_package_path(package)
217
+ if not path:
218
+ continue
219
+ try:
220
+ build_opts.update(_get_build_options_from_file(path / "build_opts.yml", run_time))
221
+ except (FileNotFoundError, TypeError, ValueError):
222
+ # legacy fallback only for sim_telarray
223
+ if package == "sim_telarray":
224
+ try:
225
+ legacy_path = path.parent / "build_opts.yml"
226
+ build_opts.update(_get_build_options_from_file(legacy_path, run_time))
227
+ except (FileNotFoundError, TypeError, ValueError):
228
+ _logger.debug(f"No build options found for {package}.")
229
+ if not build_opts:
230
+ raise FileNotFoundError("No build option file found.")
231
+
232
+ return build_opts
233
+
234
+
235
+ def _get_package_path(package):
236
+ """Get the package path from settings or environment variables."""
237
+ path = getattr(settings.config, f"{package}_path")
238
+ if path is None:
239
+ path = gen.load_environment_variables().get(f"{package}_path")
240
+ return Path(path) if path else None
241
+
242
+
243
+ def _get_build_options_from_file(build_opts_path, run_time=None):
244
+ """Read build options from file."""
220
245
  if run_time is None:
221
246
  try:
222
247
  return ascii_handler.collect_data_from_file(build_opts_path)
223
248
  except FileNotFoundError as exc:
224
- raise FileNotFoundError("No build_opts.yml file found.") from exc
249
+ raise FileNotFoundError("No build option file found.") from exc
225
250
 
226
251
  command = [*run_time, "cat", str(build_opts_path)]
227
- _logger.debug(f"Reading build_opts.yml with command: {command}")
252
+ _logger.debug(f"Reading build option with command: {command}")
228
253
 
229
254
  result = subprocess.run(command, capture_output=True, text=True, check=False)
230
255
  if result.returncode:
231
- raise FileNotFoundError(f"No build_opts.yml file found in container: {result.stderr}")
256
+ raise FileNotFoundError(f"No build option file found in container: {result.stderr}")
232
257
 
233
258
  try:
234
259
  return yaml.safe_load(result.stdout)
235
260
  except yaml.YAMLError as exc:
236
261
  raise ValueError(f"Error parsing build_opts.yml from container: {exc}") from exc
262
+
263
+
264
+ def export_build_info(output_file, run_time=None):
265
+ """
266
+ Export build and version information to a file.
267
+
268
+ Parameters
269
+ ----------
270
+ output_file : str
271
+ Path to the output file.
272
+ run_time : list, optional
273
+ Runtime environment command (e.g., Docker).
274
+ """
275
+ build_info = get_build_options(run_time)
276
+ build_info["simtools"] = __version__
277
+ build_info["database_name"] = get_database_version_or_name(version=False)
278
+ build_info["database_version"] = get_database_version_or_name(version=True)
279
+ ascii_handler.write_data_to_file(data=build_info, output_file=Path(output_file))