gammasimtools 0.8.2__py3-none-any.whl → 0.9.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.8.2.dist-info → gammasimtools-0.9.0.dist-info}/METADATA +3 -3
- {gammasimtools-0.8.2.dist-info → gammasimtools-0.9.0.dist-info}/RECORD +64 -59
- {gammasimtools-0.8.2.dist-info → gammasimtools-0.9.0.dist-info}/WHEEL +1 -1
- {gammasimtools-0.8.2.dist-info → gammasimtools-0.9.0.dist-info}/entry_points.txt +2 -0
- simtools/_version.py +2 -2
- simtools/applications/convert_all_model_parameters_from_simtel.py +1 -1
- simtools/applications/convert_geo_coordinates_of_array_elements.py +8 -9
- simtools/applications/convert_model_parameter_from_simtel.py +1 -1
- simtools/applications/db_add_model_parameters_from_repository_to_db.py +2 -10
- simtools/applications/db_add_value_from_json_to_db.py +1 -9
- simtools/applications/db_get_array_layouts_from_db.py +3 -1
- simtools/applications/db_get_parameter_from_db.py +1 -1
- simtools/applications/derive_mirror_rnda.py +10 -1
- simtools/applications/derive_psf_parameters.py +1 -1
- simtools/applications/generate_array_config.py +1 -5
- simtools/applications/generate_regular_arrays.py +9 -6
- simtools/applications/plot_array_layout.py +3 -1
- simtools/applications/plot_tabular_data.py +84 -0
- simtools/applications/production_scale_events.py +1 -2
- simtools/applications/simulate_light_emission.py +2 -2
- simtools/applications/simulate_prod.py +24 -59
- simtools/applications/simulate_prod_htcondor_generator.py +95 -0
- simtools/applications/submit_data_from_external.py +1 -1
- simtools/applications/validate_camera_efficiency.py +1 -1
- simtools/applications/validate_camera_fov.py +3 -7
- simtools/applications/validate_cumulative_psf.py +3 -7
- simtools/applications/validate_file_using_schema.py +31 -21
- simtools/applications/validate_optics.py +3 -4
- simtools/camera_efficiency.py +1 -4
- simtools/configuration/commandline_parser.py +7 -13
- simtools/configuration/configurator.py +6 -19
- simtools/data_model/metadata_collector.py +18 -0
- simtools/data_model/metadata_model.py +18 -5
- simtools/data_model/model_data_writer.py +1 -1
- simtools/data_model/validate_data.py +67 -10
- simtools/db/db_handler.py +92 -315
- simtools/io_operations/legacy_data_handler.py +61 -0
- simtools/job_execution/htcondor_script_generator.py +133 -0
- simtools/job_execution/job_manager.py +77 -50
- simtools/model/camera.py +4 -2
- simtools/model/model_parameter.py +40 -10
- simtools/model/site_model.py +1 -1
- simtools/ray_tracing/mirror_panel_psf.py +47 -27
- simtools/runners/corsika_runner.py +14 -3
- simtools/runners/runner_services.py +3 -3
- simtools/runners/simtel_runner.py +27 -8
- simtools/schemas/integration_tests_config.metaschema.yml +15 -5
- simtools/schemas/model_parameter.metaschema.yml +90 -2
- simtools/schemas/model_parameters/effective_focal_length.schema.yml +31 -1
- simtools/simtel/simtel_table_reader.py +410 -0
- simtools/simtel/simulator_camera_efficiency.py +6 -4
- simtools/simtel/simulator_light_emission.py +2 -2
- simtools/simtel/simulator_ray_tracing.py +1 -2
- simtools/simulator.py +80 -33
- simtools/testing/configuration.py +12 -8
- simtools/testing/helpers.py +5 -5
- simtools/testing/validate_output.py +26 -26
- simtools/utils/general.py +50 -3
- simtools/utils/names.py +2 -2
- simtools/utils/value_conversion.py +9 -1
- simtools/visualization/plot_tables.py +106 -0
- simtools/visualization/visualize.py +43 -5
- simtools/db/db_from_repo_handler.py +0 -106
- {gammasimtools-0.8.2.dist-info → gammasimtools-0.9.0.dist-info}/LICENSE +0 -0
- {gammasimtools-0.8.2.dist-info → gammasimtools-0.9.0.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"""Base class for running sim_telarray simulations."""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
import
|
|
4
|
+
import stat
|
|
5
|
+
import subprocess
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
|
|
7
8
|
import simtools.utils.general as gen
|
|
@@ -96,7 +97,7 @@ class SimtelRunner:
|
|
|
96
97
|
|
|
97
98
|
file.write('\necho "RUNTIME: $SECONDS"\n')
|
|
98
99
|
|
|
99
|
-
|
|
100
|
+
script_file_path.chmod(script_file_path.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP)
|
|
100
101
|
return script_file_path
|
|
101
102
|
|
|
102
103
|
def run(self, test=False, input_file=None, run_number=None):
|
|
@@ -114,15 +115,17 @@ class SimtelRunner:
|
|
|
114
115
|
"""
|
|
115
116
|
self._logger.debug("Running sim_telarray")
|
|
116
117
|
|
|
117
|
-
command = self._make_run_command(
|
|
118
|
+
command, stdout_file, stderr_file = self._make_run_command(
|
|
119
|
+
run_number=run_number, input_file=input_file
|
|
120
|
+
)
|
|
118
121
|
|
|
119
122
|
if test:
|
|
120
123
|
self._logger.info(f"Running (test) with command: {command}")
|
|
121
|
-
self._run_simtel_and_check_output(command)
|
|
124
|
+
self._run_simtel_and_check_output(command, stdout_file, stderr_file)
|
|
122
125
|
else:
|
|
123
126
|
self._logger.debug(f"Running ({self.runs_per_set}x) with command: {command}")
|
|
124
127
|
for _ in range(self.runs_per_set):
|
|
125
|
-
self._run_simtel_and_check_output(command)
|
|
128
|
+
self._run_simtel_and_check_output(command, stdout_file, stderr_file)
|
|
126
129
|
|
|
127
130
|
self._check_run_result(run_number=run_number)
|
|
128
131
|
|
|
@@ -148,7 +151,7 @@ class SimtelRunner:
|
|
|
148
151
|
self._logger.error(msg)
|
|
149
152
|
raise SimtelExecutionError(msg)
|
|
150
153
|
|
|
151
|
-
def _run_simtel_and_check_output(self, command):
|
|
154
|
+
def _run_simtel_and_check_output(self, command, stdout_file, stderr_file):
|
|
152
155
|
"""
|
|
153
156
|
Run the sim_telarray command and check the exit code.
|
|
154
157
|
|
|
@@ -157,8 +160,24 @@ class SimtelRunner:
|
|
|
157
160
|
SimtelExecutionError
|
|
158
161
|
if run was not successful.
|
|
159
162
|
"""
|
|
160
|
-
if
|
|
163
|
+
stdout_file = stdout_file if stdout_file else "/dev/null"
|
|
164
|
+
stderr_file = stderr_file if stderr_file else "/dev/null"
|
|
165
|
+
with (
|
|
166
|
+
open(f"{stdout_file}", "w", encoding="utf-8") as stdout,
|
|
167
|
+
open(f"{stderr_file}", "w", encoding="utf-8") as stderr,
|
|
168
|
+
):
|
|
169
|
+
result = subprocess.run(
|
|
170
|
+
command,
|
|
171
|
+
shell=True,
|
|
172
|
+
text=True,
|
|
173
|
+
stdout=stdout,
|
|
174
|
+
stderr=stderr,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
if result.returncode != 0:
|
|
178
|
+
self._logger.error(result.stderr)
|
|
161
179
|
self._raise_simtel_error()
|
|
180
|
+
return result.returncode
|
|
162
181
|
|
|
163
182
|
def _make_run_command(self, run_number=None, input_file=None):
|
|
164
183
|
self._logger.debug(
|
|
@@ -167,7 +186,7 @@ class SimtelRunner:
|
|
|
167
186
|
)
|
|
168
187
|
input_file = input_file if input_file else "nofile"
|
|
169
188
|
run_number = run_number if run_number else 1
|
|
170
|
-
return f"{input_file}-{run_number}"
|
|
189
|
+
return f"{input_file}-{run_number}", None, None
|
|
171
190
|
|
|
172
191
|
@staticmethod
|
|
173
192
|
def get_config_option(par, value=None, weak_option=False):
|
|
@@ -3,7 +3,7 @@ $schema: http://json-schema.org/draft-06/schema#
|
|
|
3
3
|
$ref: '#/definitions/SimtoolsIntegrationTestConfiguration'
|
|
4
4
|
title: SimPipe integration test configuration metaschema
|
|
5
5
|
description: YAML representation of integration test configuration metaschema
|
|
6
|
-
version: 0.
|
|
6
|
+
version: 0.2.0
|
|
7
7
|
name: integration_tests_config.metaschema
|
|
8
8
|
type: object
|
|
9
9
|
additionalProperties: false
|
|
@@ -60,6 +60,20 @@ definitions:
|
|
|
60
60
|
description: |
|
|
61
61
|
"Path of output file tested to be generated by integration test."
|
|
62
62
|
type: string
|
|
63
|
+
TEST_OUTPUT_FILES:
|
|
64
|
+
type: array
|
|
65
|
+
items:
|
|
66
|
+
type: object
|
|
67
|
+
properties:
|
|
68
|
+
PATH_DESCRIPTOR:
|
|
69
|
+
type: string
|
|
70
|
+
FILE:
|
|
71
|
+
type: string
|
|
72
|
+
EXPECTED_OUTPUT:
|
|
73
|
+
description: |
|
|
74
|
+
"Expected output of integration test."
|
|
75
|
+
type: object
|
|
76
|
+
required: ["PATH_DESCRIPTOR", "FILE"]
|
|
63
77
|
FILE_TYPE:
|
|
64
78
|
description: |
|
|
65
79
|
"Expected file type of output file generated by integration test."
|
|
@@ -68,10 +82,6 @@ definitions:
|
|
|
68
82
|
description: |
|
|
69
83
|
"Reference file used for comparison."
|
|
70
84
|
type: string
|
|
71
|
-
EXPECTED_OUTPUT:
|
|
72
|
-
description: |
|
|
73
|
-
"Expected output of integration test."
|
|
74
|
-
type: object
|
|
75
85
|
TOLERANCE:
|
|
76
86
|
description: "Allowed tolerance for floating point comparison."
|
|
77
87
|
type: number
|
|
@@ -2,12 +2,99 @@
|
|
|
2
2
|
$schema: http://json-schema.org/draft-06/schema#
|
|
3
3
|
$ref: '#/definitions/SimtoolsModelParameters'
|
|
4
4
|
title: SimPipe DB Model Parameter Metaschema
|
|
5
|
-
description:
|
|
6
|
-
|
|
5
|
+
description: |
|
|
6
|
+
YAML representation of db model parameter metaschema
|
|
7
|
+
(based on simulation model DB).
|
|
8
|
+
version: 0.2.0
|
|
7
9
|
name: modelparameter.metaschema
|
|
8
10
|
type: object
|
|
9
11
|
additionalProperties: false
|
|
10
12
|
|
|
13
|
+
definitions:
|
|
14
|
+
SimtoolsModelParameters:
|
|
15
|
+
description: ""
|
|
16
|
+
type: object
|
|
17
|
+
properties:
|
|
18
|
+
_id:
|
|
19
|
+
type: string
|
|
20
|
+
description: "DB unique identifier."
|
|
21
|
+
entry_date:
|
|
22
|
+
type: string
|
|
23
|
+
description: "Value entry date."
|
|
24
|
+
file:
|
|
25
|
+
type: boolean
|
|
26
|
+
description: "This parameter is a file."
|
|
27
|
+
instrument:
|
|
28
|
+
type: string
|
|
29
|
+
description: "Associated instrument."
|
|
30
|
+
site:
|
|
31
|
+
type: string
|
|
32
|
+
description: "Associated CTAO site."
|
|
33
|
+
enum:
|
|
34
|
+
- North
|
|
35
|
+
- South
|
|
36
|
+
type:
|
|
37
|
+
type: string
|
|
38
|
+
description: "Data type"
|
|
39
|
+
enum:
|
|
40
|
+
- boolean
|
|
41
|
+
- dict
|
|
42
|
+
- double
|
|
43
|
+
- file
|
|
44
|
+
- float64
|
|
45
|
+
- int
|
|
46
|
+
- int64
|
|
47
|
+
- string
|
|
48
|
+
- uint
|
|
49
|
+
- uint32
|
|
50
|
+
- uint64
|
|
51
|
+
unit:
|
|
52
|
+
anyOf:
|
|
53
|
+
- type: string
|
|
54
|
+
- type: "null"
|
|
55
|
+
description: "Unit of the parameter."
|
|
56
|
+
value:
|
|
57
|
+
anyOf:
|
|
58
|
+
- type: boolean
|
|
59
|
+
- type: number
|
|
60
|
+
- type: string
|
|
61
|
+
- type: "null"
|
|
62
|
+
- type: array
|
|
63
|
+
description: "Value of the parameter."
|
|
64
|
+
parameter_version:
|
|
65
|
+
anyOf:
|
|
66
|
+
- type: string
|
|
67
|
+
description: "Parameter version."
|
|
68
|
+
schema_version:
|
|
69
|
+
anyOf:
|
|
70
|
+
- type: string
|
|
71
|
+
description: "Metaschema version."
|
|
72
|
+
unique_id:
|
|
73
|
+
anyOf:
|
|
74
|
+
- type: string
|
|
75
|
+
- type: "null"
|
|
76
|
+
description: "Unique ID of parameter definition."
|
|
77
|
+
required:
|
|
78
|
+
- file
|
|
79
|
+
- instrument
|
|
80
|
+
- parameter_version
|
|
81
|
+
- schema_version
|
|
82
|
+
- site
|
|
83
|
+
- type
|
|
84
|
+
- unit
|
|
85
|
+
- value
|
|
86
|
+
...
|
|
87
|
+
---
|
|
88
|
+
$schema: http://json-schema.org/draft-06/schema#
|
|
89
|
+
$ref: '#/definitions/SimtoolsModelParameters'
|
|
90
|
+
title: SimPipe DB Model Parameter Metaschema
|
|
91
|
+
description: |
|
|
92
|
+
YAML representation of db model parameter metaschema
|
|
93
|
+
(based on model_parameters DB).
|
|
94
|
+
version: 0.1.0
|
|
95
|
+
name: modelparameter.metaschema
|
|
96
|
+
type: object
|
|
97
|
+
additionalProperties: false
|
|
11
98
|
|
|
12
99
|
definitions:
|
|
13
100
|
SimtoolsModelParameters:
|
|
@@ -48,6 +135,7 @@ definitions:
|
|
|
48
135
|
- boolean
|
|
49
136
|
- double
|
|
50
137
|
- int
|
|
138
|
+
- int64
|
|
51
139
|
- string
|
|
52
140
|
- uint
|
|
53
141
|
- file
|
|
@@ -33,12 +33,42 @@ short_description: |-
|
|
|
33
33
|
Effective focal length.
|
|
34
34
|
Only to be used for image analysis, has no effect on the simulation.
|
|
35
35
|
data:
|
|
36
|
-
-
|
|
36
|
+
- description: |-
|
|
37
|
+
Mean effective length for all directions of incidence.
|
|
38
|
+
type: double
|
|
37
39
|
unit: cm
|
|
38
40
|
default: 0.0
|
|
39
41
|
allowed_range:
|
|
40
42
|
min: 0.0
|
|
41
43
|
max: 10000.0
|
|
44
|
+
- description: |-
|
|
45
|
+
Effective length for incidence directions in mirror/camera x-z plane
|
|
46
|
+
(if non-zero).
|
|
47
|
+
type: double
|
|
48
|
+
unit: cm
|
|
49
|
+
default: 0.0
|
|
50
|
+
allowed_range:
|
|
51
|
+
min: 0.0
|
|
52
|
+
max: 10000.0
|
|
53
|
+
- description: |-
|
|
54
|
+
Effective length for incidence directions in mirror/camera y-z plane
|
|
55
|
+
(if non-zero).
|
|
56
|
+
type: double
|
|
57
|
+
unit: cm
|
|
58
|
+
default: 0.0
|
|
59
|
+
allowed_range:
|
|
60
|
+
min: 0.0
|
|
61
|
+
max: 10000.0
|
|
62
|
+
- description: |-
|
|
63
|
+
Any displacement along x in the focal plane from asymmetric PSF behavior.
|
|
64
|
+
type: double
|
|
65
|
+
unit: cm
|
|
66
|
+
default: 0.0
|
|
67
|
+
- description: |-
|
|
68
|
+
Any displacement along y in the focal plane from asymmetric PSF behavior.
|
|
69
|
+
type: double
|
|
70
|
+
unit: cm
|
|
71
|
+
default: 0.0
|
|
42
72
|
instrument:
|
|
43
73
|
class: Structure
|
|
44
74
|
type:
|
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
"""Read tabular data in sim_telarray format and return as astropy table."""
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
import astropy.units as u
|
|
8
|
+
from astropy.table import Table
|
|
9
|
+
|
|
10
|
+
from simtools.utils import general as gen
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _data_columns(parameter_name, n_columns, n_dim):
|
|
16
|
+
"""
|
|
17
|
+
Get column information for a given parameter.
|
|
18
|
+
|
|
19
|
+
Individual functions are adapted to the specific format of the sim_telarray tables.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
parameter_name: str
|
|
24
|
+
Model parameter name.
|
|
25
|
+
n_columns: int
|
|
26
|
+
Number of columns in the table.
|
|
27
|
+
n_dim: list
|
|
28
|
+
List of columns for n-dimensional tables defined by RPOL lines.
|
|
29
|
+
|
|
30
|
+
Returns
|
|
31
|
+
-------
|
|
32
|
+
list, str
|
|
33
|
+
List of columns for n-dimensional tables, description.
|
|
34
|
+
"""
|
|
35
|
+
if parameter_name == "mirror_reflectivity":
|
|
36
|
+
return _data_columns_mirror_reflectivity(n_columns, n_dim)
|
|
37
|
+
if parameter_name in ("discriminator_pulse_shape", "fadc_pulse_shape"):
|
|
38
|
+
return _data_columns_pulse_shape(n_columns)
|
|
39
|
+
try:
|
|
40
|
+
return globals()[f"_data_columns_{parameter_name}"]()
|
|
41
|
+
except KeyError as exc:
|
|
42
|
+
raise ValueError(
|
|
43
|
+
f"Unsupported parameter for sim_telarray table reading: {parameter_name}"
|
|
44
|
+
) from exc
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _data_columns_atmospheric_profile():
|
|
48
|
+
"""Column representation for parameter atmospheric_profile."""
|
|
49
|
+
return (
|
|
50
|
+
[
|
|
51
|
+
{"name": "altitude", "description": "Altitude", "unit": "km"},
|
|
52
|
+
{"name": "density", "description": "Density", "unit": "g/cm^3"},
|
|
53
|
+
{"name": "thickness", "description": "Thickness", "unit": "g/cm^2"},
|
|
54
|
+
{
|
|
55
|
+
"name": "refractive_index",
|
|
56
|
+
"description": "Refractive index (n-1)",
|
|
57
|
+
"unit": None,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"name": "temperature",
|
|
61
|
+
"description": "Temperature",
|
|
62
|
+
"unit": "K",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"name": "pressure",
|
|
66
|
+
"description": "Pressure",
|
|
67
|
+
"unit": "mbar",
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"name": "pw/w",
|
|
71
|
+
"description": "Partial pressure of water vapor",
|
|
72
|
+
"unit": None,
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
"Atmospheric profile",
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _data_columns_pm_photoelectron_spectrum():
|
|
80
|
+
"""Column description for parameter pm_photoelectron_spectrum."""
|
|
81
|
+
return (
|
|
82
|
+
[
|
|
83
|
+
{"name": "amplitude", "description": "Signal amplitude", "unit": None},
|
|
84
|
+
{
|
|
85
|
+
"name": "response",
|
|
86
|
+
"description": "response without afterpulsing component",
|
|
87
|
+
"unit": None,
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"name": "response_with_ap",
|
|
91
|
+
"description": "response including afterpulsing component",
|
|
92
|
+
"unit": None,
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
"Photoelectron spectrum",
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _data_columns_quantum_efficiency():
|
|
100
|
+
"""Column description for parameter quantum_efficiency."""
|
|
101
|
+
return (
|
|
102
|
+
[
|
|
103
|
+
{"name": "wavelength", "description": "Wavelength", "unit": "nm"},
|
|
104
|
+
{
|
|
105
|
+
"name": "efficiency",
|
|
106
|
+
"description": "Quantum efficiency",
|
|
107
|
+
"unit": None,
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"name": "efficiency_rms",
|
|
111
|
+
"description": "Quantum efficiency (standard deviation)",
|
|
112
|
+
"unit": None,
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
"Quantum efficiency",
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _data_columns_camera_filter():
|
|
120
|
+
"""Column description for parameter camera_filter."""
|
|
121
|
+
return (
|
|
122
|
+
[
|
|
123
|
+
{"name": "wavelength", "description": "Wavelength", "unit": "nm"},
|
|
124
|
+
{
|
|
125
|
+
"name": "transmission",
|
|
126
|
+
"description": "Average transmission",
|
|
127
|
+
"unit": None,
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
"Camera window transmission",
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _data_columns_lightguide_efficiency_vs_wavelength():
|
|
135
|
+
"""Column description for parameter lightguide_efficiency_vs_wavelength."""
|
|
136
|
+
return _data_columns_lightguide_efficiency_vs_incidence_angle()
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _data_columns_lightguide_efficiency_vs_incidence_angle():
|
|
140
|
+
"""Column description for (parameter lightguide_efficiency_vs_incidence_angle."""
|
|
141
|
+
return (
|
|
142
|
+
[
|
|
143
|
+
{"name": "angle", "description": "Incidence angle", "unit": "deg"},
|
|
144
|
+
{
|
|
145
|
+
"name": "efficiency",
|
|
146
|
+
"description": "Light guide efficiency",
|
|
147
|
+
"unit": None,
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
"Light guide efficiency vs incidence angle",
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _data_columns_mirror_reflectivity(n_columns, n_dim):
|
|
155
|
+
"""Column description for parameter mirror_reflectivity."""
|
|
156
|
+
_columns = [
|
|
157
|
+
{"name": "wavelength", "description": "Wavelength", "unit": "nm"},
|
|
158
|
+
]
|
|
159
|
+
if n_dim:
|
|
160
|
+
for angle in n_dim:
|
|
161
|
+
_columns.append(
|
|
162
|
+
{
|
|
163
|
+
"name": f"reflectivity_{angle}deg",
|
|
164
|
+
"description": f"Mirror reflectivity at {angle} deg",
|
|
165
|
+
"unit": None,
|
|
166
|
+
},
|
|
167
|
+
)
|
|
168
|
+
else:
|
|
169
|
+
_columns.append(
|
|
170
|
+
{
|
|
171
|
+
"name": "reflectivity",
|
|
172
|
+
"description": "Mirror reflectivity",
|
|
173
|
+
"unit": None,
|
|
174
|
+
},
|
|
175
|
+
)
|
|
176
|
+
if n_columns == 3:
|
|
177
|
+
_columns.append(
|
|
178
|
+
{
|
|
179
|
+
"name": "reflectivity_rms",
|
|
180
|
+
"description": "Mirror reflectivity (standard deviation)",
|
|
181
|
+
"unit": None,
|
|
182
|
+
},
|
|
183
|
+
)
|
|
184
|
+
if n_columns == 4:
|
|
185
|
+
_columns.append(
|
|
186
|
+
{
|
|
187
|
+
"name": "reflectivity_min",
|
|
188
|
+
"description": "Mirror reflectivity (min)",
|
|
189
|
+
"unit": None,
|
|
190
|
+
},
|
|
191
|
+
)
|
|
192
|
+
_columns.append(
|
|
193
|
+
{
|
|
194
|
+
"name": "reflectivity_max",
|
|
195
|
+
"description": "Mirror reflectivity (max)",
|
|
196
|
+
"unit": None,
|
|
197
|
+
},
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
return _columns, "Mirror reflectivity"
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _data_columns_pulse_shape(n_columns):
|
|
204
|
+
"""Column description for parameters discriminator_pulse_shape, fadc_pulse_shape."""
|
|
205
|
+
_columns = [
|
|
206
|
+
{"name": "time", "description": "Time", "unit": "ns"},
|
|
207
|
+
{
|
|
208
|
+
"name": "amplitude",
|
|
209
|
+
"description": "Amplitude",
|
|
210
|
+
"unit": None,
|
|
211
|
+
},
|
|
212
|
+
]
|
|
213
|
+
if n_columns == 3:
|
|
214
|
+
_columns.append(
|
|
215
|
+
{
|
|
216
|
+
"name": "amplitude (low gain)",
|
|
217
|
+
"description": "Amplitude (low gain)",
|
|
218
|
+
"unit": None,
|
|
219
|
+
},
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
return _columns, "Pulse shape"
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _data_columns_nsb_reference_spectrum():
|
|
226
|
+
"""Column description for parameter nsb_reference_spectrum."""
|
|
227
|
+
return (
|
|
228
|
+
[
|
|
229
|
+
{"name": "wavelength", "description": "Wavelength", "unit": "nm"},
|
|
230
|
+
{
|
|
231
|
+
"name": "differential photon rate",
|
|
232
|
+
"description": "Differential photon rate",
|
|
233
|
+
"unit": "1.e9 / (nm s m^2 sr)",
|
|
234
|
+
},
|
|
235
|
+
],
|
|
236
|
+
"NSB reference spectrum",
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def read_simtel_table(parameter_name, file_path):
|
|
241
|
+
"""
|
|
242
|
+
Read sim_telarray table file for a given parameter.
|
|
243
|
+
|
|
244
|
+
Parameters
|
|
245
|
+
----------
|
|
246
|
+
parameter_name: str
|
|
247
|
+
Model parameter name.
|
|
248
|
+
file_path: Path
|
|
249
|
+
Name (full path) of the sim_telarray table file.
|
|
250
|
+
|
|
251
|
+
Returns
|
|
252
|
+
-------
|
|
253
|
+
Table
|
|
254
|
+
Astropy table.
|
|
255
|
+
"""
|
|
256
|
+
if parameter_name == "atmospheric_transmission":
|
|
257
|
+
return _read_simtel_data_for_atmospheric_transmission(file_path)
|
|
258
|
+
|
|
259
|
+
rows, meta_from_simtel, n_columns, n_dim = _read_simtel_data(file_path)
|
|
260
|
+
columns_info, description = _data_columns(parameter_name, n_columns, n_dim)
|
|
261
|
+
|
|
262
|
+
rows = _adjust_columns_length(rows, len(columns_info))
|
|
263
|
+
|
|
264
|
+
metadata = {
|
|
265
|
+
"Name": parameter_name,
|
|
266
|
+
"File": str(file_path),
|
|
267
|
+
"Description:": description,
|
|
268
|
+
"Context_from_sim_telarray": meta_from_simtel,
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
table = Table(rows=rows, names=[col["name"] for col in columns_info])
|
|
272
|
+
for col, info in zip(table.colnames, columns_info):
|
|
273
|
+
table[col].unit = info.get("unit")
|
|
274
|
+
table[col].description = info.get("description")
|
|
275
|
+
table.meta.update(metadata)
|
|
276
|
+
|
|
277
|
+
return table
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _adjust_columns_length(rows, n_columns):
|
|
281
|
+
"""
|
|
282
|
+
Adjust row lengths to match the specified column count.
|
|
283
|
+
|
|
284
|
+
- Truncate rows with extra columns beyond the specified count 'n_columns'.
|
|
285
|
+
- Pad shorter rows with zeros.
|
|
286
|
+
"""
|
|
287
|
+
return [row[:n_columns] + [0.0] * max(0, n_columns - len(row)) for row in rows]
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _read_simtel_data(file_path):
|
|
291
|
+
"""
|
|
292
|
+
Read data, comments, and (if available) axis definition from sim_telarray table.
|
|
293
|
+
|
|
294
|
+
Parameters
|
|
295
|
+
----------
|
|
296
|
+
file_path: Path
|
|
297
|
+
Path to the sim_telarray table file.
|
|
298
|
+
|
|
299
|
+
Returns
|
|
300
|
+
-------
|
|
301
|
+
str, str, int, str
|
|
302
|
+
data, metadata (comments), number of columns (max value), n-dimensional axis description.
|
|
303
|
+
"""
|
|
304
|
+
logger.debug(f"Reading sim_telarray table from {file_path}")
|
|
305
|
+
meta_lines = []
|
|
306
|
+
data_lines = []
|
|
307
|
+
n_dim_axis = None
|
|
308
|
+
r_pol_axis = None
|
|
309
|
+
|
|
310
|
+
lines = gen.read_file_encoded_in_utf_or_latin(file_path)
|
|
311
|
+
|
|
312
|
+
for line in lines:
|
|
313
|
+
stripped = line.strip()
|
|
314
|
+
if "@RPOL@" in stripped: # RPOL description for N-dimensional tables
|
|
315
|
+
match = re.search(r"#@RPOL@\[(\w+)=\]", stripped)
|
|
316
|
+
if match:
|
|
317
|
+
r_pol_axis = match.group(1)
|
|
318
|
+
elif r_pol_axis and r_pol_axis in stripped: # N-dimensional axis description
|
|
319
|
+
n_dim_axis = stripped.split("=")[1].split()
|
|
320
|
+
elif stripped.startswith("#"): # Metadata
|
|
321
|
+
meta_lines.append(stripped.lstrip("#").strip())
|
|
322
|
+
elif stripped: # Data
|
|
323
|
+
data_lines.append(stripped.split("%%%")[0].split("#")[0].strip()) # Remove comments
|
|
324
|
+
|
|
325
|
+
rows = [[float(part) for part in line.split()] for line in data_lines]
|
|
326
|
+
n_columns = max(len(row) for row in rows) if rows else 0
|
|
327
|
+
|
|
328
|
+
return rows, "\n".join(meta_lines), n_columns, n_dim_axis
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def _read_simtel_data_for_atmospheric_transmission(file_path):
|
|
332
|
+
"""
|
|
333
|
+
Read data and comments from sim_telarray table for atmospheric_transmission.
|
|
334
|
+
|
|
335
|
+
Parameters
|
|
336
|
+
----------
|
|
337
|
+
file_path: Path
|
|
338
|
+
Path to the sim_telarray table file.
|
|
339
|
+
|
|
340
|
+
Returns
|
|
341
|
+
-------
|
|
342
|
+
astropy table
|
|
343
|
+
Table with atmospheric transmission.
|
|
344
|
+
"""
|
|
345
|
+
lines = lines = gen.read_file_encoded_in_utf_or_latin(file_path)
|
|
346
|
+
|
|
347
|
+
observatory_level, height_bins = _read_header_line_for_atmospheric_transmission(
|
|
348
|
+
lines, file_path
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
wavelengths = []
|
|
352
|
+
heights = []
|
|
353
|
+
extinctions = []
|
|
354
|
+
meta_lines = []
|
|
355
|
+
|
|
356
|
+
for line in lines:
|
|
357
|
+
if line.startswith("#") or not line.strip():
|
|
358
|
+
meta_lines.append(line.lstrip("#").strip())
|
|
359
|
+
continue
|
|
360
|
+
parts = line.split()
|
|
361
|
+
try:
|
|
362
|
+
wl = float(parts[0])
|
|
363
|
+
for i, height in enumerate(height_bins):
|
|
364
|
+
extinction_value = float(parts[i + 1])
|
|
365
|
+
if extinction_value == 99999.0:
|
|
366
|
+
continue
|
|
367
|
+
wavelengths.append(wl)
|
|
368
|
+
heights.append(height)
|
|
369
|
+
extinctions.append(extinction_value)
|
|
370
|
+
except (ValueError, IndexError):
|
|
371
|
+
logger.debug(f"Skipping malformed line: {line.strip()}")
|
|
372
|
+
|
|
373
|
+
table = Table()
|
|
374
|
+
table["wavelength"] = wavelengths
|
|
375
|
+
table["altitude"] = heights
|
|
376
|
+
table["extinction"] = extinctions
|
|
377
|
+
|
|
378
|
+
table.meta.update(
|
|
379
|
+
{
|
|
380
|
+
"Name": "atmospheric_transmission",
|
|
381
|
+
"File": str(file_path),
|
|
382
|
+
"Description": "Atmospheric transmission",
|
|
383
|
+
"Context_from_sim_telarray": "\n".join(meta_lines),
|
|
384
|
+
"observatory_level": observatory_level,
|
|
385
|
+
}
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
return table
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def _read_header_line_for_atmospheric_transmission(lines, file_path):
|
|
392
|
+
"""Reader observatory level and height bins from header line for atmospheric transmission."""
|
|
393
|
+
header_line = None
|
|
394
|
+
observatory_level = None
|
|
395
|
+
for line in lines:
|
|
396
|
+
if "H2=" in line and "H1=" in line:
|
|
397
|
+
match_h2 = re.search(r"H2=\s*([\d.]+)", line)
|
|
398
|
+
if match_h2:
|
|
399
|
+
observatory_level = float(match_h2.group(1)) * u.km
|
|
400
|
+
|
|
401
|
+
if "H1=" in line:
|
|
402
|
+
header_line = line.split("H1=")[-1].strip()
|
|
403
|
+
break
|
|
404
|
+
|
|
405
|
+
if header_line is None:
|
|
406
|
+
raise ValueError(f"Header with 'H1=' not found file {file_path}")
|
|
407
|
+
|
|
408
|
+
height_bins = [float(x.replace(",", "")) for x in header_line.split()]
|
|
409
|
+
|
|
410
|
+
return observatory_level, height_bins
|