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,14 +1,11 @@
1
1
  #!/usr/bin/python3
2
2
 
3
- import logging
4
3
  import math
5
4
 
6
5
  from simtools.utils import names
7
6
 
8
7
  __all__ = [
9
8
  "compute_telescope_transmission",
10
- "get_telescope_class",
11
- "get_camera_name",
12
9
  "is_two_mirror_telescope",
13
10
  "split_simtel_parameter",
14
11
  ]
@@ -62,71 +59,6 @@ def compute_telescope_transmission(pars, off_axis):
62
59
  return pars[0] / (1.0 + pars[2] * t ** pars[4])
63
60
 
64
61
 
65
- def get_camera_name(telescope_model_name):
66
- """
67
- Get camera name from the telescope name.
68
-
69
- Parameters
70
- ----------
71
- telescope_model_name: str
72
- Telescope model name (e.g., LST-1).
73
-
74
- Returns
75
- -------
76
- str
77
- Camera name (validated by util.names).
78
- """
79
-
80
- _logger = logging.getLogger(__name__)
81
- camera_name = ""
82
- tel_class, tel_type = names.split_telescope_model_name(telescope_model_name)
83
- if tel_class == "LST":
84
- camera_name = "LST"
85
- elif tel_class == "MST":
86
- if "FlashCam" in tel_type:
87
- camera_name = "FlashCam"
88
- elif "NectarCam" in tel_type:
89
- camera_name = "NectarCam"
90
- else:
91
- _logger.error("Camera not found for MST class telescope")
92
- elif tel_class == "SCT":
93
- camera_name = "SCT"
94
- elif tel_class == "SST":
95
- if "ASTRI" in tel_type:
96
- camera_name = "ASTRI"
97
- elif "GCT" in tel_type:
98
- camera_name = "GCT"
99
- elif "1M" in tel_type:
100
- camera_name = "1M"
101
- else:
102
- camera_name = "SST"
103
- else:
104
- _logger.error("Invalid telescope name - please validate it first")
105
-
106
- camera_name = names.validate_camera_name(camera_name)
107
- _logger.debug(f"Camera name - {camera_name}")
108
- return camera_name
109
-
110
-
111
- def get_telescope_class(telescope_model_name):
112
- """
113
- Get telescope class from telescope name.
114
-
115
- Parameters
116
- ----------
117
- telescope_model_name: str
118
- Telescope model name (ex. LST-1).
119
-
120
- Returns
121
- -------
122
- str
123
- Telescope class (SST, MST, ...).
124
- """
125
-
126
- tel_class, _ = names.split_telescope_model_name(telescope_model_name)
127
- return tel_class
128
-
129
-
130
62
  def is_two_mirror_telescope(telescope_model_name):
131
63
  """
132
64
  Check if the telescope is a two mirror design.
@@ -141,7 +73,7 @@ def is_two_mirror_telescope(telescope_model_name):
141
73
  bool
142
74
  True if the telescope is a two mirror one.
143
75
  """
144
- tel_class, tel_type = names.split_telescope_model_name(telescope_model_name)
76
+ tel_class, tel_type, _ = names.split_telescope_model_name(telescope_model_name)
145
77
  if tel_class == "SST":
146
78
  # Only 1M is False
147
79
  return "1M" not in tel_type
@@ -9,7 +9,8 @@ from astropy import units as u
9
9
  from astropy.table import Table
10
10
 
11
11
  import simtools.utils.general as gen
12
- from simtools import db_handler, io_handler
12
+ from simtools import db_handler
13
+ from simtools.io_operations import io_handler
13
14
  from simtools.model.camera import Camera
14
15
  from simtools.model.mirrors import Mirrors
15
16
  from simtools.simtel.simtel_config_writer import SimtelConfigWriter
@@ -46,8 +47,9 @@ class TelescopeModel:
46
47
  self,
47
48
  site,
48
49
  telescope_model_name,
49
- mongo_db_config,
50
- model_version="Current",
50
+ mongo_db_config=None,
51
+ model_version="Released",
52
+ db=None,
51
53
  label=None,
52
54
  ):
53
55
  """
@@ -70,7 +72,12 @@ class TelescopeModel:
70
72
  self._camera = None
71
73
 
72
74
  self.io_handler = io_handler.IOHandler()
73
- self.mongo_db_config = mongo_db_config
75
+ self.db = None
76
+ if db is not None:
77
+ self.db = db
78
+ elif mongo_db_config is not None:
79
+ self._logger.debug("Connecting to DB")
80
+ self.db = db_handler.DatabaseHandler(mongo_db_config=mongo_db_config)
74
81
 
75
82
  self._parameters = {}
76
83
 
@@ -131,8 +138,9 @@ class TelescopeModel:
131
138
 
132
139
  Notes
133
140
  -----
134
- Todo: Dealing with ifdef/indef etc. By now it just keeps the last version of the parameters
135
- in the file.
141
+ This function does not deal with ifdef/indef etc., it just keeps the last version
142
+ of the parameters in the file. This is fine for now since we do not
143
+ expect to read from sim_telarray config files in the future.
136
144
 
137
145
  Parameters
138
146
  ----------
@@ -225,7 +233,9 @@ class TelescopeModel:
225
233
  def _set_config_file_directory_and_name(self):
226
234
  """Define the variable _config_file_directory and create directories, if needed."""
227
235
 
228
- self._config_file_directory = self.io_handler.get_output_directory(self.label, "model")
236
+ self._config_file_directory = self.io_handler.get_output_directory(
237
+ label=self.label, sub_dir="model"
238
+ )
229
239
 
230
240
  # Setting file name and the location
231
241
  config_file_name = names.simtel_telescope_config_file_name(
@@ -236,19 +246,20 @@ class TelescopeModel:
236
246
  def _load_parameters_from_db(self):
237
247
  """Read parameters from DB and store them in _parameters."""
238
248
 
239
- if self.mongo_db_config is None:
249
+ if self.db is None:
240
250
  return
241
251
 
242
252
  self._logger.debug("Reading telescope parameters from DB")
243
253
 
244
254
  self._set_config_file_directory_and_name()
245
- db = db_handler.DatabaseHandler(mongo_db_config=self.mongo_db_config)
246
- self._parameters = db.get_model_parameters(
255
+ self._parameters = self.db.get_model_parameters(
247
256
  self.site, self.name, self.model_version, only_applicable=True
248
257
  )
249
258
 
250
259
  self._logger.debug("Reading site parameters from DB")
251
- _site_pars = db.get_site_parameters(self.site, self.model_version, only_applicable=True)
260
+ _site_pars = self.db.get_site_parameters(
261
+ self.site, self.model_version, only_applicable=True
262
+ )
252
263
  self._parameters.update(_site_pars)
253
264
 
254
265
  def has_parameter(self, par_name):
@@ -494,7 +505,6 @@ class TelescopeModel:
494
505
 
495
506
  def export_model_files(self):
496
507
  """Exports the model files into the config file directory."""
497
- db = db_handler.DatabaseHandler(mongo_db_config=self.mongo_db_config)
498
508
 
499
509
  # Removing parameter files added manually (which are not in DB)
500
510
  pars_from_db = copy(self._parameters)
@@ -502,7 +512,7 @@ class TelescopeModel:
502
512
  for par in self._added_parameter_files:
503
513
  pars_from_db.pop(par)
504
514
 
505
- db.export_model_files(pars_from_db, self._config_file_directory)
515
+ self.db.export_model_files(pars_from_db, self._config_file_directory)
506
516
  self._is_exported_model_files_up_to_date = True
507
517
 
508
518
  def print_parameters(self):
@@ -526,12 +536,11 @@ class TelescopeModel:
526
536
  def export_derived_files(self):
527
537
  """Write to disk a file from the derived values DB."""
528
538
 
529
- db = db_handler.DatabaseHandler(mongo_db_config=self.mongo_db_config)
530
539
  for par_now in self.derived.values():
531
540
  if par_now["File"]:
532
- db.export_file_db(
533
- db_name=db.DB_DERIVED_VALUES,
534
- dest=self.io_handler.get_output_directory(self.label, "derived"),
541
+ self.db.export_file_db(
542
+ db_name=self.db.DB_DERIVED_VALUES,
543
+ dest=self.io_handler.get_output_directory(label=self.label, sub_dir="derived"),
535
544
  file_name=par_now["Value"],
536
545
  )
537
546
 
@@ -655,21 +664,19 @@ class TelescopeModel:
655
664
  "Mirror_list_file was not found in the config directory - "
656
665
  "Using the one found in the model_path"
657
666
  )
658
- self._mirrors = Mirrors(mirror_list_file)
667
+ self._mirrors = Mirrors(mirror_list_file, parameters=self._parameters)
659
668
 
660
669
  def _load_reference_data(self):
661
670
  """Load the reference data for this telescope from the DB."""
662
671
  self._logger.debug("Reading reference data from DB")
663
- db = db_handler.DatabaseHandler(mongo_db_config=self.mongo_db_config)
664
- self._reference_data = db.get_reference_data(
672
+ self._reference_data = self.db.get_reference_data(
665
673
  self.site, self.model_version, only_applicable=True
666
674
  )
667
675
 
668
676
  def _load_derived_values(self):
669
677
  """Load the derived values for this telescope from the DB."""
670
678
  self._logger.debug("Reading derived data from DB")
671
- db = db_handler.DatabaseHandler(mongo_db_config=self.mongo_db_config)
672
- self._derived = db.get_derived_values(
679
+ self._derived = self.db.get_derived_values(
673
680
  self.site,
674
681
  self.name,
675
682
  self.model_version,
@@ -710,7 +717,7 @@ class TelescopeModel:
710
717
  label=self.label,
711
718
  )
712
719
 
713
- def is_file_2D(self, par):
720
+ def is_file_2d(self, par):
714
721
  """
715
722
  Check if the file referenced by par is a 2D table.
716
723
 
@@ -731,8 +738,8 @@ class TelescopeModel:
731
738
  file_name = self.get_parameter_value(par)
732
739
  file = self.get_config_directory().joinpath(file_name)
733
740
  with open(file, "r", encoding="utf-8") as f:
734
- is2D = "@RPOL@" in f.read()
735
- return is2D
741
+ is_2d = "@RPOL@" in f.read()
742
+ return is_2d
736
743
 
737
744
  def read_two_dim_wavelength_angle(self, file_name):
738
745
  """
simtools/psf_analysis.py CHANGED
@@ -8,6 +8,7 @@ Author: Raul R Prado
8
8
 
9
9
  import logging
10
10
  from math import fabs, pi, sqrt
11
+ from pathlib import Path
11
12
 
12
13
  import astropy.units as u
13
14
  import matplotlib.pyplot as plt
@@ -50,20 +51,20 @@ class PSFImage:
50
51
  self.centroid_x = None
51
52
  self.centroid_y = None
52
53
  self._total_area = total_scattered_area
53
- self._stored_PSF = {}
54
+ self._stored_psf = {}
54
55
  if focal_length is not None:
55
56
  self._cm_to_deg = 180.0 / pi / focal_length
56
57
  self._has_focal_length = True
57
58
  else:
58
59
  self._has_focal_length = False
59
60
 
60
- def read_photon_list_from_simtel_file(self, file):
61
+ def read_photon_list_from_simtel_file(self, photons_file):
61
62
  """
62
63
  Read photon list file generated by sim_telarray and store the photon positions (2D).
63
64
 
64
65
  Parameters
65
66
  ----------
66
- file: str
67
+ photons_file: str
67
68
  Name of sim_telarray file with photon list.
68
69
 
69
70
  Raises
@@ -72,9 +73,15 @@ class PSFImage:
72
73
  If photon positions X and Y are not compatible or are empty.
73
74
 
74
75
  """
75
- self._logger.info(f"Reading sim_telarray file {file}")
76
+ self._logger.info(f"Reading sim_telarray file {photons_file}")
76
77
  self._total_photons = 0
77
- with open(file, "r", encoding="utf-8") as f:
78
+ if Path(photons_file).suffix == ".gz":
79
+ import gzip # pylint: disable=import-outside-toplevel
80
+
81
+ file_open_function = gzip.open
82
+ else:
83
+ file_open_function = open
84
+ with file_open_function(photons_file, "rb") as f:
78
85
  for line in f:
79
86
  self._process_simtel_line(line)
80
87
 
@@ -120,7 +127,7 @@ class PSFImage:
120
127
  A line from the photon list file generated by sim_telarray.
121
128
  """
122
129
  words = line.split()
123
- if "falling on an area of" in line:
130
+ if b"falling on an area of" in line:
124
131
  self._total_photons += int(words[4])
125
132
  total_area_in_file = float(words[14])
126
133
  if self._total_area is None:
@@ -134,7 +141,7 @@ class PSFImage:
134
141
  else:
135
142
  # Do nothing - Keep the original value of _total_area
136
143
  pass
137
- elif "#" in line or len(words) == 0:
144
+ elif b"#" in line or len(words) == 0:
138
145
  # Skipping comments
139
146
  pass
140
147
  else:
@@ -190,10 +197,10 @@ class PSFImage:
190
197
  if unit == "deg" and not self._has_focal_length:
191
198
  self._logger.error("PSF cannot be computed in deg because focal length is not set")
192
199
  return None
193
- if fraction not in self._stored_PSF:
200
+ if fraction not in self._stored_psf:
194
201
  self._compute_psf(fraction)
195
202
  unit_factor = 1 if unit == "cm" else self._cm_to_deg
196
- return self._stored_PSF[fraction] * unit_factor
203
+ return self._stored_psf[fraction] * unit_factor
197
204
 
198
205
  def set_psf(self, value, fraction=0.8, unit="cm"):
199
206
  """
@@ -212,7 +219,7 @@ class PSFImage:
212
219
  self._logger.error("PSF cannot be set in deg because focal length is not set")
213
220
  return
214
221
  unit_factor = 1 if unit == "cm" else 1.0 / self._cm_to_deg
215
- self._stored_PSF[fraction] = value * unit_factor
222
+ self._stored_psf[fraction] = value * unit_factor
216
223
 
217
224
  def _compute_psf(self, fraction):
218
225
  """
@@ -223,7 +230,7 @@ class PSFImage:
223
230
  fraction: float
224
231
  Fraction of photons within the containing radius
225
232
  """
226
- self._stored_PSF[fraction] = self._find_psf(fraction)
233
+ self._stored_psf[fraction] = self._find_psf(fraction)
227
234
 
228
235
  def _find_psf(self, fraction):
229
236
  """
@@ -252,21 +259,21 @@ class PSFImage:
252
259
  target_number = fraction * self._number_of_detected_photons
253
260
  current_radius = 1.5 * radius_sig
254
261
  start_number = self._sum_photons_in_radius(current_radius)
255
- SCALE = 0.5 * sqrt(current_radius * current_radius / start_number)
262
+ scale = 0.5 * sqrt(current_radius * current_radius / start_number)
256
263
  delta_number = start_number - target_number
257
264
  n_iter = 0
258
- MAX_ITER = 100
259
- TOLERANCE = self._number_of_detected_photons / 1000.0
265
+ max_iter = 100
266
+ tolerance = self._number_of_detected_photons / 1000.0
260
267
  found_radius = False
261
- while not found_radius and n_iter < MAX_ITER:
268
+ while not found_radius and n_iter < max_iter:
262
269
  n_iter += 1
263
- dr = -delta_number * SCALE / sqrt(target_number)
270
+ dr = -delta_number * scale / sqrt(target_number)
264
271
  while current_radius + dr < 0:
265
272
  dr *= 0.5
266
273
  current_radius += dr
267
274
  current_number = self._sum_photons_in_radius(current_radius)
268
275
  delta_number = current_number - target_number
269
- found_radius = fabs(delta_number) < TOLERANCE
276
+ found_radius = fabs(delta_number) < tolerance
270
277
 
271
278
  if found_radius:
272
279
  # Diameter = 2 * radius
@@ -385,7 +392,7 @@ class PSFImage:
385
392
  psf_ls="--",
386
393
  )
387
394
  kwargs_for_image = collect_kwargs("image", kwargs)
388
- kwargs_for_PSF = collect_kwargs("psf", kwargs)
395
+ kwargs_for_psf = collect_kwargs("psf", kwargs)
389
396
 
390
397
  ax = plt.gca()
391
398
  # Image histogram
@@ -394,7 +401,7 @@ class PSFImage:
394
401
 
395
402
  # PSF circle
396
403
  center = (0, 0) if centralized else (self.centroid_x, self.centroid_y)
397
- circle = plt.Circle(center, self.get_psf(0.8) / 2, **kwargs_for_PSF)
404
+ circle = plt.Circle(center, self.get_psf(0.8) / 2, **kwargs_for_psf)
398
405
  ax.add_artist(circle)
399
406
 
400
407
  ax.axhline(0, color="k", linestyle="--", zorder=3, linewidth=0.5)
simtools/ray_tracing.py CHANGED
@@ -1,5 +1,7 @@
1
+ import gzip
1
2
  import logging
2
3
  import shlex
4
+ import shutil
3
5
  import subprocess
4
6
  from copy import copy
5
7
  from math import pi, tan
@@ -12,7 +14,7 @@ import numpy as np
12
14
  from astropy.table import QTable
13
15
 
14
16
  import simtools.utils.general as gen
15
- from simtools import io_handler
17
+ from simtools.io_operations import io_handler
16
18
  from simtools.model.model_utils import compute_telescope_transmission
17
19
  from simtools.model.telescope_model import TelescopeModel
18
20
  from simtools.psf_analysis import PSFImage
@@ -61,18 +63,17 @@ class RayTracing:
61
63
  """
62
64
 
63
65
  self._logger = logging.getLogger(__name__)
66
+ self._logger.debug("Initializing RayTracing class")
64
67
 
65
68
  self._simtel_source_path = Path(simtel_source_path)
66
69
  self._io_handler = io_handler.IOHandler()
67
70
 
68
71
  self._telescope_model = self._validate_telescope_model(telescope_model)
69
72
 
70
- _config_data_in = gen.collect_data_from_yaml_or_dict(config_file, config_data)
71
- _parameter_file = self._io_handler.get_input_data_file(
72
- parent_dir="parameters", file_name="ray-tracing_parameters.yml"
73
+ self.config = gen.validate_config_data(
74
+ gen.collect_data_from_file_or_dict(config_file, config_data),
75
+ SimtelRunnerRayTracing.ray_tracing_default_configuration(False),
73
76
  )
74
- _parameters = gen.collect_data_from_yaml_or_dict(_parameter_file, None)
75
- self.config = gen.validate_config_data(_config_data_in, _parameters)
76
77
 
77
78
  # Due to float representation, round the off-axis angles so the values in results table
78
79
  # are the same as provided.
@@ -80,13 +81,16 @@ class RayTracing:
80
81
 
81
82
  self.label = label if label is not None else self._telescope_model.label
82
83
 
83
- self._output_directory = self._io_handler.get_output_directory(self.label, "ray-tracing")
84
+ self._output_directory = self._io_handler.get_output_directory(
85
+ label=self.label, sub_dir="ray-tracing"
86
+ )
84
87
 
85
88
  # Loading relevant attributes in case of single mirror mode.
86
89
  if self.config.single_mirror_mode:
87
90
  # Recalculating source distance.
88
91
  self._logger.debug(
89
- "Single mirror mode is activate - source distance is being recalculated to 2 * flen"
92
+ "Single mirror mode is activated - "
93
+ "source distance is being recalculated to 2 * flen"
90
94
  )
91
95
  mir_flen = self._telescope_model.get_parameter_value("mirror_focal_length")
92
96
  self._source_distance = 2 * float(mir_flen) * u.cm.to(u.km) # km
@@ -178,18 +182,40 @@ class RayTracing:
178
182
  simtel = SimtelRunnerRayTracing(
179
183
  simtel_source_path=self._simtel_source_path,
180
184
  telescope_model=self._telescope_model,
185
+ test=test,
181
186
  config_data={
182
187
  "zenith_angle": self.config.zenith_angle * u.deg,
183
188
  "source_distance": self._source_distance * u.km,
184
189
  "off_axis_angle": this_off_axis * u.deg,
185
- "mirror_number": this_mirror,
190
+ "mirror_numbers": this_mirror,
186
191
  "use_random_focal_length": self.config.use_random_focal_length,
192
+ "single_mirror_mode": self.config.single_mirror_mode,
187
193
  },
188
- single_mirror_mode=self.config.single_mirror_mode,
189
194
  force_simulate=force,
190
195
  )
191
196
  simtel.run(test=test, force=force)
192
197
 
198
+ photons_file_name = names.ray_tracing_file_name(
199
+ self._telescope_model.site,
200
+ self._telescope_model.name,
201
+ self._source_distance,
202
+ self.config.zenith_angle,
203
+ this_off_axis,
204
+ this_mirror if self.config.single_mirror_mode else None,
205
+ self.label,
206
+ "photons",
207
+ )
208
+ photons_file = self._output_directory.joinpath(photons_file_name)
209
+
210
+ self._logger.debug("Using gzip to compress the photons file.")
211
+
212
+ with open(photons_file, "rb") as f_in:
213
+ with gzip.open(
214
+ photons_file.with_suffix(photons_file.suffix + ".gz"), "wb"
215
+ ) as f_out:
216
+ shutil.copyfileobj(f_in, f_out)
217
+ photons_file.unlink()
218
+
193
219
  def analyze(
194
220
  self,
195
221
  export=True,
@@ -254,7 +280,7 @@ class RayTracing:
254
280
  "photons",
255
281
  )
256
282
 
257
- photons_file = self._output_directory.joinpath(photons_file_name)
283
+ photons_file = self._output_directory.joinpath(photons_file_name + ".gz")
258
284
  tel_transmission = compute_telescope_transmission(
259
285
  tel_transmission_pars, this_off_axis
260
286
  )
@@ -326,23 +352,25 @@ class RayTracing:
326
352
  """
327
353
 
328
354
  try:
329
- with open(file, encoding="utf-8") as _stdin:
330
- rx_output = subprocess.Popen( # pylint: disable=consider-using-with
331
- shlex.split(
332
- f"{self._simtel_source_path}/sim_telarray/bin/rx "
333
- f"-f {containment_fraction:.2f} -v"
334
- ),
335
- stdin=_stdin,
336
- stdout=subprocess.PIPE,
337
- ).communicate()[0]
355
+ rx_output = subprocess.Popen( # pylint: disable=consider-using-with
356
+ shlex.split(
357
+ f"{self._simtel_source_path}/sim_telarray/bin/rx "
358
+ f"-f {containment_fraction:.2f} -v"
359
+ ),
360
+ stdin=subprocess.PIPE,
361
+ stdout=subprocess.PIPE,
362
+ )
363
+ with gzip.open(file, "rb") as _stdin:
364
+ with rx_output.stdin:
365
+ shutil.copyfileobj(_stdin, rx_output.stdin)
366
+ try:
367
+ rx_output = rx_output.communicate()[0].splitlines()[-1:][0].split()
368
+ except IndexError:
369
+ self._logger.error(f"Invalid output from rx: {rx_output}")
370
+ raise
338
371
  except FileNotFoundError:
339
372
  self._logger.error(f"Photon list file not found: {file}")
340
373
  raise
341
- try:
342
- rx_output = rx_output.splitlines()[-1:][0].split()
343
- except IndexError:
344
- self._logger.error(f"Invalid output from rx: {rx_output}")
345
- raise
346
374
  containment_diameter_cm = 2 * float(rx_output[0])
347
375
  x_mean = float(rx_output[1])
348
376
  y_mean = float(rx_output[2])
@@ -489,7 +517,7 @@ class RayTracing:
489
517
  """
490
518
  images = []
491
519
  for this_off_axis in self.config.off_axis_angle:
492
- if this_off_axis in self._psf_images:
520
+ if self._psf_images and this_off_axis in self._psf_images:
493
521
  images.append(self._psf_images[this_off_axis])
494
522
  if len(images) == 0:
495
523
  self._logger.error("No image found")