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.
- {gammasimtools-0.5.1.dist-info → gammasimtools-0.6.1.dist-info}/METADATA +80 -28
- gammasimtools-0.6.1.dist-info/RECORD +91 -0
- {gammasimtools-0.5.1.dist-info → gammasimtools-0.6.1.dist-info}/WHEEL +1 -1
- {gammasimtools-0.5.1.dist-info → gammasimtools-0.6.1.dist-info}/entry_points.txt +4 -2
- simtools/_version.py +14 -2
- simtools/applications/add_file_to_db.py +2 -1
- simtools/applications/compare_cumulative_psf.py +10 -15
- simtools/applications/db_development_tools/add_new_parameter_to_db.py +12 -6
- simtools/applications/derive_mirror_rnda.py +95 -71
- simtools/applications/generate_corsika_histograms.py +216 -131
- simtools/applications/generate_default_metadata.py +110 -0
- simtools/applications/generate_simtel_array_histograms.py +192 -0
- simtools/applications/get_file_from_db.py +1 -1
- simtools/applications/get_parameter.py +3 -3
- simtools/applications/make_regular_arrays.py +89 -93
- simtools/applications/{plot_layout_array.py → plot_array_layout.py} +15 -14
- simtools/applications/print_array_elements.py +81 -34
- simtools/applications/produce_array_config.py +2 -2
- simtools/applications/production.py +39 -5
- simtools/applications/sim_showers_for_trigger_rates.py +26 -30
- simtools/applications/simulate_prod.py +49 -107
- simtools/applications/submit_data_from_external.py +8 -10
- simtools/applications/tune_psf.py +16 -18
- simtools/applications/validate_camera_efficiency.py +63 -9
- simtools/applications/validate_camera_fov.py +9 -13
- simtools/applications/validate_file_using_schema.py +127 -0
- simtools/applications/validate_optics.py +13 -15
- simtools/camera_efficiency.py +73 -80
- simtools/configuration/commandline_parser.py +52 -22
- simtools/configuration/configurator.py +98 -33
- simtools/constants.py +9 -0
- simtools/corsika/corsika_config.py +28 -22
- simtools/corsika/corsika_default_config.py +282 -0
- simtools/corsika/corsika_histograms.py +328 -282
- simtools/corsika/corsika_histograms_visualize.py +162 -163
- simtools/corsika/corsika_runner.py +8 -4
- simtools/corsika_simtel/corsika_simtel_runner.py +18 -23
- simtools/data_model/data_reader.py +129 -0
- simtools/data_model/metadata_collector.py +346 -118
- simtools/data_model/metadata_model.py +123 -218
- simtools/data_model/model_data_writer.py +79 -22
- simtools/data_model/validate_data.py +96 -46
- simtools/db_handler.py +67 -42
- simtools/io_operations/__init__.py +0 -0
- simtools/io_operations/hdf5_handler.py +112 -0
- simtools/{io_handler.py → io_operations/io_handler.py} +51 -22
- simtools/job_execution/job_manager.py +1 -1
- simtools/layout/{layout_array.py → array_layout.py} +168 -199
- simtools/layout/geo_coordinates.py +196 -0
- simtools/layout/telescope_position.py +12 -12
- simtools/model/array_model.py +16 -14
- simtools/model/camera.py +5 -8
- simtools/model/mirrors.py +136 -73
- simtools/model/model_utils.py +1 -69
- simtools/model/telescope_model.py +32 -25
- simtools/psf_analysis.py +26 -19
- simtools/ray_tracing.py +54 -26
- simtools/schemas/data.metaschema.yml +400 -0
- simtools/schemas/metadata.metaschema.yml +566 -0
- simtools/simtel/simtel_config_writer.py +14 -5
- simtools/simtel/simtel_histograms.py +266 -83
- simtools/simtel/simtel_runner.py +8 -7
- simtools/simtel/simtel_runner_array.py +7 -8
- simtools/simtel/simtel_runner_camera_efficiency.py +48 -2
- simtools/simtel/simtel_runner_ray_tracing.py +61 -25
- simtools/simulator.py +43 -50
- simtools/utils/general.py +232 -286
- simtools/utils/geometry.py +163 -0
- simtools/utils/names.py +294 -142
- simtools/visualization/legend_handlers.py +115 -9
- simtools/visualization/visualize.py +13 -13
- gammasimtools-0.5.1.dist-info/RECORD +0 -83
- simtools/applications/plot_simtel_histograms.py +0 -120
- simtools/applications/validate_schema_files.py +0 -135
- simtools/corsika/corsika_output_visualize.py +0 -345
- simtools/data_model/validate_schema.py +0 -285
- {gammasimtools-0.5.1.dist-info → gammasimtools-0.6.1.dist-info}/LICENSE +0 -0
- {gammasimtools-0.5.1.dist-info → gammasimtools-0.6.1.dist-info}/top_level.txt +0 -0
simtools/model/model_utils.py
CHANGED
|
@@ -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
|
|
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="
|
|
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.
|
|
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
|
-
|
|
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(
|
|
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.
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
735
|
-
return
|
|
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.
|
|
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,
|
|
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
|
-
|
|
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 {
|
|
76
|
+
self._logger.info(f"Reading sim_telarray file {photons_file}")
|
|
76
77
|
self._total_photons = 0
|
|
77
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
259
|
-
|
|
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 <
|
|
268
|
+
while not found_radius and n_iter < max_iter:
|
|
262
269
|
n_iter += 1
|
|
263
|
-
dr = -delta_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) <
|
|
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
|
-
|
|
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, **
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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(
|
|
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
|
|
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
|
-
"
|
|
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
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
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")
|