gammasimtools 0.26.0__py3-none-any.whl → 0.27.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.26.0.dist-info → gammasimtools-0.27.1.dist-info}/METADATA +5 -1
- {gammasimtools-0.26.0.dist-info → gammasimtools-0.27.1.dist-info}/RECORD +70 -66
- {gammasimtools-0.26.0.dist-info → gammasimtools-0.27.1.dist-info}/WHEEL +1 -1
- {gammasimtools-0.26.0.dist-info → gammasimtools-0.27.1.dist-info}/entry_points.txt +1 -1
- simtools/_version.py +2 -2
- simtools/applications/convert_geo_coordinates_of_array_elements.py +2 -1
- simtools/applications/db_get_array_layouts_from_db.py +1 -1
- simtools/applications/{calculate_incident_angles.py → derive_incident_angle.py} +16 -16
- simtools/applications/derive_mirror_rnda.py +111 -177
- simtools/applications/generate_corsika_histograms.py +38 -1
- simtools/applications/generate_regular_arrays.py +73 -36
- simtools/applications/simulate_flasher.py +3 -13
- simtools/applications/simulate_illuminator.py +2 -10
- simtools/applications/simulate_pedestals.py +1 -1
- simtools/applications/simulate_prod.py +8 -7
- simtools/applications/submit_data_from_external.py +2 -1
- simtools/applications/validate_camera_efficiency.py +28 -27
- simtools/applications/validate_cumulative_psf.py +1 -3
- simtools/applications/validate_optics.py +2 -1
- simtools/atmosphere.py +83 -0
- simtools/camera/camera_efficiency.py +171 -48
- simtools/camera/single_photon_electron_spectrum.py +6 -6
- simtools/configuration/commandline_parser.py +47 -9
- simtools/constants.py +5 -0
- simtools/corsika/corsika_config.py +88 -185
- simtools/corsika/corsika_histograms.py +246 -69
- simtools/data_model/model_data_writer.py +46 -49
- simtools/data_model/schema.py +2 -0
- simtools/db/db_handler.py +4 -2
- simtools/db/mongo_db.py +2 -2
- simtools/io/ascii_handler.py +52 -4
- simtools/io/io_handler.py +23 -12
- simtools/job_execution/job_manager.py +154 -79
- simtools/job_execution/process_pool.py +137 -0
- simtools/layout/array_layout.py +0 -1
- simtools/layout/array_layout_utils.py +143 -21
- simtools/model/array_model.py +22 -50
- simtools/model/calibration_model.py +4 -4
- simtools/model/model_parameter.py +123 -73
- simtools/model/model_utils.py +40 -1
- simtools/model/site_model.py +4 -4
- simtools/model/telescope_model.py +4 -5
- simtools/ray_tracing/incident_angles.py +87 -6
- simtools/ray_tracing/mirror_panel_psf.py +337 -217
- simtools/ray_tracing/psf_analysis.py +57 -42
- simtools/ray_tracing/psf_parameter_optimisation.py +3 -2
- simtools/ray_tracing/ray_tracing.py +37 -10
- simtools/runners/corsika_runner.py +52 -191
- simtools/runners/corsika_simtel_runner.py +74 -100
- simtools/runners/runner_services.py +214 -213
- simtools/runners/simtel_runner.py +27 -155
- simtools/runners/simtools_runner.py +9 -69
- simtools/schemas/application_workflow.metaschema.yml +8 -0
- simtools/settings.py +19 -0
- simtools/simtel/simtel_config_writer.py +0 -55
- simtools/simtel/simtel_seeds.py +184 -0
- simtools/simtel/simulator_array.py +115 -103
- simtools/simtel/simulator_camera_efficiency.py +66 -42
- simtools/simtel/simulator_light_emission.py +110 -123
- simtools/simtel/simulator_ray_tracing.py +78 -63
- simtools/simulator.py +135 -346
- simtools/testing/sim_telarray_metadata.py +13 -11
- simtools/testing/validate_output.py +87 -19
- simtools/utils/general.py +6 -17
- simtools/utils/random.py +36 -0
- simtools/visualization/plot_corsika_histograms.py +2 -0
- simtools/visualization/plot_incident_angles.py +48 -1
- simtools/visualization/plot_psf.py +160 -18
- {gammasimtools-0.26.0.dist-info → gammasimtools-0.27.1.dist-info}/licenses/LICENSE +0 -0
- {gammasimtools-0.26.0.dist-info → gammasimtools-0.27.1.dist-info}/top_level.txt +0 -0
|
@@ -57,7 +57,8 @@ r"""
|
|
|
57
57
|
"""
|
|
58
58
|
|
|
59
59
|
from simtools.application_control import startup_application
|
|
60
|
-
from simtools.configuration import configurator
|
|
60
|
+
from simtools.configuration import commandline_parser, configurator
|
|
61
|
+
from simtools.constants import CORSIKA_MAX_SEED
|
|
61
62
|
from simtools.simulator import Simulator
|
|
62
63
|
|
|
63
64
|
|
|
@@ -97,11 +98,11 @@ def _parse():
|
|
|
97
98
|
default=False,
|
|
98
99
|
)
|
|
99
100
|
config.parser.add_argument(
|
|
100
|
-
"--
|
|
101
|
-
help="Use
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
101
|
+
"--corsika_seeds",
|
|
102
|
+
help="Use fixed random seeds for CORSIKA for testing purposes.",
|
|
103
|
+
nargs=4,
|
|
104
|
+
type=commandline_parser.CommandLineParser.bounded_int(1, CORSIKA_MAX_SEED),
|
|
105
|
+
metavar=("S1", "S2", "S3", "S4"),
|
|
105
106
|
)
|
|
106
107
|
config.parser.add_argument(
|
|
107
108
|
"--sequential",
|
|
@@ -127,7 +128,7 @@ def main():
|
|
|
127
128
|
"""Run simulations for productions."""
|
|
128
129
|
app_context = startup_application(_parse, setup_io_handler=False)
|
|
129
130
|
|
|
130
|
-
simulator = Simulator(label=app_context.args.get("label")
|
|
131
|
+
simulator = Simulator(label=app_context.args.get("label"))
|
|
131
132
|
|
|
132
133
|
simulator.simulate()
|
|
133
134
|
simulator.validate_metadata()
|
|
@@ -95,7 +95,8 @@ def main():
|
|
|
95
95
|
)
|
|
96
96
|
|
|
97
97
|
writer.ModelDataWriter.dump(
|
|
98
|
-
|
|
98
|
+
output_file=app_context.args["output_file"],
|
|
99
|
+
output_file_format=app_context.args.get("output_file_format"),
|
|
99
100
|
metadata=_metadata,
|
|
100
101
|
product_data=data_validator.validate_and_transform(),
|
|
101
102
|
)
|
|
@@ -44,12 +44,11 @@ r"""
|
|
|
44
44
|
The output is saved in simtools-output/validate_camera_efficiency.
|
|
45
45
|
"""
|
|
46
46
|
|
|
47
|
-
from pathlib import Path
|
|
48
|
-
|
|
49
|
-
import simtools.data_model.model_data_writer as writer
|
|
50
47
|
from simtools.application_control import get_application_label, startup_application
|
|
51
48
|
from simtools.camera.camera_efficiency import CameraEfficiency
|
|
52
49
|
from simtools.configuration import configurator
|
|
50
|
+
from simtools.io.ascii_handler import write_data_to_file
|
|
51
|
+
from simtools.utils import names
|
|
53
52
|
|
|
54
53
|
|
|
55
54
|
def _parse():
|
|
@@ -106,31 +105,33 @@ def main():
|
|
|
106
105
|
"""Calculate the camera efficiency and NSB pixel rates."""
|
|
107
106
|
app_context = startup_application(_parse)
|
|
108
107
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
108
|
+
results = {}
|
|
109
|
+
for efficiency_type in ["Shower", "NSB", "Muon"]:
|
|
110
|
+
ce = CameraEfficiency(
|
|
111
|
+
label=app_context.args.get("label"),
|
|
112
|
+
config_data=app_context.args,
|
|
113
|
+
efficiency_type=efficiency_type,
|
|
114
|
+
)
|
|
115
|
+
ce.simulate()
|
|
116
|
+
ce.analyze(force=True)
|
|
117
|
+
results |= ce.results_summary()
|
|
118
|
+
ce.plot_efficiency(save_fig=True)
|
|
119
|
+
|
|
120
|
+
if ce.efficiency_type == "nsb":
|
|
121
|
+
ce.dump_nsb_pixel_rate()
|
|
122
|
+
if ce.efficiency_type == "muon":
|
|
123
|
+
ce.calc_partial_efficiency()
|
|
124
|
+
|
|
125
|
+
results_file = app_context.io_handler.get_output_directory() / names.generate_file_name(
|
|
126
|
+
file_type="camera_efficiency_summary",
|
|
127
|
+
suffix=".yml",
|
|
128
|
+
site=app_context.args["site"],
|
|
129
|
+
telescope_model_name=app_context.args["telescope"],
|
|
130
|
+
zenith_angle=app_context.args["zenith_angle"].value,
|
|
131
|
+
azimuth_angle=app_context.args["azimuth_angle"].value,
|
|
133
132
|
)
|
|
133
|
+
app_context.logger.info(f"Writing results summary to {results_file}")
|
|
134
|
+
write_data_to_file(results, results_file, unique_lines=True)
|
|
134
135
|
|
|
135
136
|
|
|
136
137
|
if __name__ == "__main__":
|
|
@@ -135,12 +135,10 @@ def main():
|
|
|
135
135
|
model_version=app_context.args["model_version"],
|
|
136
136
|
)
|
|
137
137
|
|
|
138
|
-
if app_context.args.get("overwrite_model_parameters"):
|
|
139
|
-
tel_model.overwrite_parameters_from_file(app_context.args["overwrite_model_parameters"])
|
|
140
|
-
|
|
141
138
|
ray = RayTracing(
|
|
142
139
|
telescope_model=tel_model,
|
|
143
140
|
site_model=site_model,
|
|
141
|
+
label=app_context.args.get("label"),
|
|
144
142
|
zenith_angle=app_context.args["zenith"] * u.deg,
|
|
145
143
|
source_distance=app_context.args["src_distance"] * u.km,
|
|
146
144
|
off_axis_angle=[0.0] * u.deg,
|
|
@@ -134,6 +134,7 @@ def main():
|
|
|
134
134
|
ray = RayTracing(
|
|
135
135
|
telescope_model=tel_model,
|
|
136
136
|
site_model=site_model,
|
|
137
|
+
label=app_context.args.get("label") or Path(__file__).stem,
|
|
137
138
|
zenith_angle=app_context.args["zenith"] * u.deg,
|
|
138
139
|
source_distance=app_context.args["src_distance"] * u.km,
|
|
139
140
|
off_axis_angle=np.linspace(
|
|
@@ -147,7 +148,7 @@ def main():
|
|
|
147
148
|
ray.analyze(force=True)
|
|
148
149
|
|
|
149
150
|
# Plotting
|
|
150
|
-
for key in ["
|
|
151
|
+
for key in ["psf_deg", "psf_cm", "eff_area", "eff_flen"]:
|
|
151
152
|
plt.figure(figsize=(8, 6), tight_layout=True)
|
|
152
153
|
|
|
153
154
|
ray.plot(key, marker="o", linestyle=":", color="k")
|
simtools/atmosphere.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Profiles of atmospheric parameters as a function of altitude."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import astropy.units as u
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AtmosphereProfile:
|
|
11
|
+
"""
|
|
12
|
+
Profiles of atmospheric parameters as a function of altitude.
|
|
13
|
+
|
|
14
|
+
Parameters
|
|
15
|
+
----------
|
|
16
|
+
filename: str or Path
|
|
17
|
+
Path to the atmosphere profile file (CORSIKA table format)
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, filename):
|
|
21
|
+
"""AtmosphereProfile initialization."""
|
|
22
|
+
self._logger = logging.getLogger(__name__)
|
|
23
|
+
self.columns = {}
|
|
24
|
+
self.read_atmosphere_profile(filename)
|
|
25
|
+
|
|
26
|
+
def read_atmosphere_profile(self, filename):
|
|
27
|
+
"""
|
|
28
|
+
Read atmosphere profile from file.
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
filename: str or Path
|
|
33
|
+
Path to the atmosphere profile file (CORSIKA table format)
|
|
34
|
+
|
|
35
|
+
"""
|
|
36
|
+
filename = Path(filename)
|
|
37
|
+
data = []
|
|
38
|
+
self._logger.debug(f"Reading atmosphere profile from {filename}")
|
|
39
|
+
with filename.open(encoding="utf-8") as f:
|
|
40
|
+
for line in f:
|
|
41
|
+
if not line.strip() or line.lstrip().startswith("#"):
|
|
42
|
+
continue
|
|
43
|
+
data.append([float(x) for x in line.split()])
|
|
44
|
+
|
|
45
|
+
self.data = np.array(data)
|
|
46
|
+
self.columns = {
|
|
47
|
+
"alt": 0,
|
|
48
|
+
"rho": 1,
|
|
49
|
+
"thick": 2,
|
|
50
|
+
"n_minus_1": 3,
|
|
51
|
+
"T": 4,
|
|
52
|
+
"p": 5,
|
|
53
|
+
"pw_over_p": 6,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
def interpolate(self, altitude, column="thick"):
|
|
57
|
+
"""
|
|
58
|
+
Interpolate the atmosphere profile at a given altitude.
|
|
59
|
+
|
|
60
|
+
Parameters
|
|
61
|
+
----------
|
|
62
|
+
altitude: astropy.units.Quantity
|
|
63
|
+
Altitude
|
|
64
|
+
column: str
|
|
65
|
+
Column name to interpolate.
|
|
66
|
+
|
|
67
|
+
Returns
|
|
68
|
+
-------
|
|
69
|
+
float
|
|
70
|
+
Interpolated value.
|
|
71
|
+
"""
|
|
72
|
+
if column not in self.columns:
|
|
73
|
+
raise KeyError(f"Unknown column: {column}")
|
|
74
|
+
|
|
75
|
+
altitude = altitude.to(u.km).value
|
|
76
|
+
|
|
77
|
+
x = self.data[:, 0]
|
|
78
|
+
y = self.data[:, self.columns[column]]
|
|
79
|
+
|
|
80
|
+
if altitude < x.min() or altitude > x.max():
|
|
81
|
+
raise ValueError("Altitude out of bounds")
|
|
82
|
+
|
|
83
|
+
return np.interp(altitude, x, y)
|
|
@@ -3,13 +3,17 @@
|
|
|
3
3
|
import logging
|
|
4
4
|
import re
|
|
5
5
|
from collections import defaultdict
|
|
6
|
+
from pathlib import Path
|
|
6
7
|
|
|
7
8
|
import astropy.io.ascii
|
|
8
9
|
import astropy.units as u
|
|
9
10
|
import numpy as np
|
|
10
11
|
from astropy.table import Table
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
import simtools.data_model.model_data_writer as writer
|
|
14
|
+
from simtools import settings
|
|
15
|
+
from simtools.atmosphere import AtmosphereProfile
|
|
16
|
+
from simtools.io import ascii_handler, io_handler
|
|
13
17
|
from simtools.model.model_utils import initialize_simulation_models
|
|
14
18
|
from simtools.simtel.simulator_camera_efficiency import SimulatorCameraEfficiency
|
|
15
19
|
from simtools.utils import names
|
|
@@ -26,9 +30,11 @@ class CameraEfficiency:
|
|
|
26
30
|
Instance label, optional.
|
|
27
31
|
config_data: dict.
|
|
28
32
|
Dict containing the configurable parameters.
|
|
33
|
+
efficiency_type: str
|
|
34
|
+
The type of efficiency to simulate (e.g., 'Shower', 'Muon', or 'NSB').
|
|
29
35
|
"""
|
|
30
36
|
|
|
31
|
-
def __init__(self, config_data,
|
|
37
|
+
def __init__(self, label, config_data, efficiency_type):
|
|
32
38
|
"""Initialize the CameraEfficiency class."""
|
|
33
39
|
self._logger = logging.getLogger(__name__)
|
|
34
40
|
|
|
@@ -45,8 +51,9 @@ class CameraEfficiency:
|
|
|
45
51
|
|
|
46
52
|
self._results = None
|
|
47
53
|
self._has_results = False
|
|
54
|
+
self.efficiency_type = efficiency_type.lower()
|
|
48
55
|
|
|
49
|
-
self.config = self.
|
|
56
|
+
self.config = self._configuration(config_data)
|
|
50
57
|
self._file = self._load_files()
|
|
51
58
|
|
|
52
59
|
self.nsb_pixel_pe_per_ns = None
|
|
@@ -56,9 +63,9 @@ class CameraEfficiency:
|
|
|
56
63
|
"""Return string representation of the CameraEfficiency instance."""
|
|
57
64
|
return f"CameraEfficiency(label={self.label})\n"
|
|
58
65
|
|
|
59
|
-
def
|
|
66
|
+
def _configuration(self, config_data):
|
|
60
67
|
"""
|
|
61
|
-
Extract configuration data from command line
|
|
68
|
+
Extract configuration data from command line and class parameters.
|
|
62
69
|
|
|
63
70
|
Parameters
|
|
64
71
|
----------
|
|
@@ -74,6 +81,7 @@ class CameraEfficiency:
|
|
|
74
81
|
"zenith_angle": config_data["zenith_angle"].to("deg").value,
|
|
75
82
|
"azimuth_angle": config_data["azimuth_angle"].to("deg").value,
|
|
76
83
|
"nsb_spectrum": config_data.get("nsb_spectrum", None),
|
|
84
|
+
"efficiency_type": self.efficiency_type,
|
|
77
85
|
}
|
|
78
86
|
|
|
79
87
|
def _load_files(self):
|
|
@@ -84,15 +92,13 @@ class CameraEfficiency:
|
|
|
84
92
|
[".ecsv", ".dat", ".log"],
|
|
85
93
|
):
|
|
86
94
|
file_name = names.generate_file_name(
|
|
87
|
-
file_type=
|
|
88
|
-
"camera_efficiency_table" if label == "results" else "camera_efficiency"
|
|
89
|
-
),
|
|
95
|
+
file_type="camera_efficiency",
|
|
90
96
|
suffix=suffix,
|
|
91
97
|
site=self.telescope_model.site,
|
|
92
98
|
telescope_model_name=self.telescope_model.name,
|
|
93
99
|
zenith_angle=self.config["zenith_angle"],
|
|
94
100
|
azimuth_angle=self.config["azimuth_angle"],
|
|
95
|
-
label=self.
|
|
101
|
+
label=self.efficiency_type,
|
|
96
102
|
)
|
|
97
103
|
|
|
98
104
|
_file[label] = self.io_handler.get_output_directory().joinpath(file_name)
|
|
@@ -111,6 +117,7 @@ class CameraEfficiency:
|
|
|
111
117
|
file_simtel=self._file["sim_telarray"],
|
|
112
118
|
file_log=self._file["log"],
|
|
113
119
|
label=self.label,
|
|
120
|
+
x_max=self._get_x_max_for_efficiency_type(),
|
|
114
121
|
nsb_spectrum=self.config["nsb_spectrum"],
|
|
115
122
|
skip_correction_to_nsb_spectrum=self.config.get(
|
|
116
123
|
"skip_correction_to_nsb_spectrum", False
|
|
@@ -245,34 +252,78 @@ class CameraEfficiency:
|
|
|
245
252
|
|
|
246
253
|
def results_summary(self):
|
|
247
254
|
"""
|
|
248
|
-
|
|
255
|
+
Fill a dictionary with summary of the results.
|
|
249
256
|
|
|
250
257
|
Include a header for the zenith/azimuth settings and the NSB spectrum file which was used.
|
|
251
258
|
The summary includes the various CTAO requirements and the final expected NSB pixel rate.
|
|
259
|
+
|
|
260
|
+
Returns
|
|
261
|
+
-------
|
|
262
|
+
dict
|
|
263
|
+
Summary of the results.
|
|
252
264
|
"""
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
265
|
+
meta = {
|
|
266
|
+
"meta": {
|
|
267
|
+
"tel": self.telescope_model.name,
|
|
268
|
+
"model_version": self.telescope_model.model_version,
|
|
269
|
+
"zen": self.config["zenith_angle"],
|
|
270
|
+
"az": self.config["azimuth_angle"],
|
|
271
|
+
"nsb": (
|
|
272
|
+
self.config["nsb_spectrum"]
|
|
273
|
+
if self.config["nsb_spectrum"]
|
|
274
|
+
else "default sim_telarray spectrum"
|
|
275
|
+
),
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
metrics = {}
|
|
280
|
+
if self.efficiency_type == "shower":
|
|
281
|
+
metrics |= {
|
|
282
|
+
"reflectivity": {
|
|
283
|
+
"value": self.calc_reflectivity(),
|
|
284
|
+
"description": "Spectrum weighted reflectivity",
|
|
285
|
+
},
|
|
286
|
+
"cam_eff": {
|
|
287
|
+
"value": self.calc_camera_efficiency(),
|
|
288
|
+
"description": "Camera nominal efficiency with gaps (B-TEL-1170)",
|
|
289
|
+
},
|
|
290
|
+
"tel_eff": {
|
|
291
|
+
"value": self.calc_tel_efficiency(),
|
|
292
|
+
"description": "Telescope total efficiency with gaps (was A-PERF-2020)",
|
|
293
|
+
},
|
|
294
|
+
"tot_sens": {
|
|
295
|
+
"value": self.calc_tot_efficiency(self.calc_tel_efficiency()),
|
|
296
|
+
"description": (
|
|
297
|
+
"Telescope total Cherenkov light efficiency / sqrt(total NSB efficiency) "
|
|
298
|
+
"(A-PERF-2025/B-TEL-0090)"
|
|
299
|
+
),
|
|
300
|
+
},
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
elif self.efficiency_type == "nsb":
|
|
304
|
+
metrics |= {
|
|
305
|
+
"nsb_rate": {
|
|
306
|
+
"value": self.nsb_pixel_pe_per_ns,
|
|
307
|
+
"description": "Expected NSB pixel rate for the provided NSB spectrum",
|
|
308
|
+
},
|
|
309
|
+
"nsb_ref": {
|
|
310
|
+
"value": self.nsb_rate_ref_conditions,
|
|
311
|
+
"description": "Expected NSB pixel rate for the reference NSB",
|
|
312
|
+
},
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
elif self.efficiency_type == "muon":
|
|
316
|
+
metrics |= {
|
|
317
|
+
"muon_frac": {
|
|
318
|
+
"value": self.calc_partial_efficiency(lambda_min=200.0, lambda_max=290.0),
|
|
319
|
+
"description": (
|
|
320
|
+
"Fraction of light (from muons) in the wavelength range 200-290 nm "
|
|
321
|
+
"(B-TEL-0095)"
|
|
322
|
+
),
|
|
323
|
+
},
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return meta | metrics
|
|
276
327
|
|
|
277
328
|
def export_results(self):
|
|
278
329
|
"""Export results to a ecsv file."""
|
|
@@ -283,12 +334,9 @@ class CameraEfficiency:
|
|
|
283
334
|
astropy.io.ascii.write(
|
|
284
335
|
self._results, self._file["results"], format="basic", overwrite=True
|
|
285
336
|
)
|
|
286
|
-
_results_summary_file = (
|
|
287
|
-
str(self._file["results"]).replace(".ecsv", ".txt").replace("_table_", "_summary_")
|
|
288
|
-
)
|
|
337
|
+
_results_summary_file = str(self._file["results"]).replace(".ecsv", "_summary.yml")
|
|
289
338
|
self._logger.info(f"Exporting summary results to {_results_summary_file}")
|
|
290
|
-
|
|
291
|
-
file.write(self.results_summary())
|
|
339
|
+
ascii_handler.write_data_to_file(self.results_summary(), Path(_results_summary_file))
|
|
292
340
|
|
|
293
341
|
def _read_results(self):
|
|
294
342
|
"""Read existing results file and store it in _results."""
|
|
@@ -363,6 +411,39 @@ class CameraEfficiency:
|
|
|
363
411
|
|
|
364
412
|
return tel_efficiency / np.sqrt(tel_efficiency_nsb)
|
|
365
413
|
|
|
414
|
+
def calc_partial_efficiency(self, lambda_min=200.0, lambda_max=290.0):
|
|
415
|
+
"""
|
|
416
|
+
Compare efficiency in a given wavelength range with total efficiency.
|
|
417
|
+
|
|
418
|
+
Parameters
|
|
419
|
+
----------
|
|
420
|
+
lambda_min: float
|
|
421
|
+
Minimum wavelength in nm.
|
|
422
|
+
lambda_max: float
|
|
423
|
+
Maximum wavelength in nm.
|
|
424
|
+
|
|
425
|
+
Returns
|
|
426
|
+
-------
|
|
427
|
+
Float
|
|
428
|
+
Fraction of light in the given wavelength range compared to total efficiency.
|
|
429
|
+
|
|
430
|
+
"""
|
|
431
|
+
# Sum(C4) from lamba_min to lambda_max nm:
|
|
432
|
+
c4_reduced_wl = self._results["C4"][
|
|
433
|
+
[lambda_min < wl_now < lambda_max for wl_now in self._results["wl"]]
|
|
434
|
+
]
|
|
435
|
+
c4_sum = np.sum(c4_reduced_wl)
|
|
436
|
+
# Sum(C4) from 200 - 999 nm:
|
|
437
|
+
c4_sum_total = np.sum(self._results["C4"])
|
|
438
|
+
# (no need to apply masts or fill factors as in calc_tel_efficiency, they cancel out)
|
|
439
|
+
|
|
440
|
+
self._logger.info(
|
|
441
|
+
f"Fraction of light in the wavelength range {lambda_min}-{lambda_max} nm: "
|
|
442
|
+
f"{c4_sum / c4_sum_total:.4f}"
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
return c4_sum / c4_sum_total
|
|
446
|
+
|
|
366
447
|
def calc_reflectivity(self):
|
|
367
448
|
"""
|
|
368
449
|
Calculate the Cherenkov spectrum weighted reflectivity in the range 300-550 nm.
|
|
@@ -428,14 +509,12 @@ class CameraEfficiency:
|
|
|
428
509
|
)
|
|
429
510
|
return self.nsb_pixel_pe_per_ns * u.GHz, self.nsb_rate_ref_conditions * u.GHz
|
|
430
511
|
|
|
431
|
-
def plot_efficiency(self,
|
|
512
|
+
def plot_efficiency(self, save_fig=False):
|
|
432
513
|
"""
|
|
433
514
|
Plot efficiency vs wavelength.
|
|
434
515
|
|
|
435
516
|
Parameters
|
|
436
517
|
----------
|
|
437
|
-
efficiency_type: str
|
|
438
|
-
The type of efficiency to plot (Cherenkov 'C' or NSB 'N')
|
|
439
518
|
save_fig: bool
|
|
440
519
|
If True, the figure will be saved to a file.
|
|
441
520
|
|
|
@@ -444,9 +523,9 @@ class CameraEfficiency:
|
|
|
444
523
|
fig
|
|
445
524
|
The figure instance of pyplot
|
|
446
525
|
"""
|
|
447
|
-
self._logger.info(f"Plotting {efficiency_type} efficiency vs wavelength")
|
|
526
|
+
self._logger.info(f"Plotting {self.efficiency_type} efficiency vs wavelength")
|
|
448
527
|
|
|
449
|
-
_col_type = "C" if efficiency_type
|
|
528
|
+
_col_type = "C" if self.efficiency_type in ("shower", "muon") else "N"
|
|
450
529
|
|
|
451
530
|
column_titles = {
|
|
452
531
|
"wl": "Wavelength [nm]",
|
|
@@ -464,21 +543,21 @@ class CameraEfficiency:
|
|
|
464
543
|
for column_now, column_title in column_titles.items():
|
|
465
544
|
table_to_plot.rename_column(column_now, column_title)
|
|
466
545
|
|
|
467
|
-
y_title = f"{efficiency_type} light efficiency"
|
|
468
|
-
if efficiency_type == "
|
|
546
|
+
y_title = f"{self.efficiency_type} light efficiency"
|
|
547
|
+
if self.efficiency_type == "nsb":
|
|
469
548
|
y_title = r"Diff. ph. rate [$10^{9} \times $ph/(nm s m$^2$ sr)]"
|
|
470
549
|
plot = visualize.plot_table(
|
|
471
550
|
table_to_plot,
|
|
472
551
|
y_title=y_title,
|
|
473
|
-
title=f"{self.telescope_model.name} response to {efficiency_type} light",
|
|
552
|
+
title=f"{self.telescope_model.name} response to {self.efficiency_type} light",
|
|
474
553
|
no_markers=True,
|
|
475
554
|
)
|
|
476
|
-
if efficiency_type == "
|
|
555
|
+
if self.efficiency_type == "nsb":
|
|
477
556
|
plot.gca().set_yscale("log")
|
|
478
557
|
ylim = plot.gca().get_ylim()
|
|
479
558
|
plot.gca().set_ylim(1e-3, ylim[1])
|
|
480
559
|
if save_fig:
|
|
481
|
-
self._save_plot(plot, efficiency_type
|
|
560
|
+
self._save_plot(plot, self.efficiency_type)
|
|
482
561
|
return plot
|
|
483
562
|
|
|
484
563
|
def _save_plot(self, fig, plot_title):
|
|
@@ -496,3 +575,47 @@ class CameraEfficiency:
|
|
|
496
575
|
self.label + "_" + self.telescope_model.name + "_" + plot_title
|
|
497
576
|
)
|
|
498
577
|
visualize.save_figure(fig, plot_file, log_title=f"{plot_title} efficiency")
|
|
578
|
+
|
|
579
|
+
def dump_nsb_pixel_rate(self):
|
|
580
|
+
"""Write NSB pixel rate parameter file."""
|
|
581
|
+
cfg = settings.config.args
|
|
582
|
+
|
|
583
|
+
writer.ModelDataWriter.dump_model_parameter(
|
|
584
|
+
parameter_name="nsb_pixel_rate",
|
|
585
|
+
value=self.get_nsb_pixel_rate(
|
|
586
|
+
reference_conditions=settings.config.args.get(
|
|
587
|
+
"write_reference_nsb_rate_as_parameter", False
|
|
588
|
+
)
|
|
589
|
+
),
|
|
590
|
+
instrument=cfg.get("telescope"),
|
|
591
|
+
parameter_version=cfg.get("parameter_version") or "0.0.0",
|
|
592
|
+
output_file=Path(f"nsb_pixel_rate-{cfg.get('parameter_version', '0.0.0')}.json"),
|
|
593
|
+
output_path=self.output_dir / cfg.get("telescope") / "nsb_pixel_rate",
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
def _get_x_max_for_efficiency_type(self):
|
|
597
|
+
"""
|
|
598
|
+
Get X max value in g/cm2 depending on the efficiency type.
|
|
599
|
+
|
|
600
|
+
Returns
|
|
601
|
+
-------
|
|
602
|
+
float
|
|
603
|
+
max value in g/cm2
|
|
604
|
+
"""
|
|
605
|
+
# typical value for shower X-max around 10 km (not relevant for NSB type)
|
|
606
|
+
x_max = 300.0
|
|
607
|
+
obs_level = self.site_model.get_parameter_value_with_unit("corsika_observation_level")
|
|
608
|
+
if self.efficiency_type == "muon":
|
|
609
|
+
atmo = AtmosphereProfile(
|
|
610
|
+
self.site_model.config_file_directory
|
|
611
|
+
/ self.site_model.get_parameter_value("atmospheric_profile")
|
|
612
|
+
)
|
|
613
|
+
alt = obs_level.to(u.km) + 0.1 * u.km
|
|
614
|
+
x_max = atmo.interpolate(altitude=alt, column="thick")
|
|
615
|
+
|
|
616
|
+
self._logger.info(
|
|
617
|
+
f"Using X-max for {self.efficiency_type} efficiency: {x_max:.2f} g/cm2"
|
|
618
|
+
f" (at observation level: {obs_level:.2f})"
|
|
619
|
+
)
|
|
620
|
+
|
|
621
|
+
return x_max
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
import re
|
|
5
|
-
import subprocess
|
|
6
5
|
import tempfile
|
|
7
6
|
from io import BytesIO
|
|
8
7
|
from pathlib import Path
|
|
@@ -17,6 +16,7 @@ from simtools.constants import MODEL_PARAMETER_SCHEMA_URL, SCHEMA_PATH
|
|
|
17
16
|
from simtools.data_model import validate_data
|
|
18
17
|
from simtools.data_model.metadata_collector import MetadataCollector
|
|
19
18
|
from simtools.io import io_handler
|
|
19
|
+
from simtools.job_execution import job_manager
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class SinglePhotonElectronSpectrum:
|
|
@@ -97,7 +97,8 @@ class SinglePhotonElectronSpectrum:
|
|
|
97
97
|
)
|
|
98
98
|
|
|
99
99
|
writer.ModelDataWriter.dump(
|
|
100
|
-
|
|
100
|
+
output_file=self.args_dict["output_file"],
|
|
101
|
+
output_file_format=self.args_dict.get("output_file_format"),
|
|
101
102
|
metadata=self.metadata,
|
|
102
103
|
product_data=table,
|
|
103
104
|
validate_schema_file=None,
|
|
@@ -126,7 +127,7 @@ class SinglePhotonElectronSpectrum:
|
|
|
126
127
|
|
|
127
128
|
Raises
|
|
128
129
|
------
|
|
129
|
-
|
|
130
|
+
job_manager.JobExecutionError
|
|
130
131
|
If the command execution fails.
|
|
131
132
|
"""
|
|
132
133
|
tmp_input_file = self._get_input_data(
|
|
@@ -153,10 +154,9 @@ class SinglePhotonElectronSpectrum:
|
|
|
153
154
|
|
|
154
155
|
self._logger.info(f"Running norm_spe command: {' '.join(command)}")
|
|
155
156
|
try:
|
|
156
|
-
result =
|
|
157
|
-
except
|
|
157
|
+
result = job_manager.submit(command)
|
|
158
|
+
except job_manager.JobExecutionError as exc:
|
|
158
159
|
self._logger.error(f"Error running norm_spe: {exc}")
|
|
159
|
-
self._logger.error(f"stderr: {exc.stderr}")
|
|
160
160
|
raise exc
|
|
161
161
|
finally:
|
|
162
162
|
for tmp_file in [tmp_input_file, tmp_ap_file]:
|