lsurf 1.0.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.
- lsurf/__init__.py +471 -0
- lsurf/analysis/__init__.py +107 -0
- lsurf/analysis/healpix_utils.py +418 -0
- lsurf/analysis/sphere_viz.py +1280 -0
- lsurf/cli/__init__.py +48 -0
- lsurf/cli/build.py +398 -0
- lsurf/cli/config_schema.py +318 -0
- lsurf/cli/gui_cmd.py +76 -0
- lsurf/cli/interactive.py +850 -0
- lsurf/cli/main.py +81 -0
- lsurf/cli/run.py +806 -0
- lsurf/detectors/__init__.py +266 -0
- lsurf/detectors/analysis.py +289 -0
- lsurf/detectors/base.py +284 -0
- lsurf/detectors/constant_size_rings.py +485 -0
- lsurf/detectors/directional.py +45 -0
- lsurf/detectors/extended/__init__.py +73 -0
- lsurf/detectors/extended/local_sphere.py +353 -0
- lsurf/detectors/extended/recording_sphere.py +368 -0
- lsurf/detectors/planar.py +45 -0
- lsurf/detectors/protocol.py +187 -0
- lsurf/detectors/recording_spheres.py +63 -0
- lsurf/detectors/results.py +1140 -0
- lsurf/detectors/small/__init__.py +79 -0
- lsurf/detectors/small/directional.py +330 -0
- lsurf/detectors/small/planar.py +401 -0
- lsurf/detectors/small/spherical.py +450 -0
- lsurf/detectors/spherical.py +45 -0
- lsurf/geometry/__init__.py +199 -0
- lsurf/geometry/builder.py +478 -0
- lsurf/geometry/cell.py +228 -0
- lsurf/geometry/cell_geometry.py +247 -0
- lsurf/geometry/detector_arrays.py +1785 -0
- lsurf/geometry/geometry.py +222 -0
- lsurf/geometry/surface_analysis.py +375 -0
- lsurf/geometry/validation.py +91 -0
- lsurf/gui/__init__.py +51 -0
- lsurf/gui/app.py +903 -0
- lsurf/gui/core/__init__.py +39 -0
- lsurf/gui/core/scene.py +343 -0
- lsurf/gui/core/simulation.py +264 -0
- lsurf/gui/renderers/__init__.py +40 -0
- lsurf/gui/renderers/ray_renderer.py +353 -0
- lsurf/gui/renderers/source_renderer.py +505 -0
- lsurf/gui/renderers/surface_renderer.py +477 -0
- lsurf/gui/views/__init__.py +48 -0
- lsurf/gui/views/config_editor.py +3199 -0
- lsurf/gui/views/properties.py +257 -0
- lsurf/gui/views/results.py +291 -0
- lsurf/gui/views/scene_tree.py +180 -0
- lsurf/gui/views/viewport_3d.py +555 -0
- lsurf/gui/views/visualizations.py +712 -0
- lsurf/materials/__init__.py +169 -0
- lsurf/materials/base/__init__.py +64 -0
- lsurf/materials/base/full_inhomogeneous.py +208 -0
- lsurf/materials/base/grid_inhomogeneous.py +319 -0
- lsurf/materials/base/homogeneous.py +342 -0
- lsurf/materials/base/material_field.py +527 -0
- lsurf/materials/base/simple_inhomogeneous.py +418 -0
- lsurf/materials/base/spectral_inhomogeneous.py +497 -0
- lsurf/materials/implementations/__init__.py +120 -0
- lsurf/materials/implementations/data/alpha_values_typical_atmosphere_updated.txt +24 -0
- lsurf/materials/implementations/duct_atmosphere.py +390 -0
- lsurf/materials/implementations/exponential_atmosphere.py +435 -0
- lsurf/materials/implementations/gaussian_lens.py +120 -0
- lsurf/materials/implementations/interpolated_data.py +123 -0
- lsurf/materials/implementations/layered_atmosphere.py +134 -0
- lsurf/materials/implementations/linear_gradient.py +109 -0
- lsurf/materials/implementations/linsley_atmosphere.py +764 -0
- lsurf/materials/implementations/standard_materials.py +126 -0
- lsurf/materials/implementations/turbulent_atmosphere.py +135 -0
- lsurf/materials/implementations/us_standard_atmosphere.py +149 -0
- lsurf/materials/utils/__init__.py +77 -0
- lsurf/materials/utils/constants.py +45 -0
- lsurf/materials/utils/device_functions.py +117 -0
- lsurf/materials/utils/dispersion.py +160 -0
- lsurf/materials/utils/factories.py +142 -0
- lsurf/propagation/__init__.py +91 -0
- lsurf/propagation/detector_gpu.py +67 -0
- lsurf/propagation/gpu_device_rays.py +294 -0
- lsurf/propagation/kernels/__init__.py +175 -0
- lsurf/propagation/kernels/absorption/__init__.py +61 -0
- lsurf/propagation/kernels/absorption/grid.py +240 -0
- lsurf/propagation/kernels/absorption/simple.py +232 -0
- lsurf/propagation/kernels/absorption/spectral.py +410 -0
- lsurf/propagation/kernels/detection/__init__.py +64 -0
- lsurf/propagation/kernels/detection/protocol.py +102 -0
- lsurf/propagation/kernels/detection/spherical.py +255 -0
- lsurf/propagation/kernels/device_functions.py +790 -0
- lsurf/propagation/kernels/fresnel/__init__.py +64 -0
- lsurf/propagation/kernels/fresnel/protocol.py +97 -0
- lsurf/propagation/kernels/fresnel/standard.py +258 -0
- lsurf/propagation/kernels/intersection/__init__.py +79 -0
- lsurf/propagation/kernels/intersection/annular_plane.py +207 -0
- lsurf/propagation/kernels/intersection/bounded_plane.py +205 -0
- lsurf/propagation/kernels/intersection/plane.py +166 -0
- lsurf/propagation/kernels/intersection/protocol.py +95 -0
- lsurf/propagation/kernels/intersection/signed_distance.py +742 -0
- lsurf/propagation/kernels/intersection/sphere.py +190 -0
- lsurf/propagation/kernels/propagation/__init__.py +85 -0
- lsurf/propagation/kernels/propagation/grid.py +527 -0
- lsurf/propagation/kernels/propagation/protocol.py +105 -0
- lsurf/propagation/kernels/propagation/simple.py +460 -0
- lsurf/propagation/kernels/propagation/spectral.py +875 -0
- lsurf/propagation/kernels/registry.py +331 -0
- lsurf/propagation/kernels/surface/__init__.py +72 -0
- lsurf/propagation/kernels/surface/bisection.py +232 -0
- lsurf/propagation/kernels/surface/detection.py +402 -0
- lsurf/propagation/kernels/surface/reduction.py +166 -0
- lsurf/propagation/propagator_protocol.py +222 -0
- lsurf/propagation/propagators/__init__.py +101 -0
- lsurf/propagation/propagators/detector_handler.py +354 -0
- lsurf/propagation/propagators/factory.py +200 -0
- lsurf/propagation/propagators/fresnel_handler.py +305 -0
- lsurf/propagation/propagators/gpu_gradient.py +566 -0
- lsurf/propagation/propagators/gpu_surface_propagator.py +707 -0
- lsurf/propagation/propagators/gradient.py +429 -0
- lsurf/propagation/propagators/intersection_handler.py +327 -0
- lsurf/propagation/propagators/material_propagator.py +398 -0
- lsurf/propagation/propagators/signed_distance_handler.py +522 -0
- lsurf/propagation/propagators/spectral_gpu_gradient.py +553 -0
- lsurf/propagation/propagators/surface_interaction.py +616 -0
- lsurf/propagation/propagators/surface_propagator.py +719 -0
- lsurf/py.typed +1 -0
- lsurf/simulation/__init__.py +70 -0
- lsurf/simulation/config.py +164 -0
- lsurf/simulation/orchestrator.py +462 -0
- lsurf/simulation/result.py +299 -0
- lsurf/simulation/simulation.py +262 -0
- lsurf/sources/__init__.py +128 -0
- lsurf/sources/base.py +264 -0
- lsurf/sources/collimated.py +252 -0
- lsurf/sources/custom.py +409 -0
- lsurf/sources/diverging.py +228 -0
- lsurf/sources/gaussian.py +272 -0
- lsurf/sources/parallel_from_positions.py +197 -0
- lsurf/sources/point.py +172 -0
- lsurf/sources/uniform_diverging.py +258 -0
- lsurf/surfaces/__init__.py +184 -0
- lsurf/surfaces/cpu/__init__.py +50 -0
- lsurf/surfaces/cpu/curved_wave.py +463 -0
- lsurf/surfaces/cpu/gerstner_wave.py +381 -0
- lsurf/surfaces/cpu/wave_params.py +118 -0
- lsurf/surfaces/gpu/__init__.py +72 -0
- lsurf/surfaces/gpu/annular_plane.py +453 -0
- lsurf/surfaces/gpu/bounded_plane.py +390 -0
- lsurf/surfaces/gpu/curved_wave.py +483 -0
- lsurf/surfaces/gpu/gerstner_wave.py +377 -0
- lsurf/surfaces/gpu/multi_curved_wave.py +520 -0
- lsurf/surfaces/gpu/plane.py +299 -0
- lsurf/surfaces/gpu/recording_sphere.py +587 -0
- lsurf/surfaces/gpu/sphere.py +311 -0
- lsurf/surfaces/protocol.py +336 -0
- lsurf/surfaces/registry.py +373 -0
- lsurf/utilities/__init__.py +175 -0
- lsurf/utilities/detector_analysis.py +814 -0
- lsurf/utilities/fresnel.py +628 -0
- lsurf/utilities/interactions.py +1215 -0
- lsurf/utilities/propagation.py +602 -0
- lsurf/utilities/ray_data.py +532 -0
- lsurf/utilities/recording_sphere.py +745 -0
- lsurf/utilities/time_spread.py +463 -0
- lsurf/visualization/__init__.py +329 -0
- lsurf/visualization/absorption_plots.py +334 -0
- lsurf/visualization/atmospheric_plots.py +754 -0
- lsurf/visualization/common.py +348 -0
- lsurf/visualization/detector_plots.py +1350 -0
- lsurf/visualization/detector_sphere_plots.py +1173 -0
- lsurf/visualization/fresnel_plots.py +1061 -0
- lsurf/visualization/ocean_simulation_plots.py +999 -0
- lsurf/visualization/polarization_plots.py +916 -0
- lsurf/visualization/raytracing_plots.py +1521 -0
- lsurf/visualization/ring_detector_plots.py +1867 -0
- lsurf/visualization/time_spread_plots.py +531 -0
- lsurf-1.0.0.dist-info/METADATA +381 -0
- lsurf-1.0.0.dist-info/RECORD +180 -0
- lsurf-1.0.0.dist-info/WHEEL +5 -0
- lsurf-1.0.0.dist-info/entry_points.txt +2 -0
- lsurf-1.0.0.dist-info/licenses/LICENSE +32 -0
- lsurf-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
# The Clear BSD License
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2026 Tobias Heibges
|
|
4
|
+
# All rights reserved.
|
|
5
|
+
#
|
|
6
|
+
# Redistribution and use in source and binary forms, with or without
|
|
7
|
+
# modification, are permitted (subject to the limitations in the disclaimer
|
|
8
|
+
# below) provided that the following conditions are met:
|
|
9
|
+
#
|
|
10
|
+
# * Redistributions of source code must retain the above copyright notice,
|
|
11
|
+
# this list of conditions and the following disclaimer.
|
|
12
|
+
#
|
|
13
|
+
# * Redistributions in binary form must reproduce the above copyright
|
|
14
|
+
# notice, this list of conditions and the following disclaimer in the
|
|
15
|
+
# documentation and/or other materials provided with the distribution.
|
|
16
|
+
#
|
|
17
|
+
# * Neither the name of the copyright holder nor the names of its
|
|
18
|
+
# contributors may be used to endorse or promote products derived from this
|
|
19
|
+
# software without specific prior written permission.
|
|
20
|
+
#
|
|
21
|
+
# NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY
|
|
22
|
+
# THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
|
23
|
+
# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
24
|
+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
|
25
|
+
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
|
26
|
+
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
27
|
+
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
28
|
+
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
|
29
|
+
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
|
|
30
|
+
# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
31
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
32
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
|
33
|
+
|
|
34
|
+
"""
|
|
35
|
+
Kernel and Propagator Registry
|
|
36
|
+
|
|
37
|
+
Central registry for all kernel types in the ray tracing system:
|
|
38
|
+
- PropagationKernelID: Material propagation kernels
|
|
39
|
+
- IntersectionKernelID: Surface intersection kernels
|
|
40
|
+
- DetectionKernelID: Ray detection kernels
|
|
41
|
+
- FresnelKernelID: Fresnel reflection/refraction kernels
|
|
42
|
+
- PropagatorID: Propagator implementations
|
|
43
|
+
|
|
44
|
+
This module provides:
|
|
45
|
+
- Kernel ID enums for each category
|
|
46
|
+
- Registration decorator and lookup functions
|
|
47
|
+
- Unified registry for all kernel types
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
from enum import Enum, auto
|
|
51
|
+
from typing import Callable
|
|
52
|
+
|
|
53
|
+
__all__ = [
|
|
54
|
+
# Enums
|
|
55
|
+
"PropagationKernelID",
|
|
56
|
+
"IntersectionKernelID",
|
|
57
|
+
"DetectionKernelID",
|
|
58
|
+
"FresnelKernelID",
|
|
59
|
+
"PropagatorID",
|
|
60
|
+
"KernelID",
|
|
61
|
+
# Registration
|
|
62
|
+
"register_kernel",
|
|
63
|
+
"get_kernel",
|
|
64
|
+
"get_registered_kernels",
|
|
65
|
+
"list_kernels",
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# =============================================================================
|
|
70
|
+
# Kernel Identifier Enums
|
|
71
|
+
# =============================================================================
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class PropagationKernelID(Enum):
|
|
75
|
+
"""
|
|
76
|
+
Identifiers for material propagation kernels.
|
|
77
|
+
|
|
78
|
+
Each kernel corresponds to a specific integration method and material type.
|
|
79
|
+
Materials declare which kernels they support, and propagators use these
|
|
80
|
+
IDs to select the appropriate CUDA kernel.
|
|
81
|
+
|
|
82
|
+
Naming convention: {MATERIAL_TYPE}_{INTEGRATION_METHOD}
|
|
83
|
+
|
|
84
|
+
Examples
|
|
85
|
+
--------
|
|
86
|
+
>>> from lsurf.propagation.kernels import PropagationKernelID
|
|
87
|
+
>>> PropagationKernelID.SIMPLE_RK4
|
|
88
|
+
<PropagationKernelID.SIMPLE_RK4: ...>
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
# Simple inhomogeneous (1D LUT - altitude only)
|
|
92
|
+
SIMPLE_EULER = auto()
|
|
93
|
+
SIMPLE_RK4 = auto()
|
|
94
|
+
|
|
95
|
+
# Spectral inhomogeneous (2D LUT - altitude × wavelength)
|
|
96
|
+
SPECTRAL_EULER = auto()
|
|
97
|
+
SPECTRAL_RK4 = auto()
|
|
98
|
+
SPECTRAL_EULER_PERRAY = auto()
|
|
99
|
+
SPECTRAL_RK4_PERRAY = auto()
|
|
100
|
+
|
|
101
|
+
# Grid inhomogeneous (3D grid with trilinear interpolation)
|
|
102
|
+
GRID_EULER = auto()
|
|
103
|
+
GRID_RK4 = auto()
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class IntersectionKernelID(Enum):
|
|
107
|
+
"""
|
|
108
|
+
Identifiers for surface intersection kernels.
|
|
109
|
+
|
|
110
|
+
Each kernel corresponds to a specific geometry type and intersection method.
|
|
111
|
+
Surfaces declare which kernels they support for ray-surface intersection.
|
|
112
|
+
|
|
113
|
+
Note: Complex surfaces (waves, curved surfaces) use the generic signed
|
|
114
|
+
distance kernels which dispatch to device functions based on geometry_id.
|
|
115
|
+
|
|
116
|
+
Naming convention: {GEOMETRY_TYPE}_{METHOD}
|
|
117
|
+
|
|
118
|
+
Examples
|
|
119
|
+
--------
|
|
120
|
+
>>> from lsurf.propagation.kernels import IntersectionKernelID
|
|
121
|
+
>>> IntersectionKernelID.PLANE_ANALYTICAL
|
|
122
|
+
<IntersectionKernelID.PLANE_ANALYTICAL: ...>
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
# Analytical (GPU-capable, fast) - specific geometry kernels
|
|
126
|
+
PLANE_ANALYTICAL = auto() # Ray-plane intersection
|
|
127
|
+
SPHERE_ANALYTICAL = auto() # Ray-sphere intersection
|
|
128
|
+
BOUNDED_PLANE_ANALYTICAL = auto() # Ray-bounded-plane intersection
|
|
129
|
+
ANNULAR_PLANE_ANALYTICAL = auto() # Ray-annular-plane intersection
|
|
130
|
+
|
|
131
|
+
# Generic dispatch kernels (GPU-capable, all geometry types)
|
|
132
|
+
SIGNED_DISTANCE_GENERIC = auto() # Signed distance for any surface via geometry_id
|
|
133
|
+
SURFACE_NORMAL_GENERIC = auto() # Surface normal for any surface via geometry_id
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class DetectionKernelID(Enum):
|
|
137
|
+
"""
|
|
138
|
+
Identifiers for ray detection kernels.
|
|
139
|
+
|
|
140
|
+
Each kernel corresponds to a specific detector geometry.
|
|
141
|
+
|
|
142
|
+
Naming convention: {GEOMETRY_TYPE}_{VARIANT}
|
|
143
|
+
|
|
144
|
+
Examples
|
|
145
|
+
--------
|
|
146
|
+
>>> from lsurf.propagation.kernels import DetectionKernelID
|
|
147
|
+
>>> DetectionKernelID.SPHERICAL_SINGLE
|
|
148
|
+
<DetectionKernelID.SPHERICAL_SINGLE: ...>
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
SPHERICAL_SINGLE = auto() # Single spherical detector
|
|
152
|
+
SPHERICAL_MULTI = auto() # Multiple spherical detectors (scan mode)
|
|
153
|
+
PLANAR_SINGLE = auto() # Single planar detector
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class FresnelKernelID(Enum):
|
|
157
|
+
"""
|
|
158
|
+
Identifiers for Fresnel reflection/refraction kernels.
|
|
159
|
+
|
|
160
|
+
Each kernel implements a specific approach to computing Fresnel coefficients.
|
|
161
|
+
|
|
162
|
+
Examples
|
|
163
|
+
--------
|
|
164
|
+
>>> from lsurf.propagation.kernels import FresnelKernelID
|
|
165
|
+
>>> FresnelKernelID.STANDARD
|
|
166
|
+
<FresnelKernelID.STANDARD: ...>
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
STANDARD = auto() # Standard Fresnel equations (unpolarized)
|
|
170
|
+
POLARIZED = auto() # Polarization-aware Fresnel equations
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class PropagatorID(Enum):
|
|
174
|
+
"""
|
|
175
|
+
Identifiers for available propagator implementations.
|
|
176
|
+
|
|
177
|
+
Each propagator ID corresponds to a specific propagator class that handles
|
|
178
|
+
ray propagation through materials.
|
|
179
|
+
|
|
180
|
+
Examples
|
|
181
|
+
--------
|
|
182
|
+
>>> from lsurf.propagation.kernels import PropagatorID
|
|
183
|
+
>>> PropagatorID.GPU_GRADIENT
|
|
184
|
+
<PropagatorID.GPU_GRADIENT: ...>
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
CPU_GRADIENT = auto() # GradientPropagator (CPU, any material)
|
|
188
|
+
GPU_GRADIENT = auto() # GPUGradientPropagator (GPU, scalar wavelength)
|
|
189
|
+
GPU_SPECTRAL = auto() # SpectralGPUGradientPropagator (GPU, per-ray wavelength)
|
|
190
|
+
GPU_SURFACE = auto() # SurfacePropagator (GPU, with intersection detection)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
# =============================================================================
|
|
194
|
+
# Type Aliases
|
|
195
|
+
# =============================================================================
|
|
196
|
+
|
|
197
|
+
# Union type for any kernel ID
|
|
198
|
+
KernelID = (
|
|
199
|
+
PropagationKernelID | IntersectionKernelID | DetectionKernelID | FresnelKernelID
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
# =============================================================================
|
|
204
|
+
# Kernel Registration System
|
|
205
|
+
# =============================================================================
|
|
206
|
+
|
|
207
|
+
# Global registry mapping kernel ID → kernel function
|
|
208
|
+
_KERNEL_REGISTRY: dict[KernelID, Callable] = {}
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def register_kernel(kernel_id: KernelID):
|
|
212
|
+
"""
|
|
213
|
+
Decorator to register a kernel function with the global registry.
|
|
214
|
+
|
|
215
|
+
Parameters
|
|
216
|
+
----------
|
|
217
|
+
kernel_id : KernelID
|
|
218
|
+
The unique identifier for this kernel. Can be any kernel ID type:
|
|
219
|
+
PropagationKernelID, IntersectionKernelID, DetectionKernelID,
|
|
220
|
+
or FresnelKernelID.
|
|
221
|
+
|
|
222
|
+
Returns
|
|
223
|
+
-------
|
|
224
|
+
decorator
|
|
225
|
+
A decorator that registers the function and returns it unchanged.
|
|
226
|
+
|
|
227
|
+
Examples
|
|
228
|
+
--------
|
|
229
|
+
>>> @register_kernel(PropagationKernelID.SIMPLE_EULER)
|
|
230
|
+
... def my_euler_kernel(positions, directions, ...):
|
|
231
|
+
... ...
|
|
232
|
+
|
|
233
|
+
>>> @register_kernel(DetectionKernelID.SPHERICAL_SINGLE)
|
|
234
|
+
... def my_detection_kernel(positions, directions, ...):
|
|
235
|
+
... ...
|
|
236
|
+
|
|
237
|
+
Notes
|
|
238
|
+
-----
|
|
239
|
+
Registration happens at import time. The registered function can be
|
|
240
|
+
retrieved later using get_kernel().
|
|
241
|
+
"""
|
|
242
|
+
|
|
243
|
+
def decorator(func: Callable) -> Callable:
|
|
244
|
+
if kernel_id in _KERNEL_REGISTRY:
|
|
245
|
+
existing = _KERNEL_REGISTRY[kernel_id]
|
|
246
|
+
raise ValueError(
|
|
247
|
+
f"Kernel {kernel_id} already registered by {existing.__name__}. "
|
|
248
|
+
f"Cannot register {func.__name__}."
|
|
249
|
+
)
|
|
250
|
+
_KERNEL_REGISTRY[kernel_id] = func
|
|
251
|
+
return func
|
|
252
|
+
|
|
253
|
+
return decorator
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def get_kernel(kernel_id: KernelID) -> Callable:
|
|
257
|
+
"""
|
|
258
|
+
Retrieve a registered kernel function by ID.
|
|
259
|
+
|
|
260
|
+
Parameters
|
|
261
|
+
----------
|
|
262
|
+
kernel_id : KernelID
|
|
263
|
+
The identifier of the kernel to retrieve. Can be any kernel ID type.
|
|
264
|
+
|
|
265
|
+
Returns
|
|
266
|
+
-------
|
|
267
|
+
Callable
|
|
268
|
+
The registered kernel function.
|
|
269
|
+
|
|
270
|
+
Raises
|
|
271
|
+
------
|
|
272
|
+
KeyError
|
|
273
|
+
If the kernel ID is not registered.
|
|
274
|
+
|
|
275
|
+
Examples
|
|
276
|
+
--------
|
|
277
|
+
>>> kernel = get_kernel(PropagationKernelID.SIMPLE_RK4)
|
|
278
|
+
>>> kernel(positions, directions, ...)
|
|
279
|
+
|
|
280
|
+
>>> detect = get_kernel(DetectionKernelID.SPHERICAL_SINGLE)
|
|
281
|
+
>>> detect(positions, directions, ...)
|
|
282
|
+
"""
|
|
283
|
+
if kernel_id not in _KERNEL_REGISTRY:
|
|
284
|
+
registered = list(_KERNEL_REGISTRY.keys())
|
|
285
|
+
raise KeyError(
|
|
286
|
+
f"Kernel {kernel_id} not registered. " f"Available kernels: {registered}"
|
|
287
|
+
)
|
|
288
|
+
return _KERNEL_REGISTRY[kernel_id]
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def get_registered_kernels() -> dict[KernelID, Callable]:
|
|
292
|
+
"""
|
|
293
|
+
Return a copy of all registered kernels.
|
|
294
|
+
|
|
295
|
+
Returns
|
|
296
|
+
-------
|
|
297
|
+
dict
|
|
298
|
+
Dictionary mapping kernel IDs to kernel functions.
|
|
299
|
+
"""
|
|
300
|
+
return _KERNEL_REGISTRY.copy()
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def list_kernels(kernel_type: type[Enum] | None = None) -> list[KernelID]:
|
|
304
|
+
"""
|
|
305
|
+
List all registered kernels, optionally filtered by type.
|
|
306
|
+
|
|
307
|
+
Parameters
|
|
308
|
+
----------
|
|
309
|
+
kernel_type : type[Enum], optional
|
|
310
|
+
If provided, only return kernels of this enum type.
|
|
311
|
+
For example, PropagationKernelID to list only propagation kernels.
|
|
312
|
+
|
|
313
|
+
Returns
|
|
314
|
+
-------
|
|
315
|
+
list[KernelID]
|
|
316
|
+
List of registered kernel IDs.
|
|
317
|
+
|
|
318
|
+
Examples
|
|
319
|
+
--------
|
|
320
|
+
>>> # List all registered kernels
|
|
321
|
+
>>> all_kernels = list_kernels()
|
|
322
|
+
|
|
323
|
+
>>> # List only propagation kernels
|
|
324
|
+
>>> prop_kernels = list_kernels(PropagationKernelID)
|
|
325
|
+
|
|
326
|
+
>>> # List only detection kernels
|
|
327
|
+
>>> detect_kernels = list_kernels(DetectionKernelID)
|
|
328
|
+
"""
|
|
329
|
+
if kernel_type is None:
|
|
330
|
+
return list(_KERNEL_REGISTRY.keys())
|
|
331
|
+
return [k for k in _KERNEL_REGISTRY.keys() if isinstance(k, kernel_type)]
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# The Clear BSD License
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2026 Tobias Heibges
|
|
4
|
+
# All rights reserved.
|
|
5
|
+
#
|
|
6
|
+
# Redistribution and use in source and binary forms, with or without
|
|
7
|
+
# modification, are permitted (subject to the limitations in the disclaimer
|
|
8
|
+
# below) provided that the following conditions are met:
|
|
9
|
+
#
|
|
10
|
+
# * Redistributions of source code must retain the above copyright notice,
|
|
11
|
+
# this list of conditions and the following disclaimer.
|
|
12
|
+
#
|
|
13
|
+
# * Redistributions in binary form must reproduce the above copyright
|
|
14
|
+
# notice, this list of conditions and the following disclaimer in the
|
|
15
|
+
# documentation and/or other materials provided with the distribution.
|
|
16
|
+
#
|
|
17
|
+
# * Neither the name of the copyright holder nor the names of its
|
|
18
|
+
# contributors may be used to endorse or promote products derived from this
|
|
19
|
+
# software without specific prior written permission.
|
|
20
|
+
#
|
|
21
|
+
# NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY
|
|
22
|
+
# THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
|
23
|
+
# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
24
|
+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
|
25
|
+
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
|
26
|
+
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
27
|
+
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
28
|
+
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
|
29
|
+
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
|
|
30
|
+
# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
31
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
32
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
|
33
|
+
|
|
34
|
+
"""
|
|
35
|
+
Surface Detection Kernels
|
|
36
|
+
|
|
37
|
+
GPU kernels for surface intersection detection, bisection refinement,
|
|
38
|
+
and status reduction. These kernels work with GPUDeviceRays to enable
|
|
39
|
+
GPU-resident surface propagation.
|
|
40
|
+
|
|
41
|
+
Kernels
|
|
42
|
+
-------
|
|
43
|
+
kernel_save_prev_positions
|
|
44
|
+
Save current positions before propagation step (for bisection)
|
|
45
|
+
kernel_detect_crossing
|
|
46
|
+
Check all surfaces for sign changes in signed distance
|
|
47
|
+
kernel_bisect_crossing
|
|
48
|
+
Bisection refinement for rays that crossed a surface
|
|
49
|
+
kernel_reduce_status
|
|
50
|
+
Count active rays and check for crossings
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
from .detection import (
|
|
54
|
+
kernel_save_prev_positions,
|
|
55
|
+
kernel_detect_crossing,
|
|
56
|
+
kernel_init_signed_distances,
|
|
57
|
+
kernel_compute_min_surface_distance,
|
|
58
|
+
kernel_compute_adaptive_steps,
|
|
59
|
+
)
|
|
60
|
+
from .bisection import kernel_bisect_crossing
|
|
61
|
+
from .reduction import kernel_reduce_status, kernel_reduce_status_shared
|
|
62
|
+
|
|
63
|
+
__all__ = [
|
|
64
|
+
"kernel_save_prev_positions",
|
|
65
|
+
"kernel_detect_crossing",
|
|
66
|
+
"kernel_init_signed_distances",
|
|
67
|
+
"kernel_compute_min_surface_distance",
|
|
68
|
+
"kernel_compute_adaptive_steps",
|
|
69
|
+
"kernel_bisect_crossing",
|
|
70
|
+
"kernel_reduce_status",
|
|
71
|
+
"kernel_reduce_status_shared",
|
|
72
|
+
]
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# The Clear BSD License
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2026 Tobias Heibges
|
|
4
|
+
# All rights reserved.
|
|
5
|
+
#
|
|
6
|
+
# Redistribution and use in source and binary forms, with or without
|
|
7
|
+
# modification, are permitted (subject to the limitations in the disclaimer
|
|
8
|
+
# below) provided that the following conditions are met:
|
|
9
|
+
#
|
|
10
|
+
# * Redistributions of source code must retain the above copyright notice,
|
|
11
|
+
# this list of conditions and the following disclaimer.
|
|
12
|
+
#
|
|
13
|
+
# * Redistributions in binary form must reproduce the above copyright
|
|
14
|
+
# notice, this list of conditions and the following disclaimer in the
|
|
15
|
+
# documentation and/or other materials provided with the distribution.
|
|
16
|
+
#
|
|
17
|
+
# * Neither the name of the copyright holder nor the names of its
|
|
18
|
+
# contributors may be used to endorse or promote products derived from this
|
|
19
|
+
# software without specific prior written permission.
|
|
20
|
+
#
|
|
21
|
+
# NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY
|
|
22
|
+
# THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
|
23
|
+
# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
24
|
+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
|
25
|
+
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
|
26
|
+
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
27
|
+
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
28
|
+
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
|
29
|
+
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
|
|
30
|
+
# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
31
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
32
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
|
33
|
+
|
|
34
|
+
"""
|
|
35
|
+
Surface Bisection Refinement Kernel
|
|
36
|
+
|
|
37
|
+
GPU kernel for refining intersection points using bisection method
|
|
38
|
+
after a crossing has been detected.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
import math
|
|
42
|
+
|
|
43
|
+
# GPU support is optional
|
|
44
|
+
try:
|
|
45
|
+
from numba import cuda
|
|
46
|
+
|
|
47
|
+
HAS_CUDA = cuda.is_available()
|
|
48
|
+
except ImportError:
|
|
49
|
+
HAS_CUDA = False
|
|
50
|
+
|
|
51
|
+
class _FakeCuda:
|
|
52
|
+
@staticmethod
|
|
53
|
+
def jit(*args, **kwargs):
|
|
54
|
+
def decorator(func):
|
|
55
|
+
return func
|
|
56
|
+
|
|
57
|
+
if args and callable(args[0]):
|
|
58
|
+
return args[0]
|
|
59
|
+
return decorator
|
|
60
|
+
|
|
61
|
+
@staticmethod
|
|
62
|
+
def grid(n):
|
|
63
|
+
return 0
|
|
64
|
+
|
|
65
|
+
cuda = _FakeCuda() # type: ignore[assignment]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# Import device functions for signed distance computation
|
|
69
|
+
from ..intersection.signed_distance import (
|
|
70
|
+
_device_plane_sd,
|
|
71
|
+
_device_sphere_sd,
|
|
72
|
+
_device_gerstner_sd,
|
|
73
|
+
_device_curved_wave_sd,
|
|
74
|
+
_device_multi_curved_wave_sd,
|
|
75
|
+
_device_annular_plane_sd,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@cuda.jit(device=True)
|
|
80
|
+
def _device_signed_distance_bisect(
|
|
81
|
+
x: float, y: float, z: float, geometry_id: int, params, param_offset: int
|
|
82
|
+
) -> float:
|
|
83
|
+
"""
|
|
84
|
+
Compute signed distance for bisection.
|
|
85
|
+
|
|
86
|
+
Same as _device_signed_distance but duplicated to avoid circular import.
|
|
87
|
+
"""
|
|
88
|
+
if geometry_id == 1:
|
|
89
|
+
return _device_plane_sd(x, y, z, params[param_offset:])
|
|
90
|
+
elif geometry_id == 2:
|
|
91
|
+
return _device_sphere_sd(x, y, z, params[param_offset:])
|
|
92
|
+
elif geometry_id == 3:
|
|
93
|
+
return _device_gerstner_sd(x, y, z, params[param_offset:])
|
|
94
|
+
elif geometry_id == 4:
|
|
95
|
+
return _device_curved_wave_sd(x, y, z, params[param_offset:])
|
|
96
|
+
elif geometry_id == 5:
|
|
97
|
+
return _device_multi_curved_wave_sd(x, y, z, params[param_offset:])
|
|
98
|
+
elif geometry_id == 7:
|
|
99
|
+
return _device_annular_plane_sd(x, y, z, params[param_offset:])
|
|
100
|
+
else:
|
|
101
|
+
return math.inf
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@cuda.jit
|
|
105
|
+
def kernel_bisect_crossing(
|
|
106
|
+
prev_positions, # (N, 3) positions before step
|
|
107
|
+
curr_positions, # (N, 3) positions after step
|
|
108
|
+
crossing_mask, # (N,) rays that crossed
|
|
109
|
+
hit_surface_idx, # (N,) which surface was hit
|
|
110
|
+
geometry_ids, # (S,) geometry ID per surface
|
|
111
|
+
surface_params, # (S * MAX_PARAMS,) all surface parameters
|
|
112
|
+
max_params, # int: MAX_SURFACE_PARAMS
|
|
113
|
+
hit_positions, # (N, 3) output: refined hit positions
|
|
114
|
+
active, # (N,) in/out: deactivate rays that hit
|
|
115
|
+
num_iterations, # int: bisection iterations (default 10)
|
|
116
|
+
geo_path=None, # (N,) geometric path length to correct
|
|
117
|
+
opt_path=None, # (N,) optical path length to correct
|
|
118
|
+
acc_time=None, # (N,) accumulated time to correct
|
|
119
|
+
step_sizes=None, # (N,) step size per ray for correction
|
|
120
|
+
n=1.0, # refractive index for correction
|
|
121
|
+
c=299792458.0, # speed of light for correction
|
|
122
|
+
):
|
|
123
|
+
"""
|
|
124
|
+
Bisection refinement for rays that crossed a surface.
|
|
125
|
+
|
|
126
|
+
For each ray that crossed a surface, use bisection to find
|
|
127
|
+
the exact intersection point between prev_positions and curr_positions.
|
|
128
|
+
|
|
129
|
+
Parameters
|
|
130
|
+
----------
|
|
131
|
+
prev_positions : (N, 3) float32 device array
|
|
132
|
+
Ray positions before propagation step
|
|
133
|
+
curr_positions : (N, 3) float32 device array
|
|
134
|
+
Ray positions after propagation step
|
|
135
|
+
crossing_mask : (N,) bool device array
|
|
136
|
+
True for rays that crossed a surface
|
|
137
|
+
hit_surface_idx : (N,) int32 device array
|
|
138
|
+
Surface index that was crossed
|
|
139
|
+
geometry_ids : (S,) int32 device array
|
|
140
|
+
Geometry ID for each surface
|
|
141
|
+
surface_params : (S * MAX_PARAMS,) float32 device array
|
|
142
|
+
Concatenated surface parameters
|
|
143
|
+
max_params : int
|
|
144
|
+
MAX_SURFACE_PARAMS constant
|
|
145
|
+
hit_positions : (N, 3) float32 device array
|
|
146
|
+
Output: Refined intersection positions
|
|
147
|
+
active : (N,) bool device array
|
|
148
|
+
In/out: Rays are deactivated after intersection
|
|
149
|
+
num_iterations : int
|
|
150
|
+
Number of bisection iterations (typically 10)
|
|
151
|
+
"""
|
|
152
|
+
idx = cuda.grid(1)
|
|
153
|
+
if idx >= curr_positions.shape[0] or not crossing_mask[idx]:
|
|
154
|
+
return
|
|
155
|
+
|
|
156
|
+
surf = hit_surface_idx[idx]
|
|
157
|
+
geo_id = geometry_ids[surf]
|
|
158
|
+
param_offset = surf * max_params
|
|
159
|
+
|
|
160
|
+
# Load endpoints
|
|
161
|
+
p0x = prev_positions[idx, 0]
|
|
162
|
+
p0y = prev_positions[idx, 1]
|
|
163
|
+
p0z = prev_positions[idx, 2]
|
|
164
|
+
p1x = curr_positions[idx, 0]
|
|
165
|
+
p1y = curr_positions[idx, 1]
|
|
166
|
+
p1z = curr_positions[idx, 2]
|
|
167
|
+
|
|
168
|
+
# Get signed distance at p0 to know which side is positive
|
|
169
|
+
sd_p0 = _device_signed_distance_bisect(
|
|
170
|
+
p0x, p0y, p0z, geo_id, surface_params, param_offset
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# Bisection loop
|
|
174
|
+
for _ in range(num_iterations):
|
|
175
|
+
midx = (p0x + p1x) * 0.5
|
|
176
|
+
midy = (p0y + p1y) * 0.5
|
|
177
|
+
midz = (p0z + p1z) * 0.5
|
|
178
|
+
|
|
179
|
+
sd_mid = _device_signed_distance_bisect(
|
|
180
|
+
midx, midy, midz, geo_id, surface_params, param_offset
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
# Check for convergence
|
|
184
|
+
if abs(sd_mid) < 1e-6:
|
|
185
|
+
break
|
|
186
|
+
|
|
187
|
+
# Determine which half contains the crossing
|
|
188
|
+
if (sd_p0 >= 0) != (sd_mid >= 0):
|
|
189
|
+
# Crossing is in first half
|
|
190
|
+
p1x, p1y, p1z = midx, midy, midz
|
|
191
|
+
else:
|
|
192
|
+
# Crossing is in second half
|
|
193
|
+
p0x, p0y, p0z = midx, midy, midz
|
|
194
|
+
sd_p0 = sd_mid
|
|
195
|
+
|
|
196
|
+
# Store hit position (midpoint of final interval)
|
|
197
|
+
hit_x = (p0x + p1x) * 0.5
|
|
198
|
+
hit_y = (p0y + p1y) * 0.5
|
|
199
|
+
hit_z = (p0z + p1z) * 0.5
|
|
200
|
+
hit_positions[idx, 0] = hit_x
|
|
201
|
+
hit_positions[idx, 1] = hit_y
|
|
202
|
+
hit_positions[idx, 2] = hit_z
|
|
203
|
+
|
|
204
|
+
# Correct accumulated path/time for exact intersection distance
|
|
205
|
+
# (only if correction arrays are provided)
|
|
206
|
+
if geo_path is not None and step_sizes is not None:
|
|
207
|
+
# Compute actual distance traveled (prev_pos to hit_pos)
|
|
208
|
+
dx = hit_x - prev_positions[idx, 0]
|
|
209
|
+
dy = hit_y - prev_positions[idx, 1]
|
|
210
|
+
dz = hit_z - prev_positions[idx, 2]
|
|
211
|
+
actual_distance = math.sqrt(dx * dx + dy * dy + dz * dz)
|
|
212
|
+
|
|
213
|
+
# Compute excess distance (step already accumulated - actual)
|
|
214
|
+
excess_distance = step_sizes[idx] - actual_distance
|
|
215
|
+
|
|
216
|
+
# Remove excess from accumulated values
|
|
217
|
+
geo_path[idx] -= excess_distance
|
|
218
|
+
if opt_path is not None:
|
|
219
|
+
opt_path[idx] -= n * excess_distance
|
|
220
|
+
if acc_time is not None:
|
|
221
|
+
acc_time[idx] -= n * excess_distance / c
|
|
222
|
+
|
|
223
|
+
# Update current position to hit position
|
|
224
|
+
curr_positions[idx, 0] = hit_x
|
|
225
|
+
curr_positions[idx, 1] = hit_y
|
|
226
|
+
curr_positions[idx, 2] = hit_z
|
|
227
|
+
|
|
228
|
+
# Mark ray as inactive
|
|
229
|
+
active[idx] = False
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
__all__ = ["kernel_bisect_crossing"]
|