geant4-python-application 0.1.0__cp311-cp311-macosx_11_0_x86_64.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.
Files changed (66) hide show
  1. geant4_python_application/.dylibs/libG4FR.dylib +0 -0
  2. geant4_python_application/.dylibs/libG4GMocren.dylib +0 -0
  3. geant4_python_application/.dylibs/libG4OpenGL.dylib +0 -0
  4. geant4_python_application/.dylibs/libG4RayTracer.dylib +0 -0
  5. geant4_python_application/.dylibs/libG4ToolsSG.dylib +0 -0
  6. geant4_python_application/.dylibs/libG4Tree.dylib +0 -0
  7. geant4_python_application/.dylibs/libG4VRML.dylib +0 -0
  8. geant4_python_application/.dylibs/libG4analysis.dylib +0 -0
  9. geant4_python_application/.dylibs/libG4clhep.dylib +0 -0
  10. geant4_python_application/.dylibs/libG4digits_hits.dylib +0 -0
  11. geant4_python_application/.dylibs/libG4error_propagation.dylib +0 -0
  12. geant4_python_application/.dylibs/libG4event.dylib +0 -0
  13. geant4_python_application/.dylibs/libG4expat.dylib +0 -0
  14. geant4_python_application/.dylibs/libG4gdml.dylib +0 -0
  15. geant4_python_application/.dylibs/libG4geometry.dylib +0 -0
  16. geant4_python_application/.dylibs/libG4geomtext.dylib +0 -0
  17. geant4_python_application/.dylibs/libG4global.dylib +0 -0
  18. geant4_python_application/.dylibs/libG4graphics_reps.dylib +0 -0
  19. geant4_python_application/.dylibs/libG4intercoms.dylib +0 -0
  20. geant4_python_application/.dylibs/libG4interfaces.dylib +0 -0
  21. geant4_python_application/.dylibs/libG4materials.dylib +0 -0
  22. geant4_python_application/.dylibs/libG4mctruth.dylib +0 -0
  23. geant4_python_application/.dylibs/libG4modeling.dylib +0 -0
  24. geant4_python_application/.dylibs/libG4parmodels.dylib +0 -0
  25. geant4_python_application/.dylibs/libG4particles.dylib +0 -0
  26. geant4_python_application/.dylibs/libG4physicslists.dylib +0 -0
  27. geant4_python_application/.dylibs/libG4processes.dylib +0 -0
  28. geant4_python_application/.dylibs/libG4ptl.2.3.3.dylib +0 -0
  29. geant4_python_application/.dylibs/libG4readout.dylib +0 -0
  30. geant4_python_application/.dylibs/libG4run.dylib +0 -0
  31. geant4_python_application/.dylibs/libG4track.dylib +0 -0
  32. geant4_python_application/.dylibs/libG4tracking.dylib +0 -0
  33. geant4_python_application/.dylibs/libG4visHepRep.dylib +0 -0
  34. geant4_python_application/.dylibs/libG4vis_management.dylib +0 -0
  35. geant4_python_application/__init__.py +49 -0
  36. geant4_python_application/_geant4_application.cpython-311-darwin.so +0 -0
  37. geant4_python_application/_geant4_vis.cpython-311-darwin.so +0 -0
  38. geant4_python_application/_qt_from_pyqt5_qt5 +1 -0
  39. geant4_python_application/_version.py +24 -0
  40. geant4_python_application/application.py +442 -0
  41. geant4_python_application/cad.py +124 -0
  42. geant4_python_application/detector.py +97 -0
  43. geant4_python_application/distributed.py +45 -0
  44. geant4_python_application/events.py +87 -0
  45. geant4_python_application/files/__init__.py +1 -0
  46. geant4_python_application/files/datasets.py +297 -0
  47. geant4_python_application/files/directories.py +40 -0
  48. geant4_python_application/gdml.py +39 -0
  49. geant4_python_application/generator.py +63 -0
  50. geant4_python_application/geometry.py +52 -0
  51. geant4_python_application/io.py +154 -0
  52. geant4_python_application/optical.py +199 -0
  53. geant4_python_application/scoring.py +118 -0
  54. geant4_python_application-0.1.0.dist-info/METADATA +220 -0
  55. geant4_python_application-0.1.0.dist-info/RECORD +66 -0
  56. geant4_python_application-0.1.0.dist-info/WHEEL +5 -0
  57. geant4_python_application-0.1.0.dist-info/licenses/LICENSE +28 -0
  58. geant4_python_application-0.1.0.dist-info/licenses/NOTICE +21 -0
  59. geant4_python_application-0.1.0.dist-info/licenses/licenses/CLHEP-COPYING +674 -0
  60. geant4_python_application-0.1.0.dist-info/licenses/licenses/CLHEP-COPYING.LESSER +165 -0
  61. geant4_python_application-0.1.0.dist-info/licenses/licenses/Expat-COPYING +22 -0
  62. geant4_python_application-0.1.0.dist-info/licenses/licenses/Geant4-LICENSE +73 -0
  63. geant4_python_application-0.1.0.dist-info/licenses/licenses/PTL-LICENSE +21 -0
  64. geant4_python_application-0.1.0.dist-info/licenses/licenses/Xerces-C-LICENSE +202 -0
  65. geant4_python_application-0.1.0.dist-info/licenses/licenses/Xerces-C-NOTICE +10 -0
  66. geant4_python_application-0.1.0.dist-info/licenses/licenses/zlib-LICENSE +22 -0
@@ -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
+ ]
@@ -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