gammasimtools 0.25.0__py3-none-any.whl → 0.27.0__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.25.0.dist-info → gammasimtools-0.27.0.dist-info}/METADATA +6 -1
- {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/RECORD +135 -130
- {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/WHEEL +1 -1
- {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/entry_points.txt +3 -2
- {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/licenses/LICENSE +1 -1
- simtools/_version.py +2 -2
- simtools/application_control.py +35 -7
- simtools/applications/convert_geo_coordinates_of_array_elements.py +3 -3
- simtools/applications/db_add_file_to_db.py +1 -1
- simtools/applications/db_add_simulation_model_from_repository_to_db.py +1 -1
- simtools/applications/db_add_value_from_json_to_db.py +1 -1
- simtools/applications/db_generate_compound_indexes.py +1 -1
- simtools/applications/db_get_array_layouts_from_db.py +3 -7
- simtools/applications/db_get_file_from_db.py +1 -1
- simtools/applications/db_get_parameter_from_db.py +1 -1
- simtools/applications/db_inspect_databases.py +1 -1
- simtools/applications/db_upload_model_repository.py +1 -1
- simtools/applications/derive_ctao_array_layouts.py +1 -2
- simtools/applications/{calculate_incident_angles.py → derive_incident_angle.py} +16 -18
- simtools/applications/derive_mirror_rnda.py +112 -180
- simtools/applications/derive_psf_parameters.py +0 -1
- simtools/applications/derive_pulse_shape_parameters.py +0 -1
- simtools/applications/derive_trigger_rates.py +1 -1
- simtools/applications/docs_produce_array_element_report.py +2 -8
- simtools/applications/docs_produce_calibration_reports.py +1 -3
- simtools/applications/docs_produce_model_parameter_reports.py +0 -2
- simtools/applications/docs_produce_simulation_configuration_report.py +1 -3
- simtools/applications/generate_array_config.py +0 -1
- simtools/applications/generate_corsika_histograms.py +79 -229
- simtools/applications/generate_regular_arrays.py +76 -69
- simtools/applications/generate_simtel_event_data.py +2 -2
- simtools/applications/maintain_simulation_model_add_production.py +2 -2
- simtools/applications/maintain_simulation_model_write_array_element_positions.py +87 -0
- simtools/applications/plot_array_layout.py +5 -111
- simtools/applications/plot_simulated_event_distributions.py +57 -0
- simtools/applications/plot_tabular_data.py +0 -1
- simtools/applications/plot_tabular_data_for_model_parameter.py +1 -6
- simtools/applications/production_derive_corsika_limits.py +1 -1
- simtools/applications/production_generate_grid.py +0 -1
- simtools/applications/run_application.py +1 -1
- simtools/applications/simulate_flasher.py +3 -15
- simtools/applications/simulate_illuminator.py +2 -11
- simtools/applications/simulate_pedestals.py +1 -5
- simtools/applications/simulate_prod.py +8 -11
- simtools/applications/simulate_prod_htcondor_generator.py +1 -1
- simtools/applications/submit_array_layouts.py +2 -4
- simtools/applications/submit_data_from_external.py +2 -1
- simtools/applications/submit_model_parameter_from_external.py +1 -3
- simtools/applications/validate_camera_efficiency.py +28 -28
- simtools/applications/validate_camera_fov.py +0 -1
- simtools/applications/validate_cumulative_psf.py +1 -5
- simtools/applications/validate_optics.py +2 -14
- simtools/atmosphere.py +83 -0
- simtools/camera/camera_efficiency.py +171 -53
- simtools/camera/single_photon_electron_spectrum.py +8 -7
- simtools/configuration/commandline_parser.py +82 -11
- simtools/configuration/configurator.py +6 -11
- simtools/constants.py +5 -0
- simtools/corsika/corsika_config.py +100 -202
- simtools/corsika/corsika_histograms.py +561 -1708
- simtools/corsika/primary_particle.py +1 -1
- simtools/data_model/metadata_collector.py +5 -2
- simtools/data_model/metadata_model.py +0 -4
- simtools/data_model/model_data_writer.py +59 -64
- simtools/data_model/schema.py +2 -0
- simtools/data_model/validate_data.py +1 -3
- simtools/db/db_handler.py +23 -10
- simtools/db/mongo_db.py +2 -2
- simtools/dependencies.py +81 -38
- simtools/io/ascii_handler.py +55 -5
- simtools/io/io_handler.py +23 -12
- simtools/io/table_handler.py +1 -1
- simtools/job_execution/job_manager.py +154 -79
- simtools/job_execution/process_pool.py +137 -0
- simtools/layout/array_layout.py +4 -13
- simtools/layout/array_layout_utils.py +348 -57
- simtools/model/array_model.py +23 -63
- simtools/model/calibration_model.py +4 -8
- simtools/model/legacy_model_parameter.py +134 -0
- simtools/model/model_parameter.py +147 -86
- simtools/model/model_utils.py +40 -6
- simtools/model/site_model.py +4 -8
- simtools/model/telescope_model.py +10 -16
- simtools/production_configuration/derive_corsika_limits.py +6 -11
- simtools/production_configuration/interpolation_handler.py +16 -16
- simtools/ray_tracing/incident_angles.py +92 -17
- simtools/ray_tracing/mirror_panel_psf.py +338 -222
- simtools/ray_tracing/psf_analysis.py +62 -48
- simtools/ray_tracing/psf_parameter_optimisation.py +3 -3
- simtools/ray_tracing/ray_tracing.py +43 -25
- simtools/reporting/docs_auto_report_generator.py +8 -13
- simtools/reporting/docs_read_parameters.py +2 -8
- simtools/runners/corsika_runner.py +52 -195
- simtools/runners/corsika_simtel_runner.py +77 -108
- simtools/runners/runner_services.py +214 -213
- simtools/runners/simtel_runner.py +27 -160
- simtools/runners/simtools_runner.py +11 -73
- simtools/schemas/application_workflow.metaschema.yml +8 -0
- simtools/settings.py +173 -0
- simtools/{io/eventio_handler.py → sim_events/file_info.py} +3 -3
- simtools/{simtel/simtel_io_event_histograms.py → sim_events/histograms.py} +25 -15
- simtools/{simtel/simtel_io_event_reader.py → sim_events/reader.py} +20 -17
- simtools/{simtel/simtel_io_event_writer.py → sim_events/writer.py} +84 -25
- simtools/simtel/pulse_shapes.py +7 -2
- simtools/simtel/simtel_config_writer.py +79 -91
- simtools/simtel/simtel_seeds.py +184 -0
- simtools/simtel/simtel_table_reader.py +6 -4
- simtools/simtel/simulator_array.py +114 -109
- simtools/simtel/simulator_camera_efficiency.py +68 -46
- simtools/simtel/simulator_light_emission.py +164 -132
- simtools/simtel/simulator_ray_tracing.py +80 -71
- simtools/simulator.py +137 -355
- simtools/telescope_trigger_rates.py +3 -4
- simtools/testing/assertions.py +84 -33
- simtools/testing/configuration.py +1 -2
- simtools/testing/helpers.py +2 -3
- simtools/testing/log_inspector.py +1 -0
- simtools/testing/sim_telarray_metadata.py +14 -12
- simtools/testing/validate_output.py +121 -42
- simtools/utils/general.py +43 -17
- simtools/utils/geometry.py +0 -77
- simtools/utils/names.py +5 -5
- simtools/utils/random.py +36 -0
- simtools/visualization/legend_handlers.py +7 -6
- simtools/visualization/plot_array_layout.py +91 -16
- simtools/visualization/plot_corsika_histograms.py +145 -605
- simtools/visualization/plot_incident_angles.py +48 -1
- simtools/visualization/plot_mirrors.py +1 -4
- simtools/visualization/plot_pixels.py +2 -4
- simtools/visualization/plot_psf.py +160 -19
- simtools/visualization/plot_simtel_event_histograms.py +4 -4
- simtools/visualization/plot_simtel_events.py +6 -11
- simtools/visualization/plot_tables.py +8 -19
- simtools/visualization/visualize.py +22 -2
- simtools/applications/db_development_tools/write_array_elements_positions_to_repository.py +0 -160
- simtools/applications/print_version.py +0 -53
- simtools/io/hdf5_handler.py +0 -139
- {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/top_level.txt +0 -0
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
|
|
@@ -22,25 +26,23 @@ class CameraEfficiency:
|
|
|
22
26
|
|
|
23
27
|
Parameters
|
|
24
28
|
----------
|
|
25
|
-
db_config: dict
|
|
26
|
-
Configuration for the database.
|
|
27
29
|
label: str
|
|
28
30
|
Instance label, optional.
|
|
29
31
|
config_data: dict.
|
|
30
32
|
Dict containing the configurable parameters.
|
|
33
|
+
efficiency_type: str
|
|
34
|
+
The type of efficiency to simulate (e.g., 'Shower', 'Muon', or 'NSB').
|
|
31
35
|
"""
|
|
32
36
|
|
|
33
|
-
def __init__(self,
|
|
37
|
+
def __init__(self, label, config_data, efficiency_type):
|
|
34
38
|
"""Initialize the CameraEfficiency class."""
|
|
35
39
|
self._logger = logging.getLogger(__name__)
|
|
36
40
|
|
|
37
|
-
self._simtel_path = config_data.get("simtel_path")
|
|
38
41
|
self.label = label
|
|
39
42
|
|
|
40
43
|
self.io_handler = io_handler.IOHandler()
|
|
41
44
|
self.telescope_model, self.site_model, _ = initialize_simulation_models(
|
|
42
45
|
label=self.label,
|
|
43
|
-
db_config=db_config,
|
|
44
46
|
model_version=config_data["model_version"],
|
|
45
47
|
site=config_data["site"],
|
|
46
48
|
telescope_name=config_data["telescope"],
|
|
@@ -49,8 +51,9 @@ class CameraEfficiency:
|
|
|
49
51
|
|
|
50
52
|
self._results = None
|
|
51
53
|
self._has_results = False
|
|
54
|
+
self.efficiency_type = efficiency_type.lower()
|
|
52
55
|
|
|
53
|
-
self.config = self.
|
|
56
|
+
self.config = self._configuration(config_data)
|
|
54
57
|
self._file = self._load_files()
|
|
55
58
|
|
|
56
59
|
self.nsb_pixel_pe_per_ns = None
|
|
@@ -60,9 +63,9 @@ class CameraEfficiency:
|
|
|
60
63
|
"""Return string representation of the CameraEfficiency instance."""
|
|
61
64
|
return f"CameraEfficiency(label={self.label})\n"
|
|
62
65
|
|
|
63
|
-
def
|
|
66
|
+
def _configuration(self, config_data):
|
|
64
67
|
"""
|
|
65
|
-
Extract configuration data from command line
|
|
68
|
+
Extract configuration data from command line and class parameters.
|
|
66
69
|
|
|
67
70
|
Parameters
|
|
68
71
|
----------
|
|
@@ -78,6 +81,7 @@ class CameraEfficiency:
|
|
|
78
81
|
"zenith_angle": config_data["zenith_angle"].to("deg").value,
|
|
79
82
|
"azimuth_angle": config_data["azimuth_angle"].to("deg").value,
|
|
80
83
|
"nsb_spectrum": config_data.get("nsb_spectrum", None),
|
|
84
|
+
"efficiency_type": self.efficiency_type,
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
def _load_files(self):
|
|
@@ -88,15 +92,13 @@ class CameraEfficiency:
|
|
|
88
92
|
[".ecsv", ".dat", ".log"],
|
|
89
93
|
):
|
|
90
94
|
file_name = names.generate_file_name(
|
|
91
|
-
file_type=
|
|
92
|
-
"camera_efficiency_table" if label == "results" else "camera_efficiency"
|
|
93
|
-
),
|
|
95
|
+
file_type="camera_efficiency",
|
|
94
96
|
suffix=suffix,
|
|
95
97
|
site=self.telescope_model.site,
|
|
96
98
|
telescope_model_name=self.telescope_model.name,
|
|
97
99
|
zenith_angle=self.config["zenith_angle"],
|
|
98
100
|
azimuth_angle=self.config["azimuth_angle"],
|
|
99
|
-
label=self.
|
|
101
|
+
label=self.efficiency_type,
|
|
100
102
|
)
|
|
101
103
|
|
|
102
104
|
_file[label] = self.io_handler.get_output_directory().joinpath(file_name)
|
|
@@ -109,13 +111,13 @@ class CameraEfficiency:
|
|
|
109
111
|
self.export_model_files()
|
|
110
112
|
|
|
111
113
|
simtel = SimulatorCameraEfficiency(
|
|
112
|
-
simtel_path=self._simtel_path,
|
|
113
114
|
telescope_model=self.telescope_model,
|
|
114
115
|
site_model=self.site_model,
|
|
115
116
|
zenith_angle=self.config["zenith_angle"],
|
|
116
117
|
file_simtel=self._file["sim_telarray"],
|
|
117
118
|
file_log=self._file["log"],
|
|
118
119
|
label=self.label,
|
|
120
|
+
x_max=self._get_x_max_for_efficiency_type(),
|
|
119
121
|
nsb_spectrum=self.config["nsb_spectrum"],
|
|
120
122
|
skip_correction_to_nsb_spectrum=self.config.get(
|
|
121
123
|
"skip_correction_to_nsb_spectrum", False
|
|
@@ -250,34 +252,78 @@ class CameraEfficiency:
|
|
|
250
252
|
|
|
251
253
|
def results_summary(self):
|
|
252
254
|
"""
|
|
253
|
-
|
|
255
|
+
Fill a dictionary with summary of the results.
|
|
254
256
|
|
|
255
257
|
Include a header for the zenith/azimuth settings and the NSB spectrum file which was used.
|
|
256
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.
|
|
257
264
|
"""
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
|
281
327
|
|
|
282
328
|
def export_results(self):
|
|
283
329
|
"""Export results to a ecsv file."""
|
|
@@ -288,12 +334,9 @@ class CameraEfficiency:
|
|
|
288
334
|
astropy.io.ascii.write(
|
|
289
335
|
self._results, self._file["results"], format="basic", overwrite=True
|
|
290
336
|
)
|
|
291
|
-
_results_summary_file = (
|
|
292
|
-
str(self._file["results"]).replace(".ecsv", ".txt").replace("_table_", "_summary_")
|
|
293
|
-
)
|
|
337
|
+
_results_summary_file = str(self._file["results"]).replace(".ecsv", "_summary.yml")
|
|
294
338
|
self._logger.info(f"Exporting summary results to {_results_summary_file}")
|
|
295
|
-
|
|
296
|
-
file.write(self.results_summary())
|
|
339
|
+
ascii_handler.write_data_to_file(self.results_summary(), Path(_results_summary_file))
|
|
297
340
|
|
|
298
341
|
def _read_results(self):
|
|
299
342
|
"""Read existing results file and store it in _results."""
|
|
@@ -368,6 +411,39 @@ class CameraEfficiency:
|
|
|
368
411
|
|
|
369
412
|
return tel_efficiency / np.sqrt(tel_efficiency_nsb)
|
|
370
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
|
+
|
|
371
447
|
def calc_reflectivity(self):
|
|
372
448
|
"""
|
|
373
449
|
Calculate the Cherenkov spectrum weighted reflectivity in the range 300-550 nm.
|
|
@@ -433,14 +509,12 @@ class CameraEfficiency:
|
|
|
433
509
|
)
|
|
434
510
|
return self.nsb_pixel_pe_per_ns * u.GHz, self.nsb_rate_ref_conditions * u.GHz
|
|
435
511
|
|
|
436
|
-
def plot_efficiency(self,
|
|
512
|
+
def plot_efficiency(self, save_fig=False):
|
|
437
513
|
"""
|
|
438
514
|
Plot efficiency vs wavelength.
|
|
439
515
|
|
|
440
516
|
Parameters
|
|
441
517
|
----------
|
|
442
|
-
efficiency_type: str
|
|
443
|
-
The type of efficiency to plot (Cherenkov 'C' or NSB 'N')
|
|
444
518
|
save_fig: bool
|
|
445
519
|
If True, the figure will be saved to a file.
|
|
446
520
|
|
|
@@ -449,9 +523,9 @@ class CameraEfficiency:
|
|
|
449
523
|
fig
|
|
450
524
|
The figure instance of pyplot
|
|
451
525
|
"""
|
|
452
|
-
self._logger.info(f"Plotting {efficiency_type} efficiency vs wavelength")
|
|
526
|
+
self._logger.info(f"Plotting {self.efficiency_type} efficiency vs wavelength")
|
|
453
527
|
|
|
454
|
-
_col_type = "C" if efficiency_type
|
|
528
|
+
_col_type = "C" if self.efficiency_type in ("shower", "muon") else "N"
|
|
455
529
|
|
|
456
530
|
column_titles = {
|
|
457
531
|
"wl": "Wavelength [nm]",
|
|
@@ -469,21 +543,21 @@ class CameraEfficiency:
|
|
|
469
543
|
for column_now, column_title in column_titles.items():
|
|
470
544
|
table_to_plot.rename_column(column_now, column_title)
|
|
471
545
|
|
|
472
|
-
y_title = f"{efficiency_type} light efficiency"
|
|
473
|
-
if efficiency_type == "
|
|
546
|
+
y_title = f"{self.efficiency_type} light efficiency"
|
|
547
|
+
if self.efficiency_type == "nsb":
|
|
474
548
|
y_title = r"Diff. ph. rate [$10^{9} \times $ph/(nm s m$^2$ sr)]"
|
|
475
549
|
plot = visualize.plot_table(
|
|
476
550
|
table_to_plot,
|
|
477
551
|
y_title=y_title,
|
|
478
|
-
title=f"{self.telescope_model.name} response to {efficiency_type} light",
|
|
552
|
+
title=f"{self.telescope_model.name} response to {self.efficiency_type} light",
|
|
479
553
|
no_markers=True,
|
|
480
554
|
)
|
|
481
|
-
if efficiency_type == "
|
|
555
|
+
if self.efficiency_type == "nsb":
|
|
482
556
|
plot.gca().set_yscale("log")
|
|
483
557
|
ylim = plot.gca().get_ylim()
|
|
484
558
|
plot.gca().set_ylim(1e-3, ylim[1])
|
|
485
559
|
if save_fig:
|
|
486
|
-
self._save_plot(plot, efficiency_type
|
|
560
|
+
self._save_plot(plot, self.efficiency_type)
|
|
487
561
|
return plot
|
|
488
562
|
|
|
489
563
|
def _save_plot(self, fig, plot_title):
|
|
@@ -501,3 +575,47 @@ class CameraEfficiency:
|
|
|
501
575
|
self.label + "_" + self.telescope_model.name + "_" + plot_title
|
|
502
576
|
)
|
|
503
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
|
|
@@ -12,10 +11,12 @@ from astropy.table import Table
|
|
|
12
11
|
from scipy.optimize import curve_fit
|
|
13
12
|
|
|
14
13
|
import simtools.data_model.model_data_writer as writer
|
|
14
|
+
from simtools import settings
|
|
15
15
|
from simtools.constants import MODEL_PARAMETER_SCHEMA_URL, SCHEMA_PATH
|
|
16
16
|
from simtools.data_model import validate_data
|
|
17
17
|
from simtools.data_model.metadata_collector import MetadataCollector
|
|
18
18
|
from simtools.io import io_handler
|
|
19
|
+
from simtools.job_execution import job_manager
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
class SinglePhotonElectronSpectrum:
|
|
@@ -96,7 +97,8 @@ class SinglePhotonElectronSpectrum:
|
|
|
96
97
|
)
|
|
97
98
|
|
|
98
99
|
writer.ModelDataWriter.dump(
|
|
99
|
-
|
|
100
|
+
output_file=self.args_dict["output_file"],
|
|
101
|
+
output_file_format=self.args_dict.get("output_file_format"),
|
|
100
102
|
metadata=self.metadata,
|
|
101
103
|
product_data=table,
|
|
102
104
|
validate_schema_file=None,
|
|
@@ -125,7 +127,7 @@ class SinglePhotonElectronSpectrum:
|
|
|
125
127
|
|
|
126
128
|
Raises
|
|
127
129
|
------
|
|
128
|
-
|
|
130
|
+
job_manager.JobExecutionError
|
|
129
131
|
If the command execution fails.
|
|
130
132
|
"""
|
|
131
133
|
tmp_input_file = self._get_input_data(
|
|
@@ -140,7 +142,7 @@ class SinglePhotonElectronSpectrum:
|
|
|
140
142
|
)
|
|
141
143
|
|
|
142
144
|
command = [
|
|
143
|
-
f"{
|
|
145
|
+
f"{settings.config.sim_telarray_path}/bin/norm_spe",
|
|
144
146
|
"-r",
|
|
145
147
|
f"{self.args_dict['step_size']},{self.args_dict['max_amplitude']}",
|
|
146
148
|
]
|
|
@@ -152,10 +154,9 @@ class SinglePhotonElectronSpectrum:
|
|
|
152
154
|
|
|
153
155
|
self._logger.info(f"Running norm_spe command: {' '.join(command)}")
|
|
154
156
|
try:
|
|
155
|
-
result =
|
|
156
|
-
except
|
|
157
|
+
result = job_manager.submit(command)
|
|
158
|
+
except job_manager.JobExecutionError as exc:
|
|
157
159
|
self._logger.error(f"Error running norm_spe: {exc}")
|
|
158
|
-
self._logger.error(f"stderr: {exc.stderr}")
|
|
159
160
|
raise exc
|
|
160
161
|
finally:
|
|
161
162
|
for tmp_file in [tmp_input_file, tmp_ap_file]:
|
|
@@ -8,6 +8,7 @@ from pathlib import Path
|
|
|
8
8
|
import astropy.units as u
|
|
9
9
|
|
|
10
10
|
import simtools.version
|
|
11
|
+
from simtools import constants
|
|
11
12
|
from simtools.utils import names
|
|
12
13
|
|
|
13
14
|
|
|
@@ -106,7 +107,7 @@ class CommandLineParser(argparse.ArgumentParser):
|
|
|
106
107
|
required=False,
|
|
107
108
|
)
|
|
108
109
|
_job_group.add_argument(
|
|
109
|
-
"--
|
|
110
|
+
"--sim_telarray_path",
|
|
110
111
|
help="path pointing to sim_telarray installation",
|
|
111
112
|
type=Path,
|
|
112
113
|
required=False,
|
|
@@ -160,6 +161,18 @@ class CommandLineParser(argparse.ArgumentParser):
|
|
|
160
161
|
_job_group.add_argument(
|
|
161
162
|
"--version", action="version", version=f"%(prog)s {simtools.version.__version__}"
|
|
162
163
|
)
|
|
164
|
+
_job_group.add_argument(
|
|
165
|
+
"--build_info",
|
|
166
|
+
action=BuildInfoAction,
|
|
167
|
+
build_info=f"%(prog)s {simtools.version.__version__}",
|
|
168
|
+
help="show build information and exit",
|
|
169
|
+
)
|
|
170
|
+
_job_group.add_argument(
|
|
171
|
+
"--export_build_info",
|
|
172
|
+
help="export build information to file",
|
|
173
|
+
required=False,
|
|
174
|
+
type=str,
|
|
175
|
+
)
|
|
163
176
|
|
|
164
177
|
def initialize_user_arguments(self):
|
|
165
178
|
"""Initialize user arguments."""
|
|
@@ -269,7 +282,7 @@ class CommandLineParser(argparse.ArgumentParser):
|
|
|
269
282
|
type=self.telescope,
|
|
270
283
|
)
|
|
271
284
|
if "layout" in model_options or "layout_file" in model_options:
|
|
272
|
-
|
|
285
|
+
self._add_model_option_layout(
|
|
273
286
|
job_group=_job_group,
|
|
274
287
|
model_options=model_options,
|
|
275
288
|
# layout info is always required for layout related tasks with the exception
|
|
@@ -277,6 +290,13 @@ class CommandLineParser(argparse.ArgumentParser):
|
|
|
277
290
|
required="--list_available_layouts" not in self._option_string_actions,
|
|
278
291
|
)
|
|
279
292
|
|
|
293
|
+
_job_group.add_argument(
|
|
294
|
+
"--ignore_missing_design_model",
|
|
295
|
+
help="Ignore missing design model definition of DB",
|
|
296
|
+
action="store_true",
|
|
297
|
+
required=False,
|
|
298
|
+
)
|
|
299
|
+
|
|
280
300
|
def initialize_simulation_configuration_arguments(self, simulation_configuration):
|
|
281
301
|
"""
|
|
282
302
|
Initialize default arguments for simulation configuration and simulation software.
|
|
@@ -438,21 +458,35 @@ class CommandLineParser(argparse.ArgumentParser):
|
|
|
438
458
|
def _get_dictionary_with_sim_telarray_configuration():
|
|
439
459
|
"""Return dictionary with sim_telarray configuration parameters."""
|
|
440
460
|
return {
|
|
441
|
-
"
|
|
442
|
-
"help":
|
|
443
|
-
|
|
444
|
-
"If '--sim_telarray_random_instrument_instances' is not set: "
|
|
445
|
-
"use as sim_telarray seed ('random_seed' parameter). Otherwise: "
|
|
446
|
-
"use as base seed to generate the random instrument instance seeds."
|
|
447
|
-
),
|
|
448
|
-
"type": str,
|
|
461
|
+
"sim_telarray_instrument_seed": {
|
|
462
|
+
"help": "Random seed used for sim_telarray instrument setup.",
|
|
463
|
+
"type": CommandLineParser.bounded_int(1, constants.SIMTEL_MAX_SEED),
|
|
449
464
|
"required": False,
|
|
450
465
|
},
|
|
451
466
|
"sim_telarray_random_instrument_instances": {
|
|
452
467
|
"help": "Number of random instrument instances initialized in sim_telarray.",
|
|
453
|
-
"type":
|
|
468
|
+
"type": CommandLineParser.bounded_int(1, 1024),
|
|
469
|
+
"required": False,
|
|
470
|
+
"default": 1,
|
|
471
|
+
},
|
|
472
|
+
"sim_telarray_seed": {
|
|
473
|
+
"help": (
|
|
474
|
+
"Random seed used for sim_telarray simulation. "
|
|
475
|
+
"Single value: seed for event simulation. "
|
|
476
|
+
"Two values: [instrument_seed, simulation_seed] (use for testing only)."
|
|
477
|
+
),
|
|
478
|
+
"type": CommandLineParser.bounded_int(1, constants.SIMTEL_MAX_SEED),
|
|
479
|
+
"nargs": "+",
|
|
454
480
|
"required": False,
|
|
455
481
|
},
|
|
482
|
+
# hidden argument to specify the sim_telarray seeds file name
|
|
483
|
+
# (defined it here for convenience)
|
|
484
|
+
"sim_telarray_seed_file": {
|
|
485
|
+
"help": argparse.SUPPRESS,
|
|
486
|
+
"type": str,
|
|
487
|
+
"required": False,
|
|
488
|
+
"default": "sim_telarray_instrument_seeds.txt",
|
|
489
|
+
},
|
|
456
490
|
}
|
|
457
491
|
|
|
458
492
|
def _initialize_simulation_configuration(
|
|
@@ -825,3 +859,40 @@ class CommandLineParser(argparse.ArgumentParser):
|
|
|
825
859
|
raise ValueError("Input string does not contain an integer and a astropy quantity.")
|
|
826
860
|
|
|
827
861
|
return (int(match.group(1)), u.Quantity(float(match.group(2)), match.group(3)))
|
|
862
|
+
|
|
863
|
+
@staticmethod
|
|
864
|
+
def bounded_int(min_value, max_value):
|
|
865
|
+
"""Argument parser type to check that an integer is within a given interval."""
|
|
866
|
+
|
|
867
|
+
def bounded_int_type(value):
|
|
868
|
+
try:
|
|
869
|
+
int_value = int(value)
|
|
870
|
+
except ValueError as exc:
|
|
871
|
+
raise ValueError(f"expected an integer in [{min_value},{max_value}]") from exc
|
|
872
|
+
|
|
873
|
+
if min_value <= int_value <= max_value:
|
|
874
|
+
return int_value
|
|
875
|
+
raise ValueError(f"{int_value} not in [{min_value},{max_value}]")
|
|
876
|
+
|
|
877
|
+
return bounded_int_type
|
|
878
|
+
|
|
879
|
+
|
|
880
|
+
class BuildInfoAction(argparse.Action):
|
|
881
|
+
"""Custom argparse action to display build information."""
|
|
882
|
+
|
|
883
|
+
def __init__(self, option_strings, dest=argparse.SUPPRESS, default=argparse.SUPPRESS, **kwargs):
|
|
884
|
+
"""Initialize BuildInfoAction."""
|
|
885
|
+
self.build_info = kwargs.pop("build_info", "Build information")
|
|
886
|
+
kwargs.pop("nargs", None)
|
|
887
|
+
super().__init__(option_strings, dest=dest, default=default, nargs=0, **kwargs)
|
|
888
|
+
|
|
889
|
+
def __call__(self, parser, namespace, values, option_string=None):
|
|
890
|
+
"""Display build information and exit."""
|
|
891
|
+
# for efficiency reason, allow import here
|
|
892
|
+
from simtools import dependencies # pylint: disable=C0415
|
|
893
|
+
|
|
894
|
+
build_options = dependencies.get_build_options()
|
|
895
|
+
print(f"{self.build_info}")
|
|
896
|
+
for key, value in build_options.items():
|
|
897
|
+
print(f"{key}: {value}")
|
|
898
|
+
parser.exit()
|