gammasimtools 0.5.1__py3-none-any.whl → 0.6.1__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 (78) hide show
  1. {gammasimtools-0.5.1.dist-info → gammasimtools-0.6.1.dist-info}/METADATA +80 -28
  2. gammasimtools-0.6.1.dist-info/RECORD +91 -0
  3. {gammasimtools-0.5.1.dist-info → gammasimtools-0.6.1.dist-info}/WHEEL +1 -1
  4. {gammasimtools-0.5.1.dist-info → gammasimtools-0.6.1.dist-info}/entry_points.txt +4 -2
  5. simtools/_version.py +14 -2
  6. simtools/applications/add_file_to_db.py +2 -1
  7. simtools/applications/compare_cumulative_psf.py +10 -15
  8. simtools/applications/db_development_tools/add_new_parameter_to_db.py +12 -6
  9. simtools/applications/derive_mirror_rnda.py +95 -71
  10. simtools/applications/generate_corsika_histograms.py +216 -131
  11. simtools/applications/generate_default_metadata.py +110 -0
  12. simtools/applications/generate_simtel_array_histograms.py +192 -0
  13. simtools/applications/get_file_from_db.py +1 -1
  14. simtools/applications/get_parameter.py +3 -3
  15. simtools/applications/make_regular_arrays.py +89 -93
  16. simtools/applications/{plot_layout_array.py → plot_array_layout.py} +15 -14
  17. simtools/applications/print_array_elements.py +81 -34
  18. simtools/applications/produce_array_config.py +2 -2
  19. simtools/applications/production.py +39 -5
  20. simtools/applications/sim_showers_for_trigger_rates.py +26 -30
  21. simtools/applications/simulate_prod.py +49 -107
  22. simtools/applications/submit_data_from_external.py +8 -10
  23. simtools/applications/tune_psf.py +16 -18
  24. simtools/applications/validate_camera_efficiency.py +63 -9
  25. simtools/applications/validate_camera_fov.py +9 -13
  26. simtools/applications/validate_file_using_schema.py +127 -0
  27. simtools/applications/validate_optics.py +13 -15
  28. simtools/camera_efficiency.py +73 -80
  29. simtools/configuration/commandline_parser.py +52 -22
  30. simtools/configuration/configurator.py +98 -33
  31. simtools/constants.py +9 -0
  32. simtools/corsika/corsika_config.py +28 -22
  33. simtools/corsika/corsika_default_config.py +282 -0
  34. simtools/corsika/corsika_histograms.py +328 -282
  35. simtools/corsika/corsika_histograms_visualize.py +162 -163
  36. simtools/corsika/corsika_runner.py +8 -4
  37. simtools/corsika_simtel/corsika_simtel_runner.py +18 -23
  38. simtools/data_model/data_reader.py +129 -0
  39. simtools/data_model/metadata_collector.py +346 -118
  40. simtools/data_model/metadata_model.py +123 -218
  41. simtools/data_model/model_data_writer.py +79 -22
  42. simtools/data_model/validate_data.py +96 -46
  43. simtools/db_handler.py +67 -42
  44. simtools/io_operations/__init__.py +0 -0
  45. simtools/io_operations/hdf5_handler.py +112 -0
  46. simtools/{io_handler.py → io_operations/io_handler.py} +51 -22
  47. simtools/job_execution/job_manager.py +1 -1
  48. simtools/layout/{layout_array.py → array_layout.py} +168 -199
  49. simtools/layout/geo_coordinates.py +196 -0
  50. simtools/layout/telescope_position.py +12 -12
  51. simtools/model/array_model.py +16 -14
  52. simtools/model/camera.py +5 -8
  53. simtools/model/mirrors.py +136 -73
  54. simtools/model/model_utils.py +1 -69
  55. simtools/model/telescope_model.py +32 -25
  56. simtools/psf_analysis.py +26 -19
  57. simtools/ray_tracing.py +54 -26
  58. simtools/schemas/data.metaschema.yml +400 -0
  59. simtools/schemas/metadata.metaschema.yml +566 -0
  60. simtools/simtel/simtel_config_writer.py +14 -5
  61. simtools/simtel/simtel_histograms.py +266 -83
  62. simtools/simtel/simtel_runner.py +8 -7
  63. simtools/simtel/simtel_runner_array.py +7 -8
  64. simtools/simtel/simtel_runner_camera_efficiency.py +48 -2
  65. simtools/simtel/simtel_runner_ray_tracing.py +61 -25
  66. simtools/simulator.py +43 -50
  67. simtools/utils/general.py +232 -286
  68. simtools/utils/geometry.py +163 -0
  69. simtools/utils/names.py +294 -142
  70. simtools/visualization/legend_handlers.py +115 -9
  71. simtools/visualization/visualize.py +13 -13
  72. gammasimtools-0.5.1.dist-info/RECORD +0 -83
  73. simtools/applications/plot_simtel_histograms.py +0 -120
  74. simtools/applications/validate_schema_files.py +0 -135
  75. simtools/corsika/corsika_output_visualize.py +0 -345
  76. simtools/data_model/validate_schema.py +0 -285
  77. {gammasimtools-0.5.1.dist-info → gammasimtools-0.6.1.dist-info}/LICENSE +0 -0
  78. {gammasimtools-0.5.1.dist-info → gammasimtools-0.6.1.dist-info}/top_level.txt +0 -0
@@ -1,13 +1,15 @@
1
+ import argparse
1
2
  import logging
2
3
  import os
3
4
  import sys
4
5
  import uuid
5
6
 
6
7
  import astropy.units as u
7
- import yaml
8
+ from dotenv import load_dotenv
8
9
 
9
10
  import simtools.configuration.commandline_parser as argparser
10
- from simtools import io_handler
11
+ from simtools.io_operations import io_handler
12
+ from simtools.utils import general as gen
11
13
 
12
14
  __all__ = [
13
15
  "Configurator",
@@ -30,7 +32,7 @@ class Configurator:
30
32
  - configuration dict when calling the class
31
33
  - environmental variables
32
34
 
33
- Assigns unique ACIVITY_ID to this configuration (uuid).
35
+ Assigns unique ACTIVITY_ID to this configuration (uuid).
34
36
 
35
37
  Configuration parameter names are converted always to lower case.
36
38
 
@@ -60,7 +62,11 @@ class Configurator:
60
62
  self.label = label
61
63
  self.config = {}
62
64
  self.parser = argparser.CommandLineParser(
63
- prog=self.label, usage=usage, description=description, epilog=epilog
65
+ prog=self.label,
66
+ usage=usage,
67
+ description=description,
68
+ epilog=epilog,
69
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
64
70
  )
65
71
 
66
72
  def default_config(self, arg_list=None, add_db_config=False):
@@ -91,9 +97,11 @@ class Configurator:
91
97
 
92
98
  def initialize(
93
99
  self,
100
+ require_command_line=True,
94
101
  paths=True,
95
102
  output=False,
96
103
  telescope_model=False,
104
+ site_model=False,
97
105
  db_config=False,
98
106
  job_submission=False,
99
107
  ):
@@ -110,12 +118,16 @@ class Configurator:
110
118
 
111
119
  Parameters
112
120
  ----------
121
+ require_command_line: bool
122
+ Require at least one command line argument.
113
123
  paths: bool
114
124
  Add path configuration to list of args.
115
125
  output: bool
116
126
  Add output file configuration to list of args.
117
127
  telescope_model: bool
118
128
  Add telescope model configuration to list of args.
129
+ site_model: bool
130
+ Add site model configuration to list of args (not required if telescope_model is set)
119
131
  db_config: bool
120
132
  Add database configuration parameters to list of args.
121
133
  job_submission: bool
@@ -139,15 +151,13 @@ class Configurator:
139
151
  paths=paths,
140
152
  output=output,
141
153
  telescope_model=telescope_model,
154
+ site_model=site_model,
142
155
  db_config=db_config,
143
156
  job_submission=job_submission,
144
157
  )
145
158
 
146
- self._fill_from_command_line()
147
- try:
148
- self._fill_from_config_file(self.config["config"])
149
- except KeyError:
150
- pass
159
+ self._fill_from_command_line(require_command_line=require_command_line)
160
+ self._fill_from_config_file(self.config.get("config"))
151
161
  self._fill_from_config_dict(self.config_class_init)
152
162
  self._fill_from_environmental_variables()
153
163
 
@@ -157,37 +167,76 @@ class Configurator:
157
167
  self.config["label"] = self.label
158
168
 
159
169
  self._initialize_io_handler()
160
- self._initialize_output()
170
+ if output:
171
+ self._initialize_output()
161
172
  _db_dict = self._get_db_parameters()
162
173
 
163
174
  return self.config, _db_dict
164
175
 
165
- def _fill_from_command_line(self, arg_list=None):
176
+ def _fill_from_command_line(self, arg_list=None, require_command_line=True):
166
177
  """
167
178
  Fill configuration parameters from command line arguments.
179
+ Triggers a print of the help if no command line arguments are given and
180
+ require_command_line is set.
181
+
182
+ Parameters
183
+ ----------
184
+ arg_list: list
185
+ List of arguments.
186
+ require_command_line: bool
187
+ Require at least one command line argument.
188
+
189
+ Raises
190
+ ------
191
+ InvalidConfigurationParameter
192
+ invalid configuration.
168
193
 
169
194
  """
170
195
 
171
196
  if arg_list is None:
172
197
  arg_list = sys.argv[1:]
173
198
 
199
+ if require_command_line and len(arg_list) == 0:
200
+ self._logger.debug("No command line arguments given, printing help.")
201
+ arg_list = ["--help"]
202
+
203
+ if "--config" in arg_list:
204
+ self._reset_required_arguments()
205
+
174
206
  self._fill_config(arg_list)
175
207
 
176
- def _fill_from_config_dict(self, _input_dict):
208
+ def _reset_required_arguments(self):
209
+ """
210
+ Reset required parser arguments (i.e., arguments added with "required=True").
211
+ Includes also mutually exclusive groups.
212
+
213
+ Access protected attributes of parser (no public method available).
214
+
215
+ """
216
+
217
+ for group in self.parser._mutually_exclusive_groups: # pylint: disable=protected-access
218
+ group.required = False
219
+ for action in self.parser._actions: # pylint: disable=protected-access
220
+ action.required = False
221
+
222
+ def _fill_from_config_dict(self, input_dict, overwrite=False):
177
223
  """
178
224
  Fill configuration parameters from dictionary. Enforce that configuration parameter names\
179
225
  are lower case.
180
226
 
181
227
  Parameters
182
228
  ----------
183
- _input_dict: dict
229
+ input_dict: dict
184
230
  dictionary with configuration parameters.
231
+ overwrite: bool
232
+ overwrite existing configuration parameters.
185
233
 
186
234
  """
187
235
  _tmp_config = {}
188
236
  try:
189
- for key, value in _input_dict.items():
190
- self._check_parameter_configuration_status(key, value)
237
+ for key, value in input_dict.items():
238
+ if not overwrite:
239
+ self._check_parameter_configuration_status(key, value)
191
240
  _tmp_config[key.lower()] = value
192
241
  except AttributeError:
193
242
  pass
@@ -242,17 +291,27 @@ class Configurator:
242
291
 
243
292
  try:
244
293
  self._logger.debug(f"Reading configuration from {config_file}")
245
- with open(config_file, "r", encoding="utf-8") as stream:
246
- _config_dict = yaml.safe_load(stream)
247
- if "CTASIMPIPE" in _config_dict:
294
+ _config_dict = gen.collect_data_from_file_or_dict(
295
+ file_name=config_file, in_dict=None, allow_empty=True
296
+ )
297
+ # yaml parser adds \n in multiline strings, remove them
298
+ _config_dict = gen.remove_substring_recursively_from_dict(_config_dict, substring="\n")
299
+ if "CTA_SIMPIPE" in _config_dict:
248
300
  try:
249
- self._fill_from_config_dict(_config_dict["CTASIMPIPE"]["CONFIGURATION"])
301
+ self._fill_from_config_dict(
302
+ input_dict=gen.change_dict_keys_case(
303
+ _config_dict["CTA_SIMPIPE"]["CONFIGURATION"],
304
+ ),
305
+ overwrite=True,
306
+ )
250
307
  except KeyError:
251
- self._logger.info(f"No CTASIMPIPE:CONFIGURATION dict found in {config_file}.")
308
+ self._logger.info(f"No CTA_SIMPIPE:CONFIGURATION dict found in {config_file}.")
252
309
  else:
253
- self._fill_from_config_dict(_config_dict)
310
+ self._fill_from_config_dict(
311
+ input_dict=gen.change_dict_keys_case(_config_dict), overwrite=True
312
+ )
254
313
  # TypeError is raised for config_file=None
255
- except TypeError:
314
+ except (TypeError, AttributeError):
256
315
  pass
257
316
  except FileNotFoundError:
258
317
  self._logger.error(f"Configuration file not found: {config_file}")
@@ -260,18 +319,24 @@ class Configurator:
260
319
 
261
320
  def _fill_from_environmental_variables(self):
262
321
  """
263
- Fill any unconfigured configuration parameters (i.e., parameter is None) \
264
- from environmental variables.
322
+ Fill any configuration parameters which is not already configured (i.e., parameter is None)
323
+ from environmental variables or from file (default: ".env").
265
324
 
266
325
  """
267
326
 
268
327
  _env_dict = {}
269
328
  try:
270
- for key, value in self.config.items():
271
- if value is None:
272
- _env_dict[key] = os.environ.get(key.upper())
273
- except AttributeError:
329
+ load_dotenv(self.config["env_file"])
330
+ except KeyError:
274
331
  pass
332
+ for key, value in self.config.items():
333
+ # environmental variables for simtools should always start with SIMTOOLS_
334
+ env_variable_to_read = f"SIMTOOLS_{key.upper()}"
335
+ if value is None:
336
+ env_value = os.environ.get(env_variable_to_read)
337
+ if env_value is not None:
338
+ env_value = env_value.split("#")[0].strip().replace('"', "").replace("'", "")
339
+ _env_dict[key] = env_value
275
340
 
276
341
  self._fill_from_config_dict(_env_dict)
277
342
 
@@ -290,20 +355,20 @@ class Configurator:
290
355
 
291
356
  def _initialize_output(self):
292
357
  """
293
- Initialize default output file names (in out output_file is not configured).
358
+ Initialize default output file names (in case output_file is not configured).
294
359
 
295
360
  """
296
361
  if self.config.get("output_file", None) is None:
297
362
  prefix = "TEST"
298
- label = extention = ""
363
+ label = extension = ""
299
364
  if not self.config.get("test", False):
300
365
  prefix = self.config["activity_id"]
301
366
  if self.config.get("label", "") and len(self.config.get("label", "")) > 0:
302
367
  label = f"-{self.config['label']}"
303
368
  if len(self.config.get("output_file_format", "")) > 0:
304
- extention = f".{self.config['output_file_format']}"
369
+ extension = f".{self.config['output_file_format']}"
305
370
 
306
- self.config["output_file"] = f"{prefix}{label}{extention}"
371
+ self.config["output_file"] = f"{prefix}{label}{extension}"
307
372
 
308
373
  @staticmethod
309
374
  def _arglist_from_config(input_var):
@@ -339,7 +404,7 @@ class Configurator:
339
404
  _list_args += value
340
405
  elif isinstance(value, u.Quantity):
341
406
  _list_args.append("--" + key)
342
- _list_args.append(str(value.value))
407
+ _list_args.append(str(value))
343
408
  elif not isinstance(value, bool) and value is not None and len(str(value)) > 0:
344
409
  _list_args.append("--" + key)
345
410
  _list_args.append(str(value))
simtools/constants.py ADDED
@@ -0,0 +1,9 @@
1
+ """Project wide constants."""
2
+
3
+ # Path to metadata jsonschema
4
+ METADATA_JSON_SCHEMA = "schemas/metadata.metaschema.yml"
5
+ # Path to data and modelparameter jsonschema
6
+ DATA_JSON_SCHEMA = "schemas/data.metaschema.yml"
7
+
8
+ # URL to the schema repository
9
+ SCHEMA_URL = "https://raw.githubusercontent.com/gammasim/workflows/main/schemas/"
@@ -7,10 +7,10 @@ import numpy as np
7
7
  from astropy.io.misc import yaml
8
8
 
9
9
  import simtools.utils.general as gen
10
- from simtools import io_handler
11
- from simtools.layout.layout_array import LayoutArray
10
+ from simtools.io_operations import io_handler
11
+ from simtools.layout.array_layout import ArrayLayout
12
12
  from simtools.utils import names
13
- from simtools.utils.general import collect_data_from_yaml_or_dict
13
+ from simtools.utils.general import collect_data_from_file_or_dict
14
14
 
15
15
  __all__ = [
16
16
  "CorsikaConfig",
@@ -90,17 +90,18 @@ class CorsikaConfig:
90
90
  self.label = label
91
91
  self.site = names.validate_site_name(site)
92
92
  self.primary = None
93
- self._config_file_path = None
93
+ self.eslope = None
94
+ self.config_file_path = None
94
95
  self._output_generic_file_name = None
95
96
  self._simtel_source_path = simtel_source_path
96
97
 
97
98
  self.io_handler = io_handler.IOHandler()
98
99
 
99
- # Grabbing layout name and building LayoutArray
100
- self.layout_name = names.validate_layout_array_name(layout_name)
101
- self.layout = LayoutArray.from_layout_array_name(
100
+ # Grabbing layout name and building ArrayLayout
101
+ self.layout_name = names.validate_array_layout_name(layout_name)
102
+ self.layout = ArrayLayout.from_array_layout_name(
102
103
  mongo_db_config=mongo_db_config,
103
- layout_array_name=f"{self.site}-{self.layout_name}",
104
+ array_layout_name=f"{self.site}-{self.layout_name}",
104
105
  label=self.label,
105
106
  )
106
107
 
@@ -112,7 +113,7 @@ class CorsikaConfig:
112
113
 
113
114
  self._corsika_parameters = self.load_corsika_parameters_file(corsika_parameters_file)
114
115
 
115
- corsika_config_data = collect_data_from_yaml_or_dict(
116
+ corsika_config_data = collect_data_from_file_or_dict(
116
117
  corsika_config_file, corsika_config_data
117
118
  )
118
119
  self.set_user_parameters(corsika_config_data)
@@ -246,6 +247,8 @@ class CorsikaConfig:
246
247
  value_args = [0 * par_info["unit"][0], value_args[0]]
247
248
  elif par_name == "PRMPAR":
248
249
  value_args = self._convert_primary_input_and_store_primary_name(value_args)
250
+ elif par_name == "ESLOPE":
251
+ self.eslope = value_args[0]
249
252
 
250
253
  if len(value_args) != par_info["len"]:
251
254
  msg = f"CORSIKA input entry with wrong len: {par_name}"
@@ -352,9 +355,9 @@ class CorsikaConfig:
352
355
  the output directly to sim_telarray or not.
353
356
  """
354
357
 
355
- dir_type = "corsika_simtel" if use_multipipe else "corsika"
356
- self._set_output_file_and_directory(dir_type)
357
- self._logger.debug(f"Exporting CORSIKA input file to {self._config_file_path}")
358
+ sub_dir = "corsika_simtel" if use_multipipe else "corsika"
359
+ self._set_output_file_and_directory(sub_dir)
360
+ self._logger.debug(f"Exporting CORSIKA input file to {self.config_file_path}")
358
361
 
359
362
  def _get_text_single_line(pars):
360
363
  text = ""
@@ -374,7 +377,7 @@ class CorsikaConfig:
374
377
  text += _get_text_single_line(new_pars)
375
378
  return text
376
379
 
377
- with open(self._config_file_path, "w", encoding="utf-8") as file:
380
+ with open(self.config_file_path, "w", encoding="utf-8") as file:
378
381
  file.write("\n* [ RUN PARAMETERS ]\n")
379
382
  # Removing AZM entry first
380
383
  _user_pars_temp = copy.copy(self._user_parameters)
@@ -421,7 +424,7 @@ class CorsikaConfig:
421
424
 
422
425
  file.write("\n* [ OUTUPUT FILE ]\n")
423
426
  if use_multipipe:
424
- run_cta_script = Path(self._config_file_path.parent).joinpath("run_cta_multipipe")
427
+ run_cta_script = Path(self.config_file_path.parent).joinpath("run_cta_multipipe")
425
428
  file.write(f"TELFIL |{str(run_cta_script)}\n")
426
429
  else:
427
430
  file.write(f"TELFIL {self._output_generic_file_name}\n")
@@ -474,20 +477,23 @@ class CorsikaConfig:
474
477
  )
475
478
  file_name = (
476
479
  f"{self.primary}_{self.site}_{self.layout_name}_"
477
- f"za{int(self._user_parameters['THETAP'][0]):d}-"
478
- f"{int(self._user_parameters['THETAP'][1]):d}deg"
480
+ f"za{int(self._user_parameters['THETAP'][0]):03}-"
481
+ f"azm{int(self._user_parameters['AZM'][0]):03}deg"
479
482
  f"{view_cone}{file_label}"
480
483
  )
481
484
 
482
485
  if file_type == "config_tmp":
483
486
  if run_number is not None:
484
- return f"corsika_config_run{run_number}_{file_name}.txt"
487
+ return f"corsika_config_run{run_number:06}_{file_name}.txt"
485
488
  raise ValueError("Must provide a run number for a temporary CORSIKA config file")
486
489
  if file_type == "config":
487
490
  return f"corsika_config_{file_name}.input"
488
491
  if file_type == "output_generic":
492
+ # The XXXXXX will be replaced by the run number after the pfp step with sed
489
493
  file_name = (
490
- "corsika_run${RUNNR}_${PRMNAME}_za${ZA}deg_azm${AZM}deg"
494
+ f"corsika_runXXXXXX_"
495
+ f"{self.primary}_za{int(self._user_parameters['THETAP'][0]):03}deg_"
496
+ f"azm{int(self._user_parameters['AZM'][0]):03}deg"
491
497
  f"_{self.site}_{self.layout_name}{file_label}.zst"
492
498
  )
493
499
  return file_name
@@ -496,12 +502,12 @@ class CorsikaConfig:
496
502
 
497
503
  raise ValueError(f"The requested file type ({file_type}) is unknown")
498
504
 
499
- def _set_output_file_and_directory(self, dir_type="corsika"):
505
+ def _set_output_file_and_directory(self, sub_dir="corsika"):
500
506
  config_file_name = self.get_file_name(file_type="config")
501
- file_directory = self.io_handler.get_output_directory(label=self.label, dir_type=dir_type)
507
+ file_directory = self.io_handler.get_output_directory(label=self.label, sub_dir=sub_dir)
502
508
  self._logger.info(f"Creating directory {file_directory}, if needed.")
503
509
  file_directory.mkdir(parents=True, exist_ok=True)
504
- self._config_file_path = file_directory.joinpath(config_file_name)
510
+ self.config_file_path = file_directory.joinpath(config_file_name)
505
511
 
506
512
  self._output_generic_file_name = self.get_file_name(file_type="output_generic")
507
513
 
@@ -532,4 +538,4 @@ class CorsikaConfig:
532
538
  """
533
539
  if not self._is_file_updated:
534
540
  self.export_input_file(use_multipipe)
535
- return self._config_file_path
541
+ return self.config_file_path