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
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""HT Condor script generator for simulation production."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import astropy.units as u
|
|
7
|
+
|
|
8
|
+
_logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def generate_submission_script(args_dict):
|
|
12
|
+
"""
|
|
13
|
+
Generate the HT Condor submission script.
|
|
14
|
+
|
|
15
|
+
Parameters
|
|
16
|
+
----------
|
|
17
|
+
args_dict: dict
|
|
18
|
+
Arguments dictionary.
|
|
19
|
+
"""
|
|
20
|
+
_logger.info("Generating HT Condor submission scripts ")
|
|
21
|
+
|
|
22
|
+
work_dir = Path(args_dict["output_path"])
|
|
23
|
+
log_dir = work_dir / "logs"
|
|
24
|
+
work_dir.mkdir(parents=True, exist_ok=True)
|
|
25
|
+
log_dir.mkdir(parents=True, exist_ok=True)
|
|
26
|
+
submit_file_name = "simulate_prod.submit"
|
|
27
|
+
|
|
28
|
+
with open(work_dir / f"{submit_file_name}.condor", "w", encoding="utf-8") as submit_file_handle:
|
|
29
|
+
submit_file_handle.write(
|
|
30
|
+
_get_submit_file(
|
|
31
|
+
f"{submit_file_name}.sh",
|
|
32
|
+
args_dict["apptainer_image"],
|
|
33
|
+
args_dict["priority"],
|
|
34
|
+
+args_dict["number_of_runs"],
|
|
35
|
+
)
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
with open(work_dir / f"{submit_file_name}.sh", "w", encoding="utf-8") as submit_script_handle:
|
|
39
|
+
submit_script_handle.write(_get_submit_script(args_dict))
|
|
40
|
+
|
|
41
|
+
Path(work_dir / f"{submit_file_name}.sh").chmod(0o755)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _get_submit_file(executable, apptainer_image, priority, n_jobs):
|
|
45
|
+
"""
|
|
46
|
+
Return HT Condor submit file.
|
|
47
|
+
|
|
48
|
+
Database access variables are passed through the environment file.
|
|
49
|
+
|
|
50
|
+
Parameters
|
|
51
|
+
----------
|
|
52
|
+
executable: str
|
|
53
|
+
Name of the executable script.
|
|
54
|
+
apptainer_image: str
|
|
55
|
+
Path to the Apptainer image.
|
|
56
|
+
priority: int
|
|
57
|
+
Priority of the job.
|
|
58
|
+
n_jobs: int
|
|
59
|
+
Number of jobs to queue.
|
|
60
|
+
|
|
61
|
+
Returns
|
|
62
|
+
-------
|
|
63
|
+
str
|
|
64
|
+
HT Condor submit file content.
|
|
65
|
+
"""
|
|
66
|
+
return f"""universe = container
|
|
67
|
+
container_image = {apptainer_image}
|
|
68
|
+
transfer_container = false
|
|
69
|
+
|
|
70
|
+
executable = {executable}
|
|
71
|
+
error = logs/err.$(cluster)_$(process)
|
|
72
|
+
output = logs/out.$(cluster)_$(process)
|
|
73
|
+
log = logs/log.$(cluster)_$(process)
|
|
74
|
+
|
|
75
|
+
priority = {priority}
|
|
76
|
+
arguments = "$(process) env.txt"
|
|
77
|
+
|
|
78
|
+
queue {n_jobs}
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _get_submit_script(args_dict):
|
|
83
|
+
"""
|
|
84
|
+
Return HT Condor submit script.
|
|
85
|
+
|
|
86
|
+
Parameters
|
|
87
|
+
----------
|
|
88
|
+
args_dict: dict
|
|
89
|
+
Arguments dictionary.
|
|
90
|
+
|
|
91
|
+
Returns
|
|
92
|
+
-------
|
|
93
|
+
str
|
|
94
|
+
HT Condor submit script content.
|
|
95
|
+
"""
|
|
96
|
+
azimuth_angle_string = f"{args_dict['azimuth_angle'].to(u.deg).value}"
|
|
97
|
+
zenith_angle_string = f"{args_dict['zenith_angle'].to(u.deg).value}"
|
|
98
|
+
energy_range = args_dict["energy_range"]
|
|
99
|
+
energy_range_string = (
|
|
100
|
+
f'"{energy_range[0].to(u.GeV).value} GeV {energy_range[1].to(u.GeV).value} GeV"'
|
|
101
|
+
)
|
|
102
|
+
core_scatter = args_dict["core_scatter"]
|
|
103
|
+
core_scatter_string = f'"{core_scatter[0]} {core_scatter[1].to(u.m).value} m"'
|
|
104
|
+
|
|
105
|
+
label = args_dict["label"] if args_dict["label"] else "simulate-prod"
|
|
106
|
+
|
|
107
|
+
return f"""#!/usr/bin/env bash
|
|
108
|
+
|
|
109
|
+
# Process ID used to generate run number
|
|
110
|
+
process_id="$1"
|
|
111
|
+
# Load environment variables (for DB access)
|
|
112
|
+
set -a; source "$2"
|
|
113
|
+
|
|
114
|
+
simtools-simulate-prod \\
|
|
115
|
+
--simulation_software {args_dict["simulation_software"]} \\
|
|
116
|
+
--label {label} \\
|
|
117
|
+
--model_version {args_dict["model_version"]} \\
|
|
118
|
+
--site {args_dict["site"]} \\
|
|
119
|
+
--array_layout_name {args_dict["array_layout_name"]} \\
|
|
120
|
+
--primary {args_dict["primary"]} \\
|
|
121
|
+
--azimuth_angle {azimuth_angle_string} \\
|
|
122
|
+
--zenith_angle {zenith_angle_string} \\
|
|
123
|
+
--nshow {args_dict["nshow"]} \\
|
|
124
|
+
--energy_range {energy_range_string} \\
|
|
125
|
+
--core_scatter {core_scatter_string} \\
|
|
126
|
+
--run_number_start $((process_id + {args_dict["run_number_start"]})) \\
|
|
127
|
+
--number_of_runs 1 \\
|
|
128
|
+
--submit_engine \"local\" \\
|
|
129
|
+
--data_directory /tmp/simtools-data \\
|
|
130
|
+
--output_path /tmp/simtools-output \\
|
|
131
|
+
--log_level {args_dict["log_level"]} \\
|
|
132
|
+
--pack_for_grid_register simtools-output
|
|
133
|
+
"""
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Interface to workload managers like gridengine or HTCondor."""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
import
|
|
4
|
+
import subprocess
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
7
|
import simtools.utils.general as gen
|
|
@@ -65,11 +65,9 @@ class JobManager:
|
|
|
65
65
|
ValueError
|
|
66
66
|
if invalid submit engine.
|
|
67
67
|
"""
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
raise ValueError(f"Invalid submit command: {value}")
|
|
72
|
-
self._submit_engine = value
|
|
68
|
+
self._submit_engine = value or "local"
|
|
69
|
+
if self._submit_engine not in self.engines:
|
|
70
|
+
raise ValueError(f"Invalid submit command: {self._submit_engine}")
|
|
73
71
|
|
|
74
72
|
def check_submission_system(self):
|
|
75
73
|
"""
|
|
@@ -77,14 +75,17 @@ class JobManager:
|
|
|
77
75
|
|
|
78
76
|
Raises
|
|
79
77
|
------
|
|
80
|
-
|
|
78
|
+
JobExecutionError
|
|
81
79
|
if workflow manager is not found.
|
|
82
80
|
"""
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
81
|
+
try:
|
|
82
|
+
if self.submit_engine in (None, "local") or gen.program_is_executable(
|
|
83
|
+
self.engines[self.submit_engine]
|
|
84
|
+
):
|
|
85
|
+
return
|
|
86
|
+
except KeyError:
|
|
87
|
+
pass
|
|
88
|
+
raise JobExecutionError(f"Submit engine {self.submit_engine} not found")
|
|
88
89
|
|
|
89
90
|
def submit(self, run_script=None, run_out_file=None, log_file=None):
|
|
90
91
|
"""
|
|
@@ -109,12 +110,14 @@ class JobManager:
|
|
|
109
110
|
self._logger.info(f"Job error stream {self.run_out_file + '.err'}")
|
|
110
111
|
self._logger.info(f"Job log stream {self.run_out_file + '.job'}")
|
|
111
112
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
113
|
+
submit_result = 0
|
|
114
|
+
if self.submit_engine == "local":
|
|
115
|
+
submit_result = self._submit_local(log_file)
|
|
116
|
+
else:
|
|
117
|
+
submit_result = getattr(self, f"_submit_{self.submit_engine}")()
|
|
118
|
+
|
|
119
|
+
if submit_result != 0:
|
|
120
|
+
raise JobExecutionError(f"Job submission failed with return code {submit_result}")
|
|
118
121
|
|
|
119
122
|
def _submit_local(self, log_file):
|
|
120
123
|
"""
|
|
@@ -125,50 +128,72 @@ class JobManager:
|
|
|
125
128
|
log_file: str or Path
|
|
126
129
|
The log file of the actual simulator (CORSIKA or sim_telarray).
|
|
127
130
|
Provided in order to print the log excerpt in case of run time error.
|
|
131
|
+
|
|
132
|
+
Returns
|
|
133
|
+
-------
|
|
134
|
+
int
|
|
135
|
+
Return code of the executed script
|
|
128
136
|
"""
|
|
129
137
|
self._logger.info("Running script locally")
|
|
130
138
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if not self.test:
|
|
134
|
-
sys_output = os.system(shell_command)
|
|
135
|
-
if sys_output != 0:
|
|
136
|
-
msg = gen.get_log_excerpt(f"{self.run_out_file}.err")
|
|
137
|
-
self._logger.error(msg)
|
|
138
|
-
if log_file.exists() and gen.get_file_age(log_file) < 5:
|
|
139
|
-
msg = gen.get_log_excerpt(log_file)
|
|
140
|
-
self._logger.error(msg)
|
|
141
|
-
raise JobExecutionError("See excerpt from log file above\n")
|
|
142
|
-
else:
|
|
139
|
+
if self.test:
|
|
143
140
|
self._logger.info("Testing (local)")
|
|
141
|
+
return 0
|
|
142
|
+
|
|
143
|
+
result = None
|
|
144
|
+
try:
|
|
145
|
+
with (
|
|
146
|
+
open(f"{self.run_out_file}.out", "w", encoding="utf-8") as stdout,
|
|
147
|
+
open(f"{self.run_out_file}.err", "w", encoding="utf-8") as stderr,
|
|
148
|
+
):
|
|
149
|
+
result = subprocess.run(
|
|
150
|
+
f"{self.run_script}",
|
|
151
|
+
shell=True,
|
|
152
|
+
check=True,
|
|
153
|
+
text=True,
|
|
154
|
+
stdout=stdout,
|
|
155
|
+
stderr=stderr,
|
|
156
|
+
)
|
|
157
|
+
except subprocess.CalledProcessError as exc:
|
|
158
|
+
self._logger.error(gen.get_log_excerpt(f"{self.run_out_file}.err"))
|
|
159
|
+
if log_file.exists() and gen.get_file_age(log_file) < 5:
|
|
160
|
+
self._logger.error(gen.get_log_excerpt(log_file))
|
|
161
|
+
raise JobExecutionError("See excerpt from log file above\n") from exc
|
|
162
|
+
|
|
163
|
+
return result.returncode if result else 0
|
|
144
164
|
|
|
145
165
|
def _submit_htcondor(self):
|
|
146
166
|
"""Submit a job described by a shell script to HTcondor."""
|
|
147
167
|
_condor_file = self.run_script + ".condor"
|
|
168
|
+
lines = [
|
|
169
|
+
f"Executable = {self.run_script}",
|
|
170
|
+
f"Output = {self.run_out_file}.out",
|
|
171
|
+
f"Error = {self.run_out_file}.err",
|
|
172
|
+
f"Log = {self.run_out_file}.job",
|
|
173
|
+
]
|
|
174
|
+
if self.submit_options:
|
|
175
|
+
lines.extend(option.lstrip() for option in self.submit_options.split(","))
|
|
176
|
+
lines.append("queue 1")
|
|
148
177
|
try:
|
|
149
178
|
with open(_condor_file, "w", encoding="utf-8") as file:
|
|
150
|
-
file.write(
|
|
151
|
-
file.write(f"Output = {self.run_out_file + '.out'}\n")
|
|
152
|
-
file.write(f"Error = {self.run_out_file + '.err'}\n")
|
|
153
|
-
file.write(f"Log = {self.run_out_file + '.job'}\n")
|
|
154
|
-
if self.submit_options:
|
|
155
|
-
submit_option_list = self.submit_options.split(",")
|
|
156
|
-
for option in submit_option_list:
|
|
157
|
-
file.write(option.lstrip() + "\n")
|
|
158
|
-
file.write("queue 1\n")
|
|
179
|
+
file.write("\n".join(lines) + "\n")
|
|
159
180
|
except FileNotFoundError as exc:
|
|
160
181
|
self._logger.error(f"Failed creating condor submission file {_condor_file}")
|
|
161
182
|
raise JobExecutionError from exc
|
|
162
183
|
|
|
163
|
-
self._execute(self.submit_engine, self.engines[self.submit_engine]
|
|
184
|
+
return self._execute(self.submit_engine, [self.engines[self.submit_engine], _condor_file])
|
|
164
185
|
|
|
165
186
|
def _submit_gridengine(self):
|
|
166
187
|
"""Submit a job described by a shell script to gridengine."""
|
|
167
|
-
this_sub_cmd =
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
188
|
+
this_sub_cmd = [
|
|
189
|
+
self.engines[self.submit_engine],
|
|
190
|
+
"-o",
|
|
191
|
+
self.run_out_file + ".out",
|
|
192
|
+
"-e",
|
|
193
|
+
self.run_out_file + ".err",
|
|
194
|
+
self.run_script,
|
|
195
|
+
]
|
|
196
|
+
return self._execute(self.submit_engine, this_sub_cmd)
|
|
172
197
|
|
|
173
198
|
def _execute(self, engine, shell_command):
|
|
174
199
|
"""
|
|
@@ -178,13 +203,15 @@ class JobManager:
|
|
|
178
203
|
----------
|
|
179
204
|
engine : str
|
|
180
205
|
Engine to use.
|
|
181
|
-
shell_command :
|
|
182
|
-
|
|
206
|
+
shell_command : list
|
|
207
|
+
List of shell command plus arguments.
|
|
183
208
|
"""
|
|
184
209
|
self._logger.info(f"Submitting script to {engine}")
|
|
185
210
|
self._logger.debug(shell_command)
|
|
211
|
+
result = None
|
|
186
212
|
if not self.test:
|
|
187
|
-
|
|
213
|
+
result = subprocess.run(shell_command, shell=True, check=True)
|
|
188
214
|
else:
|
|
189
|
-
self._logger.info(f"Testing ({engine})")
|
|
190
|
-
|
|
215
|
+
self._logger.info(f"Testing ({engine}: {shell_command})")
|
|
216
|
+
|
|
217
|
+
return result.returncode if result else 0
|
simtools/model/camera.py
CHANGED
|
@@ -5,8 +5,6 @@ from pathlib import Path
|
|
|
5
5
|
|
|
6
6
|
import astropy.units as u
|
|
7
7
|
import numpy as np
|
|
8
|
-
from scipy.spatial import cKDTree as KDTree
|
|
9
|
-
from scipy.spatial import distance
|
|
10
8
|
|
|
11
9
|
from simtools.utils.geometry import rotate
|
|
12
10
|
|
|
@@ -304,6 +302,8 @@ class Camera:
|
|
|
304
302
|
float
|
|
305
303
|
The camera fill factor.
|
|
306
304
|
"""
|
|
305
|
+
from scipy.spatial import distance # pylint: disable=import-outside-toplevel
|
|
306
|
+
|
|
307
307
|
if self.pixels["pixel_spacing"] == 9999:
|
|
308
308
|
points = np.array([self.pixels["x"], self.pixels["y"]]).T
|
|
309
309
|
pixel_distances = distance.cdist(points, points, "euclidean")
|
|
@@ -403,6 +403,8 @@ class Camera:
|
|
|
403
403
|
list of lists
|
|
404
404
|
Array of neighbor indices in a list for each pixel
|
|
405
405
|
"""
|
|
406
|
+
from scipy.spatial import cKDTree as KDTree # pylint: disable=import-outside-toplevel
|
|
407
|
+
|
|
406
408
|
tree = KDTree(np.column_stack([x_pos, y_pos]))
|
|
407
409
|
neighbors = tree.query_ball_tree(tree, radius)
|
|
408
410
|
return [list(np.setdiff1d(neigh, [i])) for i, neigh in enumerate(neighbors)]
|
|
@@ -6,10 +6,12 @@ import shutil
|
|
|
6
6
|
from copy import copy
|
|
7
7
|
|
|
8
8
|
import astropy.units as u
|
|
9
|
+
from astropy.table import Table
|
|
9
10
|
|
|
10
11
|
import simtools.utils.general as gen
|
|
11
12
|
from simtools.db import db_handler
|
|
12
13
|
from simtools.io_operations import io_handler
|
|
14
|
+
from simtools.simtel import simtel_table_reader
|
|
13
15
|
from simtools.simtel.simtel_config_writer import SimtelConfigWriter
|
|
14
16
|
from simtools.utils import names
|
|
15
17
|
|
|
@@ -273,7 +275,7 @@ class ModelParameter:
|
|
|
273
275
|
|
|
274
276
|
def _set_config_file_directory_and_name(self):
|
|
275
277
|
"""Set and create the directory and the name of the config file."""
|
|
276
|
-
if self.name is None:
|
|
278
|
+
if self.name is None and self.site is None:
|
|
277
279
|
return
|
|
278
280
|
|
|
279
281
|
self._config_file_directory = self.io_handler.get_output_directory(
|
|
@@ -281,15 +283,14 @@ class ModelParameter:
|
|
|
281
283
|
)
|
|
282
284
|
|
|
283
285
|
# Setting file name and the location
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
self._config_file_path = self.config_file_directory.joinpath(config_file_name)
|
|
286
|
+
config_file_name = names.simtel_config_file_name(
|
|
287
|
+
self.site,
|
|
288
|
+
self.model_version,
|
|
289
|
+
telescope_model_name=self.name,
|
|
290
|
+
label=self.label,
|
|
291
|
+
extra_label=self._extra_label,
|
|
292
|
+
)
|
|
293
|
+
self._config_file_path = self.config_file_directory.joinpath(config_file_name)
|
|
293
294
|
|
|
294
295
|
self._logger.debug(f"Config file path: {self._config_file_path}")
|
|
295
296
|
|
|
@@ -515,6 +516,35 @@ class ModelParameter:
|
|
|
515
516
|
self.db.export_model_files(pars_from_db, self.config_file_directory)
|
|
516
517
|
self._is_exported_model_files_up_to_date = True
|
|
517
518
|
|
|
519
|
+
def get_model_file_as_table(self, par_name):
|
|
520
|
+
"""
|
|
521
|
+
Return tabular data from file as astropy table.
|
|
522
|
+
|
|
523
|
+
Parameters
|
|
524
|
+
----------
|
|
525
|
+
par_name: str
|
|
526
|
+
Name of the parameter.
|
|
527
|
+
|
|
528
|
+
Returns
|
|
529
|
+
-------
|
|
530
|
+
Table
|
|
531
|
+
Astropy table.
|
|
532
|
+
"""
|
|
533
|
+
_par_entry = {}
|
|
534
|
+
try:
|
|
535
|
+
_par_entry[par_name] = self._parameters[par_name]
|
|
536
|
+
except KeyError as exc:
|
|
537
|
+
raise ValueError(f"Parameter {par_name} not found in the model.") from exc
|
|
538
|
+
self.db.export_model_files(_par_entry, self.config_file_directory)
|
|
539
|
+
if _par_entry[par_name]["value"].endswith("ecsv"):
|
|
540
|
+
return Table.read(
|
|
541
|
+
self.config_file_directory.joinpath(_par_entry[par_name]["value"]),
|
|
542
|
+
format="ascii.ecsv",
|
|
543
|
+
)
|
|
544
|
+
return simtel_table_reader.read_simtel_table(
|
|
545
|
+
par_name, self.config_file_directory.joinpath(_par_entry[par_name]["value"])
|
|
546
|
+
)
|
|
547
|
+
|
|
518
548
|
def export_config_file(self):
|
|
519
549
|
"""Export the config file used by sim_telarray."""
|
|
520
550
|
# Exporting model file
|
simtools/model/site_model.py
CHANGED
|
@@ -80,7 +80,7 @@ class SiteModel(ModelParameter):
|
|
|
80
80
|
Site-related CORSIKA parameters as dict
|
|
81
81
|
"""
|
|
82
82
|
if config_file_style:
|
|
83
|
-
model_directory = model_directory or Path(
|
|
83
|
+
model_directory = model_directory or Path()
|
|
84
84
|
return {
|
|
85
85
|
"OBSLEV": [
|
|
86
86
|
self.get_parameter_value_with_unit("corsika_observation_level").to_value("cm")
|
|
@@ -138,43 +138,63 @@ class MirrorPanelPSF:
|
|
|
138
138
|
self.rnda_opt, save_figures=save_figures
|
|
139
139
|
)
|
|
140
140
|
|
|
141
|
-
def _optimize_reflection_angle(self, step_size=0.1):
|
|
142
|
-
"""
|
|
141
|
+
def _optimize_reflection_angle(self, step_size=0.1, max_iteration=100):
|
|
142
|
+
"""
|
|
143
|
+
Optimize the random reflection angle to minimize the difference in D80 containment.
|
|
144
|
+
|
|
145
|
+
Parameters
|
|
146
|
+
----------
|
|
147
|
+
step_size: float
|
|
148
|
+
Initial step size for optimization.
|
|
149
|
+
max_iteration: int
|
|
150
|
+
Maximum number of iterations.
|
|
151
|
+
|
|
152
|
+
Raises
|
|
153
|
+
------
|
|
154
|
+
ValueError
|
|
155
|
+
If the optimization reaches the maximum number of iterations without converging.
|
|
156
|
+
|
|
157
|
+
"""
|
|
158
|
+
relative_tolerance_d80 = self.args_dict["rtol_psf_containment"]
|
|
159
|
+
self._logger.info(
|
|
160
|
+
"Optimizing random reflection angle "
|
|
161
|
+
f"(relative tolerance = {relative_tolerance_d80}, "
|
|
162
|
+
f"step size = {step_size}, max iteration = {max_iteration})"
|
|
163
|
+
)
|
|
143
164
|
|
|
144
165
|
def collect_results(rnda, mean, sig):
|
|
145
166
|
self.results_rnda.append(rnda)
|
|
146
167
|
self.results_mean.append(mean)
|
|
147
168
|
self.results_sig.append(sig)
|
|
148
169
|
|
|
149
|
-
|
|
150
|
-
mean_d80, sig_d80 = self.run_simulations_and_analysis(self.rnda_start)
|
|
170
|
+
reference_d80 = self.args_dict["psf_measurement_containment_mean"]
|
|
151
171
|
rnda = self.rnda_start
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if rnda < 0:
|
|
157
|
-
rnda = 0
|
|
158
|
-
collect_results(rnda, mean_d80, sig_d80)
|
|
159
|
-
break
|
|
172
|
+
prev_error_d80 = float("inf")
|
|
173
|
+
iteration = 0
|
|
174
|
+
|
|
175
|
+
while True:
|
|
160
176
|
mean_d80, sig_d80 = self.run_simulations_and_analysis(rnda)
|
|
161
|
-
|
|
162
|
-
stop = new_sign_delta != sign_delta
|
|
163
|
-
sign_delta = new_sign_delta
|
|
177
|
+
error_d80 = abs(1 - mean_d80 / reference_d80)
|
|
164
178
|
collect_results(rnda, mean_d80, sig_d80)
|
|
165
179
|
|
|
166
|
-
|
|
180
|
+
if error_d80 < relative_tolerance_d80:
|
|
181
|
+
break
|
|
182
|
+
|
|
183
|
+
if mean_d80 < reference_d80:
|
|
184
|
+
rnda += step_size * self.rnda_start
|
|
185
|
+
else:
|
|
186
|
+
rnda -= step_size * self.rnda_start
|
|
167
187
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
188
|
+
if error_d80 >= prev_error_d80:
|
|
189
|
+
step_size = step_size / 2
|
|
190
|
+
prev_error_d80 = error_d80
|
|
191
|
+
iteration += 1
|
|
192
|
+
if iteration > max_iteration:
|
|
193
|
+
raise ValueError(
|
|
194
|
+
f"Maximum iterations ({max_iteration}) reached without convergence."
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
self.rnda_opt = rnda
|
|
178
198
|
|
|
179
199
|
def _get_starting_value(self):
|
|
180
200
|
"""Get optimization starting value from command line or previous model."""
|
|
@@ -275,6 +295,6 @@ class MirrorPanelPSF:
|
|
|
275
295
|
)
|
|
276
296
|
writer.ModelDataWriter.dump(
|
|
277
297
|
args_dict=self.args_dict,
|
|
278
|
-
metadata=MetadataCollector(args_dict=self.args_dict).
|
|
298
|
+
metadata=MetadataCollector(args_dict=self.args_dict).get_top_level_metadata(),
|
|
279
299
|
product_data=result_table,
|
|
280
300
|
)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Generate run scripts and directories for CORSIKA simulations."""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
import
|
|
4
|
+
import stat
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
7
|
from simtools.io_operations import io_handler
|
|
@@ -99,6 +99,15 @@ class CorsikaRunner:
|
|
|
99
99
|
file_type="config_tmp", run_number=self.corsika_config.run_number
|
|
100
100
|
)
|
|
101
101
|
corsika_input_tmp_file = self._directory["inputs"].joinpath(corsika_input_tmp_name)
|
|
102
|
+
# CORSIKA log file naming (temporary and final)
|
|
103
|
+
corsika_log_tmp_file = (
|
|
104
|
+
self._directory["data"]
|
|
105
|
+
.joinpath(f"run{self.corsika_config.run_number:06}")
|
|
106
|
+
.joinpath(f"run{self.corsika_config.run_number}.log")
|
|
107
|
+
)
|
|
108
|
+
corsika_log_file = self.get_file_name(
|
|
109
|
+
file_type="corsika_log", run_number=self.corsika_config.run_number
|
|
110
|
+
)
|
|
102
111
|
|
|
103
112
|
if use_pfp:
|
|
104
113
|
pfp_command = self._get_pfp_command(corsika_input_tmp_file, corsika_input_file)
|
|
@@ -138,11 +147,13 @@ class CorsikaRunner:
|
|
|
138
147
|
file.write(f"cp {corsika_input_file} {corsika_input_tmp_file}")
|
|
139
148
|
file.write("\n# Running corsika_autoinputs\n")
|
|
140
149
|
file.write(autoinputs_command)
|
|
150
|
+
file.write("\n# Moving log files to the corsika log directory\n")
|
|
151
|
+
file.write(f"gzip {corsika_log_tmp_file}\n")
|
|
152
|
+
file.write(f"mv -v {corsika_log_tmp_file}.gz {corsika_log_file}\n")
|
|
141
153
|
|
|
142
154
|
file.write('\necho "RUNTIME: $SECONDS"\n')
|
|
143
155
|
|
|
144
|
-
|
|
145
|
-
|
|
156
|
+
script_file_path.chmod(script_file_path.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP)
|
|
146
157
|
return script_file_path
|
|
147
158
|
|
|
148
159
|
def get_resources(self, run_number=None):
|
|
@@ -163,6 +163,7 @@ class RunnerServices:
|
|
|
163
163
|
log_suffixes = {
|
|
164
164
|
"log": ".log.gz",
|
|
165
165
|
"histogram": ".hdata.zst",
|
|
166
|
+
"corsika_log": ".corsika.log.gz",
|
|
166
167
|
}
|
|
167
168
|
return self.directory["logs"].joinpath(f"{file_name}{log_suffixes[file_type]}")
|
|
168
169
|
|
|
@@ -187,7 +188,6 @@ class RunnerServices:
|
|
|
187
188
|
data_suffixes = {
|
|
188
189
|
"output": ".zst",
|
|
189
190
|
"corsika_output": ".zst",
|
|
190
|
-
"corsika_log": ".log",
|
|
191
191
|
"simtel_output": ".simtel.zst",
|
|
192
192
|
}
|
|
193
193
|
run_dir = self._get_run_number_string(run_number)
|
|
@@ -245,10 +245,10 @@ class RunnerServices:
|
|
|
245
245
|
"""
|
|
246
246
|
file_name = self._get_file_basename(run_number)
|
|
247
247
|
|
|
248
|
-
if file_type in ["log", "histogram"]:
|
|
248
|
+
if file_type in ["log", "histogram", "corsika_log"]:
|
|
249
249
|
return self._get_log_file_path(file_type, file_name)
|
|
250
250
|
|
|
251
|
-
if file_type in ["output", "corsika_output", "
|
|
251
|
+
if file_type in ["output", "corsika_output", "simtel_output"]:
|
|
252
252
|
return self._get_data_file_path(file_type, file_name, run_number)
|
|
253
253
|
|
|
254
254
|
if file_type in ("sub_log", "sub_script"):
|