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,410 @@
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
+ Absorption Kernels for SpectralInhomogeneousModel
36
+
37
+ GPU kernels for applying Beer-Lambert absorption using 2D altitude×wavelength LUT.
38
+ Supports both scalar wavelength (all rays same λ) and per-ray wavelength modes.
39
+ """
40
+
41
+ import math
42
+
43
+ # GPU support is optional
44
+ try:
45
+ from numba import cuda
46
+
47
+ HAS_CUDA = True
48
+ except ImportError:
49
+
50
+ class _FakeCuda:
51
+ """Fake cuda module for when numba is not installed."""
52
+
53
+ class devicearray:
54
+ """Fake devicearray submodule."""
55
+
56
+ DeviceNDArray = object
57
+
58
+ @staticmethod
59
+ def jit(*args, **kwargs):
60
+ """Return a no-op decorator."""
61
+
62
+ def decorator(func):
63
+ return func
64
+
65
+ if args and callable(args[0]):
66
+ return args[0]
67
+ return decorator
68
+
69
+ @staticmethod
70
+ def is_available():
71
+ return False
72
+
73
+ @staticmethod
74
+ def grid(n):
75
+ return 0
76
+
77
+ @staticmethod
78
+ def synchronize():
79
+ pass
80
+
81
+ cuda = _FakeCuda() # type: ignore[assignment]
82
+ HAS_CUDA = False
83
+
84
+
85
+ @cuda.jit(device=True)
86
+ def _device_bilinear_interpolate(
87
+ val1: float,
88
+ val2: float,
89
+ lut: cuda.devicearray.DeviceNDArray,
90
+ min1: float,
91
+ delta1: float,
92
+ n1: int,
93
+ min2: float,
94
+ delta2: float,
95
+ n2: int,
96
+ ) -> float:
97
+ """
98
+ Bilinear interpolation in 2D LUT.
99
+
100
+ Parameters
101
+ ----------
102
+ val1 : float
103
+ Value in first dimension (e.g., altitude)
104
+ val2 : float
105
+ Value in second dimension (e.g., wavelength)
106
+ lut : device array
107
+ 2D lookup table [dim1, dim2]
108
+ min1 : float
109
+ Minimum value in first dimension
110
+ delta1 : float
111
+ Spacing in first dimension
112
+ n1 : int
113
+ Number of entries in first dimension
114
+ min2 : float
115
+ Minimum value in second dimension
116
+ delta2 : float
117
+ Spacing in second dimension
118
+ n2 : int
119
+ Number of entries in second dimension
120
+
121
+ Returns
122
+ -------
123
+ float
124
+ Interpolated value
125
+ """
126
+ # Normalize coordinates to fractional indices
127
+ idx1 = (val1 - min1) / delta1
128
+ idx2 = (val2 - min2) / delta2
129
+
130
+ # Clamp to valid bounds
131
+ if idx1 < 0.0:
132
+ idx1 = 0.0
133
+ if idx1 > n1 - 1.001:
134
+ idx1 = n1 - 1.001
135
+ if idx2 < 0.0:
136
+ idx2 = 0.0
137
+ if idx2 > n2 - 1.001:
138
+ idx2 = n2 - 1.001
139
+
140
+ # Get integer indices
141
+ i0 = int(idx1)
142
+ j0 = int(idx2)
143
+ i1 = i0 + 1
144
+ j1 = j0 + 1
145
+
146
+ # Clamp upper indices
147
+ if i1 > n1 - 1:
148
+ i1 = n1 - 1
149
+ if j1 > n2 - 1:
150
+ j1 = n2 - 1
151
+
152
+ # Fractional parts
153
+ fi = idx1 - i0
154
+ fj = idx2 - j0
155
+
156
+ # Bilinear interpolation
157
+ c00 = lut[i0, j0]
158
+ c01 = lut[i0, j1]
159
+ c10 = lut[i1, j0]
160
+ c11 = lut[i1, j1]
161
+
162
+ # Interpolate along second dimension first
163
+ c0 = c00 * (1.0 - fj) + c01 * fj
164
+ c1 = c10 * (1.0 - fj) + c11 * fj
165
+
166
+ # Then interpolate along first dimension
167
+ return c0 * (1.0 - fi) + c1 * fi
168
+
169
+
170
+ @cuda.jit
171
+ def _kernel_absorption_spectral(
172
+ positions,
173
+ directions,
174
+ active,
175
+ intensities,
176
+ optical_depth,
177
+ step_size: float,
178
+ num_steps: int,
179
+ center_x: float,
180
+ center_y: float,
181
+ center_z: float,
182
+ ref_radius: float,
183
+ alt_min: float,
184
+ alt_delta: float,
185
+ n_alt: int,
186
+ wl_min: float,
187
+ wl_delta: float,
188
+ n_wl: int,
189
+ lut_alpha,
190
+ wavelength: float,
191
+ ):
192
+ """
193
+ Apply Beer-Lambert absorption for SpectralInhomogeneousModel.
194
+
195
+ Integrates absorption along the ray path using 2D LUT interpolation
196
+ for wavelength-dependent absorption. All rays use the same wavelength.
197
+
198
+ Parameters
199
+ ----------
200
+ positions : device array, shape (N, 3)
201
+ Ray positions (after propagation)
202
+ directions : device array, shape (N, 3)
203
+ Ray directions (unit vectors)
204
+ active : device array, shape (N,)
205
+ Boolean mask for active rays
206
+ intensities : device array, shape (N,)
207
+ Ray intensities (modified in-place)
208
+ optical_depth : device array, shape (N,)
209
+ Accumulated optical depth (modified in-place)
210
+ step_size : float
211
+ Integration step size in meters
212
+ num_steps : int
213
+ Number of steps taken in propagation
214
+ center_x, center_y, center_z : float
215
+ Center of spherical symmetry
216
+ ref_radius : float
217
+ Reference radius
218
+ alt_min : float
219
+ Minimum altitude in LUT
220
+ alt_delta : float
221
+ Altitude spacing in LUT
222
+ n_alt : int
223
+ Number of altitude samples
224
+ wl_min : float
225
+ Minimum wavelength in LUT
226
+ wl_delta : float
227
+ Wavelength spacing in LUT
228
+ n_wl : int
229
+ Number of wavelength samples
230
+ lut_alpha : device array, shape (n_alt, n_wl)
231
+ 2D lookup table for α(altitude, wavelength)
232
+ wavelength : float
233
+ Wavelength in meters (same for all rays)
234
+ """
235
+ idx = cuda.grid(1)
236
+ if idx >= positions.shape[0]:
237
+ return
238
+ if not active[idx]:
239
+ return
240
+
241
+ # Get current position and direction
242
+ x = positions[idx, 0]
243
+ y = positions[idx, 1]
244
+ z = positions[idx, 2]
245
+ dx_dir = directions[idx, 0]
246
+ dy_dir = directions[idx, 1]
247
+ dz_dir = directions[idx, 2]
248
+
249
+ # Integrate absorption along path by stepping backwards
250
+ delta_tau = 0.0
251
+
252
+ for step in range(num_steps):
253
+ # Position at this step (walking backwards from current)
254
+ back_dist = step * step_size
255
+ px = x - dx_dir * back_dist
256
+ py = y - dy_dir * back_dist
257
+ pz = z - dz_dir * back_dist
258
+
259
+ # Compute altitude from position
260
+ rx = px - center_x
261
+ ry = py - center_y
262
+ rz = pz - center_z
263
+ r = math.sqrt(rx * rx + ry * ry + rz * rz)
264
+ altitude = r - ref_radius
265
+ if altitude < 0.0:
266
+ altitude = 0.0
267
+
268
+ # Look up absorption coefficient (2D interpolation)
269
+ alpha = _device_bilinear_interpolate(
270
+ altitude,
271
+ wavelength,
272
+ lut_alpha,
273
+ alt_min,
274
+ alt_delta,
275
+ n_alt,
276
+ wl_min,
277
+ wl_delta,
278
+ n_wl,
279
+ )
280
+
281
+ # Accumulate optical depth for this step
282
+ delta_tau += alpha * step_size
283
+
284
+ # Apply Beer-Lambert decay: I = I₀ * exp(-τ)
285
+ intensities[idx] *= math.exp(-delta_tau)
286
+
287
+ # Accumulate optical depth
288
+ optical_depth[idx] += delta_tau
289
+
290
+
291
+ @cuda.jit
292
+ def _kernel_absorption_spectral_perray(
293
+ positions,
294
+ directions,
295
+ wavelengths,
296
+ active,
297
+ intensities,
298
+ optical_depth,
299
+ step_size: float,
300
+ num_steps: int,
301
+ center_x: float,
302
+ center_y: float,
303
+ center_z: float,
304
+ ref_radius: float,
305
+ alt_min: float,
306
+ alt_delta: float,
307
+ n_alt: int,
308
+ wl_min: float,
309
+ wl_delta: float,
310
+ n_wl: int,
311
+ lut_alpha,
312
+ ):
313
+ """
314
+ Apply Beer-Lambert absorption with per-ray wavelengths.
315
+
316
+ Integrates absorption along the ray path. Each ray has its own
317
+ wavelength, enabling chromatic absorption simulation.
318
+
319
+ Parameters
320
+ ----------
321
+ positions : device array, shape (N, 3)
322
+ Ray positions (after propagation)
323
+ directions : device array, shape (N, 3)
324
+ Ray directions (unit vectors)
325
+ wavelengths : device array, shape (N,)
326
+ Per-ray wavelengths in meters
327
+ active : device array, shape (N,)
328
+ Boolean mask for active rays
329
+ intensities : device array, shape (N,)
330
+ Ray intensities (modified in-place)
331
+ optical_depth : device array, shape (N,)
332
+ Accumulated optical depth (modified in-place)
333
+ step_size : float
334
+ Integration step size in meters
335
+ num_steps : int
336
+ Number of steps taken in propagation
337
+ center_x, center_y, center_z : float
338
+ Center of spherical symmetry
339
+ ref_radius : float
340
+ Reference radius
341
+ alt_min : float
342
+ Minimum altitude in LUT
343
+ alt_delta : float
344
+ Altitude spacing in LUT
345
+ n_alt : int
346
+ Number of altitude samples
347
+ wl_min : float
348
+ Minimum wavelength in LUT
349
+ wl_delta : float
350
+ Wavelength spacing in LUT
351
+ n_wl : int
352
+ Number of wavelength samples
353
+ lut_alpha : device array, shape (n_alt, n_wl)
354
+ 2D lookup table for α(altitude, wavelength)
355
+ """
356
+ idx = cuda.grid(1)
357
+ if idx >= positions.shape[0]:
358
+ return
359
+ if not active[idx]:
360
+ return
361
+
362
+ # Get current position, direction, and wavelength
363
+ x = positions[idx, 0]
364
+ y = positions[idx, 1]
365
+ z = positions[idx, 2]
366
+ dx_dir = directions[idx, 0]
367
+ dy_dir = directions[idx, 1]
368
+ dz_dir = directions[idx, 2]
369
+ wavelength = wavelengths[idx]
370
+
371
+ # Integrate absorption along path by stepping backwards
372
+ delta_tau = 0.0
373
+
374
+ for step in range(num_steps):
375
+ # Position at this step (walking backwards from current)
376
+ back_dist = step * step_size
377
+ px = x - dx_dir * back_dist
378
+ py = y - dy_dir * back_dist
379
+ pz = z - dz_dir * back_dist
380
+
381
+ # Compute altitude from position
382
+ rx = px - center_x
383
+ ry = py - center_y
384
+ rz = pz - center_z
385
+ r = math.sqrt(rx * rx + ry * ry + rz * rz)
386
+ altitude = r - ref_radius
387
+ if altitude < 0.0:
388
+ altitude = 0.0
389
+
390
+ # Look up absorption coefficient (2D interpolation)
391
+ alpha = _device_bilinear_interpolate(
392
+ altitude,
393
+ wavelength,
394
+ lut_alpha,
395
+ alt_min,
396
+ alt_delta,
397
+ n_alt,
398
+ wl_min,
399
+ wl_delta,
400
+ n_wl,
401
+ )
402
+
403
+ # Accumulate optical depth for this step
404
+ delta_tau += alpha * step_size
405
+
406
+ # Apply Beer-Lambert decay: I = I₀ * exp(-τ)
407
+ intensities[idx] *= math.exp(-delta_tau)
408
+
409
+ # Accumulate optical depth
410
+ optical_depth[idx] += delta_tau
@@ -0,0 +1,64 @@
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
+ Detection Kernels
36
+
37
+ CUDA kernels for GPU-accelerated ray detection.
38
+
39
+ This module exports ONLY pure CUDA kernels.
40
+ GPU memory management is handled by the propagator layer (handlers).
41
+
42
+ Kernel Types
43
+ ------------
44
+ - SPHERICAL_SINGLE: Single spherical detector
45
+ - SPHERICAL_MULTI: Multiple spherical detectors (scan mode)
46
+ - PLANAR_SINGLE: Single planar detector (TODO)
47
+ """
48
+
49
+ from .protocol import DetectionKernelProtocol
50
+ from .spherical import (
51
+ kernel_spherical_detect_single,
52
+ kernel_spherical_detect_multi,
53
+ HAS_CUDA,
54
+ )
55
+
56
+ __all__ = [
57
+ # Protocol
58
+ "DetectionKernelProtocol",
59
+ # Kernels (pure CUDA only)
60
+ "kernel_spherical_detect_single",
61
+ "kernel_spherical_detect_multi",
62
+ # CUDA availability
63
+ "HAS_CUDA",
64
+ ]
@@ -0,0 +1,102 @@
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
+ Detection Kernel Protocol
36
+
37
+ Defines the interface for ray detection kernels that find
38
+ rays hitting detector surfaces.
39
+ """
40
+
41
+ from __future__ import annotations
42
+
43
+ from typing import TYPE_CHECKING, Protocol, runtime_checkable
44
+
45
+ if TYPE_CHECKING:
46
+ from numba.cuda.devicearray import DeviceNDArray
47
+
48
+
49
+ @runtime_checkable
50
+ class DetectionKernelProtocol(Protocol):
51
+ """
52
+ Protocol for ray detection kernels.
53
+
54
+ Detection kernels find rays that intersect with detector surfaces
55
+ and compute detection parameters (hit time, position, intensity).
56
+
57
+ Common Parameters
58
+ -----------------
59
+ ray_positions : DeviceNDArray, shape (N, 3)
60
+ Ray positions in meters
61
+ ray_directions : DeviceNDArray, shape (N, 3)
62
+ Ray direction unit vectors
63
+ ray_active : DeviceNDArray, shape (N,)
64
+ Boolean mask of active rays
65
+ ray_times : DeviceNDArray, shape (N,)
66
+ Accumulated ray times in seconds
67
+ ray_intensities : DeviceNDArray, shape (N,)
68
+ Ray intensities
69
+ hit_mask_out : DeviceNDArray, shape (N,)
70
+ Output: boolean mask of rays that hit detector
71
+ hit_distances_out : DeviceNDArray, shape (N,)
72
+ Output: distance to detector
73
+ hit_times_out : DeviceNDArray, shape (N,)
74
+ Output: arrival time at detector
75
+
76
+ Detector-Specific Parameters
77
+ ----------------------------
78
+ Additional parameters depend on the detector type:
79
+ - Spherical: center position, radius
80
+ - Planar: normal vector, point on plane
81
+
82
+ Notes
83
+ -----
84
+ Kernels are registered with the central registry using @register_kernel
85
+ decorator and DetectionKernelID enum values.
86
+ """
87
+
88
+ def __call__(
89
+ self,
90
+ ray_positions: DeviceNDArray,
91
+ ray_directions: DeviceNDArray,
92
+ ray_active: DeviceNDArray,
93
+ ray_times: DeviceNDArray,
94
+ ray_intensities: DeviceNDArray,
95
+ hit_mask_out: DeviceNDArray,
96
+ hit_distances_out: DeviceNDArray,
97
+ hit_times_out: DeviceNDArray,
98
+ *args,
99
+ **kwargs,
100
+ ) -> None:
101
+ """Execute the detection kernel."""
102
+ ...