gammasimtools 0.9.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 (135) hide show
  1. {gammasimtools-0.9.0.dist-info → gammasimtools-0.11.0.dist-info}/METADATA +4 -2
  2. {gammasimtools-0.9.0.dist-info → gammasimtools-0.11.0.dist-info}/RECORD +133 -117
  3. {gammasimtools-0.9.0.dist-info → gammasimtools-0.11.0.dist-info}/WHEEL +1 -1
  4. {gammasimtools-0.9.0.dist-info → gammasimtools-0.11.0.dist-info}/entry_points.txt +6 -1
  5. simtools/_version.py +9 -4
  6. simtools/applications/calculate_trigger_rate.py +15 -38
  7. simtools/applications/convert_all_model_parameters_from_simtel.py +9 -29
  8. simtools/applications/convert_geo_coordinates_of_array_elements.py +47 -45
  9. simtools/applications/convert_model_parameter_from_simtel.py +2 -3
  10. simtools/applications/db_add_file_to_db.py +1 -3
  11. simtools/applications/db_add_simulation_model_from_repository_to_db.py +110 -0
  12. simtools/applications/db_add_value_from_json_to_db.py +1 -2
  13. simtools/applications/db_development_tools/write_array_elements_positions_to_repository.py +6 -6
  14. simtools/applications/db_get_file_from_db.py +11 -12
  15. simtools/applications/db_get_parameter_from_db.py +26 -35
  16. simtools/applications/derive_mirror_rnda.py +1 -2
  17. simtools/applications/derive_photon_electron_spectrum.py +99 -0
  18. simtools/applications/derive_psf_parameters.py +1 -0
  19. simtools/applications/docs_produce_array_element_report.py +71 -0
  20. simtools/applications/docs_produce_model_parameter_reports.py +63 -0
  21. simtools/applications/generate_array_config.py +17 -17
  22. simtools/applications/generate_corsika_histograms.py +2 -2
  23. simtools/applications/generate_regular_arrays.py +19 -17
  24. simtools/applications/generate_simtel_array_histograms.py +11 -48
  25. simtools/applications/production_derive_limits.py +95 -0
  26. simtools/applications/production_generate_simulation_config.py +37 -33
  27. simtools/applications/production_scale_events.py +4 -9
  28. simtools/applications/run_application.py +165 -0
  29. simtools/applications/simulate_light_emission.py +0 -4
  30. simtools/applications/simulate_prod.py +1 -1
  31. simtools/applications/simulate_prod_htcondor_generator.py +26 -26
  32. simtools/applications/submit_data_from_external.py +12 -4
  33. simtools/applications/submit_model_parameter_from_external.py +18 -11
  34. simtools/applications/validate_camera_efficiency.py +2 -2
  35. simtools/applications/validate_file_using_schema.py +26 -22
  36. simtools/camera/single_photon_electron_spectrum.py +168 -0
  37. simtools/configuration/commandline_parser.py +37 -1
  38. simtools/configuration/configurator.py +8 -10
  39. simtools/constants.py +10 -3
  40. simtools/corsika/corsika_config.py +19 -17
  41. simtools/corsika/corsika_histograms.py +5 -7
  42. simtools/corsika/corsika_histograms_visualize.py +2 -4
  43. simtools/data_model/data_reader.py +0 -3
  44. simtools/data_model/metadata_collector.py +20 -12
  45. simtools/data_model/metadata_model.py +8 -124
  46. simtools/data_model/model_data_writer.py +81 -75
  47. simtools/data_model/schema.py +220 -0
  48. simtools/data_model/validate_data.py +79 -68
  49. simtools/db/db_handler.py +350 -492
  50. simtools/db/db_model_upload.py +139 -0
  51. simtools/dependencies.py +112 -0
  52. simtools/io_operations/hdf5_handler.py +54 -24
  53. simtools/layout/array_layout.py +38 -32
  54. simtools/model/array_model.py +13 -7
  55. simtools/model/model_parameter.py +55 -54
  56. simtools/model/site_model.py +2 -2
  57. simtools/production_configuration/calculate_statistical_errors_grid_point.py +119 -145
  58. simtools/production_configuration/event_scaler.py +9 -35
  59. simtools/production_configuration/generate_simulation_config.py +9 -44
  60. simtools/production_configuration/interpolation_handler.py +9 -15
  61. simtools/production_configuration/limits_calculation.py +202 -0
  62. simtools/reporting/docs_read_parameters.py +310 -0
  63. simtools/runners/corsika_simtel_runner.py +4 -4
  64. simtools/schemas/{integration_tests_config.metaschema.yml → application_workflow.metaschema.yml} +61 -27
  65. simtools/schemas/array_elements.yml +8 -0
  66. simtools/schemas/input/MST_mirror_2f_measurements.schema.yml +39 -0
  67. simtools/schemas/input/single_pe_spectrum.schema.yml +38 -0
  68. simtools/schemas/model_parameter.metaschema.yml +103 -2
  69. simtools/schemas/model_parameter_and_data_schema.metaschema.yml +4 -1
  70. simtools/schemas/model_parameters/array_element_position_utm.schema.yml +1 -1
  71. simtools/schemas/model_parameters/array_window.schema.yml +37 -0
  72. simtools/schemas/model_parameters/asum_clipping.schema.yml +0 -4
  73. simtools/schemas/model_parameters/channels_per_chip.schema.yml +1 -1
  74. simtools/schemas/model_parameters/correct_nsb_spectrum_to_telescope_altitude.schema.yml +1 -1
  75. simtools/schemas/model_parameters/corsika_cherenkov_photon_bunch_size.schema.yml +2 -0
  76. simtools/schemas/model_parameters/corsika_cherenkov_photon_wavelength_range.schema.yml +2 -0
  77. simtools/schemas/model_parameters/corsika_first_interaction_height.schema.yml +2 -0
  78. simtools/schemas/model_parameters/corsika_iact_io_buffer.schema.yml +4 -2
  79. simtools/schemas/model_parameters/corsika_iact_max_bunches.schema.yml +2 -0
  80. simtools/schemas/model_parameters/corsika_iact_split_auto.schema.yml +2 -0
  81. simtools/schemas/model_parameters/corsika_longitudinal_shower_development.schema.yml +2 -0
  82. simtools/schemas/model_parameters/corsika_particle_kinetic_energy_cutoff.schema.yml +2 -0
  83. simtools/schemas/model_parameters/corsika_starting_grammage.schema.yml +2 -0
  84. simtools/schemas/model_parameters/dsum_clipping.schema.yml +0 -2
  85. simtools/schemas/model_parameters/dsum_ignore_below.schema.yml +0 -2
  86. simtools/schemas/model_parameters/dsum_offset.schema.yml +0 -2
  87. simtools/schemas/model_parameters/dsum_pedsub.schema.yml +0 -2
  88. simtools/schemas/model_parameters/dsum_pre_clipping.schema.yml +0 -2
  89. simtools/schemas/model_parameters/dsum_prescale.schema.yml +0 -2
  90. simtools/schemas/model_parameters/dsum_presum_max.schema.yml +0 -2
  91. simtools/schemas/model_parameters/dsum_presum_shift.schema.yml +0 -2
  92. simtools/schemas/model_parameters/dsum_shaping.schema.yml +0 -2
  93. simtools/schemas/model_parameters/dsum_shaping_renormalize.schema.yml +0 -2
  94. simtools/schemas/model_parameters/dsum_threshold.schema.yml +0 -2
  95. simtools/schemas/model_parameters/dsum_zero_clip.schema.yml +0 -2
  96. simtools/schemas/model_parameters/fadc_compensate_pedestal.schema.yml +1 -1
  97. simtools/schemas/model_parameters/fadc_lg_compensate_pedestal.schema.yml +1 -1
  98. simtools/schemas/model_parameters/fadc_noise.schema.yml +3 -3
  99. simtools/schemas/model_parameters/fake_mirror_list.schema.yml +33 -0
  100. simtools/schemas/model_parameters/iobuf_maximum.schema.yml +1 -1
  101. simtools/schemas/model_parameters/iobuf_output_maximum.schema.yml +1 -1
  102. simtools/schemas/model_parameters/laser_photons.schema.yml +2 -2
  103. simtools/schemas/model_parameters/lightguide_efficiency_vs_incidence_angle.schema.yml +1 -1
  104. simtools/schemas/model_parameters/lightguide_efficiency_vs_wavelength.schema.yml +1 -1
  105. simtools/schemas/model_parameters/min_photoelectrons.schema.yml +1 -1
  106. simtools/schemas/model_parameters/min_photons.schema.yml +1 -1
  107. simtools/schemas/model_parameters/random_generator.schema.yml +1 -1
  108. simtools/schemas/model_parameters/sampled_output.schema.yml +1 -1
  109. simtools/schemas/model_parameters/save_pe_with_amplitude.schema.yml +1 -1
  110. simtools/schemas/model_parameters/secondary_mirror_degraded_reflection.schema.yml +1 -1
  111. simtools/schemas/model_parameters/store_photoelectrons.schema.yml +1 -1
  112. simtools/schemas/model_parameters/tailcut_scale.schema.yml +1 -1
  113. simtools/schemas/production_configuration_metrics.schema.yml +68 -0
  114. simtools/schemas/production_tables.schema.yml +41 -0
  115. simtools/simtel/simtel_config_reader.py +1 -2
  116. simtools/simtel/simtel_config_writer.py +6 -8
  117. simtools/simtel/simtel_io_histogram.py +32 -68
  118. simtools/simtel/simtel_io_histograms.py +17 -34
  119. simtools/simtel/simulator_array.py +2 -1
  120. simtools/simtel/simulator_camera_efficiency.py +6 -3
  121. simtools/simtel/simulator_light_emission.py +5 -6
  122. simtools/simtel/simulator_ray_tracing.py +3 -4
  123. simtools/testing/configuration.py +2 -1
  124. simtools/testing/helpers.py +6 -13
  125. simtools/testing/validate_output.py +141 -47
  126. simtools/utils/general.py +114 -14
  127. simtools/utils/names.py +299 -157
  128. simtools/utils/value_conversion.py +17 -13
  129. simtools/version.py +2 -2
  130. simtools/visualization/legend_handlers.py +2 -0
  131. simtools/applications/db_add_model_parameters_from_repository_to_db.py +0 -176
  132. simtools/db/db_array_elements.py +0 -130
  133. {gammasimtools-0.9.0.dist-info → gammasimtools-0.11.0.dist-info}/LICENSE +0 -0
  134. {gammasimtools-0.9.0.dist-info → gammasimtools-0.11.0.dist-info}/top_level.txt +0 -0
  135. /simtools/{camera_efficiency.py → camera/camera_efficiency.py} +0 -0
@@ -36,12 +36,10 @@ import logging
36
36
  import re
37
37
  from pathlib import Path
38
38
 
39
- import jsonschema
40
-
41
39
  import simtools.utils.general as gen
42
40
  from simtools.configuration import configurator
43
41
  from simtools.constants import MODEL_PARAMETER_SCHEMA_PATH
44
- from simtools.data_model import metadata_collector, metadata_model, validate_data
42
+ from simtools.data_model import metadata_collector, schema, validate_data
45
43
 
46
44
 
47
45
  def _parse(label, description):
@@ -116,7 +114,20 @@ def _get_schema_file_name(args_dict, data_dict=None):
116
114
  return schema_file
117
115
 
118
116
 
119
- def validate_schema(args_dict, logger):
117
+ def _get_json_file_list(file_directory=None, file_name=None):
118
+ """Return list of json files in a directory."""
119
+ file_list = []
120
+ if file_directory is not None:
121
+ file_list = list(Path(file_directory).rglob("*.json"))
122
+ if not file_list:
123
+ raise FileNotFoundError(f"No files found in {file_directory}")
124
+ elif file_name is not None:
125
+ file_list = [file_name]
126
+
127
+ return file_list
128
+
129
+
130
+ def validate_dict_using_schema(args_dict, logger):
120
131
  """
121
132
  Validate a schema file (or several files) given in yaml or json format.
122
133
 
@@ -124,33 +135,26 @@ def validate_schema(args_dict, logger):
124
135
  the metadata section of the data dictionary.
125
136
 
126
137
  """
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:
138
+ for file_name in _get_json_file_list(
139
+ args_dict.get("file_directory"), args_dict.get("file_name")
140
+ ):
132
141
  try:
133
142
  data = gen.collect_data_from_file(file_name=file_name)
134
143
  except FileNotFoundError as exc:
135
144
  logger.error(f"Error reading schema file from {file_name}")
136
145
  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
146
+ schema.validate_dict_using_schema(data, _get_schema_file_name(args_dict, data))
142
147
  logger.info(f"Successful validation of file {file_name}")
143
148
 
144
149
 
145
150
  def validate_data_files(args_dict, logger):
146
151
  """Validate data files."""
147
- file_directory = args_dict.get("file_directory")
148
- if file_directory is not None:
152
+ if args_dict.get("file_directory") is not None:
149
153
  tmp_args_dict = {}
150
- for file_name in Path(file_directory).rglob("*.json"):
154
+ for file_name in _get_json_file_list(args_dict.get("file_directory")):
151
155
  tmp_args_dict["file_name"] = file_name
152
156
  parameter_name = re.sub(r"-\d+\.\d+\.\d+", "", file_name.stem)
153
- schema_file = MODEL_PARAMETER_SCHEMA_PATH / f"{parameter_name}.schema.yml"
157
+ schema_file = schema.get_model_parameter_schema_file(f"{parameter_name}")
154
158
  tmp_args_dict["schema"] = schema_file
155
159
  tmp_args_dict["data_type"] = "model_parameter"
156
160
  tmp_args_dict["require_exact_data_type"] = args_dict["require_exact_data_type"]
@@ -166,9 +170,9 @@ def validate_data_file(args_dict, logger):
166
170
  data_file=args_dict["file_name"],
167
171
  check_exact_data_type=args_dict["require_exact_data_type"],
168
172
  )
169
- data_validator.validate_and_transform(is_model_parameter=True)
170
- if args_dict["data_type"].lower() == "model_parameter":
171
- 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
+ )
172
176
 
173
177
  logger.info(f"Successful validation of data file {args_dict['file_name']}")
174
178
 
@@ -192,7 +196,7 @@ def main(): # noqa: D103
192
196
  if args_dict["data_type"].lower() == "metadata":
193
197
  validate_metadata(args_dict, logger)
194
198
  elif args_dict["data_type"].lower() == "schema":
195
- validate_schema(args_dict, logger)
199
+ validate_dict_using_schema(args_dict, logger)
196
200
  else:
197
201
  validate_data_files(args_dict, logger)
198
202
 
@@ -0,0 +1,168 @@
1
+ """Single photon electron spectral analysis."""
2
+
3
+ import logging
4
+ import re
5
+ import subprocess
6
+ import tempfile
7
+ from io import BytesIO
8
+ from pathlib import Path
9
+
10
+ from astropy.table import Table
11
+
12
+ import simtools.data_model.model_data_writer as writer
13
+ from simtools.constants import SCHEMA_PATH
14
+ from simtools.data_model import validate_data
15
+ from simtools.data_model.metadata_collector import MetadataCollector
16
+ from simtools.io_operations import io_handler
17
+
18
+
19
+ class SinglePhotonElectronSpectrum:
20
+ """
21
+ Single photon electron spectral analysis.
22
+
23
+ Parameters
24
+ ----------
25
+ args_dict: dict
26
+ Dictionary with input arguments.
27
+ """
28
+
29
+ prompt_column = "frequency (prompt)"
30
+ prompt_plus_afterpulse_column = "frequency (prompt+afterpulsing)"
31
+ afterpulse_column = "frequency (afterpulsing)"
32
+
33
+ input_schema = SCHEMA_PATH / "input" / "single_pe_spectrum.schema.yml"
34
+
35
+ def __init__(self, args_dict):
36
+ """Initialize SinglePhotonElectronSpectrum class."""
37
+ self._logger = logging.getLogger(__name__)
38
+ self._logger.debug("Initialize SinglePhotonElectronSpectrum class.")
39
+
40
+ self.args_dict = args_dict
41
+ # default output is of ecsv format
42
+ self.args_dict["output_file"] = str(
43
+ Path(self.args_dict["output_file"]).with_suffix(".ecsv")
44
+ )
45
+ self.io_handler = io_handler.IOHandler()
46
+ self.data = "" # Single photon electron spectrum data (as string)
47
+ self.metadata = MetadataCollector(args_dict=self.args_dict)
48
+
49
+ def derive_single_pe_spectrum(self):
50
+ """Derive single photon electron spectrum."""
51
+ if self.args_dict.get("use_norm_spe"):
52
+ return self._derive_spectrum_norm_spe()
53
+
54
+ raise NotImplementedError(
55
+ "Derivation of single photon electron spectrum using a simtool is not yet implemented."
56
+ )
57
+
58
+ def write_single_pe_spectrum(self):
59
+ """
60
+ Write single photon electron spectrum plus metadata to disk.
61
+
62
+ Includes writing in simtel and simtools (ecsv) formats.
63
+
64
+ """
65
+ simtel_file = self.io_handler.get_output_directory() / Path(
66
+ self.args_dict["output_file"]
67
+ ).with_suffix(".dat")
68
+ self._logger.debug(f"norm_spe output file: {simtel_file}")
69
+ with open(simtel_file, "w", encoding="utf-8") as simtel:
70
+ simtel.write(self.data)
71
+
72
+ cleaned_data = re.sub(r"%%%.+", "", self.data) # remove norm_spe row metadata
73
+ table = Table.read(
74
+ BytesIO(cleaned_data.encode("utf-8")),
75
+ format="ascii.no_header",
76
+ comment="#",
77
+ delimiter="\t",
78
+ )
79
+ table.rename_columns(
80
+ ["col1", "col2", "col3"],
81
+ ["amplitude", self.prompt_column, self.prompt_plus_afterpulse_column],
82
+ )
83
+
84
+ writer.ModelDataWriter.dump(
85
+ args_dict=self.args_dict,
86
+ metadata=self.metadata.top_level_meta,
87
+ product_data=table,
88
+ validate_schema_file=None,
89
+ )
90
+
91
+ def _derive_spectrum_norm_spe(self):
92
+ """
93
+ Derive single photon electron spectrum using sim_telarray tool 'norm_spe'.
94
+
95
+ Returns
96
+ -------
97
+ int
98
+ Return code of the executed command
99
+
100
+ Raises
101
+ ------
102
+ subprocess.CalledProcessError
103
+ If the command execution fails.
104
+ """
105
+ tmp_input_file = self._get_input_data(
106
+ input_file=self.args_dict["input_spectrum"],
107
+ frequency_column=self.prompt_column,
108
+ )
109
+ tmp_ap_file = self._get_input_data(
110
+ input_file=self.args_dict.get("afterpulse_spectrum"),
111
+ frequency_column=self.afterpulse_column,
112
+ )
113
+
114
+ command = [
115
+ f"{self.args_dict['simtel_path']}/sim_telarray/bin/norm_spe",
116
+ "-r",
117
+ f"{self.args_dict['step_size']},{self.args_dict['max_amplitude']}",
118
+ tmp_input_file.name,
119
+ ]
120
+ if tmp_ap_file:
121
+ command.insert(1, "-a")
122
+ command.insert(2, f"{tmp_ap_file.name}")
123
+
124
+ self._logger.debug(f"Running norm_spe command: {' '.join(command)}")
125
+ try:
126
+ result = subprocess.run(command, capture_output=True, text=True, check=True)
127
+ except subprocess.CalledProcessError as exc:
128
+ self._logger.error(f"Error running norm_spe: {exc}")
129
+ self._logger.error(f"stderr: {exc.stderr}")
130
+ raise exc
131
+ finally:
132
+ for tmp_file in [tmp_input_file, tmp_ap_file]:
133
+ try:
134
+ Path(tmp_file.name).unlink()
135
+ except (AttributeError, FileNotFoundError):
136
+ pass
137
+
138
+ self.data = result.stdout
139
+ return result.returncode
140
+
141
+ def _get_input_data(self, input_file, frequency_column):
142
+ """
143
+ Return input data for norm_spe command.
144
+
145
+ Input data need to be space separated values of the amplitude spectrum.
146
+ """
147
+ input_data = ""
148
+ if not input_file:
149
+ return None
150
+ input_file = Path(input_file)
151
+
152
+ if input_file.suffix == ".ecsv":
153
+ data_validator = validate_data.DataValidator(
154
+ schema_file=self.input_schema, data_file=input_file
155
+ )
156
+ table = data_validator.validate_and_transform()
157
+ input_data = "\n".join(f"{row['amplitude']} {row[frequency_column]}" for row in table)
158
+ else:
159
+ with open(input_file, encoding="utf-8") as f:
160
+ input_data = (
161
+ f.read().replace(",", " ")
162
+ if frequency_column == self.prompt_column
163
+ else f.read()
164
+ )
165
+
166
+ with tempfile.NamedTemporaryFile(delete=False, mode="w", encoding="utf-8") as tmpfile:
167
+ tmpfile.write(input_data)
168
+ return tmpfile
@@ -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")
@@ -239,7 +268,14 @@ class CommandLineParser(argparse.ArgumentParser):
239
268
  if "model_version" in model_options:
240
269
  _job_group.add_argument(
241
270
  "--model_version",
242
- help="model version",
271
+ help="production model version",
272
+ type=str,
273
+ default=None,
274
+ )
275
+ if "parameter_version" in model_options:
276
+ _job_group.add_argument(
277
+ "--parameter_version",
278
+ help="model parameter version",
243
279
  type=str,
244
280
  default=None,
245
281
  )
@@ -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
simtools/constants.py CHANGED
@@ -2,8 +2,15 @@
2
2
 
3
3
  from importlib.resources import files
4
4
 
5
+ # Schema path
6
+ SCHEMA_PATH = files("simtools") / "schemas"
5
7
  # Path to metadata jsonschema
6
- METADATA_JSON_SCHEMA = files("simtools") / "schemas/metadata.metaschema.yml"
7
-
8
+ METADATA_JSON_SCHEMA = SCHEMA_PATH / "metadata.metaschema.yml"
9
+ # Path to model parameter metaschema
10
+ MODEL_PARAMETER_METASCHEMA = SCHEMA_PATH / "model_parameter.metaschema.yml"
11
+ # Path to model parameter description metaschema
12
+ MODEL_PARAMETER_DESCRIPTION_METASCHEMA = (
13
+ SCHEMA_PATH / "model_parameter_and_data_schema.metaschema.yml"
14
+ )
8
15
  # Path to model parameter schema files
9
- MODEL_PARAMETER_SCHEMA_PATH = files("simtools") / "schemas/model_parameters"
16
+ MODEL_PARAMETER_SCHEMA_PATH = SCHEMA_PATH / "model_parameters"
@@ -6,7 +6,6 @@ from pathlib import Path
6
6
  import numpy as np
7
7
  from astropy import units as u
8
8
 
9
- import simtools.utils.general as gen
10
9
  from simtools.corsika.primary_particle import PrimaryParticle
11
10
  from simtools.io_operations import io_handler
12
11
  from simtools.model.model_parameter import ModelParameter
@@ -111,8 +110,6 @@ class CorsikaConfig:
111
110
  if args_dict is None:
112
111
  return {}
113
112
 
114
- self._logger.debug("Setting CORSIKA parameters ")
115
-
116
113
  self._is_file_updated = False
117
114
  self.azimuth_angle = int(args_dict["azimuth_angle"].to("deg").value)
118
115
  self.zenith_angle = args_dict["zenith_angle"].to("deg").value
@@ -235,25 +232,25 @@ class CorsikaConfig:
235
232
 
236
233
  def _input_config_first_interaction_height(self, entry):
237
234
  """Return FIXHEI parameter CORSIKA format."""
238
- 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"]
239
236
 
240
237
  def _input_config_corsika_starting_grammage(self, entry):
241
238
  """Return FIXCHI parameter CORSIKA format."""
242
- return f"{entry['value']*u.Unit(entry['unit']).to('g/cm2')}"
239
+ return f"{entry['value'] * u.Unit(entry['unit']).to('g/cm2')}"
243
240
 
244
241
  def _input_config_corsika_particle_kinetic_energy_cutoff(self, entry):
245
242
  """Return ECUTS parameter CORSIKA format."""
246
- e_cuts = gen.convert_string_to_list(entry["value"])
243
+ e_cuts = entry["value"]
247
244
  return [
248
- f"{e_cuts[0]*u.Unit(entry['unit']).to('GeV')} "
249
- f"{e_cuts[1]*u.Unit(entry['unit']).to('GeV')} "
250
- f"{e_cuts[2]*u.Unit(entry['unit']).to('GeV')} "
251
- 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')}"
252
249
  ]
253
250
 
254
251
  def _input_config_corsika_longitudinal_parameters(self, entry):
255
252
  """Return LONGI parameter CORSIKA format."""
256
- 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"]
257
254
 
258
255
  def _corsika_configuration_cherenkov_parameters(self, parameters_from_db):
259
256
  """
@@ -280,10 +277,10 @@ class CorsikaConfig:
280
277
 
281
278
  def _input_config_corsika_cherenkov_wavelength(self, entry):
282
279
  """Return CWAVLG parameter CORSIKA format."""
283
- wavelength_range = gen.convert_string_to_list(entry["value"])
280
+ wavelength_range = entry["value"]
284
281
  return [
285
- f"{wavelength_range[0]*u.Unit(entry['unit']).to('nm')}",
286
- 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')}",
287
284
  ]
288
285
 
289
286
  def _corsika_configuration_iact_parameters(self, parameters_from_db):
@@ -314,12 +311,17 @@ class CorsikaConfig:
314
311
  return {
315
312
  "DEBUG": ["F", 6, "F", 1000000],
316
313
  "DATBAS": ["yes"],
317
- "DIRECT": ["/dev/null"],
314
+ "DIRECT": ["./"],
315
+ "PAROUT": ["F", "F"],
318
316
  }
319
317
 
320
318
  def _input_config_io_buff(self, entry):
321
- """Return IO_BUFFER parameter CORSIKA format."""
322
- return f"{entry['value']}{entry['unit']}"
319
+ """Return IO_BUFFER parameter CORSIKA format (Byte or MB required)."""
320
+ value = entry["value"] * u.Unit(entry["unit"]).to("Mbyte")
321
+ # check if value is integer-like
322
+ if value.is_integer():
323
+ return f"{int(value)}MB"
324
+ return f"{int(entry['value'] * u.Unit(entry['unit']).to('byte'))}"
323
325
 
324
326
  def _rotate_azimuth_by_180deg(self, az, correct_for_geomagnetic_field_alignment=True):
325
327
  """
@@ -678,7 +678,7 @@ class CorsikaHistograms:
678
678
  ----------
679
679
  new_individual_telescopes: bool
680
680
  if False, the histograms are supposed to be filled for all telescopes.
681
- if True, one histogram is set for each telescope sepparately.
681
+ if True, one histogram is set for each telescope separately.
682
682
  """
683
683
  if new_individual_telescopes is None:
684
684
  self._individual_telescopes = False
@@ -944,10 +944,10 @@ class CorsikaHistograms:
944
944
 
945
945
  hist_2d_values_list, x_position_list, y_position_list = self.get_2d_photon_position_distr()
946
946
 
947
- for i_hist, _ in enumerate(x_position_list):
947
+ for i_hist, x_pos in enumerate(x_position_list):
948
948
  hist_1d, bin_edges_1d = convert_2d_to_radial_distr(
949
949
  hist_2d_values_list[i_hist],
950
- x_position_list[i_hist], # pylint: disable=unnecessary-list-index-lookup
950
+ x_pos,
951
951
  y_position_list[i_hist],
952
952
  bins=bins,
953
953
  max_dist=max_dist,
@@ -1248,8 +1248,7 @@ class CorsikaHistograms:
1248
1248
  meta_data=self._meta_dict,
1249
1249
  )
1250
1250
  self._logger.info(
1251
- f"Writing 1D histogram with name {hdf5_table_name} to "
1252
- f"{self.hdf5_file_name}."
1251
+ f"Writing 1D histogram with name {hdf5_table_name} to {self.hdf5_file_name}."
1253
1252
  )
1254
1253
  # overwrite takes precedence over append
1255
1254
  if overwrite is True:
@@ -1382,8 +1381,7 @@ class CorsikaHistograms:
1382
1381
  )
1383
1382
 
1384
1383
  self._logger.info(
1385
- f"Writing 2D histogram with name {hdf5_table_name} to "
1386
- f"{self.hdf5_file_name}."
1384
+ f"Writing 2D histogram with name {hdf5_table_name} to {self.hdf5_file_name}."
1387
1385
  )
1388
1386
  # Always appending to table due to the file previously created
1389
1387
  # by self._export_1d_histograms.
@@ -93,8 +93,7 @@ def _kernel_plot_2d_photons(histograms_instance, property_name, log_z=False):
93
93
  all_figs.append(fig)
94
94
  if histograms_instance.individual_telescopes is False:
95
95
  ax.set_title(
96
- f"{histograms_instance.dict_2d_distributions[property_name]['file name']}"
97
- "_all_tels"
96
+ f"{histograms_instance.dict_2d_distributions[property_name]['file name']}_all_tels"
98
97
  )
99
98
  else:
100
99
  ax.text(
@@ -285,8 +284,7 @@ def _kernel_plot_1d_photons(histograms_instance, property_name, log_y=True):
285
284
  ax.set_yscale("log")
286
285
  if histograms_instance.individual_telescopes is False:
287
286
  ax.set_title(
288
- f"{histograms_instance.dict_1d_distributions[property_name]['file name']}"
289
- "_all_tels"
287
+ f"{histograms_instance.dict_1d_distributions[property_name]['file name']}_all_tels"
290
288
  )
291
289
  else:
292
290
  ax.set_title(
@@ -112,9 +112,6 @@ def read_value_from_file(file_name, schema_file=None, validate=False):
112
112
  _logger.info("Reading data from %s", file_name)
113
113
 
114
114
  if validate:
115
- if schema_file is None and "meta_schema_url" in data:
116
- schema_file = data["meta_schema_url"]
117
- _logger.debug(f"Using schema from meta_schema_url: {schema_file}")
118
115
  if schema_file is None:
119
116
  _collector = MetadataCollector(None, metadata_file_name=file_name)
120
117
  schema_file = _collector.get_data_model_schema_file_name()
@@ -6,17 +6,16 @@ implementation of the observatory metadata model.
6
6
 
7
7
  """
8
8
 
9
- import datetime
10
9
  import getpass
11
10
  import logging
12
11
  import uuid
13
- from importlib.resources import files
14
12
  from pathlib import Path
15
13
 
16
14
  import simtools.constants
17
15
  import simtools.utils.general as gen
18
16
  import simtools.version
19
- from simtools.data_model import metadata_model
17
+ from simtools.constants import METADATA_JSON_SCHEMA
18
+ from simtools.data_model import metadata_model, schema
20
19
  from simtools.io_operations import io_handler
21
20
  from simtools.utils import names
22
21
 
@@ -97,9 +96,9 @@ class MetadataCollector:
97
96
 
98
97
  """
99
98
  try:
100
- self.top_level_meta[self.observatory]["activity"][
101
- "end"
102
- ] = datetime.datetime.now().isoformat(timespec="seconds")
99
+ self.top_level_meta[self.observatory]["activity"]["end"] = (
100
+ gen.now_date_time_in_isoformat()
101
+ )
103
102
  except KeyError:
104
103
  pass
105
104
  return self.top_level_meta
@@ -135,7 +134,7 @@ class MetadataCollector:
135
134
  # from data model name
136
135
  if self.data_model_name:
137
136
  self._logger.debug(f"Schema file from data model name: {self.data_model_name}")
138
- return f"{files('simtools')}/schemas/model_parameters/{self.data_model_name}.schema.yml"
137
+ return str(schema.get_model_parameter_schema_file(self.data_model_name))
139
138
 
140
139
  # from input metadata
141
140
  try:
@@ -200,8 +199,17 @@ class MetadataCollector:
200
199
  contact_dict: dict
201
200
  Dictionary for contact metadata fields.
202
201
  """
203
- if contact_dict.get("name", None) is None:
202
+ contact_dict["name"] = contact_dict.get("name") or self.args_dict.get("user_name")
203
+ if contact_dict["name"] is None:
204
+ self._logger.warning("No user name provided, take user info from system level.")
204
205
  contact_dict["name"] = getpass.getuser()
206
+ meta_dict = {
207
+ "email": "user_mail",
208
+ "orcid": "user_orcid",
209
+ "organization": "user_organization",
210
+ }
211
+ for key, value in meta_dict.items():
212
+ contact_dict[key] = contact_dict.get(key) or self.args_dict.get(value)
205
213
 
206
214
  def _fill_context_meta(self, context_dict):
207
215
  """
@@ -267,7 +275,7 @@ class MetadataCollector:
267
275
  self._logger.error("Unknown metadata file format: %s", metadata_file_name)
268
276
  raise gen.InvalidConfigDataError
269
277
 
270
- metadata_model.validate_schema(_input_metadata, None)
278
+ schema.validate_dict_using_schema(_input_metadata, schema_file=METADATA_JSON_SCHEMA)
271
279
 
272
280
  return gen.change_dict_keys_case(
273
281
  self._process_metadata_from_file(_input_metadata),
@@ -327,7 +335,7 @@ class MetadataCollector:
327
335
  self.schema_dict = self.get_data_model_schema_dict()
328
336
 
329
337
  product_dict["id"] = str(uuid.uuid4())
330
- product_dict["creation_time"] = datetime.datetime.now().isoformat(timespec="seconds")
338
+ product_dict["creation_time"] = gen.now_date_time_in_isoformat()
331
339
  product_dict["description"] = self.schema_dict.get("description", None)
332
340
 
333
341
  # DATA:CATEGORY
@@ -367,7 +375,7 @@ class MetadataCollector:
367
375
  )
368
376
  if instrument_dict["ID"]:
369
377
  instrument_dict["class"] = names.get_collection_name_from_array_element_name(
370
- instrument_dict["ID"]
378
+ instrument_dict["ID"], False
371
379
  )
372
380
 
373
381
  def _fill_process_meta(self, process_dict):
@@ -395,7 +403,7 @@ class MetadataCollector:
395
403
  activity_dict["name"] = self.args_dict.get("label", None)
396
404
  activity_dict["type"] = "software"
397
405
  activity_dict["id"] = self.args_dict.get("activity_id", "UNDEFINED_ACTIVITY_ID")
398
- activity_dict["start"] = datetime.datetime.now().isoformat(timespec="seconds")
406
+ activity_dict["start"] = gen.now_date_time_in_isoformat()
399
407
  activity_dict["end"] = activity_dict["start"]
400
408
  activity_dict["software"]["name"] = "simtools"
401
409
  activity_dict["software"]["version"] = simtools.version.__version__