gammasimtools 0.10.0__py3-none-any.whl → 0.11.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 (84) hide show
  1. {gammasimtools-0.10.0.dist-info → gammasimtools-0.11.0.dist-info}/METADATA +3 -1
  2. {gammasimtools-0.10.0.dist-info → gammasimtools-0.11.0.dist-info}/RECORD +84 -77
  3. {gammasimtools-0.10.0.dist-info → gammasimtools-0.11.0.dist-info}/WHEEL +1 -1
  4. {gammasimtools-0.10.0.dist-info → gammasimtools-0.11.0.dist-info}/entry_points.txt +4 -0
  5. simtools/_version.py +9 -4
  6. simtools/applications/convert_all_model_parameters_from_simtel.py +0 -1
  7. simtools/applications/convert_model_parameter_from_simtel.py +0 -1
  8. simtools/applications/db_add_file_to_db.py +0 -1
  9. simtools/applications/db_get_parameter_from_db.py +7 -28
  10. simtools/applications/derive_mirror_rnda.py +1 -2
  11. simtools/applications/derive_psf_parameters.py +1 -0
  12. simtools/applications/docs_produce_array_element_report.py +71 -0
  13. simtools/applications/docs_produce_model_parameter_reports.py +63 -0
  14. simtools/applications/generate_corsika_histograms.py +2 -2
  15. simtools/applications/generate_regular_arrays.py +4 -2
  16. simtools/applications/production_derive_limits.py +95 -0
  17. simtools/applications/production_generate_simulation_config.py +15 -29
  18. simtools/applications/production_scale_events.py +2 -7
  19. simtools/applications/run_application.py +165 -0
  20. simtools/applications/simulate_light_emission.py +0 -4
  21. simtools/applications/submit_model_parameter_from_external.py +11 -6
  22. simtools/applications/validate_file_using_schema.py +3 -3
  23. simtools/configuration/commandline_parser.py +29 -0
  24. simtools/configuration/configurator.py +8 -10
  25. simtools/corsika/corsika_config.py +11 -10
  26. simtools/corsika/corsika_histograms.py +4 -6
  27. simtools/corsika/corsika_histograms_visualize.py +2 -4
  28. simtools/data_model/metadata_collector.py +18 -9
  29. simtools/data_model/model_data_writer.py +67 -15
  30. simtools/data_model/schema.py +10 -3
  31. simtools/data_model/validate_data.py +70 -24
  32. simtools/db/db_handler.py +42 -12
  33. simtools/dependencies.py +112 -0
  34. simtools/layout/array_layout.py +5 -4
  35. simtools/model/model_parameter.py +35 -2
  36. simtools/production_configuration/calculate_statistical_errors_grid_point.py +5 -6
  37. simtools/production_configuration/event_scaler.py +3 -19
  38. simtools/production_configuration/generate_simulation_config.py +4 -12
  39. simtools/production_configuration/interpolation_handler.py +2 -5
  40. simtools/production_configuration/limits_calculation.py +202 -0
  41. simtools/reporting/docs_read_parameters.py +310 -0
  42. simtools/runners/corsika_simtel_runner.py +1 -3
  43. simtools/schemas/{integration_tests_config.metaschema.yml → application_workflow.metaschema.yml} +51 -27
  44. simtools/schemas/array_elements.yml +8 -0
  45. simtools/schemas/model_parameter.metaschema.yml +96 -0
  46. simtools/schemas/model_parameter_and_data_schema.metaschema.yml +2 -1
  47. simtools/schemas/model_parameters/correct_nsb_spectrum_to_telescope_altitude.schema.yml +1 -1
  48. simtools/schemas/model_parameters/corsika_cherenkov_photon_bunch_size.schema.yml +2 -0
  49. simtools/schemas/model_parameters/corsika_cherenkov_photon_wavelength_range.schema.yml +2 -0
  50. simtools/schemas/model_parameters/corsika_first_interaction_height.schema.yml +2 -0
  51. simtools/schemas/model_parameters/corsika_iact_io_buffer.schema.yml +2 -0
  52. simtools/schemas/model_parameters/corsika_iact_max_bunches.schema.yml +2 -0
  53. simtools/schemas/model_parameters/corsika_iact_split_auto.schema.yml +2 -0
  54. simtools/schemas/model_parameters/corsika_longitudinal_shower_development.schema.yml +2 -0
  55. simtools/schemas/model_parameters/corsika_particle_kinetic_energy_cutoff.schema.yml +2 -0
  56. simtools/schemas/model_parameters/corsika_starting_grammage.schema.yml +2 -0
  57. simtools/schemas/model_parameters/iobuf_maximum.schema.yml +1 -1
  58. simtools/schemas/model_parameters/iobuf_output_maximum.schema.yml +1 -1
  59. simtools/schemas/model_parameters/lightguide_efficiency_vs_incidence_angle.schema.yml +1 -1
  60. simtools/schemas/model_parameters/lightguide_efficiency_vs_wavelength.schema.yml +1 -1
  61. simtools/schemas/model_parameters/min_photoelectrons.schema.yml +1 -1
  62. simtools/schemas/model_parameters/min_photons.schema.yml +1 -1
  63. simtools/schemas/model_parameters/random_generator.schema.yml +1 -1
  64. simtools/schemas/model_parameters/sampled_output.schema.yml +1 -1
  65. simtools/schemas/model_parameters/save_pe_with_amplitude.schema.yml +1 -1
  66. simtools/schemas/model_parameters/store_photoelectrons.schema.yml +1 -1
  67. simtools/schemas/model_parameters/tailcut_scale.schema.yml +1 -1
  68. simtools/schemas/production_tables.schema.yml +1 -1
  69. simtools/simtel/simtel_config_reader.py +1 -2
  70. simtools/simtel/simtel_config_writer.py +1 -2
  71. simtools/simtel/simtel_io_histogram.py +0 -1
  72. simtools/simtel/simtel_io_histograms.py +2 -4
  73. simtools/simtel/simulator_camera_efficiency.py +1 -3
  74. simtools/simtel/simulator_light_emission.py +2 -5
  75. simtools/simtel/simulator_ray_tracing.py +1 -3
  76. simtools/testing/configuration.py +2 -1
  77. simtools/testing/validate_output.py +23 -13
  78. simtools/utils/general.py +12 -2
  79. simtools/utils/names.py +290 -152
  80. simtools/utils/value_conversion.py +17 -13
  81. simtools/version.py +2 -2
  82. simtools/visualization/legend_handlers.py +2 -0
  83. {gammasimtools-0.10.0.dist-info → gammasimtools-0.11.0.dist-info}/LICENSE +0 -0
  84. {gammasimtools-0.10.0.dist-info → gammasimtools-0.11.0.dist-info}/top_level.txt +0 -0
@@ -152,7 +152,7 @@ r"""
152
152
  .. code-block:: console
153
153
 
154
154
  simtools-generate-corsika-histograms --iact_file /workdir/external/simtools/\\
155
- testsresources/tel_output_10GeV-2-gamma-20deg-CTAO-South.corsikaio \\
155
+ tests/resources/tel_output_10GeV-2-gamma-20deg-CTAO-South.corsikaio \\
156
156
  --pdf --hdf5 \\
157
157
  --event_2d_histograms zenith azimuth --event_1d_histograms total_energy
158
158
 
@@ -208,7 +208,7 @@ def _parse(label, description):
208
208
 
209
209
  config.parser.add_argument(
210
210
  "--telescope_indices",
211
- help="Name of the CORSIKA IACT file from which to generate the histograms.",
211
+ help="List of telescope indices to be considered in the generation of the histograms",
212
212
  type=str,
213
213
  required=False,
214
214
  nargs="+",
@@ -77,7 +77,9 @@ def main():
77
77
  # Single telescope at the center
78
78
  if array_name[0] == "1":
79
79
  tel_name.append(
80
- names.get_array_element_name_from_type_site_id(tel_size, args_dict["site"], "01")
80
+ names.generate_array_element_name_from_type_site_id(
81
+ tel_size, args_dict["site"], "01"
82
+ )
81
83
  )
82
84
  pos_x.append(0 * u.m)
83
85
  pos_y.append(0 * u.m)
@@ -86,7 +88,7 @@ def main():
86
88
  else:
87
89
  for i in range(1, 5):
88
90
  tel_name.append(
89
- names.get_array_element_name_from_type_site_id(
91
+ names.generate_array_element_name_from_type_site_id(
90
92
  tel_size, args_dict["site"], f"0{i}"
91
93
  )
92
94
  )
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/python3
2
+
3
+ r"""
4
+ Derives the limits for energy, radial distance, and viewcone to be used in CORSIKA simulations.
5
+
6
+ The limits are derived based on the event loss fraction specified by the user.
7
+
8
+ Command line arguments
9
+ ----------------------
10
+ event_data_file (str, required)
11
+ Path to the file containing the event data.
12
+ loss_fraction (float, required)
13
+ Fraction of events to be lost.
14
+
15
+
16
+ Example
17
+ -------
18
+ Derive limits for a given file with a specified loss fraction.
19
+
20
+ .. code-block:: console
21
+
22
+ simtools-production-derive-limits\\
23
+ --event_data_file path/to/event_data_file.hdf5 \\
24
+ --loss_fraction 1e-6
25
+ """
26
+
27
+ import logging
28
+
29
+ import simtools.utils.general as gen
30
+ from simtools.configuration import configurator
31
+ from simtools.io_operations.hdf5_handler import read_hdf5
32
+ from simtools.production_configuration.limits_calculation import LimitCalculator
33
+
34
+ _logger = logging.getLogger(__name__)
35
+
36
+
37
+ def _parse():
38
+ """
39
+ Parse command line configuration.
40
+
41
+ Parameters
42
+ ----------
43
+ event_data_file: str
44
+ The event data file.
45
+ loss_fraction: float
46
+ Loss fraction of events for limit derivation.
47
+
48
+ Returns
49
+ -------
50
+ CommandLineParser
51
+ Command line parser object
52
+
53
+ """
54
+ config = configurator.Configurator(
55
+ description="Derive limits for energy, radial distance, and viewcone."
56
+ )
57
+ config.parser.add_argument(
58
+ "--event_data_file",
59
+ type=str,
60
+ required=True,
61
+ help="Path to the event data file containing the event data.",
62
+ )
63
+ config.parser.add_argument(
64
+ "--loss_fraction", type=float, required=True, help="Fraction of events to be lost."
65
+ )
66
+ return config.initialize(db_config=False)
67
+
68
+
69
+ def main():
70
+ """Derive limits for energy, radial distance, and viewcone."""
71
+ args_dict, _ = _parse()
72
+
73
+ logger = logging.getLogger()
74
+ logger.setLevel(gen.get_log_level_from_user(args_dict["log_level"]))
75
+
76
+ event_data_file_path = args_dict["event_data_file"]
77
+ loss_fraction = args_dict["loss_fraction"]
78
+
79
+ _logger.info(f"Loading event data file: {event_data_file_path}")
80
+ tables = read_hdf5(event_data_file_path)
81
+
82
+ calculator = LimitCalculator(tables)
83
+
84
+ lower_energy_limit = calculator.compute_lower_energy_limit(loss_fraction)
85
+ _logger.info(f"Lower energy threshold: {lower_energy_limit}")
86
+
87
+ upper_radial_distance = calculator.compute_upper_radial_distance(loss_fraction)
88
+ _logger.info(f"Upper radius threshold: {upper_radial_distance}")
89
+
90
+ viewcone = calculator.compute_viewcone(loss_fraction)
91
+ _logger.info(f"Viewcone radius: {viewcone}")
92
+
93
+
94
+ if __name__ == "__main__":
95
+ main()
@@ -18,28 +18,25 @@ The configuration parameters are derived according to the required precision. Th
18
18
  * statistical uncertainty on the determination of the effective area as function of primary energy
19
19
  * fraction of lost events to the selected core scatter and view cone radius (to be implemented)
20
20
  * statistical uncertainty of the energy migration matrix as function of primary energy
21
- (to be implemented)
21
+ (to be implemented)
22
22
 
23
23
  Command line arguments
24
24
  ----------------------
25
25
  azimuth (float, required)
26
- Azimuth angle in degrees.
26
+ Azimuth angle in degrees.
27
27
  elevation (float, required)
28
- Elevation angle in degrees.
28
+ Elevation angle in degrees.
29
29
  nsb (float, required)
30
- Night sky background value.
31
- ctao_data_level (str, required)
32
- The data level for the simulation (e.g., 'A', 'B', 'C').
33
- science_case (str, required)
34
- The science case for the simulation.
30
+ Night sky background value.
35
31
  file_path (str, required)
36
- Path to file with MC events at CTAO DL2 data level. Used for statistical uncertainty evaluation.
32
+ Path to file with MC events at CTAO DL2 data level.
33
+ Used for statistical uncertainty evaluation.
37
34
  file_type (str, required)
38
- Type of the DL2 MC event file ('point-like' or 'cone').
39
- metrics (str, optional)
40
- Path to a YAML file containing metrics for evaluation.
35
+ Type of the DL2 MC event file ('point-like' or 'cone').
36
+ metrics (str, required)
37
+ Path to a YAML file containing metrics for evaluation.
41
38
  site (str, required)
42
- The observatory site (North or South).
39
+ The observatory site (North or South).
43
40
 
44
41
  Example
45
42
  -------
@@ -47,11 +44,10 @@ To run the simulation configuration, execute the script as follows:
47
44
 
48
45
  .. code-block:: console
49
46
 
50
- simtools-production-generate-simulation-config --azimuth 60.0 --elevation 45.0 \
51
- --nsb 0.3 --ctao_data_level "A" --science_case "high_precision" \
52
- --file_path tests/resources/production_dl2_fits/dl2_mc_events_file.fits \
53
- --file_type "point-like" \
54
- --metrics_file tests/resources/production_simulation_config_metrics.yml --site North
47
+ simtools-production-generate-simulation-config --azimuth 60.0 --elevation 45.0 \
48
+ --nsb 0.3 --file_path tests/resources/production_dl2_fits/dl2_mc_events_file.fits \
49
+ --file_type "point-like" \
50
+ --metrics_file tests/resources/production_simulation_config_metrics.yml --site North
55
51
 
56
52
  The output will show the derived simulation parameters.
57
53
  """
@@ -86,12 +82,6 @@ def _parse(label):
86
82
  config.parser.add_argument(
87
83
  "--nsb", type=float, required=True, help="Night sky background in units of 1/(sr*ns*cm**2)."
88
84
  )
89
- config.parser.add_argument(
90
- "--ctao_data_level", type=str, required=True, help="Data level (e.g., 'A', 'B', 'C')."
91
- )
92
- config.parser.add_argument(
93
- "--science_case", type=str, required=True, help="Science case for the simulation."
94
- )
95
85
  config.parser.add_argument(
96
86
  "--file_path", type=str, required=True, help="Path to MC event file in DL2 format."
97
87
  )
@@ -140,17 +130,13 @@ def main():
140
130
  "night_sky_background": args_dict["nsb"],
141
131
  }
142
132
 
143
- metrics = (
144
- gen.collect_data_from_file(args_dict["metrics_file"]) if "metrics_file" in args_dict else {}
145
- )
133
+ metrics = gen.collect_data_from_file(args_dict["metrics_file"])
146
134
  schema.validate_dict_using_schema(
147
135
  data=metrics, schema_file="production_configuration_metrics.schema.yml"
148
136
  )
149
137
 
150
138
  simulation_config = SimulationConfig(
151
139
  grid_point=grid_point_config,
152
- ctao_data_level=args_dict["ctao_data_level"],
153
- science_case=args_dict["science_case"],
154
140
  file_path=args_dict["file_path"],
155
141
  file_type=args_dict["file_type"],
156
142
  metrics=metrics,
@@ -28,7 +28,7 @@ To evaluate statistical uncertainties and perform interpolation, run the command
28
28
 
29
29
  simtools-production-scale-events --base_path tests/resources/production_dl2_fits/ \
30
30
  --zeniths 20 52 40 60 --offsets 0 --interpolate --query_point 1 180 30 0 0 \
31
- --science_case 1 --metrics_file "path/to/metrics.yaml"
31
+ --metrics_file "path/to/metrics.yaml"
32
32
 
33
33
 
34
34
  The output will display the scaled events for the specified grid point.
@@ -96,9 +96,6 @@ def _parse(label, description):
96
96
  default="production_simulation_config_metrics.yml",
97
97
  help="Metrics definition file. (default: production_simulation_config_metrics.yml)",
98
98
  )
99
- config.parser.add_argument(
100
- "--science_case", type=str, required=True, help="Science case for the simulation."
101
- )
102
99
  return config.initialize(db_config=False)
103
100
 
104
101
 
@@ -147,9 +144,7 @@ def main():
147
144
  logger.warning(f"Offsets: {args_dict['offsets']}")
148
145
 
149
146
  # Perform interpolation for the given query point
150
- interpolation_handler = InterpolationHandler(
151
- evaluator_instances, science_case=args_dict["science_case"], metrics=metrics
152
- )
147
+ interpolation_handler = InterpolationHandler(evaluator_instances, metrics=metrics)
153
148
  query_points = np.array([args_dict["query_point"]])
154
149
  scaled_events = interpolation_handler.interpolate(query_points)
155
150
 
@@ -0,0 +1,165 @@
1
+ #!/usr/bin/python3
2
+
3
+ """
4
+ Run simtools applications from configuration files for setting workflows.
5
+
6
+ Allows to run several simtools applications with a single configuration file, which includes
7
+ both the name of the simtools application and the configuration for the application.
8
+
9
+ Strong assumption on the directory structure for input and output files of applications.
10
+
11
+ """
12
+
13
+ import logging
14
+ import subprocess
15
+ import tempfile
16
+ from pathlib import Path
17
+
18
+ import yaml
19
+
20
+ import simtools.utils.general as gen
21
+ from simtools import dependencies
22
+ from simtools.configuration import configurator
23
+
24
+
25
+ def _parse(label, description, usage):
26
+ """
27
+ Parse command line configuration.
28
+
29
+ Parameters
30
+ ----------
31
+ label : str
32
+ Label describing the application.
33
+ description : str
34
+ Description of the application.
35
+ usage : str
36
+ Example on how to use the application.
37
+
38
+ Returns
39
+ -------
40
+ CommandLineParser
41
+ Command line parser object.
42
+ """
43
+ config = configurator.Configurator(label=label, description=description, usage=usage)
44
+
45
+ config.parser.add_argument(
46
+ "--configuration_file",
47
+ help="Application configuration.",
48
+ type=str,
49
+ required=True,
50
+ default=None,
51
+ )
52
+ return config.initialize(db_config=False)
53
+
54
+
55
+ def run_application(application, configuration, logger):
56
+ """Run a simtools application and return stdout and stderr."""
57
+ with tempfile.NamedTemporaryFile(mode="w", delete=True, suffix=".yml") as temp_config:
58
+ yaml.dump(configuration, temp_config, default_flow_style=False)
59
+ temp_config.flush()
60
+ configuration_file = Path(temp_config.name)
61
+ try:
62
+ result = subprocess.run(
63
+ [application, "--config", configuration_file],
64
+ check=True,
65
+ capture_output=True,
66
+ text=True,
67
+ )
68
+ except subprocess.CalledProcessError as exc:
69
+ logger.error(f"Error running application {application}: {exc.stderr}")
70
+ raise exc
71
+ return result.stdout, result.stderr
72
+
73
+
74
+ def get_subdirectory_name(path):
75
+ """Get the first subdirectory name under 'input'."""
76
+ path = Path(path).resolve()
77
+ try:
78
+ input_index = path.parts.index("input")
79
+ return path.parts[input_index], path.parts[input_index + 1]
80
+ except (ValueError, IndexError) as exc:
81
+ raise ValueError(f"Could not find subdirectory under 'input': {exc}") from exc
82
+
83
+
84
+ def read_application_configuration(configuration_file, logger):
85
+ """
86
+ Read application configuration from file and modify for setting workflows.
87
+
88
+ Strong assumptions on the structure of input and output files:
89
+
90
+ - configuration file is expected to be in './input/<workflow directory>/<yaml file>'
91
+ - output files will be written out to './output/<workflow directory>/'
92
+
93
+ Replaces the placeholders in the configuration file with the actual values.
94
+ Sets 'USE_PLAIN_OUTPUT_PATH' to True for all applications.
95
+
96
+ Parameters
97
+ ----------
98
+ configuration_file : str
99
+ Configuration file name.
100
+ logger : Logger
101
+ Logger object.
102
+
103
+ Returns
104
+ -------
105
+ dict
106
+ Application configuration.
107
+ Path
108
+ Path to the log file.
109
+
110
+ """
111
+ application_config = gen.collect_data_from_file(configuration_file).get("CTA_SIMPIPE")
112
+ place_holder = "__SETTING_WORKFLOW__"
113
+ workflow_dir, setting_workflow = get_subdirectory_name(configuration_file)
114
+ output_path = str(workflow_dir).replace("input", "output") + setting_workflow
115
+ logger.info(f"Setting workflow output path to {output_path}")
116
+ log_file = (
117
+ Path(application_config.get("LOG_PATH", "./").replace(place_holder, setting_workflow))
118
+ / "simtools.log"
119
+ )
120
+ log_file.parent.mkdir(parents=True, exist_ok=True)
121
+ configurations = application_config.get("APPLICATIONS")
122
+ for config in configurations:
123
+ for key, value in config.get("CONFIGURATION", {}).items():
124
+ if isinstance(value, str):
125
+ config["CONFIGURATION"][key] = value.replace(place_holder, setting_workflow)
126
+ if isinstance(value, list):
127
+ config["CONFIGURATION"][key] = [
128
+ item.replace(place_holder, setting_workflow) for item in value
129
+ ]
130
+ config["CONFIGURATION"]["USE_PLAIN_OUTPUT_PATH"] = True
131
+ config["OUTPUT_PATH"] = output_path
132
+
133
+ return configurations, log_file
134
+
135
+
136
+ def main(): # noqa: D103
137
+ args_dict, _ = _parse(
138
+ Path(__file__).stem,
139
+ description="Run simtools applications from configuration file.",
140
+ usage="simtools-run-application --config_file config_file_name",
141
+ )
142
+ logger = logging.getLogger()
143
+ logger.setLevel(gen.get_log_level_from_user(args_dict["log_level"]))
144
+
145
+ configurations, log_file = read_application_configuration(
146
+ args_dict["configuration_file"], logger
147
+ )
148
+
149
+ with log_file.open("w", encoding="utf-8") as file:
150
+ file.write("Running simtools applications\n")
151
+ file.write(dependencies.get_version_string())
152
+ for config in configurations:
153
+ logger.info(f"Running application: {config.get('APPLICATION')}")
154
+ config = gen.change_dict_keys_case(config, False)
155
+ stdout, stderr = run_application(
156
+ config.get("APPLICATION"), config.get("CONFIGURATION"), logger
157
+ )
158
+ file.write("=" * 80 + "\n")
159
+ file.write(f"Application: {config.get('APPLICATION')}\n")
160
+ file.write("STDOUT:\n" + stdout)
161
+ file.write("STDERR:\n" + stderr)
162
+
163
+
164
+ if __name__ == "__main__":
165
+ main()
@@ -117,7 +117,6 @@ r"""
117
117
 
118
118
  """
119
119
 
120
-
121
120
  import logging
122
121
  import subprocess
123
122
  from pathlib import Path
@@ -390,7 +389,6 @@ def main():
390
389
  run_script = light_source.prepare_script(generate_postscript=True, **args_dict)
391
390
  log_file = f"{light_source.output_directory}/logfile.log"
392
391
  with open(log_file, "w", encoding="utf-8") as log_file:
393
-
394
392
  subprocess.run(
395
393
  run_script,
396
394
  shell=False,
@@ -430,7 +428,6 @@ def main():
430
428
  )
431
429
 
432
430
  elif args_dict["light_source_setup"] == "layout":
433
-
434
431
  light_source = SimulatorLightEmission(
435
432
  telescope_model=telescope_model,
436
433
  calibration_model=calibration_model,
@@ -443,7 +440,6 @@ def main():
443
440
  run_script = light_source.prepare_script(generate_postscript=True, **args_dict)
444
441
  log_file = f"{light_source.output_directory}/logfile.log"
445
442
  with open(log_file, "w", encoding="utf-8") as log_file:
446
-
447
443
  subprocess.run(
448
444
  run_script, shell=False, check=False, text=True, stdout=log_file, stderr=log_file
449
445
  )
@@ -73,7 +73,6 @@ def _parse(label, description):
73
73
  config.parser.add_argument(
74
74
  "--parameter_version", type=str, required=True, help="Parameter version"
75
75
  )
76
-
77
76
  config.parser.add_argument(
78
77
  "--value",
79
78
  type=str,
@@ -84,18 +83,22 @@ def _parse(label, description):
84
83
  'Examples: "--value=5", "--value=\'5 km\'", "--value=\'5 cm, 0.5 deg\'"'
85
84
  ),
86
85
  )
87
-
88
86
  config.parser.add_argument(
89
87
  "--input_meta",
90
88
  help="meta data file associated to input data",
91
89
  type=str,
92
90
  required=False,
93
91
  )
94
- return config.initialize(output=True)
92
+ config.parser.add_argument(
93
+ "--check_parameter_version",
94
+ help="Check if the parameter version exists in the database",
95
+ action="store_true",
96
+ )
97
+ return config.initialize(output=True, db_config=True)
95
98
 
96
99
 
97
100
  def main(): # noqa: D103
98
- args_dict, _ = _parse(
101
+ args_dict, db_config = _parse(
99
102
  label=Path(__file__).stem,
100
103
  description="Submit and validate a model parameters).",
101
104
  )
@@ -104,19 +107,21 @@ def main(): # noqa: D103
104
107
  logger.setLevel(gen.get_log_level_from_user(args_dict["log_level"]))
105
108
 
106
109
  output_path = (
107
- Path(args_dict["output_path"]) / args_dict["parameter_version"] / args_dict["instrument"]
110
+ Path(args_dict["output_path"]) / args_dict["instrument"] / args_dict["parameter"]
108
111
  if args_dict.get("output_path")
109
112
  else None
110
113
  )
114
+
111
115
  writer.ModelDataWriter.dump_model_parameter(
112
116
  parameter_name=args_dict["parameter"],
113
117
  value=args_dict["value"],
114
118
  instrument=args_dict["instrument"],
115
119
  parameter_version=args_dict["parameter_version"],
116
- output_file=Path(args_dict["parameter"]).with_suffix(".json"),
120
+ output_file=Path(args_dict["parameter"] + "-" + args_dict["parameter_version"] + ".json"),
117
121
  output_path=output_path,
118
122
  use_plain_output_path=args_dict.get("use_plain_output_path"),
119
123
  metadata_input_dict=args_dict,
124
+ db_config=db_config if args_dict.get("check_parameter_version") else None,
120
125
  )
121
126
 
122
127
 
@@ -170,9 +170,9 @@ def validate_data_file(args_dict, logger):
170
170
  data_file=args_dict["file_name"],
171
171
  check_exact_data_type=args_dict["require_exact_data_type"],
172
172
  )
173
- data_validator.validate_and_transform(is_model_parameter=True)
174
- if args_dict["data_type"].lower() == "model_parameter":
175
- data_validator.validate_parameter_and_file_name()
173
+ data_validator.validate_and_transform(
174
+ is_model_parameter=(args_dict["data_type"].lower() == "model_parameter")
175
+ )
176
176
 
177
177
  logger.info(f"Successful validation of data file {args_dict['file_name']}")
178
178
 
@@ -70,6 +70,7 @@ class CommandLineParser(argparse.ArgumentParser):
70
70
  self.initialize_output_arguments()
71
71
  self.initialize_config_files()
72
72
  self.initialize_application_execution_arguments()
73
+ self.initialize_user_arguments()
73
74
 
74
75
  def initialize_config_files(self):
75
76
  """Initialize configuration files."""
@@ -175,6 +176,34 @@ class CommandLineParser(argparse.ArgumentParser):
175
176
  "--version", action="version", version=f"%(prog)s {simtools.version.__version__}"
176
177
  )
177
178
 
179
+ def initialize_user_arguments(self):
180
+ """Initialize user arguments."""
181
+ _job_group = self.add_argument_group("user")
182
+ _job_group.add_argument(
183
+ "--user_name",
184
+ help="user name",
185
+ type=str,
186
+ required=False,
187
+ )
188
+ _job_group.add_argument(
189
+ "--user_organization",
190
+ help="user organization",
191
+ type=str,
192
+ required=False,
193
+ )
194
+ _job_group.add_argument(
195
+ "--user_email",
196
+ help="user email",
197
+ type=str,
198
+ required=False,
199
+ )
200
+ _job_group.add_argument(
201
+ "--user_orcid",
202
+ help="user ORCID",
203
+ type=str,
204
+ required=False,
205
+ )
206
+
178
207
  def initialize_db_config_arguments(self):
179
208
  """Initialize DB configuration parameters."""
180
209
  _job_group = self.add_argument_group("database configuration")
@@ -287,16 +287,14 @@ class Configurator:
287
287
  )
288
288
  # yaml parser adds \n in multiline strings, remove them
289
289
  _config_dict = gen.remove_substring_recursively_from_dict(_config_dict, substring="\n")
290
- if "CTA_SIMPIPE" in _config_dict:
291
- try:
292
- self._fill_from_config_dict(
293
- input_dict=gen.change_dict_keys_case(
294
- _config_dict["CTA_SIMPIPE"]["CONFIGURATION"],
295
- ),
296
- overwrite=True,
297
- )
298
- except KeyError:
299
- self._logger.info(f"No CTA_SIMPIPE:CONFIGURATION dict found in {config_file}.")
290
+ # read configuration for first application
291
+ if "CONFIGURATION" in _config_dict.get("CTA_SIMPIPE", {}).get("APPLICATIONS", [{}])[0]:
292
+ self._fill_from_config_dict(
293
+ input_dict=gen.change_dict_keys_case(
294
+ _config_dict["CTA_SIMPIPE"]["APPLICATIONS"][0]["CONFIGURATION"],
295
+ ),
296
+ overwrite=True,
297
+ )
300
298
  else:
301
299
  self._fill_from_config_dict(
302
300
  input_dict=gen.change_dict_keys_case(_config_dict), overwrite=True
@@ -232,25 +232,25 @@ class CorsikaConfig:
232
232
 
233
233
  def _input_config_first_interaction_height(self, entry):
234
234
  """Return FIXHEI parameter CORSIKA format."""
235
- return [f"{entry['value']*u.Unit(entry['unit']).to('cm'):.2f}", "0"]
235
+ return [f"{entry['value'] * u.Unit(entry['unit']).to('cm'):.2f}", "0"]
236
236
 
237
237
  def _input_config_corsika_starting_grammage(self, entry):
238
238
  """Return FIXCHI parameter CORSIKA format."""
239
- return f"{entry['value']*u.Unit(entry['unit']).to('g/cm2')}"
239
+ return f"{entry['value'] * u.Unit(entry['unit']).to('g/cm2')}"
240
240
 
241
241
  def _input_config_corsika_particle_kinetic_energy_cutoff(self, entry):
242
242
  """Return ECUTS parameter CORSIKA format."""
243
243
  e_cuts = entry["value"]
244
244
  return [
245
- f"{e_cuts[0]*u.Unit(entry['unit']).to('GeV')} "
246
- f"{e_cuts[1]*u.Unit(entry['unit']).to('GeV')} "
247
- f"{e_cuts[2]*u.Unit(entry['unit']).to('GeV')} "
248
- f"{e_cuts[3]*u.Unit(entry['unit']).to('GeV')}"
245
+ f"{e_cuts[0] * u.Unit(entry['unit']).to('GeV')} "
246
+ f"{e_cuts[1] * u.Unit(entry['unit']).to('GeV')} "
247
+ f"{e_cuts[2] * u.Unit(entry['unit']).to('GeV')} "
248
+ f"{e_cuts[3] * u.Unit(entry['unit']).to('GeV')}"
249
249
  ]
250
250
 
251
251
  def _input_config_corsika_longitudinal_parameters(self, entry):
252
252
  """Return LONGI parameter CORSIKA format."""
253
- return ["T", f"{entry['value']*u.Unit(entry['unit']).to('g/cm2')}", "F", "F"]
253
+ return ["T", f"{entry['value'] * u.Unit(entry['unit']).to('g/cm2')}", "F", "F"]
254
254
 
255
255
  def _corsika_configuration_cherenkov_parameters(self, parameters_from_db):
256
256
  """
@@ -279,8 +279,8 @@ class CorsikaConfig:
279
279
  """Return CWAVLG parameter CORSIKA format."""
280
280
  wavelength_range = entry["value"]
281
281
  return [
282
- f"{wavelength_range[0]*u.Unit(entry['unit']).to('nm')}",
283
- f"{wavelength_range[1]*u.Unit(entry['unit']).to('nm')}",
282
+ f"{wavelength_range[0] * u.Unit(entry['unit']).to('nm')}",
283
+ f"{wavelength_range[1] * u.Unit(entry['unit']).to('nm')}",
284
284
  ]
285
285
 
286
286
  def _corsika_configuration_iact_parameters(self, parameters_from_db):
@@ -311,7 +311,8 @@ class CorsikaConfig:
311
311
  return {
312
312
  "DEBUG": ["F", 6, "F", 1000000],
313
313
  "DATBAS": ["yes"],
314
- "DIRECT": ["/dev/null"],
314
+ "DIRECT": ["./"],
315
+ "PAROUT": ["F", "F"],
315
316
  }
316
317
 
317
318
  def _input_config_io_buff(self, entry):