gammasimtools 0.8.2__py3-none-any.whl → 0.9.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 (65) hide show
  1. {gammasimtools-0.8.2.dist-info → gammasimtools-0.9.0.dist-info}/METADATA +3 -3
  2. {gammasimtools-0.8.2.dist-info → gammasimtools-0.9.0.dist-info}/RECORD +64 -59
  3. {gammasimtools-0.8.2.dist-info → gammasimtools-0.9.0.dist-info}/WHEEL +1 -1
  4. {gammasimtools-0.8.2.dist-info → gammasimtools-0.9.0.dist-info}/entry_points.txt +2 -0
  5. simtools/_version.py +2 -2
  6. simtools/applications/convert_all_model_parameters_from_simtel.py +1 -1
  7. simtools/applications/convert_geo_coordinates_of_array_elements.py +8 -9
  8. simtools/applications/convert_model_parameter_from_simtel.py +1 -1
  9. simtools/applications/db_add_model_parameters_from_repository_to_db.py +2 -10
  10. simtools/applications/db_add_value_from_json_to_db.py +1 -9
  11. simtools/applications/db_get_array_layouts_from_db.py +3 -1
  12. simtools/applications/db_get_parameter_from_db.py +1 -1
  13. simtools/applications/derive_mirror_rnda.py +10 -1
  14. simtools/applications/derive_psf_parameters.py +1 -1
  15. simtools/applications/generate_array_config.py +1 -5
  16. simtools/applications/generate_regular_arrays.py +9 -6
  17. simtools/applications/plot_array_layout.py +3 -1
  18. simtools/applications/plot_tabular_data.py +84 -0
  19. simtools/applications/production_scale_events.py +1 -2
  20. simtools/applications/simulate_light_emission.py +2 -2
  21. simtools/applications/simulate_prod.py +24 -59
  22. simtools/applications/simulate_prod_htcondor_generator.py +95 -0
  23. simtools/applications/submit_data_from_external.py +1 -1
  24. simtools/applications/validate_camera_efficiency.py +1 -1
  25. simtools/applications/validate_camera_fov.py +3 -7
  26. simtools/applications/validate_cumulative_psf.py +3 -7
  27. simtools/applications/validate_file_using_schema.py +31 -21
  28. simtools/applications/validate_optics.py +3 -4
  29. simtools/camera_efficiency.py +1 -4
  30. simtools/configuration/commandline_parser.py +7 -13
  31. simtools/configuration/configurator.py +6 -19
  32. simtools/data_model/metadata_collector.py +18 -0
  33. simtools/data_model/metadata_model.py +18 -5
  34. simtools/data_model/model_data_writer.py +1 -1
  35. simtools/data_model/validate_data.py +67 -10
  36. simtools/db/db_handler.py +92 -315
  37. simtools/io_operations/legacy_data_handler.py +61 -0
  38. simtools/job_execution/htcondor_script_generator.py +133 -0
  39. simtools/job_execution/job_manager.py +77 -50
  40. simtools/model/camera.py +4 -2
  41. simtools/model/model_parameter.py +40 -10
  42. simtools/model/site_model.py +1 -1
  43. simtools/ray_tracing/mirror_panel_psf.py +47 -27
  44. simtools/runners/corsika_runner.py +14 -3
  45. simtools/runners/runner_services.py +3 -3
  46. simtools/runners/simtel_runner.py +27 -8
  47. simtools/schemas/integration_tests_config.metaschema.yml +15 -5
  48. simtools/schemas/model_parameter.metaschema.yml +90 -2
  49. simtools/schemas/model_parameters/effective_focal_length.schema.yml +31 -1
  50. simtools/simtel/simtel_table_reader.py +410 -0
  51. simtools/simtel/simulator_camera_efficiency.py +6 -4
  52. simtools/simtel/simulator_light_emission.py +2 -2
  53. simtools/simtel/simulator_ray_tracing.py +1 -2
  54. simtools/simulator.py +80 -33
  55. simtools/testing/configuration.py +12 -8
  56. simtools/testing/helpers.py +5 -5
  57. simtools/testing/validate_output.py +26 -26
  58. simtools/utils/general.py +50 -3
  59. simtools/utils/names.py +2 -2
  60. simtools/utils/value_conversion.py +9 -1
  61. simtools/visualization/plot_tables.py +106 -0
  62. simtools/visualization/visualize.py +43 -5
  63. simtools/db/db_from_repo_handler.py +0 -106
  64. {gammasimtools-0.8.2.dist-info → gammasimtools-0.9.0.dist-info}/LICENSE +0 -0
  65. {gammasimtools-0.8.2.dist-info → gammasimtools-0.9.0.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/python3
2
2
 
3
3
  r"""
4
- Generate simulation configuration and run simulations (if required).
4
+ Generate simulation configuration and run simulations.
5
5
 
6
6
  Multipipe scripts will be produced as part of this application.
7
7
  Allows to run array layout simulation including shower and detector simulations
@@ -43,9 +43,9 @@ r"""
43
43
  The location of the output directories corsika-data and simtel-data.
44
44
  the label is added to the data_directory, such that the output
45
45
  will be written to data_directory/label/simtel-data.
46
- pack_for_grid_register (bool, optional)
46
+ pack_for_grid_register (str, optional)
47
47
  Set whether to prepare a tarball for registering the output files on the grid.
48
- The files are written to the output_path/directory_for_grid_upload directory.
48
+ The files are written to the specified directory.
49
49
  log_level (str, optional)
50
50
  Log level to print.
51
51
 
@@ -68,9 +68,6 @@ r"""
68
68
  """
69
69
 
70
70
  import logging
71
- import shutil
72
- import tarfile
73
- from pathlib import Path
74
71
 
75
72
  import simtools.utils.general as gen
76
73
  from simtools.configuration import configurator
@@ -106,10 +103,10 @@ def _parse(description=None):
106
103
  )
107
104
  config.parser.add_argument(
108
105
  "--pack_for_grid_register",
109
- help="Set whether to prepare a tarball for registering the output files on the grid.",
110
- action="store_true",
106
+ help="Directory for a tarball for registering the output files on the grid.",
107
+ type=str,
111
108
  required=False,
112
- default=False,
109
+ default=None,
113
110
  )
114
111
  config.parser.add_argument(
115
112
  "--save_file_lists",
@@ -137,49 +134,11 @@ def _parse(description=None):
137
134
  return config.initialize(
138
135
  db_config=True,
139
136
  job_submission=True,
140
- simulation_model=["site", "layout", "telescope"],
137
+ simulation_model=["site", "layout", "telescope", "model_version"],
141
138
  simulation_configuration={"software": None, "corsika_configuration": ["all"]},
142
139
  )
143
140
 
144
141
 
145
- def pack_for_register(logger, simulator, args_dict):
146
- """
147
- Pack the output files for registering on the grid.
148
-
149
- Parameters
150
- ----------
151
- logger: logging.Logger
152
- Logger object.
153
- simulator: Simulator
154
- Simulator object.
155
-
156
- """
157
- logger.info("Packing the output files for registering on the grid")
158
- output_files = simulator.get_file_list(file_type="output")
159
- log_files = simulator.get_file_list(file_type="log")
160
- histogram_files = simulator.get_file_list(file_type="hist")
161
- tar_file_name = Path(log_files[0]).name.replace("log.gz", "log_hist.tar.gz")
162
- directory_for_grid_upload = Path(args_dict.get("output_path")).joinpath(
163
- "directory_for_grid_upload"
164
- )
165
- directory_for_grid_upload.mkdir(parents=True, exist_ok=True)
166
- tar_file_name = directory_for_grid_upload.joinpath(tar_file_name)
167
-
168
- with tarfile.open(tar_file_name, "w:gz") as tar:
169
- files_to_tar = log_files[:1] + histogram_files[:1]
170
- for file_to_tar in files_to_tar:
171
- tar.add(file_to_tar, arcname=Path(file_to_tar).name)
172
-
173
- for file_to_move in [*output_files]:
174
- source_file = Path(file_to_move)
175
- destination_file = directory_for_grid_upload / source_file.name
176
- # Note that this will overwrite previous files which exist in the directory
177
- # It should be fine for normal production since each run is on a separate node
178
- # so no files are expected there.
179
- shutil.move(source_file, destination_file)
180
- logger.info(f"Output files for the grid placed in {directory_for_grid_upload!s}")
181
-
182
-
183
142
  def main(): # noqa: D103
184
143
  args_dict, db_config = _parse(description="Run simulations for productions")
185
144
 
@@ -192,17 +151,23 @@ def main(): # noqa: D103
192
151
 
193
152
  simulator.simulate()
194
153
 
195
- logger.info(
196
- f"Production run is complete for primary {args_dict['primary']} showers "
197
- f"coming from {args_dict['azimuth_angle']} azimuth and zenith angle of "
198
- f"{args_dict['zenith_angle']} at the {args_dict['site']} site, "
199
- f"using the {args_dict['model_version']} simulation model."
200
- )
201
-
202
- if args_dict["pack_for_grid_register"]:
203
- pack_for_register(logger, simulator, args_dict)
204
- if args_dict["save_file_lists"]:
205
- simulator.save_file_lists()
154
+ if simulator.submit_engine == "local":
155
+ logger.info(
156
+ f"Production run complete for primary {args_dict['primary']} showers "
157
+ f"from {args_dict['azimuth_angle']} azimuth and {args_dict['zenith_angle']} zenith "
158
+ f"at {args_dict['site']} site, using {args_dict['model_version']} model."
159
+ )
160
+ if args_dict.get("pack_for_grid_register"):
161
+ simulator.pack_for_register(args_dict["pack_for_grid_register"])
162
+ if args_dict["save_file_lists"]:
163
+ simulator.save_file_lists()
164
+ else:
165
+ logger.info("Production run submitted to the workload manager")
166
+ if args_dict["pack_for_grid_register"] or args_dict["save_file_lists"]:
167
+ logger.warning(
168
+ "Packing for grid register or saving file lists not supported for "
169
+ f"{simulator.submit_engine}."
170
+ )
206
171
 
207
172
 
208
173
  if __name__ == "__main__":
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/python3
2
+
3
+ r"""
4
+ Generate a run script and submit file for HT Condor job submission of a simulation production.
5
+
6
+ This tool facilitates the submission of multiple simulations to the HT Condor batch system,
7
+ enabling:
8
+
9
+ - Execution of simulations using the "simtools-simulate-prod" application.
10
+ - 'number_of_runs' jobs are submitted to the HT Condor batch system.
11
+ - Utilization of an Apptainer image containing the SimPipe simulation software and tools.
12
+ - Packaging of data and histogram files, and writing them to a specified directory.
13
+
14
+ This tool is intended for use in an HT Condor environment. Jobs run in a container universe
15
+ using the Apptainer image specified in the command line ('--apptainer_image'). Output is written
16
+ to the 'output_path' directory, with 'simtools-output' and 'logs' subdirectories.
17
+
18
+ Requirements for the 'simtools-simulate-prod-htcondor-generator' application:
19
+
20
+ - Availability of an Apptainer image 'simtools-prod' (obtainable from the package registry on
21
+ GitHub, e.g., via 'apptainer pull --force docker://ghcr.io/gammasim/simtools-prod:latest').
22
+ - Environment parameters required to run CORSIKA and sim_telarray, as well as DB access
23
+ credentials. These should be listed similarly to a '.env' file and copied to
24
+ 'output_path/env.txt'. Ensure that the path to the simulation software is correctly set to
25
+ 'SIMTOOLS_SIMTEL_PATH=/workdir/sim_telarray'.
26
+
27
+
28
+ Command line arguments
29
+ ----------------------
30
+ output_path (str, required)
31
+ Directory where the output and the simulation data files will be written.
32
+ apptainer_image (str, optional)
33
+ Apptainer image to use for the simulation (full path).
34
+ priority (int, optional)
35
+ Job priority (default: 1).
36
+
37
+ (all other command line arguments are identical to those of :ref:`simulate_prod`).
38
+
39
+ """
40
+
41
+ import logging
42
+
43
+ import simtools.utils.general as gen
44
+ from simtools.configuration import configurator
45
+ from simtools.job_execution import htcondor_script_generator
46
+
47
+
48
+ def _parse(description=None):
49
+ """
50
+ Parse command line configuration.
51
+
52
+ Parameters
53
+ ----------
54
+ description: str
55
+ Application description.
56
+
57
+ Returns
58
+ -------
59
+ CommandLineParser
60
+ Command line parser object.
61
+
62
+ """
63
+ config = configurator.Configurator(description=description)
64
+ config.parser.add_argument(
65
+ "--apptainer_image",
66
+ help="Apptainer image to use for the simulation (full path).",
67
+ type=str,
68
+ required=False,
69
+ )
70
+ config.parser.add_argument(
71
+ "--priority",
72
+ help="Job priority.",
73
+ type=int,
74
+ required=False,
75
+ default=1,
76
+ )
77
+ return config.initialize(
78
+ db_config=False,
79
+ job_submission=False,
80
+ simulation_model=["site", "layout", "telescope", "model_version"],
81
+ simulation_configuration={"software": None, "corsika_configuration": ["all"]},
82
+ )
83
+
84
+
85
+ def main(): # noqa: D103
86
+ args_dict, _ = _parse(description="Prepare simulations production for HT Condor job submission")
87
+
88
+ logger = logging.getLogger()
89
+ logger.setLevel(gen.get_log_level_from_user(args_dict["log_level"]))
90
+
91
+ htcondor_script_generator.generate_submission_script(args_dict)
92
+
93
+
94
+ if __name__ == "__main__":
95
+ main()
@@ -104,7 +104,7 @@ def main(): # noqa: D103
104
104
 
105
105
  writer.ModelDataWriter.dump(
106
106
  args_dict=args_dict,
107
- metadata=_metadata.top_level_meta,
107
+ metadata=_metadata.get_top_level_metadata(),
108
108
  product_data=data_validator.validate_and_transform(),
109
109
  )
110
110
 
@@ -86,7 +86,7 @@ def _parse(label):
86
86
  )
87
87
  _args_dict, _db_config = config.initialize(
88
88
  db_config=True,
89
- simulation_model="telescope",
89
+ simulation_model=["telescope", "model_version"],
90
90
  simulation_configuration={"corsika_configuration": ["zenith_angle", "azimuth_angle"]},
91
91
  )
92
92
  if _args_dict["site"] is None or _args_dict["telescope"] is None:
@@ -54,7 +54,7 @@ import simtools.utils.general as gen
54
54
  from simtools.configuration import configurator
55
55
  from simtools.io_operations import io_handler
56
56
  from simtools.model.telescope_model import TelescopeModel
57
- from simtools.visualization import plot_camera
57
+ from simtools.visualization import plot_camera, visualize
58
58
 
59
59
 
60
60
  def _parse():
@@ -83,7 +83,7 @@ def _parse():
83
83
  ),
84
84
  default=50,
85
85
  )
86
- return config.initialize(db_config=True, simulation_model="telescope")
86
+ return config.initialize(db_config=True, simulation_model=["telescope", "model_version"])
87
87
 
88
88
 
89
89
  def main(): # noqa: D103
@@ -133,11 +133,7 @@ def main(): # noqa: D103
133
133
  ) from exc
134
134
  fig = plot_camera.plot_pixel_layout(camera, args_dict["camera_in_sky_coor"], pixel_ids_to_print)
135
135
  plot_file_prefix = output_dir.joinpath(f"{label}_{tel_model.name}_pixel_layout")
136
- for suffix in ["pdf", "png"]:
137
- file_name = f"{plot_file_prefix!s}.{suffix}"
138
- fig.savefig(file_name, format=suffix, bbox_inches="tight")
139
- print(f"\nSaved camera plot in {file_name}\n")
140
- fig.clf()
136
+ visualize.save_figure(fig, f"{plot_file_prefix!s}", log_title="camera")
141
137
 
142
138
 
143
139
  if __name__ == "__main__":
@@ -110,7 +110,7 @@ def _parse(label):
110
110
  help="Data file name with the measured PSF vs radius [cm]",
111
111
  type=str,
112
112
  )
113
- return config.initialize(db_config=True, simulation_model="telescope")
113
+ return config.initialize(db_config=True, simulation_model=["telescope", "model_version"])
114
114
 
115
115
 
116
116
  def load_data(datafile):
@@ -182,9 +182,7 @@ def main(): # noqa: D103
182
182
 
183
183
  plot_file_name = label + "_" + tel_model.name + "_cumulative_PSF"
184
184
  plot_file = output_dir.joinpath(plot_file_name)
185
- for f in ["pdf", "png"]:
186
- plt.savefig(str(plot_file) + "." + f, format=f, bbox_inches="tight")
187
- fig.clf()
185
+ visualize.save_figure(fig, plot_file)
188
186
 
189
187
  # Plotting image
190
188
  data_to_plot = im.get_image_data()
@@ -195,9 +193,7 @@ def main(): # noqa: D103
195
193
 
196
194
  plot_file_name = label + "_" + tel_model.name + "_image"
197
195
  plot_file = output_dir.joinpath(plot_file_name)
198
- for f in ["pdf", "png"]:
199
- fig.savefig(str(plot_file) + "." + f, format=f, bbox_inches="tight")
200
- fig.clf()
196
+ visualize.save_figure(fig, plot_file)
201
197
 
202
198
 
203
199
  if __name__ == "__main__":
@@ -33,11 +33,14 @@ r"""
33
33
  """
34
34
 
35
35
  import logging
36
- from importlib.resources import files
36
+ import re
37
37
  from pathlib import Path
38
38
 
39
+ import jsonschema
40
+
39
41
  import simtools.utils.general as gen
40
42
  from simtools.configuration import configurator
43
+ from simtools.constants import MODEL_PARAMETER_SCHEMA_PATH
41
44
  from simtools.data_model import metadata_collector, metadata_model, validate_data
42
45
 
43
46
 
@@ -62,13 +65,12 @@ def _parse(label, description):
62
65
  group = config.parser.add_mutually_exclusive_group(required=True)
63
66
  group.add_argument("--file_name", help="File to be validated")
64
67
  group.add_argument(
65
- "--model_parameters_directory",
68
+ "--file_directory",
66
69
  help=(
67
- "Directory with json files with model parameters to be validated."
68
- "All *.json files in the directory will be validated."
69
- "Schema files will be taken from simtools/schemas/model_parameters/."
70
- "Note that in this case the data_type argument is ignored"
71
- "and data_type=model_parameter is always used."
70
+ "Directory with json files to be validated. "
71
+ "If no schema file is provided, the assumption is that model "
72
+ "parameters are validated and the schema files are taken from "
73
+ f"{MODEL_PARAMETER_SCHEMA_PATH}."
72
74
  ),
73
75
  )
74
76
  config.parser.add_argument("--schema", help="Json schema file", required=False)
@@ -116,31 +118,39 @@ def _get_schema_file_name(args_dict, data_dict=None):
116
118
 
117
119
  def validate_schema(args_dict, logger):
118
120
  """
119
- Validate a schema file given in yaml or json format.
121
+ Validate a schema file (or several files) given in yaml or json format.
120
122
 
121
123
  Schema is either given as command line argument, read from the meta_schema_url or from
122
124
  the metadata section of the data dictionary.
123
125
 
124
126
  """
125
- try:
126
- data = gen.collect_data_from_file(file_name=args_dict["file_name"])
127
- except FileNotFoundError as exc:
128
- logger.error(f"Error reading schema file from {args_dict['file_name']}")
129
- raise exc
130
- metadata_model.validate_schema(data, _get_schema_file_name(args_dict, data))
131
- logger.info(f"Successful validation of schema file {args_dict['file_name']}")
127
+ if args_dict.get("file_directory") is not None:
128
+ file_list = list(Path(args_dict["file_directory"]).rglob("*.json"))
129
+ else:
130
+ file_list = [args_dict["file_name"]]
131
+ for file_name in file_list:
132
+ try:
133
+ data = gen.collect_data_from_file(file_name=file_name)
134
+ except FileNotFoundError as exc:
135
+ logger.error(f"Error reading schema file from {file_name}")
136
+ raise exc
137
+ try:
138
+ metadata_model.validate_schema(data, _get_schema_file_name(args_dict, data))
139
+ except jsonschema.exceptions.ValidationError as exc:
140
+ logger.error(f"Failed validation of file {file_name}")
141
+ raise exc
142
+ logger.info(f"Successful validation of file {file_name}")
132
143
 
133
144
 
134
145
  def validate_data_files(args_dict, logger):
135
146
  """Validate data files."""
136
- model_parameters_directory = args_dict.get("model_parameters_directory")
137
- if model_parameters_directory is not None:
147
+ file_directory = args_dict.get("file_directory")
148
+ if file_directory is not None:
138
149
  tmp_args_dict = {}
139
- for file_name in Path(model_parameters_directory).rglob("*.json"):
150
+ for file_name in Path(file_directory).rglob("*.json"):
140
151
  tmp_args_dict["file_name"] = file_name
141
- schema_file = (
142
- files("simtools") / "schemas/model_parameters" / f"{file_name.stem}.schema.yml"
143
- )
152
+ parameter_name = re.sub(r"-\d+\.\d+\.\d+", "", file_name.stem)
153
+ schema_file = MODEL_PARAMETER_SCHEMA_PATH / f"{parameter_name}.schema.yml"
144
154
  tmp_args_dict["schema"] = schema_file
145
155
  tmp_args_dict["data_type"] = "model_parameter"
146
156
  tmp_args_dict["require_exact_data_type"] = args_dict["require_exact_data_type"]
@@ -78,6 +78,7 @@ from simtools.configuration import configurator
78
78
  from simtools.io_operations import io_handler
79
79
  from simtools.model.telescope_model import TelescopeModel
80
80
  from simtools.ray_tracing.ray_tracing import RayTracing
81
+ from simtools.visualization import visualize
81
82
 
82
83
 
83
84
  def _parse(label):
@@ -114,7 +115,7 @@ def _parse(label):
114
115
  help="Produce a multiple pages pdf file with the image plots.",
115
116
  action="store_true",
116
117
  )
117
- return config.initialize(db_config=True, simulation_model=["telescope"])
118
+ return config.initialize(db_config=True, simulation_model=["telescope", "model_version"])
118
119
 
119
120
 
120
121
  def main(): # noqa: D103
@@ -171,9 +172,7 @@ def main(): # noqa: D103
171
172
 
172
173
  plot_file_name = "_".join((label, tel_model.name, key))
173
174
  plot_file = output_dir.joinpath(plot_file_name)
174
- plt.savefig(str(plot_file) + ".pdf", format="pdf", bbox_inches="tight")
175
- plt.savefig(str(plot_file) + ".png", format="png", bbox_inches="tight")
176
- plt.clf()
175
+ visualize.save_figure(plt, plot_file)
177
176
 
178
177
  # Plotting images
179
178
  if args_dict["plot_images"]:
@@ -514,7 +514,4 @@ class CameraEfficiency:
514
514
  plot_file = self.output_dir.joinpath(
515
515
  self.label + "_" + self.telescope_model.name + "_" + plot_title
516
516
  )
517
- for f in ["pdf", "png"]:
518
- fig.savefig(str(plot_file) + "." + f, format=f, bbox_inches="tight")
519
- self._logger.info(f"Plotted {plot_title} efficiency in {plot_file}")
520
- fig.clf()
517
+ visualize.save_figure(fig, plot_file, log_title=f"{plot_title} efficiency")
@@ -198,13 +198,6 @@ class CommandLineParser(argparse.ArgumentParser):
198
198
  required=False,
199
199
  default=None,
200
200
  )
201
- _job_group.add_argument(
202
- "--db_simulation_model_url",
203
- help="simulation model repository URL",
204
- type=str,
205
- required=False,
206
- default=None,
207
- )
208
201
 
209
202
  def initialize_job_submission_arguments(self):
210
203
  """Initialize job submission arguments for simulator."""
@@ -243,12 +236,13 @@ class CommandLineParser(argparse.ArgumentParser):
243
236
  return
244
237
 
245
238
  _job_group = self.add_argument_group("simulation model")
246
- _job_group.add_argument(
247
- "--model_version",
248
- help="model version",
249
- type=str,
250
- default=None,
251
- )
239
+ if "model_version" in model_options:
240
+ _job_group.add_argument(
241
+ "--model_version",
242
+ help="model version",
243
+ type=str,
244
+ default=None,
245
+ )
252
246
  if any(
253
247
  option in model_options for option in ["site", "telescope", "layout", "layout_file"]
254
248
  ):
@@ -10,6 +10,7 @@ import astropy.units as u
10
10
  from dotenv import load_dotenv
11
11
 
12
12
  import simtools.configuration.commandline_parser as argparser
13
+ from simtools.db.db_handler import jsonschema_db_dict
13
14
  from simtools.io_operations import io_handler
14
15
  from simtools.utils import general as gen
15
16
 
@@ -438,24 +439,10 @@ class Configurator:
438
439
  """
439
440
  Return parameters for DB configuration.
440
441
 
441
- Parameters
442
- ----------
442
+ Returns
443
+ -------
443
444
  dict
444
- Dictionary with DB parameters
445
+ Dictionary with DB parameters.
445
446
  """
446
- _db_dict = {}
447
- _db_para = (
448
- "db_api_user",
449
- "db_api_pw",
450
- "db_api_port",
451
- "db_server",
452
- "db_simulation_model",
453
- "db_simulation_model_url",
454
- )
455
- try:
456
- for _para in _db_para:
457
- _db_dict[_para] = self.config[_para]
458
- except KeyError:
459
- pass
460
-
461
- return _db_dict
447
+ db_params = jsonschema_db_dict["properties"].keys()
448
+ return {param: self.config.get(param) for param in db_params if param in self.config}
@@ -86,6 +86,24 @@ class MetadataCollector:
86
86
  except AttributeError:
87
87
  self._logger.debug(f"Method _fill_{meta_type}_meta not implemented")
88
88
 
89
+ def get_top_level_metadata(self):
90
+ """
91
+ Return top level metadata dictionary (with updated activity end time).
92
+
93
+ Returns
94
+ -------
95
+ dict
96
+ Top level metadata dictionary.
97
+
98
+ """
99
+ try:
100
+ self.top_level_meta[self.observatory]["activity"][
101
+ "end"
102
+ ] = datetime.datetime.now().isoformat(timespec="seconds")
103
+ except KeyError:
104
+ pass
105
+ return self.top_level_meta
106
+
89
107
  def get_data_model_schema_file_name(self):
90
108
  """
91
109
  Return data model schema file name.
@@ -38,7 +38,10 @@ def validate_schema(data, schema_file):
38
38
  if validation fails
39
39
 
40
40
  """
41
- schema, schema_file = _load_schema(schema_file)
41
+ schema, schema_file = _load_schema(
42
+ schema_file,
43
+ data.get("schema_version", "0.1.0"), # default version to ensure backward compatibility
44
+ )
42
45
 
43
46
  try:
44
47
  jsonschema.validate(data, schema=schema, format_checker=format_checkers.format_checker)
@@ -72,17 +75,17 @@ def get_default_metadata_dict(schema_file=None, observatory="CTA"):
72
75
  return _fill_defaults(schema["definitions"], observatory)
73
76
 
74
77
 
75
- def _load_schema(schema_file=None):
78
+ def _load_schema(schema_file=None, schema_version=None):
76
79
  """
77
80
  Load parameter schema from file from simpipe metadata schema.
78
81
 
79
82
  Returns
80
83
  -------
81
- schema_file dict
82
- Schema used for validation.
83
- schema_file str
84
+ schema_file: str
84
85
  File name schema is loaded from. If schema_file is not given,
85
86
  the default schema file name is returned.
87
+ schema_version: str
88
+ Schema version.
86
89
 
87
90
  Raises
88
91
  ------
@@ -98,6 +101,16 @@ def _load_schema(schema_file=None):
98
101
  except FileNotFoundError:
99
102
  schema_file = files("simtools").joinpath("schemas") / schema_file
100
103
  schema = gen.collect_data_from_file(file_name=schema_file)
104
+
105
+ if isinstance(schema, list): # schema file with several schemas defined
106
+ if schema_version is None:
107
+ raise ValueError(f"Schema version not given in {schema_file}.")
108
+ schema = next((doc for doc in schema if doc.get("version") == schema_version), None)
109
+ if schema is None:
110
+ raise ValueError(f"Schema version {schema_version} not found in {schema_file}.")
111
+ elif schema_version is not None and schema_version != schema.get("version"):
112
+ _logger.warning(f"Schema version {schema_version} does not match {schema.get('version')}")
113
+
101
114
  _logger.debug(f"Loading schema from {schema_file}")
102
115
  _add_array_elements("InstrumentTypeElement", schema)
103
116
 
@@ -170,7 +170,7 @@ class ModelDataWriter:
170
170
  metadata_input_dict["output_file"] = output_file
171
171
  metadata_input_dict["output_file_format"] = Path(output_file).suffix.lstrip(".")
172
172
  writer.write_metadata_to_yml(
173
- metadata=MetadataCollector(args_dict=metadata_input_dict).top_level_meta,
173
+ metadata=MetadataCollector(args_dict=metadata_input_dict).get_top_level_metadata(),
174
174
  yml_file=output_path / f"{Path(output_file).stem}",
175
175
  )
176
176
  return _json_dict