geant4-python-application 0.1.0__cp39-cp39-macosx_11_0_arm64.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.
- geant4_python_application/.dylibs/libG4FR.dylib +0 -0
- geant4_python_application/.dylibs/libG4GMocren.dylib +0 -0
- geant4_python_application/.dylibs/libG4OpenGL.dylib +0 -0
- geant4_python_application/.dylibs/libG4RayTracer.dylib +0 -0
- geant4_python_application/.dylibs/libG4ToolsSG.dylib +0 -0
- geant4_python_application/.dylibs/libG4Tree.dylib +0 -0
- geant4_python_application/.dylibs/libG4VRML.dylib +0 -0
- geant4_python_application/.dylibs/libG4analysis.dylib +0 -0
- geant4_python_application/.dylibs/libG4clhep.dylib +0 -0
- geant4_python_application/.dylibs/libG4digits_hits.dylib +0 -0
- geant4_python_application/.dylibs/libG4error_propagation.dylib +0 -0
- geant4_python_application/.dylibs/libG4event.dylib +0 -0
- geant4_python_application/.dylibs/libG4expat.dylib +0 -0
- geant4_python_application/.dylibs/libG4gdml.dylib +0 -0
- geant4_python_application/.dylibs/libG4geometry.dylib +0 -0
- geant4_python_application/.dylibs/libG4geomtext.dylib +0 -0
- geant4_python_application/.dylibs/libG4global.dylib +0 -0
- geant4_python_application/.dylibs/libG4graphics_reps.dylib +0 -0
- geant4_python_application/.dylibs/libG4intercoms.dylib +0 -0
- geant4_python_application/.dylibs/libG4interfaces.dylib +0 -0
- geant4_python_application/.dylibs/libG4materials.dylib +0 -0
- geant4_python_application/.dylibs/libG4mctruth.dylib +0 -0
- geant4_python_application/.dylibs/libG4modeling.dylib +0 -0
- geant4_python_application/.dylibs/libG4parmodels.dylib +0 -0
- geant4_python_application/.dylibs/libG4particles.dylib +0 -0
- geant4_python_application/.dylibs/libG4physicslists.dylib +0 -0
- geant4_python_application/.dylibs/libG4processes.dylib +0 -0
- geant4_python_application/.dylibs/libG4ptl.2.3.3.dylib +0 -0
- geant4_python_application/.dylibs/libG4readout.dylib +0 -0
- geant4_python_application/.dylibs/libG4run.dylib +0 -0
- geant4_python_application/.dylibs/libG4track.dylib +0 -0
- geant4_python_application/.dylibs/libG4tracking.dylib +0 -0
- geant4_python_application/.dylibs/libG4visHepRep.dylib +0 -0
- geant4_python_application/.dylibs/libG4vis_management.dylib +0 -0
- geant4_python_application/__init__.py +49 -0
- geant4_python_application/_geant4_application.cpython-39-darwin.so +0 -0
- geant4_python_application/_geant4_vis.cpython-39-darwin.so +0 -0
- geant4_python_application/_qt_from_pyqt5_qt5 +1 -0
- geant4_python_application/_version.py +24 -0
- geant4_python_application/application.py +442 -0
- geant4_python_application/cad.py +124 -0
- geant4_python_application/detector.py +97 -0
- geant4_python_application/distributed.py +45 -0
- geant4_python_application/events.py +87 -0
- geant4_python_application/files/__init__.py +1 -0
- geant4_python_application/files/datasets.py +297 -0
- geant4_python_application/files/directories.py +40 -0
- geant4_python_application/gdml.py +39 -0
- geant4_python_application/generator.py +63 -0
- geant4_python_application/geometry.py +52 -0
- geant4_python_application/io.py +154 -0
- geant4_python_application/optical.py +199 -0
- geant4_python_application/scoring.py +118 -0
- geant4_python_application-0.1.0.dist-info/METADATA +220 -0
- geant4_python_application-0.1.0.dist-info/RECORD +66 -0
- geant4_python_application-0.1.0.dist-info/WHEEL +5 -0
- geant4_python_application-0.1.0.dist-info/licenses/LICENSE +28 -0
- geant4_python_application-0.1.0.dist-info/licenses/NOTICE +21 -0
- geant4_python_application-0.1.0.dist-info/licenses/licenses/CLHEP-COPYING +674 -0
- geant4_python_application-0.1.0.dist-info/licenses/licenses/CLHEP-COPYING.LESSER +165 -0
- geant4_python_application-0.1.0.dist-info/licenses/licenses/Expat-COPYING +22 -0
- geant4_python_application-0.1.0.dist-info/licenses/licenses/Geant4-LICENSE +73 -0
- geant4_python_application-0.1.0.dist-info/licenses/licenses/PTL-LICENSE +21 -0
- geant4_python_application-0.1.0.dist-info/licenses/licenses/Xerces-C-LICENSE +202 -0
- geant4_python_application-0.1.0.dist-info/licenses/licenses/Xerces-C-NOTICE +10 -0
- geant4_python_application-0.1.0.dist-info/licenses/licenses/zlib-LICENSE +22 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from geant4_python_application import cad as cad
|
|
4
|
+
from geant4_python_application import distributed as distributed
|
|
5
|
+
from geant4_python_application import geometry as geometry
|
|
6
|
+
from geant4_python_application import io as io
|
|
7
|
+
from geant4_python_application import optical as optical
|
|
8
|
+
from geant4_python_application._geant4_application import (
|
|
9
|
+
awkward_version,
|
|
10
|
+
geant4_version,
|
|
11
|
+
pybind11_version,
|
|
12
|
+
)
|
|
13
|
+
from geant4_python_application._version import version, version_tuple
|
|
14
|
+
from geant4_python_application.application import Application
|
|
15
|
+
from geant4_python_application.detector import Detector
|
|
16
|
+
from geant4_python_application.files.datasets import (
|
|
17
|
+
application_data_directory as data_directory,
|
|
18
|
+
)
|
|
19
|
+
from geant4_python_application.files.datasets import install_datasets
|
|
20
|
+
from geant4_python_application.files.directories import application_directory
|
|
21
|
+
from geant4_python_application.gdml import basic_gdml
|
|
22
|
+
from geant4_python_application.generator import Generator
|
|
23
|
+
from geant4_python_application.scoring import NativeScoringMesh, Scoring
|
|
24
|
+
|
|
25
|
+
__version__ = version
|
|
26
|
+
__version__tuple__ = version_tuple
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"Application",
|
|
30
|
+
"Detector",
|
|
31
|
+
"Generator",
|
|
32
|
+
"NativeScoringMesh",
|
|
33
|
+
"Scoring",
|
|
34
|
+
"__version__",
|
|
35
|
+
"__version__tuple__",
|
|
36
|
+
"application_directory",
|
|
37
|
+
"awkward_version",
|
|
38
|
+
"basic_gdml",
|
|
39
|
+
"cad",
|
|
40
|
+
"data_directory",
|
|
41
|
+
"distributed",
|
|
42
|
+
"geant4_version",
|
|
43
|
+
"geometry",
|
|
44
|
+
"install_datasets",
|
|
45
|
+
"io",
|
|
46
|
+
"optical",
|
|
47
|
+
"pybind11_version",
|
|
48
|
+
"version",
|
|
49
|
+
]
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
PyQt5-Qt5
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '0.1.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 0)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = 'ga2353b5b4'
|
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import importlib.util
|
|
4
|
+
import multiprocessing
|
|
5
|
+
import os
|
|
6
|
+
import threading
|
|
7
|
+
from collections import namedtuple
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import awkward as ak
|
|
11
|
+
|
|
12
|
+
import geant4_python_application
|
|
13
|
+
import geant4_python_application.events
|
|
14
|
+
from geant4_python_application._geant4_application import (
|
|
15
|
+
Application as Geant4Application,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
Message = namedtuple("Message", ["target", "method", "args", "kwargs"])
|
|
19
|
+
|
|
20
|
+
_default_event_fields = {
|
|
21
|
+
"run",
|
|
22
|
+
"id",
|
|
23
|
+
"primaries",
|
|
24
|
+
"track_id",
|
|
25
|
+
"track_parent_id",
|
|
26
|
+
"track_initial_energy",
|
|
27
|
+
"track_initial_time",
|
|
28
|
+
"track_initial_position",
|
|
29
|
+
"track_particle",
|
|
30
|
+
"track_creator_process",
|
|
31
|
+
"step_energy",
|
|
32
|
+
"step_time",
|
|
33
|
+
"step_process",
|
|
34
|
+
"step_volume",
|
|
35
|
+
"step_position",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
_package_dir = Path(__file__).parent
|
|
40
|
+
# Written into wheels whose Qt-linked libraries were repointed at the Qt shipped
|
|
41
|
+
# in the PyQt5-Qt5 package (see tools/relink_qt_wheel.py).
|
|
42
|
+
_PIP_QT_MARKER = _package_dir / "_qt_from_pyqt5_qt5"
|
|
43
|
+
_PIP_QT_DIR = _package_dir.parent / "PyQt5" / "Qt5"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _visualization_problem() -> str | None:
|
|
47
|
+
"""Why the Qt viewer can't be opened, or None if it can. Never loads Qt."""
|
|
48
|
+
if importlib.util.find_spec("geant4_python_application._geant4_vis") is None:
|
|
49
|
+
return (
|
|
50
|
+
"This installation was built without the Qt viewer. Reinstall from "
|
|
51
|
+
"source with -Ccmake.define.GEANT4_PYTHON_APPLICATION_VISUALIZATION=ON "
|
|
52
|
+
"using a Geant4 built with Qt and OpenGL."
|
|
53
|
+
)
|
|
54
|
+
if _PIP_QT_MARKER.exists() and not (_PIP_QT_DIR / "lib").is_dir():
|
|
55
|
+
return (
|
|
56
|
+
"The Qt viewer needs Qt from the 'gui' extra: "
|
|
57
|
+
"pip install 'geant4_python_application[gui]'"
|
|
58
|
+
)
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _visualize(app, commands: list[str]) -> None:
|
|
63
|
+
"""Runs in the Geant4 child process, which then owns the Qt event loop."""
|
|
64
|
+
problem = _visualization_problem()
|
|
65
|
+
if problem is not None:
|
|
66
|
+
raise RuntimeError(problem)
|
|
67
|
+
# A platform plugin only loads into the exact Qt build it was compiled for,
|
|
68
|
+
# so point Qt at the plugins shipped alongside the Qt libraries in use.
|
|
69
|
+
if _PIP_QT_MARKER.exists():
|
|
70
|
+
os.environ["QT_PLUGIN_PATH"] = str(_PIP_QT_DIR / "plugins")
|
|
71
|
+
from geant4_python_application import _geant4_vis
|
|
72
|
+
|
|
73
|
+
if not app.is_initialized():
|
|
74
|
+
app.initialize()
|
|
75
|
+
_geant4_vis.start_visualization(commands)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _start_application(pipe: multiprocessing.Pipe):
|
|
79
|
+
app = Geant4Application()
|
|
80
|
+
app.set_event_fields(_default_event_fields)
|
|
81
|
+
while True:
|
|
82
|
+
counter = None
|
|
83
|
+
try:
|
|
84
|
+
message_with_counter = pipe.recv()
|
|
85
|
+
if message_with_counter is None:
|
|
86
|
+
break
|
|
87
|
+
|
|
88
|
+
counter, message = message_with_counter
|
|
89
|
+
if message.target == "" and message.method == "visualize":
|
|
90
|
+
# Handled here so the Qt viewer module is only ever imported in
|
|
91
|
+
# this process, and only when a viewer is actually requested.
|
|
92
|
+
_visualize(app, *message.args, **message.kwargs)
|
|
93
|
+
pipe.send((counter, None))
|
|
94
|
+
continue
|
|
95
|
+
target = app
|
|
96
|
+
target_list = message.target.split(".")
|
|
97
|
+
target_list = [element for element in target_list if element]
|
|
98
|
+
for target_name in target_list:
|
|
99
|
+
if not hasattr(target, target_name):
|
|
100
|
+
raise ValueError(f"{target} has no attribute {target_name}")
|
|
101
|
+
target = getattr(target, target_name)
|
|
102
|
+
|
|
103
|
+
method = message.method
|
|
104
|
+
# verify that the method exists on app
|
|
105
|
+
if not hasattr(target, method):
|
|
106
|
+
raise ValueError(f"Unknown method: {method}")
|
|
107
|
+
|
|
108
|
+
result = getattr(target, method)(*message.args, **message.kwargs)
|
|
109
|
+
pipe.send((counter, result))
|
|
110
|
+
except KeyboardInterrupt:
|
|
111
|
+
pass
|
|
112
|
+
except EOFError or BrokenPipeError:
|
|
113
|
+
break
|
|
114
|
+
except Exception as e:
|
|
115
|
+
if counter is not None:
|
|
116
|
+
pipe.send((counter, e))
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class Application:
|
|
120
|
+
def __init__(
|
|
121
|
+
self,
|
|
122
|
+
n_threads: int = 0,
|
|
123
|
+
gdml: str = None,
|
|
124
|
+
physics: str = "custom",
|
|
125
|
+
seed: int = 0,
|
|
126
|
+
*,
|
|
127
|
+
optical: bool = False,
|
|
128
|
+
):
|
|
129
|
+
geant4_python_application.install_datasets(show_progress=True)
|
|
130
|
+
|
|
131
|
+
self._pipe, child_pipe = multiprocessing.Pipe()
|
|
132
|
+
self._process = multiprocessing.Process(
|
|
133
|
+
target=_start_application, args=(child_pipe,), daemon=True
|
|
134
|
+
)
|
|
135
|
+
self._message_counter = 0
|
|
136
|
+
self._lock = threading.Lock()
|
|
137
|
+
|
|
138
|
+
self._seed = seed
|
|
139
|
+
self._detector = geant4_python_application.Detector(self)
|
|
140
|
+
self._generator = geant4_python_application.Generator(self)
|
|
141
|
+
self._n_threads = n_threads
|
|
142
|
+
self._gdml = gdml
|
|
143
|
+
self._physics = physics
|
|
144
|
+
self._optical = optical
|
|
145
|
+
|
|
146
|
+
def start(self, setup: bool = True, initialize: bool = False) -> Application:
|
|
147
|
+
if self._process.is_alive():
|
|
148
|
+
raise RuntimeError("Application is already running")
|
|
149
|
+
self._process.start()
|
|
150
|
+
|
|
151
|
+
if setup:
|
|
152
|
+
self.seed = self._seed
|
|
153
|
+
self.setup_manager(self._n_threads)
|
|
154
|
+
self.setup_physics(self._physics, optical=self._optical)
|
|
155
|
+
|
|
156
|
+
if self._gdml is not None:
|
|
157
|
+
self.setup_detector(self._gdml)
|
|
158
|
+
self.setup_action()
|
|
159
|
+
|
|
160
|
+
if initialize:
|
|
161
|
+
if not setup:
|
|
162
|
+
raise ValueError("Cannot initialize without setup")
|
|
163
|
+
self.initialize()
|
|
164
|
+
|
|
165
|
+
return self
|
|
166
|
+
|
|
167
|
+
def stop(self):
|
|
168
|
+
if not self._process.is_alive():
|
|
169
|
+
return
|
|
170
|
+
try:
|
|
171
|
+
self._pipe.send(None)
|
|
172
|
+
self._pipe.close()
|
|
173
|
+
self._process.join()
|
|
174
|
+
except (EOFError, BrokenPipeError):
|
|
175
|
+
pass
|
|
176
|
+
|
|
177
|
+
def __enter__(self) -> Application:
|
|
178
|
+
return self.start()
|
|
179
|
+
|
|
180
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
181
|
+
self.stop()
|
|
182
|
+
|
|
183
|
+
def _send(self, counter: int, message: Message):
|
|
184
|
+
self._pipe.send((counter, message))
|
|
185
|
+
|
|
186
|
+
def _recv(self):
|
|
187
|
+
return self._pipe.recv()
|
|
188
|
+
|
|
189
|
+
def _send_and_recv(self, message: Message):
|
|
190
|
+
if self._process is None or not self._process.is_alive():
|
|
191
|
+
raise RuntimeError("Application is not running")
|
|
192
|
+
with self._lock:
|
|
193
|
+
try:
|
|
194
|
+
self._send(self._message_counter, message)
|
|
195
|
+
counter, response = self._recv()
|
|
196
|
+
if counter != self._message_counter:
|
|
197
|
+
raise RuntimeError("Message counter mismatch")
|
|
198
|
+
self._message_counter += 1
|
|
199
|
+
except (EOFError, BrokenPipeError):
|
|
200
|
+
raise RuntimeError("Application process died. Recreate the application")
|
|
201
|
+
if isinstance(response, Exception):
|
|
202
|
+
raise response
|
|
203
|
+
return response
|
|
204
|
+
|
|
205
|
+
def setup_manager(self, n_threads: int = 0) -> Application:
|
|
206
|
+
self._send_and_recv(Message("", "setup_manager", (n_threads,), {}))
|
|
207
|
+
return self
|
|
208
|
+
|
|
209
|
+
def setup_physics(
|
|
210
|
+
self, physics_list: str = "custom", *, optical: bool = False
|
|
211
|
+
) -> Application:
|
|
212
|
+
self._send_and_recv(Message("", "setup_physics", (physics_list, optical), {}))
|
|
213
|
+
return self
|
|
214
|
+
|
|
215
|
+
@staticmethod
|
|
216
|
+
def available_physics_lists() -> list[str]:
|
|
217
|
+
return list(Geant4Application.available_physics_lists())
|
|
218
|
+
|
|
219
|
+
@staticmethod
|
|
220
|
+
def available_extra_physics() -> list[str]:
|
|
221
|
+
"""Extra G4VPhysicsConstructor names usable with add_physics (incl. DNA)."""
|
|
222
|
+
return list(Geant4Application.available_extra_physics())
|
|
223
|
+
|
|
224
|
+
def add_physics(self, constructor_name: str) -> Application:
|
|
225
|
+
"""Register an extra physics constructor (e.g. DNA, optical, EM extra).
|
|
226
|
+
|
|
227
|
+
Must be called after setup_physics and before initialize/run.
|
|
228
|
+
Example: app.add_physics("G4EmDNAPhysics_option2")
|
|
229
|
+
"""
|
|
230
|
+
self._send_and_recv(Message("", "add_physics", (constructor_name,), {}))
|
|
231
|
+
return self
|
|
232
|
+
|
|
233
|
+
def setup_detector(self, gdml: str) -> Application:
|
|
234
|
+
self._send_and_recv(Message("", "setup_detector", (gdml,), {}))
|
|
235
|
+
return self
|
|
236
|
+
|
|
237
|
+
def setup_action(self) -> Application:
|
|
238
|
+
self._send_and_recv(Message("", "setup_action", (), {}))
|
|
239
|
+
return self
|
|
240
|
+
|
|
241
|
+
def set_event_fields(self, fields: set[str]) -> Application:
|
|
242
|
+
self._send_and_recv(Message("", "set_event_fields", (fields,), {}))
|
|
243
|
+
return self
|
|
244
|
+
|
|
245
|
+
def get_event_fields(self) -> set[str]:
|
|
246
|
+
return self._send_and_recv(Message("", "get_event_fields", (), {}))
|
|
247
|
+
|
|
248
|
+
def get_event_fields_complete(self) -> set[str]:
|
|
249
|
+
return self._send_and_recv(Message("", "get_event_fields_complete", (), {}))
|
|
250
|
+
|
|
251
|
+
def initialize(self) -> Application:
|
|
252
|
+
self._send_and_recv(Message("", "initialize", (), {}))
|
|
253
|
+
return self
|
|
254
|
+
|
|
255
|
+
def run(self, primaries: int | ak.Array):
|
|
256
|
+
# "run" returns a list of arrays, one for each thread
|
|
257
|
+
events = self._send_and_recv(Message("", "run", (primaries,), {}))
|
|
258
|
+
concatenated_dict = {
|
|
259
|
+
key: ak.concatenate([d[key] for d in events], axis=0)
|
|
260
|
+
for key in events[0].keys()
|
|
261
|
+
}
|
|
262
|
+
# make sure they all have the same length
|
|
263
|
+
for key in concatenated_dict:
|
|
264
|
+
if len(concatenated_dict[key]) != len(
|
|
265
|
+
concatenated_dict[list(concatenated_dict.keys())[0]]
|
|
266
|
+
):
|
|
267
|
+
raise ValueError(f"Length mismatch for key {key}")
|
|
268
|
+
|
|
269
|
+
keys_to_remove = set()
|
|
270
|
+
step_array_dict = {}
|
|
271
|
+
for key in concatenated_dict:
|
|
272
|
+
prefix = "step_"
|
|
273
|
+
if key.startswith(prefix):
|
|
274
|
+
new_key = key[len(prefix) :]
|
|
275
|
+
step_array_dict[new_key] = concatenated_dict[key]
|
|
276
|
+
keys_to_remove.add(key)
|
|
277
|
+
|
|
278
|
+
track_array_dict = {}
|
|
279
|
+
for key in concatenated_dict:
|
|
280
|
+
prefix = "track_"
|
|
281
|
+
if key.startswith(prefix):
|
|
282
|
+
new_key = key[len(prefix) :]
|
|
283
|
+
track_array_dict[new_key] = concatenated_dict[key]
|
|
284
|
+
keys_to_remove.add(key)
|
|
285
|
+
|
|
286
|
+
events = ak.Array(
|
|
287
|
+
{
|
|
288
|
+
**{
|
|
289
|
+
key: concatenated_dict[key]
|
|
290
|
+
for key in [
|
|
291
|
+
key for key in concatenated_dict if key not in keys_to_remove
|
|
292
|
+
]
|
|
293
|
+
},
|
|
294
|
+
**(
|
|
295
|
+
{
|
|
296
|
+
"track": ak.Array(
|
|
297
|
+
{
|
|
298
|
+
**{
|
|
299
|
+
key: track_array_dict[key]
|
|
300
|
+
for key in track_array_dict
|
|
301
|
+
},
|
|
302
|
+
**(
|
|
303
|
+
{
|
|
304
|
+
"step": ak.Array(
|
|
305
|
+
{
|
|
306
|
+
**{
|
|
307
|
+
key: step_array_dict[key]
|
|
308
|
+
for key in step_array_dict
|
|
309
|
+
}
|
|
310
|
+
},
|
|
311
|
+
with_name="Geant4Step",
|
|
312
|
+
)
|
|
313
|
+
}
|
|
314
|
+
if len(step_array_dict) > 0
|
|
315
|
+
else {}
|
|
316
|
+
),
|
|
317
|
+
},
|
|
318
|
+
with_name="Geant4Track",
|
|
319
|
+
)
|
|
320
|
+
}
|
|
321
|
+
if len(track_array_dict) > 0 or len(step_array_dict) > 0
|
|
322
|
+
else {}
|
|
323
|
+
),
|
|
324
|
+
},
|
|
325
|
+
with_name="Geant4Event",
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
# events = ak.str.to_categorical(events)
|
|
329
|
+
if "id" in events.fields:
|
|
330
|
+
events = events[ak.argsort(events.id)]
|
|
331
|
+
return events
|
|
332
|
+
|
|
333
|
+
def run_with_callbacks(
|
|
334
|
+
self,
|
|
335
|
+
primaries: int | ak.Array,
|
|
336
|
+
*,
|
|
337
|
+
on_run=None,
|
|
338
|
+
on_event=None,
|
|
339
|
+
on_track=None,
|
|
340
|
+
on_step=None,
|
|
341
|
+
):
|
|
342
|
+
"""Run then invoke Python callbacks over run/event/track/step records.
|
|
343
|
+
|
|
344
|
+
Offline (post-run) layer over the awkward event model:
|
|
345
|
+
on_run(events), on_event(event) per event, on_track(track, event)
|
|
346
|
+
per track, on_step(step, track, event) per step. Returns events.
|
|
347
|
+
|
|
348
|
+
Note: events.track is a struct-of-lists per event (see DataModel),
|
|
349
|
+
so tracks/steps are reconstructed via ak.zip per event/track.
|
|
350
|
+
"""
|
|
351
|
+
events = self.run(primaries)
|
|
352
|
+
if on_run is not None:
|
|
353
|
+
on_run(events)
|
|
354
|
+
if on_event is None and on_track is None and on_step is None:
|
|
355
|
+
return events
|
|
356
|
+
for ei in range(len(events)):
|
|
357
|
+
event = events[ei]
|
|
358
|
+
if on_event is not None:
|
|
359
|
+
on_event(event)
|
|
360
|
+
if on_track is None and on_step is None:
|
|
361
|
+
continue
|
|
362
|
+
if "track" not in event.fields:
|
|
363
|
+
continue
|
|
364
|
+
track_struct = event.track
|
|
365
|
+
if "id" not in track_struct.fields:
|
|
366
|
+
continue
|
|
367
|
+
n_tracks = len(track_struct.id)
|
|
368
|
+
track_only_fields = [f for f in track_struct.fields if f != "step"]
|
|
369
|
+
has_steps = "step" in track_struct.fields
|
|
370
|
+
for ti in range(n_tracks):
|
|
371
|
+
# Lightweight dict view (scalars/vectors); steps as awkward array.
|
|
372
|
+
track_view = {f: track_struct[f][ti] for f in track_only_fields}
|
|
373
|
+
steps_view = None
|
|
374
|
+
if has_steps:
|
|
375
|
+
step_struct = track_struct.step
|
|
376
|
+
steps_view = ak.zip(
|
|
377
|
+
{f: step_struct[f][ti] for f in step_struct.fields}
|
|
378
|
+
)
|
|
379
|
+
track_view = dict(track_view)
|
|
380
|
+
track_view["step"] = steps_view
|
|
381
|
+
if on_track is not None:
|
|
382
|
+
on_track(track_view, event)
|
|
383
|
+
if on_step is None or steps_view is None:
|
|
384
|
+
continue
|
|
385
|
+
for step in steps_view:
|
|
386
|
+
on_step(step, track_view, event)
|
|
387
|
+
return events
|
|
388
|
+
|
|
389
|
+
@property
|
|
390
|
+
def seed(self):
|
|
391
|
+
return self._send_and_recv(Message("", "get_seed", (), {}))
|
|
392
|
+
|
|
393
|
+
@seed.setter
|
|
394
|
+
def seed(self, seed: int):
|
|
395
|
+
self._send_and_recv(Message("", "set_seed", (seed,), {}))
|
|
396
|
+
|
|
397
|
+
def command(self, command: str) -> Application:
|
|
398
|
+
self._send_and_recv(Message("", "command", (command,), {}))
|
|
399
|
+
return self
|
|
400
|
+
|
|
401
|
+
def commands(self, commands: list[str]) -> Application:
|
|
402
|
+
for command in commands:
|
|
403
|
+
self.command(command)
|
|
404
|
+
return self
|
|
405
|
+
|
|
406
|
+
def list_commands(self, directory="/") -> str:
|
|
407
|
+
return self._send_and_recv(Message("", "list_commands", (directory,), {}))
|
|
408
|
+
|
|
409
|
+
@staticmethod
|
|
410
|
+
def visualization_available() -> bool:
|
|
411
|
+
"""Whether the Qt viewer can be opened. Does not load Qt."""
|
|
412
|
+
return _visualization_problem() is None
|
|
413
|
+
|
|
414
|
+
@staticmethod
|
|
415
|
+
def multithreading_available() -> bool:
|
|
416
|
+
"""Whether the linked Geant4 runtime supports multithreaded run managers."""
|
|
417
|
+
return Geant4Application.multithreading_available()
|
|
418
|
+
|
|
419
|
+
def visualize(self, commands: list[str] | None = None) -> Application:
|
|
420
|
+
"""Open the interactive Geant4 Qt viewer.
|
|
421
|
+
|
|
422
|
+
The call returns after the viewer is closed. Geant4 commands can be
|
|
423
|
+
entered in the Qt session's command panel while it is open.
|
|
424
|
+
"""
|
|
425
|
+
if commands is None:
|
|
426
|
+
commands = [
|
|
427
|
+
"/vis/open OGL",
|
|
428
|
+
"/vis/drawVolume",
|
|
429
|
+
"/vis/viewer/set/autoRefresh true",
|
|
430
|
+
"/vis/scene/add/trajectories smooth",
|
|
431
|
+
"/vis/scene/endOfEventAction accumulate",
|
|
432
|
+
]
|
|
433
|
+
self._send_and_recv(Message("", "visualize", (commands,), {}))
|
|
434
|
+
return self
|
|
435
|
+
|
|
436
|
+
@property
|
|
437
|
+
def detector(self) -> geant4_python_application.Detector:
|
|
438
|
+
return self._detector
|
|
439
|
+
|
|
440
|
+
@property
|
|
441
|
+
def generator(self) -> geant4_python_application.Generator:
|
|
442
|
+
return self._generator
|