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,398 @@
|
|
|
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
|
+
Material Propagator
|
|
36
|
+
|
|
37
|
+
Handles in-material physics including:
|
|
38
|
+
- Ray equation integration (Euler/RK4 in gradient media)
|
|
39
|
+
- Beer-Lambert absorption
|
|
40
|
+
- Surface crossing detection (signed distance check each step)
|
|
41
|
+
- Bisection refinement for exact intersection
|
|
42
|
+
- Optical/geometric path length tracking
|
|
43
|
+
|
|
44
|
+
This is the first component in the propagator architecture:
|
|
45
|
+
MaterialPropagator -> SurfaceInteractionProcessor -> SimulationOrchestrator
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
from __future__ import annotations
|
|
49
|
+
|
|
50
|
+
from dataclasses import dataclass
|
|
51
|
+
from typing import TYPE_CHECKING, Protocol, runtime_checkable
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
from .surface_propagator import SurfacePropagator, HitData
|
|
55
|
+
|
|
56
|
+
if TYPE_CHECKING:
|
|
57
|
+
from ...materials import MaterialField
|
|
58
|
+
from ...surfaces import Surface
|
|
59
|
+
from ...utilities.ray_data import RayBatch
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@runtime_checkable
|
|
63
|
+
class MaterialPropagatorProtocol(Protocol):
|
|
64
|
+
"""
|
|
65
|
+
Protocol for material propagators.
|
|
66
|
+
|
|
67
|
+
Material propagators handle ray propagation through a material medium,
|
|
68
|
+
including ray equation integration and surface crossing detection.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def material(self) -> "MaterialField":
|
|
73
|
+
"""The material being propagated through."""
|
|
74
|
+
...
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def surfaces(self) -> list["Surface"]:
|
|
78
|
+
"""List of surfaces being checked for intersection."""
|
|
79
|
+
...
|
|
80
|
+
|
|
81
|
+
def propagate_to_surface(
|
|
82
|
+
self,
|
|
83
|
+
rays: "RayBatch",
|
|
84
|
+
step_size: float,
|
|
85
|
+
max_steps: int,
|
|
86
|
+
adaptive_stepping: bool = False,
|
|
87
|
+
min_step_size: float = 3e-4,
|
|
88
|
+
surface_proximity_factor: float = 0.5,
|
|
89
|
+
surface_proximity_threshold: float = 10.0,
|
|
90
|
+
) -> HitData:
|
|
91
|
+
"""
|
|
92
|
+
Propagate rays until they hit a surface or reach max_steps.
|
|
93
|
+
|
|
94
|
+
Parameters
|
|
95
|
+
----------
|
|
96
|
+
rays : RayBatch
|
|
97
|
+
Ray batch to propagate. Modified in-place.
|
|
98
|
+
step_size : float
|
|
99
|
+
Maximum integration step size in meters.
|
|
100
|
+
max_steps : int
|
|
101
|
+
Maximum number of propagation steps.
|
|
102
|
+
adaptive_stepping : bool, optional
|
|
103
|
+
Whether to use adaptive step sizing near surfaces.
|
|
104
|
+
min_step_size : float, optional
|
|
105
|
+
Minimum step size in meters.
|
|
106
|
+
surface_proximity_factor : float, optional
|
|
107
|
+
Step = distance * factor when within threshold.
|
|
108
|
+
surface_proximity_threshold : float, optional
|
|
109
|
+
Distance within which to start adaptive stepping.
|
|
110
|
+
|
|
111
|
+
Returns
|
|
112
|
+
-------
|
|
113
|
+
HitData
|
|
114
|
+
Information about which rays hit which surfaces.
|
|
115
|
+
"""
|
|
116
|
+
...
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@dataclass
|
|
120
|
+
class PropagationResult:
|
|
121
|
+
"""
|
|
122
|
+
Result from a single propagation leg.
|
|
123
|
+
|
|
124
|
+
Attributes
|
|
125
|
+
----------
|
|
126
|
+
hit_data : HitData
|
|
127
|
+
Information about ray-surface intersections.
|
|
128
|
+
steps_taken : int
|
|
129
|
+
Number of steps actually taken.
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
hit_data: HitData
|
|
133
|
+
steps_taken: int = 0
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class MaterialPropagator:
|
|
137
|
+
"""
|
|
138
|
+
GPU-accelerated material propagator with multi-surface detection.
|
|
139
|
+
|
|
140
|
+
This class wraps the SurfacePropagator to provide a clean interface
|
|
141
|
+
for ray propagation through gradient media with surface detection.
|
|
142
|
+
|
|
143
|
+
Parameters
|
|
144
|
+
----------
|
|
145
|
+
material : MaterialField
|
|
146
|
+
The material to propagate through. Must support SimpleInhomogeneousModel
|
|
147
|
+
GPU interface (i.e., have get_gpu_parameters() and get_gpu_arrays()).
|
|
148
|
+
surfaces : list of Surface
|
|
149
|
+
List of surfaces to check for intersections. Maximum of 16 surfaces.
|
|
150
|
+
use_gpu : bool, optional
|
|
151
|
+
Whether to use GPU acceleration. Default True.
|
|
152
|
+
threads_per_block : int, optional
|
|
153
|
+
CUDA threads per block for GPU execution. Default 256.
|
|
154
|
+
|
|
155
|
+
Examples
|
|
156
|
+
--------
|
|
157
|
+
>>> from lsurf.materials import LinsleyAtmosphere
|
|
158
|
+
>>> from lsurf.surfaces import PlaneSurface, SphereSurface, SurfaceRole
|
|
159
|
+
>>>
|
|
160
|
+
>>> atmosphere = LinsleyAtmosphere()
|
|
161
|
+
>>> detector = PlaneSurface(
|
|
162
|
+
... normal=(0, 0, 1),
|
|
163
|
+
... point=(0, 0, 35000),
|
|
164
|
+
... role=SurfaceRole.DETECTOR,
|
|
165
|
+
... )
|
|
166
|
+
>>> ocean = SphereSurface(
|
|
167
|
+
... center=(0, 0, -6.371e6),
|
|
168
|
+
... radius=6.371e6,
|
|
169
|
+
... role=SurfaceRole.OPTICAL,
|
|
170
|
+
... )
|
|
171
|
+
>>>
|
|
172
|
+
>>> propagator = MaterialPropagator(
|
|
173
|
+
... material=atmosphere,
|
|
174
|
+
... surfaces=[detector, ocean],
|
|
175
|
+
... )
|
|
176
|
+
>>> hit_data = propagator.propagate_to_surface(rays, step_size=100.0, max_steps=1000)
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
def __init__(
|
|
180
|
+
self,
|
|
181
|
+
material: "MaterialField",
|
|
182
|
+
surfaces: list["Surface"],
|
|
183
|
+
use_gpu: bool = True,
|
|
184
|
+
threads_per_block: int = 256,
|
|
185
|
+
apply_absorption: bool = False,
|
|
186
|
+
):
|
|
187
|
+
self._material = material
|
|
188
|
+
self._surfaces = list(surfaces)
|
|
189
|
+
self._use_gpu = use_gpu
|
|
190
|
+
self._threads_per_block = threads_per_block
|
|
191
|
+
self._apply_absorption = apply_absorption
|
|
192
|
+
|
|
193
|
+
# Create underlying SurfacePropagator
|
|
194
|
+
self._propagator = SurfacePropagator(
|
|
195
|
+
material=material,
|
|
196
|
+
surfaces=surfaces,
|
|
197
|
+
method="euler",
|
|
198
|
+
threads_per_block=threads_per_block,
|
|
199
|
+
use_gpu=use_gpu,
|
|
200
|
+
apply_absorption=apply_absorption,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
@property
|
|
204
|
+
def material(self) -> "MaterialField":
|
|
205
|
+
"""The material being propagated through."""
|
|
206
|
+
return self._material
|
|
207
|
+
|
|
208
|
+
@property
|
|
209
|
+
def surfaces(self) -> list["Surface"]:
|
|
210
|
+
"""List of surfaces being checked for intersection."""
|
|
211
|
+
return self._surfaces
|
|
212
|
+
|
|
213
|
+
@property
|
|
214
|
+
def num_surfaces(self) -> int:
|
|
215
|
+
"""Number of surfaces."""
|
|
216
|
+
return len(self._surfaces)
|
|
217
|
+
|
|
218
|
+
@property
|
|
219
|
+
def use_gpu(self) -> bool:
|
|
220
|
+
"""Whether GPU acceleration is enabled."""
|
|
221
|
+
return self._propagator._use_gpu
|
|
222
|
+
|
|
223
|
+
def propagate_to_surface(
|
|
224
|
+
self,
|
|
225
|
+
rays: "RayBatch",
|
|
226
|
+
step_size: float,
|
|
227
|
+
max_steps: int,
|
|
228
|
+
adaptive_stepping: bool = False,
|
|
229
|
+
min_step_size: float = 3e-4,
|
|
230
|
+
surface_proximity_factor: float = 0.5,
|
|
231
|
+
surface_proximity_threshold: float = 10.0,
|
|
232
|
+
) -> HitData:
|
|
233
|
+
"""
|
|
234
|
+
Propagate rays until they hit a surface or reach max_steps.
|
|
235
|
+
|
|
236
|
+
Each step:
|
|
237
|
+
1. Compute ray equation step (position, direction update)
|
|
238
|
+
2. Apply absorption (Beer-Lambert) - integrated in the ray equation
|
|
239
|
+
3. Check signed distance to all surfaces
|
|
240
|
+
4. If sign change detected -> bisect to find exact crossing
|
|
241
|
+
5. Record hit data, deactivate ray
|
|
242
|
+
|
|
243
|
+
Parameters
|
|
244
|
+
----------
|
|
245
|
+
rays : RayBatch
|
|
246
|
+
Ray batch to propagate. Modified in-place with updated positions,
|
|
247
|
+
directions, path lengths, and active status.
|
|
248
|
+
step_size : float
|
|
249
|
+
Maximum integration step size in meters.
|
|
250
|
+
max_steps : int
|
|
251
|
+
Maximum number of propagation steps.
|
|
252
|
+
adaptive_stepping : bool, optional
|
|
253
|
+
Whether to use adaptive step sizing near surfaces (default False).
|
|
254
|
+
min_step_size : float, optional
|
|
255
|
+
Minimum step size in meters (default 3e-4 = 0.3mm → ~1ps resolution).
|
|
256
|
+
surface_proximity_factor : float, optional
|
|
257
|
+
Step = distance * factor when within threshold (default 0.5).
|
|
258
|
+
surface_proximity_threshold : float, optional
|
|
259
|
+
Distance within which to start adaptive stepping (default 10.0 m).
|
|
260
|
+
|
|
261
|
+
Returns
|
|
262
|
+
-------
|
|
263
|
+
HitData
|
|
264
|
+
Information about which rays hit which surfaces:
|
|
265
|
+
- hit_surface_idx: which surface each ray hit (-1 = no hit)
|
|
266
|
+
- hit_positions: intersection positions
|
|
267
|
+
- hit_directions: directions at intersection
|
|
268
|
+
"""
|
|
269
|
+
return self._propagator.propagate_to_surface(
|
|
270
|
+
rays=rays,
|
|
271
|
+
step_size=step_size,
|
|
272
|
+
max_steps=max_steps,
|
|
273
|
+
adaptive_stepping=adaptive_stepping,
|
|
274
|
+
min_step_size=min_step_size,
|
|
275
|
+
surface_proximity_factor=surface_proximity_factor,
|
|
276
|
+
surface_proximity_threshold=surface_proximity_threshold,
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
def propagate(
|
|
280
|
+
self,
|
|
281
|
+
rays: "RayBatch",
|
|
282
|
+
step_size: float,
|
|
283
|
+
max_steps: int,
|
|
284
|
+
) -> PropagationResult:
|
|
285
|
+
"""
|
|
286
|
+
Propagate rays and return full result with metadata.
|
|
287
|
+
|
|
288
|
+
Parameters
|
|
289
|
+
----------
|
|
290
|
+
rays : RayBatch
|
|
291
|
+
Ray batch to propagate.
|
|
292
|
+
step_size : float
|
|
293
|
+
Integration step size in meters.
|
|
294
|
+
max_steps : int
|
|
295
|
+
Maximum number of steps.
|
|
296
|
+
|
|
297
|
+
Returns
|
|
298
|
+
-------
|
|
299
|
+
PropagationResult
|
|
300
|
+
Full propagation result including hit data and steps taken.
|
|
301
|
+
"""
|
|
302
|
+
hit_data = self.propagate_to_surface(rays, step_size, max_steps)
|
|
303
|
+
return PropagationResult(
|
|
304
|
+
hit_data=hit_data,
|
|
305
|
+
steps_taken=max_steps, # Approximate - actual may be less
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
def get_surface_by_index(self, idx: int) -> "Surface":
|
|
309
|
+
"""Get surface by index."""
|
|
310
|
+
return self._surfaces[idx]
|
|
311
|
+
|
|
312
|
+
def extract_hits_for_surface(
|
|
313
|
+
self,
|
|
314
|
+
rays: "RayBatch",
|
|
315
|
+
hit_data: HitData,
|
|
316
|
+
surface_idx: int,
|
|
317
|
+
) -> "RayBatch":
|
|
318
|
+
"""
|
|
319
|
+
Extract rays that hit a specific surface as a new RayBatch.
|
|
320
|
+
|
|
321
|
+
Parameters
|
|
322
|
+
----------
|
|
323
|
+
rays : RayBatch
|
|
324
|
+
Original ray batch.
|
|
325
|
+
hit_data : HitData
|
|
326
|
+
Hit data from propagation.
|
|
327
|
+
surface_idx : int
|
|
328
|
+
Index of surface to extract hits for.
|
|
329
|
+
|
|
330
|
+
Returns
|
|
331
|
+
-------
|
|
332
|
+
RayBatch
|
|
333
|
+
New batch containing only rays that hit the specified surface.
|
|
334
|
+
"""
|
|
335
|
+
return self._propagator.extract_hits_for_surface(rays, hit_data, surface_idx)
|
|
336
|
+
|
|
337
|
+
def extract_no_hits(
|
|
338
|
+
self,
|
|
339
|
+
rays: "RayBatch",
|
|
340
|
+
hit_data: HitData,
|
|
341
|
+
) -> "RayBatch":
|
|
342
|
+
"""
|
|
343
|
+
Extract rays that didn't hit any surface.
|
|
344
|
+
|
|
345
|
+
Parameters
|
|
346
|
+
----------
|
|
347
|
+
rays : RayBatch
|
|
348
|
+
Original ray batch.
|
|
349
|
+
hit_data : HitData
|
|
350
|
+
Hit data from propagation.
|
|
351
|
+
|
|
352
|
+
Returns
|
|
353
|
+
-------
|
|
354
|
+
RayBatch
|
|
355
|
+
New batch containing only rays that didn't hit any surface.
|
|
356
|
+
"""
|
|
357
|
+
return self._propagator.extract_no_hits(rays, hit_data)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
class CPUMaterialPropagator(MaterialPropagator):
|
|
361
|
+
"""
|
|
362
|
+
CPU-only material propagator.
|
|
363
|
+
|
|
364
|
+
Forces CPU execution even if GPU is available. Useful for testing
|
|
365
|
+
or when GPU resources are limited.
|
|
366
|
+
"""
|
|
367
|
+
|
|
368
|
+
def __init__(
|
|
369
|
+
self,
|
|
370
|
+
material: "MaterialField",
|
|
371
|
+
surfaces: list["Surface"],
|
|
372
|
+
):
|
|
373
|
+
super().__init__(
|
|
374
|
+
material=material,
|
|
375
|
+
surfaces=surfaces,
|
|
376
|
+
use_gpu=False,
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
class GPUMaterialPropagator(MaterialPropagator):
|
|
381
|
+
"""
|
|
382
|
+
GPU-accelerated material propagator.
|
|
383
|
+
|
|
384
|
+
Explicitly requests GPU acceleration. Falls back to CPU if unavailable.
|
|
385
|
+
"""
|
|
386
|
+
|
|
387
|
+
def __init__(
|
|
388
|
+
self,
|
|
389
|
+
material: "MaterialField",
|
|
390
|
+
surfaces: list["Surface"],
|
|
391
|
+
threads_per_block: int = 256,
|
|
392
|
+
):
|
|
393
|
+
super().__init__(
|
|
394
|
+
material=material,
|
|
395
|
+
surfaces=surfaces,
|
|
396
|
+
use_gpu=True,
|
|
397
|
+
threads_per_block=threads_per_block,
|
|
398
|
+
)
|