gammasimtools 0.24.0__py3-none-any.whl → 0.26.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.24.0.dist-info → gammasimtools-0.26.0.dist-info}/METADATA +2 -1
  2. {gammasimtools-0.24.0.dist-info → gammasimtools-0.26.0.dist-info}/RECORD +134 -130
  3. {gammasimtools-0.24.0.dist-info → gammasimtools-0.26.0.dist-info}/entry_points.txt +3 -1
  4. {gammasimtools-0.24.0.dist-info → gammasimtools-0.26.0.dist-info}/licenses/LICENSE +1 -1
  5. simtools/_version.py +2 -2
  6. simtools/application_control.py +78 -0
  7. simtools/applications/calculate_incident_angles.py +0 -2
  8. simtools/applications/convert_geo_coordinates_of_array_elements.py +1 -2
  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 +2 -6
  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/derive_mirror_rnda.py +1 -3
  20. simtools/applications/derive_psf_parameters.py +5 -1
  21. simtools/applications/derive_pulse_shape_parameters.py +194 -0
  22. simtools/applications/derive_trigger_rates.py +1 -1
  23. simtools/applications/docs_produce_array_element_report.py +2 -8
  24. simtools/applications/docs_produce_calibration_reports.py +1 -3
  25. simtools/applications/docs_produce_model_parameter_reports.py +0 -2
  26. simtools/applications/docs_produce_simulation_configuration_report.py +1 -3
  27. simtools/applications/generate_array_config.py +0 -1
  28. simtools/applications/generate_corsika_histograms.py +48 -235
  29. simtools/applications/generate_regular_arrays.py +5 -35
  30. simtools/applications/generate_simtel_event_data.py +2 -2
  31. simtools/applications/maintain_simulation_model_add_production.py +2 -2
  32. simtools/applications/maintain_simulation_model_write_array_element_positions.py +87 -0
  33. simtools/applications/plot_array_layout.py +64 -108
  34. simtools/applications/plot_simulated_event_distributions.py +57 -0
  35. simtools/applications/plot_tabular_data.py +0 -1
  36. simtools/applications/plot_tabular_data_for_model_parameter.py +1 -6
  37. simtools/applications/production_derive_corsika_limits.py +1 -1
  38. simtools/applications/production_generate_grid.py +0 -1
  39. simtools/applications/run_application.py +1 -1
  40. simtools/applications/simulate_flasher.py +3 -4
  41. simtools/applications/simulate_illuminator.py +0 -1
  42. simtools/applications/simulate_pedestals.py +2 -6
  43. simtools/applications/simulate_prod.py +9 -28
  44. simtools/applications/simulate_prod_htcondor_generator.py +8 -1
  45. simtools/applications/submit_array_layouts.py +7 -7
  46. simtools/applications/submit_model_parameter_from_external.py +1 -3
  47. simtools/applications/validate_camera_efficiency.py +0 -1
  48. simtools/applications/validate_camera_fov.py +0 -1
  49. simtools/applications/validate_cumulative_psf.py +0 -2
  50. simtools/applications/validate_file_using_schema.py +49 -123
  51. simtools/applications/validate_optics.py +0 -13
  52. simtools/camera/camera_efficiency.py +1 -6
  53. simtools/camera/single_photon_electron_spectrum.py +2 -1
  54. simtools/configuration/commandline_parser.py +43 -8
  55. simtools/configuration/configurator.py +6 -11
  56. simtools/corsika/corsika_config.py +204 -99
  57. simtools/corsika/corsika_histograms.py +411 -1735
  58. simtools/corsika/primary_particle.py +1 -1
  59. simtools/data_model/metadata_collector.py +5 -2
  60. simtools/data_model/metadata_model.py +0 -4
  61. simtools/data_model/model_data_writer.py +27 -17
  62. simtools/data_model/schema.py +112 -5
  63. simtools/data_model/validate_data.py +80 -48
  64. simtools/db/db_handler.py +19 -8
  65. simtools/db/db_model_upload.py +2 -1
  66. simtools/db/mongo_db.py +133 -42
  67. simtools/dependencies.py +83 -44
  68. simtools/io/ascii_handler.py +4 -2
  69. simtools/io/table_handler.py +1 -1
  70. simtools/job_execution/htcondor_script_generator.py +0 -2
  71. simtools/layout/array_layout.py +4 -12
  72. simtools/layout/array_layout_utils.py +227 -58
  73. simtools/model/array_model.py +37 -18
  74. simtools/model/calibration_model.py +0 -4
  75. simtools/model/legacy_model_parameter.py +134 -0
  76. simtools/model/model_parameter.py +24 -14
  77. simtools/model/model_repository.py +18 -5
  78. simtools/model/model_utils.py +1 -6
  79. simtools/model/site_model.py +0 -4
  80. simtools/model/telescope_model.py +6 -11
  81. simtools/production_configuration/derive_corsika_limits.py +6 -11
  82. simtools/production_configuration/interpolation_handler.py +16 -16
  83. simtools/ray_tracing/incident_angles.py +5 -11
  84. simtools/ray_tracing/mirror_panel_psf.py +3 -7
  85. simtools/ray_tracing/psf_analysis.py +29 -27
  86. simtools/ray_tracing/psf_parameter_optimisation.py +822 -680
  87. simtools/ray_tracing/ray_tracing.py +6 -15
  88. simtools/reporting/docs_auto_report_generator.py +8 -13
  89. simtools/reporting/docs_read_parameters.py +70 -16
  90. simtools/runners/corsika_runner.py +15 -10
  91. simtools/runners/corsika_simtel_runner.py +9 -8
  92. simtools/runners/runner_services.py +17 -7
  93. simtools/runners/simtel_runner.py +11 -58
  94. simtools/runners/simtools_runner.py +2 -4
  95. simtools/schemas/model_parameters/flasher_pulse_exp_decay.schema.yml +2 -0
  96. simtools/schemas/model_parameters/flasher_pulse_shape.schema.yml +50 -0
  97. simtools/schemas/model_parameters/flasher_pulse_width.schema.yml +2 -0
  98. simtools/schemas/simulation_models_info.schema.yml +2 -0
  99. simtools/settings.py +154 -0
  100. simtools/sim_events/file_info.py +128 -0
  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 +273 -0
  105. simtools/simtel/simtel_config_writer.py +146 -22
  106. simtools/simtel/simtel_table_reader.py +6 -4
  107. simtools/simtel/simulator_array.py +62 -23
  108. simtools/simtel/simulator_camera_efficiency.py +4 -6
  109. simtools/simtel/simulator_light_emission.py +101 -19
  110. simtools/simtel/simulator_ray_tracing.py +4 -10
  111. simtools/simulator.py +360 -353
  112. simtools/telescope_trigger_rates.py +3 -4
  113. simtools/testing/assertions.py +115 -8
  114. simtools/testing/configuration.py +2 -3
  115. simtools/testing/helpers.py +2 -3
  116. simtools/testing/log_inspector.py +5 -1
  117. simtools/testing/sim_telarray_metadata.py +1 -1
  118. simtools/testing/validate_output.py +69 -23
  119. simtools/utils/general.py +37 -0
  120. simtools/utils/geometry.py +0 -77
  121. simtools/utils/names.py +7 -9
  122. simtools/version.py +37 -0
  123. simtools/visualization/legend_handlers.py +21 -10
  124. simtools/visualization/plot_array_layout.py +312 -41
  125. simtools/visualization/plot_corsika_histograms.py +143 -605
  126. simtools/visualization/plot_mirrors.py +834 -0
  127. simtools/visualization/plot_pixels.py +2 -4
  128. simtools/visualization/plot_psf.py +0 -1
  129. simtools/visualization/plot_simtel_event_histograms.py +4 -4
  130. simtools/visualization/plot_simtel_events.py +6 -11
  131. simtools/visualization/plot_tables.py +8 -19
  132. simtools/visualization/visualize.py +22 -2
  133. simtools/applications/db_development_tools/write_array_elements_positions_to_repository.py +0 -160
  134. simtools/applications/print_version.py +0 -53
  135. simtools/io/hdf5_handler.py +0 -139
  136. simtools/simtel/simtel_io_file_info.py +0 -62
  137. {gammasimtools-0.24.0.dist-info → gammasimtools-0.26.0.dist-info}/WHEEL +0 -0
  138. {gammasimtools-0.24.0.dist-info → gammasimtools-0.26.0.dist-info}/top_level.txt +0 -0
simtools/db/mongo_db.py CHANGED
@@ -10,7 +10,7 @@ import gridfs
10
10
  import jsonschema
11
11
  from astropy.table import Table
12
12
  from bson.objectid import ObjectId
13
- from pymongo import MongoClient
13
+ from pymongo import MongoClient, monitoring
14
14
 
15
15
  from simtools.io import ascii_handler
16
16
 
@@ -57,6 +57,61 @@ jsonschema_db_dict = {
57
57
  }
58
58
 
59
59
 
60
+ class IdleConnectionMonitor(monitoring.ConnectionPoolListener):
61
+ """
62
+ A listener to track MongoDB connection pool activity.
63
+
64
+ Used to monitor idle connections and log connection events.
65
+ Switched on in debug mode.
66
+ """
67
+
68
+ def __init__(self):
69
+ self._logger = logging.getLogger("IdleConnectionMonitor")
70
+ self.open_connections = 0
71
+
72
+ def connection_created(self, event):
73
+ """Handle connection creation event."""
74
+ self.open_connections += 1
75
+ self._logger.debug(
76
+ f"MongoDB connection Created: {event.address}. Total in Pool: {self.open_connections}"
77
+ )
78
+
79
+ def connection_closed(self, event):
80
+ """Handle connection closure event."""
81
+ self.open_connections -= 1
82
+ self._logger.debug(
83
+ f"MongoDB connection Closed: {event.address}. Reason: {event.reason}. "
84
+ f"Total in Pool: {self.open_connections}"
85
+ )
86
+
87
+ def connection_check_out_started(self, event):
88
+ """Handle connection check out started event."""
89
+
90
+ def connection_check_out_failed(self, event):
91
+ """Handle connection check out failure event."""
92
+
93
+ def connection_checked_out(self, event):
94
+ """Handle connection checked out event."""
95
+
96
+ def connection_checked_in(self, event):
97
+ """Handle connection checked in event."""
98
+
99
+ def connection_ready(self, event):
100
+ """Handle connection ready event."""
101
+
102
+ def pool_created(self, event):
103
+ """Handle connection pool creation event."""
104
+
105
+ def pool_ready(self, event):
106
+ """Handle connection pool ready event."""
107
+
108
+ def pool_cleared(self, event):
109
+ """Handle connection pool cleared event."""
110
+
111
+ def pool_closed(self, event):
112
+ """Handle connection pool closure event."""
113
+
114
+
60
115
  class MongoDBHandler: # pylint: disable=unsubscriptable-object
61
116
  """
62
117
  MongoDBHandler provides low-level interface to MongoDB operations.
@@ -70,19 +125,90 @@ class MongoDBHandler: # pylint: disable=unsubscriptable-object
70
125
  Dictionary with the MongoDB configuration (see jsonschema_db_dict for details).
71
126
  """
72
127
 
73
- db_client: MongoClient | None = None
128
+ db_client: MongoClient = None
74
129
  _lock = Lock()
130
+ _logger = logging.getLogger(__name__)
75
131
 
76
132
  def __init__(self, db_config=None):
77
133
  """Initialize the MongoDBHandler class."""
78
- self._logger = logging.getLogger(__name__)
79
134
  self.db_config = MongoDBHandler.validate_db_config(db_config)
80
135
  self.list_of_collections = {}
81
136
 
82
- if self.db_config and MongoDBHandler.db_client is None:
83
- with MongoDBHandler._lock:
84
- if MongoDBHandler.db_client is None:
85
- MongoDBHandler.db_client = self._open_db()
137
+ if self.db_config:
138
+ self._initialize_client(self.db_config)
139
+
140
+ @classmethod
141
+ def _initialize_client(cls, db_config):
142
+ """
143
+ Initialize the MongoDB client in a thread-safe manner.
144
+
145
+ Only initializes if it hasn't been done yet. Uses double-checked locking
146
+ to ensure thread safety.
147
+
148
+ Parameters
149
+ ----------
150
+ db_config: dict
151
+ Dictionary with the MongoDB configuration.
152
+ """
153
+ if cls.db_client is not None:
154
+ return
155
+ with cls._lock:
156
+ if cls.db_client is None:
157
+ try:
158
+ uri = cls._build_uri(db_config)
159
+ client_kwargs = {"maxIdleTimeMS": 10000}
160
+
161
+ if cls._logger.isEnabledFor(logging.DEBUG):
162
+ client_kwargs["event_listeners"] = [IdleConnectionMonitor()]
163
+
164
+ cls.db_client = MongoClient(uri, **client_kwargs)
165
+ cls._logger.debug("MongoDB client initialized successfully.")
166
+ except Exception as e:
167
+ cls._logger.error(f"Failed to initialize MongoDB client: {e}")
168
+ raise
169
+
170
+ @staticmethod
171
+ def _build_uri(db_config):
172
+ """
173
+ Build MongoDB URI from configuration.
174
+
175
+ Parameters
176
+ ----------
177
+ db_config: dict
178
+ Dictionary with the MongoDB configuration.
179
+
180
+ Returns
181
+ -------
182
+ str
183
+ MongoDB connection URI.
184
+ """
185
+ direct_connection = db_config["db_server"] in (
186
+ "localhost",
187
+ "simtools-mongodb",
188
+ "mongodb",
189
+ )
190
+ auth_source = (
191
+ db_config.get("db_api_authentication_database")
192
+ if db_config.get("db_api_authentication_database")
193
+ else "admin"
194
+ )
195
+
196
+ username = db_config["db_api_user"]
197
+ password = db_config["db_api_pw"]
198
+ server = db_config["db_server"]
199
+ port = db_config["db_api_port"]
200
+
201
+ uri_base = f"mongodb://{username}:{password}@{server}:{port}/"
202
+ params = [f"authSource={auth_source}"]
203
+
204
+ if direct_connection:
205
+ params.append("directConnection=true")
206
+ else:
207
+ params.append("ssl=true")
208
+ params.append("tlsAllowInvalidHostnames=true")
209
+ params.append("tlsAllowInvalidCertificates=true")
210
+
211
+ return f"{uri_base}?{'&'.join(params)}"
86
212
 
87
213
  @staticmethod
88
214
  def validate_db_config(db_config):
@@ -112,41 +238,6 @@ class MongoDBHandler: # pylint: disable=unsubscriptable-object
112
238
  except jsonschema.exceptions.ValidationError as err:
113
239
  raise ValueError("Invalid MongoDB configuration") from err
114
240
 
115
- def _open_db(self):
116
- """
117
- Open a connection to MongoDB and return the client.
118
-
119
- Returns
120
- -------
121
- MongoClient
122
- A PyMongo DB client
123
-
124
- Raises
125
- ------
126
- KeyError
127
- If the DB configuration is invalid
128
- """
129
- direct_connection = self.db_config["db_server"] in (
130
- "localhost",
131
- "simtools-mongodb",
132
- "mongodb",
133
- )
134
- return MongoClient(
135
- self.db_config["db_server"],
136
- port=self.db_config["db_api_port"],
137
- username=self.db_config["db_api_user"],
138
- password=self.db_config["db_api_pw"],
139
- authSource=(
140
- self.db_config.get("db_api_authentication_database")
141
- if self.db_config.get("db_api_authentication_database")
142
- else "admin"
143
- ),
144
- directConnection=direct_connection,
145
- ssl=not direct_connection,
146
- tlsallowinvalidhostnames=True,
147
- tlsallowinvalidcertificates=True,
148
- )
149
-
150
241
  @staticmethod
151
242
  def get_db_name(db_name=None, db_simulation_model_version=None, model_name=None):
152
243
  """
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,20 +150,15 @@ def get_corsika_version(run_time=None):
151
150
  str
152
151
  Version of the CORSIKA package.
153
152
  """
154
- version = None
155
- sim_telarray_path = os.getenv("SIMTOOLS_SIMTEL_PATH")
156
- if sim_telarray_path is None:
157
- _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.")
158
155
  return None
159
- corsika_command = Path(sim_telarray_path) / "corsika-run" / "corsika"
160
156
 
161
157
  if run_time is None:
162
- command = [str(corsika_command)]
158
+ command = [str(settings.config.corsika_exe)]
163
159
  else:
164
- command = [*run_time, str(corsika_command)]
160
+ command = [*run_time, str(settings.config.corsika_exe)]
165
161
 
166
- # Below I do not use the standard context manager because
167
- # it makes mocking in the tests significantly more difficult
168
162
  process = subprocess.Popen( # pylint: disable=consider-using-with
169
163
  command,
170
164
  stdout=subprocess.PIPE,
@@ -173,11 +167,8 @@ def get_corsika_version(run_time=None):
173
167
  text=True,
174
168
  )
175
169
 
176
- # Capture output until it waits for input
177
- while True:
178
- line = process.stdout.readline()
179
- if not line:
180
- break
170
+ version = None
171
+ for line in process.stdout:
181
172
  # Extract the version from the line "NUMBER OF VERSION : 7.7550"
182
173
  if "NUMBER OF VERSION" in line:
183
174
  version = line.split(":")[1].strip()
@@ -201,9 +192,14 @@ def get_corsika_version(run_time=None):
201
192
 
202
193
  def get_build_options(run_time=None):
203
194
  """
204
- 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.
205
200
 
206
- 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.
207
203
 
208
204
  Parameters
209
205
  ----------
@@ -213,28 +209,71 @@ def get_build_options(run_time=None):
213
209
  Returns
214
210
  -------
215
211
  dict
216
- Build options from build_opts.yml file.
212
+ CORSIKA / sim_telarray build options.
217
213
  """
218
- sim_telarray_path = os.getenv("SIMTOOLS_SIMTEL_PATH")
219
- if sim_telarray_path is None:
220
- raise ValueError("SIMTOOLS_SIMTEL_PATH not defined.")
221
-
222
- build_opts_path = Path(sim_telarray_path) / "build_opts.yml"
223
-
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."""
224
245
  if run_time is None:
225
246
  try:
226
247
  return ascii_handler.collect_data_from_file(build_opts_path)
227
248
  except FileNotFoundError as exc:
228
- raise FileNotFoundError("No build_opts.yml file found.") from exc
249
+ raise FileNotFoundError("No build option file found.") from exc
229
250
 
230
251
  command = [*run_time, "cat", str(build_opts_path)]
231
- _logger.debug(f"Reading build_opts.yml with command: {command}")
252
+ _logger.debug(f"Reading build option with command: {command}")
232
253
 
233
254
  result = subprocess.run(command, capture_output=True, text=True, check=False)
234
255
  if result.returncode:
235
- 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}")
236
257
 
237
258
  try:
238
259
  return yaml.safe_load(result.stdout)
239
260
  except yaml.YAMLError as exc:
240
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))
@@ -208,9 +208,11 @@ def write_data_to_file(data, output_file, sort_keys=False, numpy_types=False):
208
208
  """
209
209
  output_file = Path(output_file)
210
210
  if output_file.suffix.lower() == ".json":
211
- return _write_to_json(data, output_file, sort_keys, numpy_types)
211
+ _write_to_json(data, output_file, sort_keys, numpy_types)
212
+ return
212
213
  if output_file.suffix.lower() in [".yml", ".yaml"]:
213
- return _write_to_yaml(data, output_file, sort_keys)
214
+ _write_to_yaml(data, output_file, sort_keys)
215
+ return
214
216
 
215
217
  raise ValueError(
216
218
  f"Unsupported file type {output_file.suffix}. Only .json, .yml, and .yaml are supported."
@@ -295,7 +295,7 @@ def write_table_in_hdf5(table, output_file, table_name):
295
295
  None
296
296
  """
297
297
  for col in table.colnames:
298
- if table[col].dtype.kind == "U": # hd5 does not support unicode
298
+ if table[col].dtype.kind == "U": # hdf5 does not support unicode
299
299
  table[col] = table[col].astype("S")
300
300
 
301
301
  with h5py.File(output_file, "a") as f:
@@ -136,8 +136,6 @@ simtools-simulate-prod \\
136
136
  --view_cone {view_cone_string} \\
137
137
  --run_number $((process_id)) \\
138
138
  --run_number_offset {run_number_offset} \\
139
- --number_of_runs 1 \\
140
- --data_directory /tmp/simtools-data \\
141
139
  --output_path /tmp/simtools-output \\
142
140
  --log_level {args_dict["log_level"]} \\
143
141
  --pack_for_grid_register simtools-output
@@ -32,8 +32,6 @@ class ArrayLayout:
32
32
 
33
33
  Parameters
34
34
  ----------
35
- db_config: dict
36
- Database configuration.
37
35
  site: str
38
36
  Site name or location (e.g., North/South or LaPalma/Paranal)
39
37
  model_version: str
@@ -52,7 +50,6 @@ class ArrayLayout:
52
50
 
53
51
  def __init__(
54
52
  self,
55
- db_config,
56
53
  site,
57
54
  model_version,
58
55
  label=None,
@@ -67,7 +64,6 @@ class ArrayLayout:
67
64
  self.model_version = model_version
68
65
  self.label = label
69
66
  self.name = name
70
- self.db_config = db_config
71
67
  self.site = None if site is None else names.validate_site_name(site)
72
68
  self.site_model = None
73
69
  self.io_handler = io_handler.IOHandler()
@@ -95,14 +91,11 @@ class ArrayLayout:
95
91
  def _initialize_site_parameters_from_db(self):
96
92
  """Initialize site parameters required for transformations using the database."""
97
93
  self._logger.debug("Initialize parameters from DB")
98
- if self.db_config is None:
99
- raise ValueError("No database configuration provided")
100
94
 
101
- self.site_model = SiteModel(
102
- site=self.site,
103
- model_version=self.model_version,
104
- db_config=self.db_config,
105
- )
95
+ try:
96
+ self.site_model = SiteModel(site=self.site, model_version=self.model_version)
97
+ except RuntimeError as e:
98
+ raise ValueError("No database configuration provided") from e
106
99
  self._corsika_observation_level = self.site_model.get_corsika_site_parameters().get(
107
100
  "corsika_observation_level", None
108
101
  )
@@ -419,7 +412,6 @@ class ArrayLayout:
419
412
  site=self.site,
420
413
  telescope_name=telescope_name,
421
414
  model_version=self.model_version,
422
- db_config=self.db_config,
423
415
  label=self.label,
424
416
  )
425
417