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
@@ -65,29 +65,17 @@ def main(): # noqa: D103
65
65
  _io_handler = io_handler.IOHandler()
66
66
 
67
67
  db = db_handler.DatabaseHandler(mongo_db_config=db_config)
68
- available_dbs = [
69
- db_config["db_simulation_model"],
70
- ]
71
- file_id = {}
72
- for db_name in available_dbs:
73
- try:
74
- file_id = db.export_model_files(
75
- db_name=db_name,
76
- dest=_io_handler.get_output_directory(),
77
- file_names=args_dict["file_name"],
78
- )
79
- logger.info(
80
- f"Got file {args_dict['file_name']} from DB {db_name} "
81
- f"and saved into {_io_handler.get_output_directory()}"
82
- )
83
- break
84
- except FileNotFoundError:
85
- continue
86
-
87
- for key, value in file_id.items():
88
- if value is None:
89
- logger.error(f"The file {key} was not found in any of the available DBs.")
90
- raise FileNotFoundError
68
+ file_id = db.export_model_files(
69
+ dest=_io_handler.get_output_directory(),
70
+ file_names=args_dict["file_name"],
71
+ )
72
+ if file_id is None:
73
+ logger.error(f"The file {args_dict['file_name']} was not found in {db.db_name}.")
74
+ raise FileNotFoundError
75
+ logger.info(
76
+ f"Got file {args_dict['file_name']} from DB {db.db_name} "
77
+ f"and saved into {_io_handler.get_output_directory()}"
78
+ )
91
79
 
92
80
 
93
81
  if __name__ == "__main__":
@@ -6,25 +6,22 @@ r"""
6
6
  This includes parameters mirror_reflection_random_angle, \
7
7
  mirror_align_random_horizontal and mirror_align_random_vertical.
8
8
 
9
- The telescope zenith angle and the source distance can be set by command line arguments.
10
-
11
9
  The measured cumulative PSF should be provided by using the command line argument data. \
12
10
  A file name is expected, in which the file should contain 3 columns: radial distance in mm, \
13
11
  differential value of photon intensity and its integral value.
14
12
 
15
- The derivation is performed through a random search. A number of random combination of the \
16
- parameters are tested and the best ones are selected based on the minimum value of \
17
- the Root Mean Squared Deviation between data and simulations. The range in which the \
18
- parameter are drawn uniformly are defined based on the previous value on the telescope model.
13
+ The derivation is performed through gradient descent optimization that minimizes either the \
14
+ Root Mean Squared Deviation (RMSD) between measured and simulated PSF curves (default) or the \
15
+ Kolmogorov-Smirnov (KS) statistic when the --ks_statistic flag is used.
19
16
 
20
17
  The optimization workflow includes:
21
18
 
22
19
  * Loading and preprocessing PSF data from measurement files
23
- * Generating random parameter combinations for optimization
24
- * Running ray-tracing simulations for each parameter set
25
- * Calculating RMSD between measured and simulated PSF curves
26
- * Identifying the best-fit parameters with minimum RMSD
27
- * Creating comprehensive plots and D80 vs off-axis angle analysis
20
+ * Running gradient descent optimization to minimize RMSD
21
+ * Generating cumulative PSF plots for each iteration showing optimization progression
22
+ * Logging parameter evolution through gradient descent steps
23
+ * Creating convergence plots showing RMSD and D80 evolution
24
+ * Automatically generating D80 vs off-axis angle analysis for best parameters
28
25
  * Optionally exporting optimized parameters as simulation model files
29
26
 
30
27
  The assumption are:
@@ -36,7 +33,7 @@ r"""
36
33
  One example of the plot generated by this applications are shown below.
37
34
 
38
35
  .. _derive_psf_parameters_plot:
39
- .. image:: images/derive_psf_parameters.png
36
+ .. image:: images/gradient_descent.png
40
37
  :width: 49 %
41
38
 
42
39
  Command line arguments
@@ -63,47 +60,53 @@ r"""
63
60
  If activated, application will be faster by simulating fewer photons.
64
61
  write_psf_parameters (activation mode, optional)
65
62
  Write the optimized PSF parameters as simulation model parameter files.
66
- random_seed (int, optional)
67
- Random seed for parameter generation.
68
- n_runs (int, optional)
69
- Number of parameter combinations to test.
63
+ rmsd_threshold (float, optional)
64
+ RMSD threshold for gradient descent convergence (default: 0.007).
65
+ learning_rate (float, optional)
66
+ Learning rate for gradient descent optimization (default: 0.01).
67
+ monte_carlo_analysis (activation mode, optional)
68
+ Run Monte Carlo analysis to find statistical uncertainties.
70
69
 
71
70
  Example
72
71
  -------
73
- LSTN-01 5.0.0
74
-
75
- Runtime < 3 min.
76
-
77
- Get PSF data from the DB:
72
+ --telescope LSTN-01 --model_version 6.0.0
78
73
 
79
- .. code-block:: console
80
74
 
81
- simtools-db-get-file-from-db --file_name PSFcurve_data_v2.txt
82
75
 
83
76
  Run the application:
84
77
 
85
78
  .. code-block:: console
86
79
 
87
80
  simtools-derive-psf-parameters --site North --telescope LSTN-01 \\
88
- --model_version 6.0.0 --data tests/resources/PSFcurve_data_v2.txt --plot_all --test
81
+ --model_version 6.0.0 --data tests/resources/PSFcurve_data_v2.ecsv --plot_all --test
89
82
 
90
83
  Run with parameter export:
91
84
 
92
85
  .. code-block:: console
93
86
 
94
- simtools-derive-psf-parameters --site North --telescope LSTN-01 \\
95
- --model_version 6.0.0 \\
96
- --data tests/resources/PSFcurve_data_v2.txt --write_psf_parameters
87
+ simtools-derive-psf-parameters --site North --telescope LSTN-01 --model_version 6.0.0 \\
88
+ --plot_all --test --rmsd_threshold 0.01 --learning_rate 0.001 \\
89
+ --data tests/resources/PSFcurve_data_v2.ecsv \\
90
+ --write_psf_parameters
97
91
 
98
- The output is saved in simtools-output/tune_psf.
92
+ Run monte carlo analysis:
93
+
94
+ .. code-block:: console
95
+
96
+ simtools-derive-psf-parameters --site North --telescope LSTN-01 --model_version 6.0.0 \\
97
+ --plot_all --test --monte_carlo_analysis \\
98
+ --data tests/resources/PSFcurve_data_v2.ecsv \\
99
+ --write_psf_parameters
100
+
101
+ The output is saved in simtools-output/derive_psf_parameters.
99
102
 
100
103
  Output files include:
101
104
 
102
- * Parameter optimization results in tested_psf_parameters.txt
103
- * PSF comparison plots in tune_psf_[telescope].pdf
105
+ * Gradient descent progression log in psf_gradient_descent_[telescope].log
106
+ * Gradient descent convergence plots in gradient_descent_convergence_[telescope].png
107
+ * PSF progression plots showing evolution through iterations (if --plot_all is specified)
104
108
  * D80 vs off-axis angle plots (d80_vs_offaxis_cm.png, d80_vs_offaxis_deg.png)
105
109
  * Optimized simulation model parameter files (if --write_psf_parameters is specified)
106
- * Cumulative PSF plots for all tested combinations (if --plot_all is specified)
107
110
 
108
111
  """
109
112
 
@@ -154,16 +157,32 @@ def _parse():
154
157
  required=False,
155
158
  )
156
159
  config.parser.add_argument(
157
- "--random_seed",
158
- help="Random seed for parameter generation.",
159
- type=int,
160
- default=None,
160
+ "--rmsd_threshold",
161
+ help=(
162
+ "RMSD threshold for gradient descent convergence "
163
+ "(not used with --monte_carlo_analysis)."
164
+ ),
165
+ type=float,
166
+ default=0.01,
167
+ )
168
+ config.parser.add_argument(
169
+ "--learning_rate",
170
+ help=(
171
+ "Learning rate for gradient descent optimization "
172
+ "(not used with --monte_carlo_analysis)."
173
+ ),
174
+ type=float,
175
+ default=0.01,
161
176
  )
162
177
  config.parser.add_argument(
163
- "--n_runs",
164
- help="Number of parameter combinations to test.",
165
- type=int,
166
- default=5,
178
+ "--monte_carlo_analysis",
179
+ help="Run analysis to find monte carlo uncertainties.",
180
+ action="store_true",
181
+ )
182
+ config.parser.add_argument(
183
+ "--ks_statistic",
184
+ help="Use KS statistic for monte carlo uncertainty analysis.",
185
+ action="store_true",
167
186
  )
168
187
  return config.initialize(
169
188
  db_config=True,
@@ -0,0 +1,91 @@
1
+ r"""
2
+ Derive cosmic-ray trigger rates for a single telescope or an array of telescopes.
3
+
4
+ Uses simulated background events (e.g. from proton primaries) to calculate the trigger rates.
5
+ Input is reduced event data generated from simulations for the given configuration.
6
+
7
+
8
+ Command line arguments
9
+ ----------------------
10
+ event_data_file (str, required)
11
+ Event data file containing reduced event data.
12
+ array_layout_name (list, optional)
13
+ Name of the array layout to use for the simulation.
14
+ telescope_ids (str, optional)
15
+ Path to a file containing telescope configurations.
16
+ plot_histograms (bool, optional)
17
+ Plot histograms of the event data.
18
+ model_version (str, optional)
19
+ Version of the simulation model to use.
20
+ site (str, optional)
21
+ Name of the site where the simulation is being run.
22
+
23
+
24
+ Example
25
+ -------
26
+
27
+ Derive trigger rates for the South Alpha layout:
28
+
29
+ .. code-block:: console
30
+
31
+ simtools-derive-trigger-rates \\
32
+ --site South \\
33
+ --model_version 6.0.0 \\
34
+ --event_data_file /path/to/event_data_file.h5 \\
35
+ --array_layout_name alpha\\
36
+ --plot_histograms
37
+
38
+ """
39
+
40
+ import logging
41
+
42
+ import simtools.utils.general as gen
43
+ from simtools.configuration import configurator
44
+ from simtools.telescope_trigger_rates import telescope_trigger_rates
45
+
46
+
47
+ def _parse():
48
+ """Parse command line configuration."""
49
+ config = configurator.Configurator(
50
+ description="Derive trigger rates for a single telescope or an array of telescopes.",
51
+ )
52
+ config.parser.add_argument(
53
+ "--event_data_file",
54
+ type=str,
55
+ required=True,
56
+ help="Event data file containing reduced event data.",
57
+ )
58
+ config.parser.add_argument(
59
+ "--telescope_ids",
60
+ type=str,
61
+ required=False,
62
+ help="Path to a file containing telescope configurations.",
63
+ )
64
+ config.parser.add_argument(
65
+ "--plot_histograms",
66
+ help="Plot histograms of the event data.",
67
+ action="store_true",
68
+ default=False,
69
+ )
70
+ return config.initialize(
71
+ db_config=True,
72
+ output=True,
73
+ simulation_model=[
74
+ "site",
75
+ "model_version",
76
+ "layout",
77
+ ],
78
+ )
79
+
80
+
81
+ def main(): # noqa: D103
82
+ args_dict, db_config = _parse()
83
+
84
+ logger = logging.getLogger()
85
+ logger.setLevel(gen.get_log_level_from_user(args_dict.get("log_level", "info")))
86
+
87
+ telescope_trigger_rates(args_dict, db_config)
88
+
89
+
90
+ if __name__ == "__main__":
91
+ main()
@@ -165,15 +165,11 @@ r"""
165
165
  """
166
166
 
167
167
  import logging
168
- import re
169
168
  import time
170
169
  from pathlib import Path
171
170
 
172
- import numpy as np
173
-
174
171
  import simtools.utils.general as gen
175
172
  from simtools.configuration import configurator
176
- from simtools.corsika import corsika_histograms_visualize
177
173
  from simtools.corsika.corsika_histograms import CorsikaHistograms
178
174
  from simtools.io import io_handler
179
175
 
@@ -284,134 +280,6 @@ def _parse(label, description):
284
280
  return config_parser, _
285
281
 
286
282
 
287
- def _plot_figures(corsika_histograms_instance, test=False):
288
- """
289
- Auxiliary function to centralize the plotting functions.
290
-
291
- Parameters
292
- ----------
293
- corsika_histograms_instance: CorsikaHistograms instance.
294
- The CorsikaHistograms instance created in main.
295
- test: bool
296
- If true plots the figures for the first two functions only.
297
- """
298
- plot_function_names = [
299
- plotting_method
300
- for plotting_method in dir(corsika_histograms_visualize)
301
- if plotting_method.startswith("plot_")
302
- and "event_header_distribution" not in plotting_method
303
- ]
304
- if test:
305
- plot_function_names = plot_function_names[:2]
306
-
307
- figure_list = []
308
- for function_name in plot_function_names:
309
- plot_function = getattr(corsika_histograms_visualize, function_name)
310
- figures = plot_function(corsika_histograms_instance)
311
- for fig in figures:
312
- figure_list.append(fig)
313
-
314
- figure_list = np.array(figure_list).flatten()
315
- core_name = re.sub(r"\.hdf5$", "", corsika_histograms_instance.hdf5_file_name)
316
- output_file_name = Path(corsika_histograms_instance.output_path).joinpath(f"{core_name}.pdf")
317
- corsika_histograms_visualize.save_figs_to_pdf(figure_list, output_file_name)
318
-
319
-
320
- def _derive_event_1d_histograms(
321
- corsika_histograms_instance, event_1d_header_keys, pdf, hdf5, overwrite=False
322
- ):
323
- """
324
- Auxiliary function to derive the histograms for the arguments given by event_1d_histograms.
325
-
326
- Parameters
327
- ----------
328
- corsika_histograms_instance: CorsikaHistograms instance.
329
- The CorsikaHistograms instance created in main.
330
- event_1d_header_keys: str
331
- Generate 1D histograms for elements given in event_1d_header_keys from the CORSIKA event
332
- header and save into hdf5/pdf files.
333
- pdf: bool
334
- If true, histograms are saved into a pdf file.
335
- hdf5: bool
336
- If true, histograms are saved into hdf5 files.
337
- overwrite: bool
338
- If true, overwrites the current output hdf5 file.
339
- """
340
- figure_list = []
341
- for event_header_element in event_1d_header_keys:
342
- if pdf:
343
- figure = corsika_histograms_visualize.plot_1d_event_header_distribution(
344
- corsika_histograms_instance, event_header_element
345
- )
346
- figure_list.append(figure)
347
- if hdf5:
348
- corsika_histograms_instance.export_event_header_1d_histogram(
349
- event_header_element, bins=50, hist_range=None, overwrite=overwrite
350
- )
351
- if pdf:
352
- figures_list = np.array(figure_list).flatten()
353
- output_file_name = Path(corsika_histograms_instance.output_path).joinpath(
354
- f"{corsika_histograms_instance.hdf5_file_name}_event_1d_histograms.pdf"
355
- )
356
- corsika_histograms_visualize.save_figs_to_pdf(figures_list, output_file_name)
357
-
358
-
359
- def _derive_event_2d_histograms(
360
- corsika_histograms_instance, event_2d_header_keys, pdf, hdf5, overwrite=False
361
- ):
362
- """
363
- Auxiliary function to derive the histograms for the arguments given by event_1d_histograms.
364
-
365
- If an odd number of event header keys are given, the last one is discarded.
366
-
367
- Parameters
368
- ----------
369
- corsika_histograms_instance: CorsikaHistograms instance.
370
- The CorsikaHistograms instance created in main.
371
- event_2d_header_keys: str
372
- Generate 1D histograms for elements given in event_1d_header_keys from the CORSIKA event
373
- header and save into hdf5/pdf files.
374
- pdf: bool
375
- If true, histograms are saved into a pdf file.
376
- hdf5: bool
377
- If true, histograms are saved into hdf5 files.
378
- overwrite: bool
379
- If true, overwrites the current output hdf5 file.
380
- """
381
- figure_list = []
382
- for i_event_header_element, _ in enumerate(event_2d_header_keys[::2]):
383
- # [::2] to discard the last one in case an odd number of keys are passed
384
-
385
- if len(event_2d_header_keys) % 2 == 1: # if odd number of keys
386
- msg = (
387
- "An odd number of keys was passed to generate 2D histograms."
388
- "The last key is being ignored."
389
- )
390
- logger.warning(msg)
391
-
392
- if pdf:
393
- figure = corsika_histograms_visualize.plot_2d_event_header_distribution(
394
- corsika_histograms_instance,
395
- event_2d_header_keys[i_event_header_element],
396
- event_2d_header_keys[i_event_header_element + 1],
397
- )
398
- figure_list.append(figure)
399
- if hdf5:
400
- corsika_histograms_instance.export_event_header_2d_histogram(
401
- event_2d_header_keys[i_event_header_element],
402
- event_2d_header_keys[i_event_header_element + 1],
403
- bins=50,
404
- hist_range=None,
405
- overwrite=overwrite,
406
- )
407
- if pdf:
408
- figures_list = np.array(figure_list).flatten()
409
- output_file_name = Path(corsika_histograms_instance.output_path).joinpath(
410
- f"{corsika_histograms_instance.hdf5_file_name}_event_2d_histograms.pdf"
411
- )
412
- corsika_histograms_visualize.save_figs_to_pdf(figures_list, output_file_name)
413
-
414
-
415
283
  def main(): # noqa: D103
416
284
  label = Path(__file__).stem
417
285
  description = "Generate histograms for the Cherenkov photons saved in the CORSIKA IACT file."
@@ -427,62 +295,17 @@ def main(): # noqa: D103
427
295
  corsika_histograms_instance = CorsikaHistograms(
428
296
  args_dict["iact_file"], output_path=output_path, hdf5_file_name=args_dict["hdf5_file_name"]
429
297
  )
430
- if args_dict["telescope_indices"] is not None:
431
- try:
432
- indices = np.array(args_dict["telescope_indices"]).astype(int)
433
- except ValueError:
434
- msg = (
435
- f"{args_dict['telescope_indices']} not a valid input. "
436
- f"Please use integer numbers for telescope_indices"
437
- )
438
- logger.error(msg)
439
- raise
440
- else:
441
- indices = None
442
- # If the hdf5 output file already exists, the results are appended to it.
443
- if (Path(corsika_histograms_instance.hdf5_file_name).exists()) and (
444
- args_dict["hdf5"] or args_dict["event_1d_histograms"] or args_dict["event_2d_histograms"]
445
- ):
446
- msg = (
447
- f"Output hdf5 file {corsika_histograms_instance.hdf5_file_name} already exists. "
448
- f"Overwriting it."
449
- )
450
- logger.warning(msg)
451
- overwrite = True
452
- else:
453
- overwrite = False
454
- corsika_histograms_instance.set_histograms(
455
- telescope_indices=indices,
298
+ corsika_histograms_instance.run_export_pipeline(
456
299
  individual_telescopes=args_dict["individual_telescopes"],
457
300
  hist_config=args_dict["hist_config"],
301
+ indices_arg=args_dict["telescope_indices"],
302
+ write_pdf=args_dict["pdf"],
303
+ write_hdf5=args_dict["hdf5"],
304
+ event1d=args_dict["event_1d_histograms"],
305
+ event2d=args_dict["event_2d_histograms"],
306
+ test=args_dict["test"],
458
307
  )
459
308
 
460
- # Cherenkov photons
461
- if args_dict["pdf"]:
462
- _plot_figures(
463
- corsika_histograms_instance=corsika_histograms_instance, test=args_dict["test"]
464
- )
465
- if args_dict["hdf5"]:
466
- corsika_histograms_instance.export_histograms(overwrite=overwrite)
467
-
468
- # Event information
469
- if args_dict["event_1d_histograms"] is not None:
470
- _derive_event_1d_histograms(
471
- corsika_histograms_instance,
472
- args_dict["event_1d_histograms"],
473
- args_dict["pdf"],
474
- args_dict["hdf5"],
475
- overwrite=not args_dict["hdf5"],
476
- )
477
- if args_dict["event_2d_histograms"] is not None:
478
- _derive_event_2d_histograms(
479
- corsika_histograms_instance,
480
- args_dict["event_2d_histograms"],
481
- args_dict["pdf"],
482
- args_dict["hdf5"],
483
- overwrite=not (args_dict["hdf5"] or args_dict["event_1d_histograms"]),
484
- )
485
-
486
309
  final_time = time.time()
487
310
  logger.info(
488
311
  f"Finalizing the application. Total time needed: {round(final_time - initial_time)}s."
@@ -0,0 +1,105 @@
1
+ r"""
2
+ Generate a new simulation model production and update tables and model parameters.
3
+
4
+ This script is used to maintain the simulation model repository. It allows to create
5
+ new production tables by copying an existing base version and applies modifications
6
+ to production tables and model parameters as provided in a YAML file (see the example file below).
7
+
8
+ Two main use cases are covered by this script:
9
+
10
+ 1. full_update: Create a complete new set of production tables (e.g. for new major or minor
11
+ versions of the simulation models). This will copy all production tables from the source
12
+ directory and apply the modifications to the tables that are listed in the modifications file.
13
+
14
+ 2. patch_update: Create a set of new production tables including the changes defined in the
15
+ modifications file. No unmodified tables are copied. For new production tables with patch
16
+ modifications, the key-value pair 'base_model_version: <base_model version>' is added.
17
+
18
+ Both use cases will also apply the modifications to the model parameters as defined in the
19
+ modifications file.
20
+
21
+ Example
22
+ -------
23
+
24
+ The following example applies a patch update with changes defined in a YAML file.
25
+
26
+ .. code-block:: console
27
+
28
+ simtools-maintain-simulation-model-add-new-production \\
29
+ --simulation_models_path ../simulation-models-dev/simulation-models/ \\
30
+ --base_model_version 6.0.0 \\
31
+ --modifications tests/resources/production_tables_changes_for_threshold_study_6.2.0.yml \\
32
+ --patch_update
33
+
34
+ """
35
+
36
+ import logging
37
+ from pathlib import Path
38
+
39
+ import simtools.utils.general as gen
40
+ from simtools.configuration import configurator
41
+ from simtools.model import model_repository
42
+
43
+
44
+ def _parse(label, description):
45
+ """
46
+ Parse command line arguments.
47
+
48
+ Returns
49
+ -------
50
+ dict
51
+ Parsed command-line arguments.
52
+ """
53
+ config = configurator.Configurator(label=label, description=description)
54
+ config.parser.add_argument(
55
+ "--simulation_models_path",
56
+ type=str,
57
+ required=True,
58
+ help="Path to the simulation models repository.",
59
+ )
60
+ config.parser.add_argument(
61
+ "--base_model_version",
62
+ type=str,
63
+ required=True,
64
+ help="Base model version (which is the source production table subdirectory to copy from).",
65
+ )
66
+ config.parser.add_argument(
67
+ "--modifications",
68
+ type=str,
69
+ required=True,
70
+ help="File containing the list of changes to apply.",
71
+ )
72
+ update_group = config.parser.add_mutually_exclusive_group(required=True)
73
+ update_group.add_argument(
74
+ "--full_update",
75
+ action="store_true",
76
+ default=False,
77
+ help=(
78
+ "Create a full new set of production tables by copying all tables from the "
79
+ "base version and applying the modifications to the relevant tables."
80
+ ),
81
+ )
82
+ update_group.add_argument(
83
+ "--patch_update",
84
+ action="store_true",
85
+ default=False,
86
+ help=(
87
+ "Create a new set of production tables including only the changes defined in the "
88
+ "modifications file. No unmodified tables are copied."
89
+ ),
90
+ )
91
+
92
+ return config.initialize(db_config=False, output=False)
93
+
94
+
95
+ def main(): # noqa: D103
96
+ label = Path(__file__).stem
97
+ args_dict, _ = _parse(label=label, description="Generate a new simulation model production")
98
+ logger = logging.getLogger()
99
+ logger.setLevel(gen.get_log_level_from_user(args_dict["log_level"]))
100
+
101
+ model_repository.generate_new_production(args_dict)
102
+
103
+
104
+ if __name__ == "__main__":
105
+ main()