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.
Files changed (180) hide show
  1. lsurf/__init__.py +471 -0
  2. lsurf/analysis/__init__.py +107 -0
  3. lsurf/analysis/healpix_utils.py +418 -0
  4. lsurf/analysis/sphere_viz.py +1280 -0
  5. lsurf/cli/__init__.py +48 -0
  6. lsurf/cli/build.py +398 -0
  7. lsurf/cli/config_schema.py +318 -0
  8. lsurf/cli/gui_cmd.py +76 -0
  9. lsurf/cli/interactive.py +850 -0
  10. lsurf/cli/main.py +81 -0
  11. lsurf/cli/run.py +806 -0
  12. lsurf/detectors/__init__.py +266 -0
  13. lsurf/detectors/analysis.py +289 -0
  14. lsurf/detectors/base.py +284 -0
  15. lsurf/detectors/constant_size_rings.py +485 -0
  16. lsurf/detectors/directional.py +45 -0
  17. lsurf/detectors/extended/__init__.py +73 -0
  18. lsurf/detectors/extended/local_sphere.py +353 -0
  19. lsurf/detectors/extended/recording_sphere.py +368 -0
  20. lsurf/detectors/planar.py +45 -0
  21. lsurf/detectors/protocol.py +187 -0
  22. lsurf/detectors/recording_spheres.py +63 -0
  23. lsurf/detectors/results.py +1140 -0
  24. lsurf/detectors/small/__init__.py +79 -0
  25. lsurf/detectors/small/directional.py +330 -0
  26. lsurf/detectors/small/planar.py +401 -0
  27. lsurf/detectors/small/spherical.py +450 -0
  28. lsurf/detectors/spherical.py +45 -0
  29. lsurf/geometry/__init__.py +199 -0
  30. lsurf/geometry/builder.py +478 -0
  31. lsurf/geometry/cell.py +228 -0
  32. lsurf/geometry/cell_geometry.py +247 -0
  33. lsurf/geometry/detector_arrays.py +1785 -0
  34. lsurf/geometry/geometry.py +222 -0
  35. lsurf/geometry/surface_analysis.py +375 -0
  36. lsurf/geometry/validation.py +91 -0
  37. lsurf/gui/__init__.py +51 -0
  38. lsurf/gui/app.py +903 -0
  39. lsurf/gui/core/__init__.py +39 -0
  40. lsurf/gui/core/scene.py +343 -0
  41. lsurf/gui/core/simulation.py +264 -0
  42. lsurf/gui/renderers/__init__.py +40 -0
  43. lsurf/gui/renderers/ray_renderer.py +353 -0
  44. lsurf/gui/renderers/source_renderer.py +505 -0
  45. lsurf/gui/renderers/surface_renderer.py +477 -0
  46. lsurf/gui/views/__init__.py +48 -0
  47. lsurf/gui/views/config_editor.py +3199 -0
  48. lsurf/gui/views/properties.py +257 -0
  49. lsurf/gui/views/results.py +291 -0
  50. lsurf/gui/views/scene_tree.py +180 -0
  51. lsurf/gui/views/viewport_3d.py +555 -0
  52. lsurf/gui/views/visualizations.py +712 -0
  53. lsurf/materials/__init__.py +169 -0
  54. lsurf/materials/base/__init__.py +64 -0
  55. lsurf/materials/base/full_inhomogeneous.py +208 -0
  56. lsurf/materials/base/grid_inhomogeneous.py +319 -0
  57. lsurf/materials/base/homogeneous.py +342 -0
  58. lsurf/materials/base/material_field.py +527 -0
  59. lsurf/materials/base/simple_inhomogeneous.py +418 -0
  60. lsurf/materials/base/spectral_inhomogeneous.py +497 -0
  61. lsurf/materials/implementations/__init__.py +120 -0
  62. lsurf/materials/implementations/data/alpha_values_typical_atmosphere_updated.txt +24 -0
  63. lsurf/materials/implementations/duct_atmosphere.py +390 -0
  64. lsurf/materials/implementations/exponential_atmosphere.py +435 -0
  65. lsurf/materials/implementations/gaussian_lens.py +120 -0
  66. lsurf/materials/implementations/interpolated_data.py +123 -0
  67. lsurf/materials/implementations/layered_atmosphere.py +134 -0
  68. lsurf/materials/implementations/linear_gradient.py +109 -0
  69. lsurf/materials/implementations/linsley_atmosphere.py +764 -0
  70. lsurf/materials/implementations/standard_materials.py +126 -0
  71. lsurf/materials/implementations/turbulent_atmosphere.py +135 -0
  72. lsurf/materials/implementations/us_standard_atmosphere.py +149 -0
  73. lsurf/materials/utils/__init__.py +77 -0
  74. lsurf/materials/utils/constants.py +45 -0
  75. lsurf/materials/utils/device_functions.py +117 -0
  76. lsurf/materials/utils/dispersion.py +160 -0
  77. lsurf/materials/utils/factories.py +142 -0
  78. lsurf/propagation/__init__.py +91 -0
  79. lsurf/propagation/detector_gpu.py +67 -0
  80. lsurf/propagation/gpu_device_rays.py +294 -0
  81. lsurf/propagation/kernels/__init__.py +175 -0
  82. lsurf/propagation/kernels/absorption/__init__.py +61 -0
  83. lsurf/propagation/kernels/absorption/grid.py +240 -0
  84. lsurf/propagation/kernels/absorption/simple.py +232 -0
  85. lsurf/propagation/kernels/absorption/spectral.py +410 -0
  86. lsurf/propagation/kernels/detection/__init__.py +64 -0
  87. lsurf/propagation/kernels/detection/protocol.py +102 -0
  88. lsurf/propagation/kernels/detection/spherical.py +255 -0
  89. lsurf/propagation/kernels/device_functions.py +790 -0
  90. lsurf/propagation/kernels/fresnel/__init__.py +64 -0
  91. lsurf/propagation/kernels/fresnel/protocol.py +97 -0
  92. lsurf/propagation/kernels/fresnel/standard.py +258 -0
  93. lsurf/propagation/kernels/intersection/__init__.py +79 -0
  94. lsurf/propagation/kernels/intersection/annular_plane.py +207 -0
  95. lsurf/propagation/kernels/intersection/bounded_plane.py +205 -0
  96. lsurf/propagation/kernels/intersection/plane.py +166 -0
  97. lsurf/propagation/kernels/intersection/protocol.py +95 -0
  98. lsurf/propagation/kernels/intersection/signed_distance.py +742 -0
  99. lsurf/propagation/kernels/intersection/sphere.py +190 -0
  100. lsurf/propagation/kernels/propagation/__init__.py +85 -0
  101. lsurf/propagation/kernels/propagation/grid.py +527 -0
  102. lsurf/propagation/kernels/propagation/protocol.py +105 -0
  103. lsurf/propagation/kernels/propagation/simple.py +460 -0
  104. lsurf/propagation/kernels/propagation/spectral.py +875 -0
  105. lsurf/propagation/kernels/registry.py +331 -0
  106. lsurf/propagation/kernels/surface/__init__.py +72 -0
  107. lsurf/propagation/kernels/surface/bisection.py +232 -0
  108. lsurf/propagation/kernels/surface/detection.py +402 -0
  109. lsurf/propagation/kernels/surface/reduction.py +166 -0
  110. lsurf/propagation/propagator_protocol.py +222 -0
  111. lsurf/propagation/propagators/__init__.py +101 -0
  112. lsurf/propagation/propagators/detector_handler.py +354 -0
  113. lsurf/propagation/propagators/factory.py +200 -0
  114. lsurf/propagation/propagators/fresnel_handler.py +305 -0
  115. lsurf/propagation/propagators/gpu_gradient.py +566 -0
  116. lsurf/propagation/propagators/gpu_surface_propagator.py +707 -0
  117. lsurf/propagation/propagators/gradient.py +429 -0
  118. lsurf/propagation/propagators/intersection_handler.py +327 -0
  119. lsurf/propagation/propagators/material_propagator.py +398 -0
  120. lsurf/propagation/propagators/signed_distance_handler.py +522 -0
  121. lsurf/propagation/propagators/spectral_gpu_gradient.py +553 -0
  122. lsurf/propagation/propagators/surface_interaction.py +616 -0
  123. lsurf/propagation/propagators/surface_propagator.py +719 -0
  124. lsurf/py.typed +1 -0
  125. lsurf/simulation/__init__.py +70 -0
  126. lsurf/simulation/config.py +164 -0
  127. lsurf/simulation/orchestrator.py +462 -0
  128. lsurf/simulation/result.py +299 -0
  129. lsurf/simulation/simulation.py +262 -0
  130. lsurf/sources/__init__.py +128 -0
  131. lsurf/sources/base.py +264 -0
  132. lsurf/sources/collimated.py +252 -0
  133. lsurf/sources/custom.py +409 -0
  134. lsurf/sources/diverging.py +228 -0
  135. lsurf/sources/gaussian.py +272 -0
  136. lsurf/sources/parallel_from_positions.py +197 -0
  137. lsurf/sources/point.py +172 -0
  138. lsurf/sources/uniform_diverging.py +258 -0
  139. lsurf/surfaces/__init__.py +184 -0
  140. lsurf/surfaces/cpu/__init__.py +50 -0
  141. lsurf/surfaces/cpu/curved_wave.py +463 -0
  142. lsurf/surfaces/cpu/gerstner_wave.py +381 -0
  143. lsurf/surfaces/cpu/wave_params.py +118 -0
  144. lsurf/surfaces/gpu/__init__.py +72 -0
  145. lsurf/surfaces/gpu/annular_plane.py +453 -0
  146. lsurf/surfaces/gpu/bounded_plane.py +390 -0
  147. lsurf/surfaces/gpu/curved_wave.py +483 -0
  148. lsurf/surfaces/gpu/gerstner_wave.py +377 -0
  149. lsurf/surfaces/gpu/multi_curved_wave.py +520 -0
  150. lsurf/surfaces/gpu/plane.py +299 -0
  151. lsurf/surfaces/gpu/recording_sphere.py +587 -0
  152. lsurf/surfaces/gpu/sphere.py +311 -0
  153. lsurf/surfaces/protocol.py +336 -0
  154. lsurf/surfaces/registry.py +373 -0
  155. lsurf/utilities/__init__.py +175 -0
  156. lsurf/utilities/detector_analysis.py +814 -0
  157. lsurf/utilities/fresnel.py +628 -0
  158. lsurf/utilities/interactions.py +1215 -0
  159. lsurf/utilities/propagation.py +602 -0
  160. lsurf/utilities/ray_data.py +532 -0
  161. lsurf/utilities/recording_sphere.py +745 -0
  162. lsurf/utilities/time_spread.py +463 -0
  163. lsurf/visualization/__init__.py +329 -0
  164. lsurf/visualization/absorption_plots.py +334 -0
  165. lsurf/visualization/atmospheric_plots.py +754 -0
  166. lsurf/visualization/common.py +348 -0
  167. lsurf/visualization/detector_plots.py +1350 -0
  168. lsurf/visualization/detector_sphere_plots.py +1173 -0
  169. lsurf/visualization/fresnel_plots.py +1061 -0
  170. lsurf/visualization/ocean_simulation_plots.py +999 -0
  171. lsurf/visualization/polarization_plots.py +916 -0
  172. lsurf/visualization/raytracing_plots.py +1521 -0
  173. lsurf/visualization/ring_detector_plots.py +1867 -0
  174. lsurf/visualization/time_spread_plots.py +531 -0
  175. lsurf-1.0.0.dist-info/METADATA +381 -0
  176. lsurf-1.0.0.dist-info/RECORD +180 -0
  177. lsurf-1.0.0.dist-info/WHEEL +5 -0
  178. lsurf-1.0.0.dist-info/entry_points.txt +2 -0
  179. lsurf-1.0.0.dist-info/licenses/LICENSE +32 -0
  180. 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
+ )