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
@@ -3,12 +3,11 @@ import re
3
3
  from collections import defaultdict
4
4
 
5
5
  import astropy.io.ascii
6
- import matplotlib.pyplot as plt
7
6
  import numpy as np
8
7
  from astropy.table import Table
9
8
 
10
9
  import simtools.utils.general as gen
11
- from simtools import io_handler
10
+ from simtools.io_operations import io_handler
12
11
  from simtools.model.telescope_model import TelescopeModel
13
12
  from simtools.simtel.simtel_runner_camera_efficiency import SimtelRunnerCameraEfficiency
14
13
  from simtools.utils import names
@@ -60,20 +59,20 @@ class CameraEfficiency:
60
59
  self.io_handler = io_handler.IOHandler()
61
60
  self._base_directory = self.io_handler.get_output_directory(
62
61
  label=self.label,
63
- dir_type="camera-efficiency",
64
- test=test,
62
+ sub_dir="camera-efficiency",
63
+ dir_type="test" if self.test else "simtools",
65
64
  )
66
65
 
67
66
  self._results = None
68
67
  self._has_results = False
69
68
 
70
- _config_data_in = gen.collect_data_from_yaml_or_dict(
69
+ _config_data_in = gen.collect_data_from_file_or_dict(
71
70
  config_file, config_data, allow_empty=True
72
71
  )
73
72
  _parameter_file = self.io_handler.get_input_data_file(
74
73
  "parameters", "camera-efficiency_parameters.yml"
75
74
  )
76
- _parameters = gen.collect_data_from_yaml_or_dict(_parameter_file, None)
75
+ _parameters = gen.collect_data_from_file_or_dict(_parameter_file, None)
77
76
  self.config = gen.validate_config_data(_config_data_in, _parameters)
78
77
 
79
78
  self._load_files()
@@ -133,26 +132,29 @@ class CameraEfficiency:
133
132
  """Define the variables for the file names, including the results, simtel and log file."""
134
133
  # Results file
135
134
  file_name_results = names.camera_efficiency_results_file_name(
136
- self._telescope_model.site,
137
- self._telescope_model.name,
138
- self.config.zenith_angle,
139
- self.label,
135
+ site=self._telescope_model.site,
136
+ telescope_model_name=self._telescope_model.name,
137
+ zenith_angle=self.config.zenith_angle,
138
+ azimuth_angle=self.config.azimuth_angle,
139
+ label=self.label,
140
140
  )
141
141
  self._file_results = self._base_directory.joinpath(file_name_results)
142
142
  # sim_telarray output file
143
143
  file_name_simtel = names.camera_efficiency_simtel_file_name(
144
- self._telescope_model.site,
145
- self._telescope_model.name,
146
- self.config.zenith_angle,
147
- self.label,
144
+ site=self._telescope_model.site,
145
+ telescope_model_name=self._telescope_model.name,
146
+ zenith_angle=self.config.zenith_angle,
147
+ azimuth_angle=self.config.azimuth_angle,
148
+ label=self.label,
148
149
  )
149
150
  self._file_simtel = self._base_directory.joinpath(file_name_simtel)
150
151
  # Log file
151
152
  file_name_log = names.camera_efficiency_log_file_name(
152
- self._telescope_model.site,
153
- self._telescope_model.name,
154
- self.config.zenith_angle,
155
- self.label,
153
+ site=self._telescope_model.site,
154
+ telescope_model_name=self._telescope_model.name,
155
+ zenith_angle=self.config.zenith_angle,
156
+ azimuth_angle=self.config.azimuth_angle,
157
+ label=self.label,
156
158
  )
157
159
  self._file_log = self._base_directory.joinpath(file_name_log)
158
160
 
@@ -174,6 +176,7 @@ class CameraEfficiency:
174
176
  file_simtel=self._file_simtel,
175
177
  file_log=self._file_log,
176
178
  label=self.label,
179
+ nsb_spectrum=self.config.nsb_spectrum,
177
180
  )
178
181
  simtel.run(test=self.test, force=force)
179
182
 
@@ -262,25 +265,7 @@ class CameraEfficiency:
262
265
  self._has_results = True
263
266
 
264
267
  print("\33[40;37;1m")
265
- self._logger.info(f"Spectrum weighted reflectivity: {self.calc_reflectivity():.4f}")
266
- self._logger.info(
267
- f"Camera nominal efficiency with gaps (B-TEL-1170): {self.calc_camera_efficiency():.4f}"
268
- )
269
- self._logger.info(
270
- "Telescope total efficiency"
271
- f" with gaps (was A-PERF-2020): {self.calc_tel_efficiency():.4f}"
272
- )
273
- self._logger.info(
274
- (
275
- "Telescope total Cherenkov light efficiency / sqrt(total NSB efficency) "
276
- "(A-PERF-2025/B-TEL-0090): "
277
- f"{self.calc_tot_efficiency(self.calc_tel_efficiency()):.4f}"
278
- )
279
- )
280
- self._logger.info(
281
- "Expected NSB pixel rate for the reference NSB: "
282
- f"{self.calc_nsb_rate():.4f} [p.e./ns]"
283
- )
268
+ self._logger.info(f"\n{self.results_summary()}")
284
269
  print("\033[0m")
285
270
 
286
271
  if export:
@@ -288,15 +273,54 @@ class CameraEfficiency:
288
273
 
289
274
  # END of analyze
290
275
 
276
+ def results_summary(self):
277
+ """
278
+ Print a summary of the results.
279
+ Include a header for the zenith/azimuth settings and the NSB spectrum file which was used.
280
+ The summary includes the various CTAO requirements and the final expected NSB pixel rate.
281
+ """
282
+ nsb_pixel_pe_per_ns, nsb_rate_ref_conditions = self.calc_nsb_rate()
283
+ nsb_spectrum_text = (
284
+ f"NSB spectrum file: {self.config.nsb_spectrum}"
285
+ if self.config.nsb_spectrum
286
+ else "default sim_telarray spectrum."
287
+ )
288
+ summary = (
289
+ f"Results summary for {self._telescope_model.name} at "
290
+ f"zenith={self.config.zenith_angle:.1f} deg, "
291
+ f"azimuth={self.config.azimuth_angle:.1f} deg\n"
292
+ f"Using the {nsb_spectrum_text}\n"
293
+ f"\nSpectrum weighted reflectivity: {self.calc_reflectivity():.4f}\n"
294
+ "Camera nominal efficiency with gaps (B-TEL-1170): "
295
+ f"{self.calc_camera_efficiency():.4f}\n"
296
+ "Telescope total efficiency"
297
+ f" with gaps (was A-PERF-2020): {self.calc_tel_efficiency():.4f}\n"
298
+ "Telescope total Cherenkov light efficiency / sqrt(total NSB efficency) "
299
+ "(A-PERF-2025/B-TEL-0090): "
300
+ f"{self.calc_tot_efficiency(self.calc_tel_efficiency()):.4f}\n"
301
+ "Expected NSB pixel rate for the provided NSB spectrum: "
302
+ f"{nsb_pixel_pe_per_ns:.4f} [p.e./ns]\n"
303
+ "Expected NSB pixel rate for the reference NSB: "
304
+ f"{nsb_rate_ref_conditions:.4f} [p.e./ns]\n"
305
+ )
306
+
307
+ return summary
308
+
291
309
  def export_results(self):
292
- """Export results to a csv file."""
310
+ """Export results to a ecsv file."""
293
311
  if not self._has_results:
294
312
  self._logger.error("Cannot export results because they do not exist")
295
313
  else:
296
- self._logger.info(f"Exporting results to {self._file_results}")
314
+ self._logger.info(f"Exporting testeff table to {self._file_results}")
297
315
  astropy.io.ascii.write(
298
316
  self._results, self._file_results, format="basic", overwrite=True
299
317
  )
318
+ _results_summary_file = (
319
+ str(self._file_results).replace(".ecsv", ".txt").replace("-table-", "-summary-")
320
+ )
321
+ self._logger.info(f"Exporting summary results to {_results_summary_file}")
322
+ with open(_results_summary_file, "w", encoding="utf-8") as file:
323
+ file.write(self.results_summary())
300
324
 
301
325
  def _read_results(self):
302
326
  """Read existing results file and store it in _results."""
@@ -404,21 +428,21 @@ class CameraEfficiency:
404
428
 
405
429
  Returns
406
430
  -------
407
- nsb_rate
408
- NSB rate in p.e./ns
431
+ nsb_rate_provided_spectrum: float
432
+ NSB pixel rate in p.e./ns for the provided NSB spectrum
433
+ nsb_rate_ref_conditions: float
434
+ NSB pixel rate in p.e./ns for reference conditions
435
+ (https://jama.cta-observatory.org/perspective.req#/items/26694?projectId=11)
409
436
  """
410
437
 
411
- nsb_pe_per_ns = (
438
+ nsb_rate_provided_spectrum = (
412
439
  np.sum(self._results["N4"])
413
440
  * self._telescope_model.camera.get_pixel_active_solid_angle()
414
441
  * self._telescope_model.get_on_axis_eff_optical_area().to("m2").value
415
442
  / self._telescope_model.get_telescope_transmission_parameters()[0]
416
443
  )
417
444
 
418
- print(self._telescope_model.get_on_axis_eff_optical_area().to("m2").value)
419
-
420
- # NSB input spectrum is from Benn&Ellison
421
- # (integral is in ph./(cm² ns sr) ) from 300 - 650 nm:
445
+ # (integral is in ph./(m^2 ns sr) ) from 300 - 650 nm:
422
446
  n1_reduced_wl = self._results["N1"][[299 < wl_now < 651 for wl_now in self._results["wl"]]]
423
447
  n1_sum = np.sum(n1_reduced_wl)
424
448
  n1_integral_edges = self._results["N1"][
@@ -426,43 +450,12 @@ class CameraEfficiency:
426
450
  ]
427
451
  n1_integral_edges_sum = np.sum(n1_integral_edges)
428
452
  nsb_integral = 0.0001 * (n1_sum - 0.5 * n1_integral_edges_sum)
429
- nsb_rate = (
430
- nsb_pe_per_ns
453
+ nsb_rate_ref_conditions = (
454
+ nsb_rate_provided_spectrum
431
455
  * self._telescope_model.reference_data["nsb_reference_value"]["Value"]
432
456
  / nsb_integral
433
457
  )
434
- return nsb_rate
435
-
436
- def plot(self, key, **kwargs): # FIXME - remove this function, probably not needed
437
- """
438
- Plot key vs wavelength.
439
-
440
- Parameters
441
- ----------
442
- key: str
443
- cherenkov or nsb
444
- **kwargs:
445
- kwargs for plt.plot
446
-
447
- Raises
448
- ------
449
- KeyError
450
- If key is not among the valid options.
451
- """
452
- if key not in ["cherenkov", "nsb"]:
453
- msg = "Invalid key to plot"
454
- self._logger.error(msg)
455
- raise KeyError(msg)
456
-
457
- ax = plt.gca()
458
- first_letter = "C" if key == "cherenkov" else "N"
459
- for par in ["1", "2", "3", "4", "4x"]:
460
- ax.plot(
461
- self._results["wl"],
462
- self._results[first_letter + par],
463
- label=first_letter + par,
464
- **kwargs,
465
- )
458
+ return nsb_rate_provided_spectrum, nsb_rate_ref_conditions
466
459
 
467
460
  def plot_cherenkov_efficiency(self):
468
461
  """
@@ -32,6 +32,7 @@ class CommandLineParser(argparse.ArgumentParser):
32
32
  paths=True,
33
33
  output=False,
34
34
  telescope_model=False,
35
+ site_model=False,
35
36
  db_config=False,
36
37
  job_submission=False,
37
38
  ):
@@ -46,6 +47,8 @@ class CommandLineParser(argparse.ArgumentParser):
46
47
  Add output file configuration to list of args.
47
48
  telescope_model: bool
48
49
  Add telescope model configuration to list of args.
50
+ site_model: bool
51
+ Add site model configuration to list of args (not required of telescope_model is True).
49
52
  db_config: bool
50
53
  Add database configuration parameters to list of args.
51
54
  job_submission: bool
@@ -54,6 +57,8 @@ class CommandLineParser(argparse.ArgumentParser):
54
57
 
55
58
  if telescope_model:
56
59
  self.initialize_telescope_model_arguments()
60
+ elif site_model:
61
+ self.initialize_telescope_model_arguments(add_telescope=False)
57
62
  if job_submission:
58
63
  self.initialize_job_submission_arguments()
59
64
  if db_config:
@@ -79,6 +84,13 @@ class CommandLineParser(argparse.ArgumentParser):
79
84
  type=str,
80
85
  required=False,
81
86
  )
87
+ _job_group.add_argument(
88
+ "--env_file",
89
+ help="file with environment variables",
90
+ default=".env",
91
+ type=str,
92
+ required=False,
93
+ )
82
94
 
83
95
  def initialize_path_arguments(self):
84
96
  """
@@ -96,12 +108,12 @@ class CommandLineParser(argparse.ArgumentParser):
96
108
  "--output_path",
97
109
  help="path pointing towards output directory",
98
110
  type=Path,
99
- default=".",
111
+ default="./simtools-output/",
100
112
  required=False,
101
113
  )
102
114
  _job_group.add_argument(
103
115
  "--use_plain_output_path",
104
- help="use plain output path (without adding the tool name and dates)",
116
+ help="use plain output path (without the tool name and dates)",
105
117
  action="store_true",
106
118
  required=False,
107
119
  )
@@ -138,6 +150,13 @@ class CommandLineParser(argparse.ArgumentParser):
138
150
  default="ecsv",
139
151
  required=False,
140
152
  )
153
+ _job_group.add_argument(
154
+ "--skip_output_validation",
155
+ help="skip output data validation against schema",
156
+ default=False,
157
+ required=False,
158
+ action="store_true",
159
+ )
141
160
 
142
161
  def initialize_application_execution_arguments(self):
143
162
  """
@@ -160,7 +179,7 @@ class CommandLineParser(argparse.ArgumentParser):
160
179
  "--log_level",
161
180
  action="store",
162
181
  default="info",
163
- help="log level to print (default is INFO)",
182
+ help="log level to print",
164
183
  required=False,
165
184
  )
166
185
  _job_group.add_argument(
@@ -181,7 +200,7 @@ class CommandLineParser(argparse.ArgumentParser):
181
200
  )
182
201
  _job_group.add_argument(
183
202
  "--db_api_authentication_database",
184
- help="database with user info (optional, default is 'admin')",
203
+ help="database with user info (optional)",
185
204
  type=str,
186
205
  required=False,
187
206
  default="admin",
@@ -223,7 +242,10 @@ class CommandLineParser(argparse.ArgumentParser):
223
242
  Set to allow a telescope name argument.
224
243
  """
225
244
 
226
- _job_group = self.add_argument_group("telescope model")
245
+ if add_telescope:
246
+ _job_group = self.add_argument_group("telescope model")
247
+ else:
248
+ _job_group = self.add_argument_group("site model")
227
249
  _job_group.add_argument(
228
250
  "--site", help="CTAO site (e.g., North, South)", type=self.site, required=False
229
251
  )
@@ -238,7 +260,7 @@ class CommandLineParser(argparse.ArgumentParser):
238
260
  "--model_version",
239
261
  help="model version",
240
262
  type=str,
241
- default="Current",
263
+ default="Released",
242
264
  )
243
265
 
244
266
  @staticmethod
@@ -263,10 +285,8 @@ class CommandLineParser(argparse.ArgumentParser):
263
285
 
264
286
  """
265
287
 
266
- fsite = str(value)
267
- if not names.validate_site_name(fsite):
268
- raise argparse.ArgumentTypeError(f"{fsite} is an invalid site")
269
- return fsite
288
+ names.validate_site_name(str(value))
289
+ return str(value)
270
290
 
271
291
  @staticmethod
272
292
  def telescope(value):
@@ -290,10 +310,8 @@ class CommandLineParser(argparse.ArgumentParser):
290
310
 
291
311
  """
292
312
 
293
- ftelescope = str(value)
294
- if not names.validate_telescope_model_name(ftelescope):
295
- raise argparse.ArgumentTypeError(f"{ftelescope} is an invalid telescope name")
296
- return ftelescope
313
+ names.validate_telescope_model_name(str(value))
314
+ return str(value)
297
315
 
298
316
  @staticmethod
299
317
  def efficiency_interval(value):
@@ -334,7 +352,7 @@ class CommandLineParser(argparse.ArgumentParser):
334
352
 
335
353
  Parameters
336
354
  ----------
337
- angle: float
355
+ angle: float, str, astropy.Quantity
338
356
  zenith angle to verify
339
357
 
340
358
  Returns
@@ -353,17 +371,22 @@ class CommandLineParser(argparse.ArgumentParser):
353
371
  logger = logging.getLogger(__name__)
354
372
 
355
373
  try:
356
- fangle = float(angle)
357
- except ValueError:
358
- logger.error("The zenith angle provided is not a valid numeric value.")
359
- raise
360
- if fangle < 0.0 or fangle > 180.0:
374
+ try:
375
+ fangle = float(angle) * u.deg
376
+ except ValueError:
377
+ fangle = u.Quantity(angle).to("deg")
378
+ except TypeError as exc:
379
+ logger.error(
380
+ "The zenith angle provided is not a valid numerical or astropy.Quantity value."
381
+ )
382
+ raise exc
383
+ if fangle < 0.0 * u.deg or fangle > 180.0 * u.deg:
361
384
  raise argparse.ArgumentTypeError(
362
385
  f"The provided zenith angle, {angle:.1f}, "
363
386
  "is outside of the allowed [0, 180] interval"
364
387
  )
365
388
 
366
- return fangle * u.deg
389
+ return fangle
367
390
 
368
391
  @staticmethod
369
392
  def azimuth_angle(angle):
@@ -380,7 +403,7 @@ class CommandLineParser(argparse.ArgumentParser):
380
403
  Returns
381
404
  -------
382
405
  Astropy.Quantity
383
- Validated/Converted aziumth angle in degrees
406
+ Validated/Converted azimuth angle in degrees
384
407
 
385
408
  Raises
386
409
  ------
@@ -403,6 +426,13 @@ class CommandLineParser(argparse.ArgumentParser):
403
426
  except ValueError:
404
427
  logger.debug(
405
428
  "The azimuth angle provided is not a valid numeric value. "
429
+ "Will check if it is an astropy.Quantity instead"
430
+ )
431
+ try:
432
+ return u.Quantity(angle).to("deg")
433
+ except TypeError:
434
+ logger.debug(
435
+ "The azimuth angle provided is not a valid astropy.Quantity. "
406
436
  "Will check if it is (north, south, east, west) instead"
407
437
  )
408
438
  if isinstance(angle, str):