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,527 @@
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
+ GPU Kernels for GridInhomogeneousModel
36
+
37
+ CUDA kernels for GPU-accelerated ray propagation through
38
+ 3D grid-based inhomogeneous materials using trilinear interpolation.
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
+ @staticmethod
54
+ def jit(*args, **kwargs):
55
+ """Return a no-op decorator."""
56
+
57
+ def decorator(func):
58
+ return func
59
+
60
+ if args and callable(args[0]):
61
+ return args[0]
62
+ return decorator
63
+
64
+ @staticmethod
65
+ def is_available():
66
+ return False
67
+
68
+ @staticmethod
69
+ def grid(n):
70
+ return 0
71
+
72
+ @staticmethod
73
+ def synchronize():
74
+ pass
75
+
76
+ cuda = _FakeCuda() # type: ignore[assignment]
77
+ HAS_CUDA = False
78
+
79
+ from ..device_functions import device_euler_step
80
+ from ..registry import PropagationKernelID, register_kernel
81
+
82
+ SPEED_OF_LIGHT = 299792458.0
83
+
84
+
85
+ @cuda.jit(device=True)
86
+ def _device_trilinear_interpolate(
87
+ x: float,
88
+ y: float,
89
+ z: float,
90
+ grid,
91
+ x_min: float,
92
+ y_min: float,
93
+ z_min: float,
94
+ dx: float,
95
+ dy: float,
96
+ dz: float,
97
+ nx: int,
98
+ ny: int,
99
+ nz: int,
100
+ ) -> float:
101
+ """Trilinear interpolation in 3D grid."""
102
+ xn = (x - x_min) / dx
103
+ yn = (y - y_min) / dy
104
+ zn = (z - z_min) / dz
105
+
106
+ xn = max(0.0, min(xn, nx - 1.001))
107
+ yn = max(0.0, min(yn, ny - 1.001))
108
+ zn = max(0.0, min(zn, nz - 1.001))
109
+
110
+ x0 = int(xn)
111
+ y0 = int(yn)
112
+ z0 = int(zn)
113
+
114
+ x1 = min(x0 + 1, nx - 1)
115
+ y1 = min(y0 + 1, ny - 1)
116
+ z1 = min(z0 + 1, nz - 1)
117
+
118
+ xd = xn - x0
119
+ yd = yn - y0
120
+ zd = zn - z0
121
+
122
+ c000 = grid[x0, y0, z0]
123
+ c001 = grid[x0, y0, z1]
124
+ c010 = grid[x0, y1, z0]
125
+ c011 = grid[x0, y1, z1]
126
+ c100 = grid[x1, y0, z0]
127
+ c101 = grid[x1, y0, z1]
128
+ c110 = grid[x1, y1, z0]
129
+ c111 = grid[x1, y1, z1]
130
+
131
+ c00 = c000 * (1 - xd) + c100 * xd
132
+ c01 = c001 * (1 - xd) + c101 * xd
133
+ c10 = c010 * (1 - xd) + c110 * xd
134
+ c11 = c011 * (1 - xd) + c111 * xd
135
+
136
+ c0 = c00 * (1 - yd) + c10 * yd
137
+ c1 = c01 * (1 - yd) + c11 * yd
138
+
139
+ return c0 * (1 - zd) + c1 * zd
140
+
141
+
142
+ @cuda.jit(device=True)
143
+ def _device_grid_n_and_gradient(
144
+ x: float,
145
+ y: float,
146
+ z: float,
147
+ n_grid,
148
+ grad_grid,
149
+ x_min: float,
150
+ y_min: float,
151
+ z_min: float,
152
+ dx: float,
153
+ dy: float,
154
+ dz: float,
155
+ nx: int,
156
+ ny: int,
157
+ nz: int,
158
+ ) -> tuple[float, float, float, float]:
159
+ """Get n and gradient from GridInhomogeneousModel."""
160
+ n = _device_trilinear_interpolate(
161
+ x, y, z, n_grid, x_min, y_min, z_min, dx, dy, dz, nx, ny, nz
162
+ )
163
+
164
+ # Normalize coordinates for gradient interpolation
165
+ xn = (x - x_min) / dx
166
+ yn = (y - y_min) / dy
167
+ zn = (z - z_min) / dz
168
+
169
+ xn = max(0.0, min(xn, nx - 1.001))
170
+ yn = max(0.0, min(yn, ny - 1.001))
171
+ zn = max(0.0, min(zn, nz - 1.001))
172
+
173
+ x0 = int(xn)
174
+ y0 = int(yn)
175
+ z0 = int(zn)
176
+
177
+ x1 = min(x0 + 1, nx - 1)
178
+ y1 = min(y0 + 1, ny - 1)
179
+ z1 = min(z0 + 1, nz - 1)
180
+
181
+ xd = xn - x0
182
+ yd = yn - y0
183
+ zd = zn - z0
184
+
185
+ # Interpolate grad_x
186
+ c000 = grad_grid[x0, y0, z0, 0]
187
+ c001 = grad_grid[x0, y0, z1, 0]
188
+ c010 = grad_grid[x0, y1, z0, 0]
189
+ c011 = grad_grid[x0, y1, z1, 0]
190
+ c100 = grad_grid[x1, y0, z0, 0]
191
+ c101 = grad_grid[x1, y0, z1, 0]
192
+ c110 = grad_grid[x1, y1, z0, 0]
193
+ c111 = grad_grid[x1, y1, z1, 0]
194
+ c00 = c000 * (1 - xd) + c100 * xd
195
+ c01 = c001 * (1 - xd) + c101 * xd
196
+ c10 = c010 * (1 - xd) + c110 * xd
197
+ c11 = c011 * (1 - xd) + c111 * xd
198
+ c0 = c00 * (1 - yd) + c10 * yd
199
+ c1 = c01 * (1 - yd) + c11 * yd
200
+ grad_x = c0 * (1 - zd) + c1 * zd
201
+
202
+ # Interpolate grad_y
203
+ c000 = grad_grid[x0, y0, z0, 1]
204
+ c001 = grad_grid[x0, y0, z1, 1]
205
+ c010 = grad_grid[x0, y1, z0, 1]
206
+ c011 = grad_grid[x0, y1, z1, 1]
207
+ c100 = grad_grid[x1, y0, z0, 1]
208
+ c101 = grad_grid[x1, y0, z1, 1]
209
+ c110 = grad_grid[x1, y1, z0, 1]
210
+ c111 = grad_grid[x1, y1, z1, 1]
211
+ c00 = c000 * (1 - xd) + c100 * xd
212
+ c01 = c001 * (1 - xd) + c101 * xd
213
+ c10 = c010 * (1 - xd) + c110 * xd
214
+ c11 = c011 * (1 - xd) + c111 * xd
215
+ c0 = c00 * (1 - yd) + c10 * yd
216
+ c1 = c01 * (1 - yd) + c11 * yd
217
+ grad_y = c0 * (1 - zd) + c1 * zd
218
+
219
+ # Interpolate grad_z
220
+ c000 = grad_grid[x0, y0, z0, 2]
221
+ c001 = grad_grid[x0, y0, z1, 2]
222
+ c010 = grad_grid[x0, y1, z0, 2]
223
+ c011 = grad_grid[x0, y1, z1, 2]
224
+ c100 = grad_grid[x1, y0, z0, 2]
225
+ c101 = grad_grid[x1, y0, z1, 2]
226
+ c110 = grad_grid[x1, y1, z0, 2]
227
+ c111 = grad_grid[x1, y1, z1, 2]
228
+ c00 = c000 * (1 - xd) + c100 * xd
229
+ c01 = c001 * (1 - xd) + c101 * xd
230
+ c10 = c010 * (1 - xd) + c110 * xd
231
+ c11 = c011 * (1 - xd) + c111 * xd
232
+ c0 = c00 * (1 - yd) + c10 * yd
233
+ c1 = c01 * (1 - yd) + c11 * yd
234
+ grad_z = c0 * (1 - zd) + c1 * zd
235
+
236
+ return n, grad_x, grad_y, grad_z
237
+
238
+
239
+ @cuda.jit(device=True)
240
+ def _device_grid_euler_step(
241
+ x: float,
242
+ y: float,
243
+ z: float,
244
+ dir_x: float,
245
+ dir_y: float,
246
+ dir_z: float,
247
+ step_size: float,
248
+ n_grid,
249
+ grad_grid,
250
+ x_min: float,
251
+ y_min: float,
252
+ z_min: float,
253
+ dx: float,
254
+ dy: float,
255
+ dz: float,
256
+ nx: int,
257
+ ny: int,
258
+ nz: int,
259
+ ) -> tuple[float, float, float, float, float, float, float]:
260
+ """Euler step for GridInhomogeneousModel."""
261
+ n, grad_x, grad_y, grad_z = _device_grid_n_and_gradient(
262
+ x, y, z, n_grid, grad_grid, x_min, y_min, z_min, dx, dy, dz, nx, ny, nz
263
+ )
264
+
265
+ new_x, new_y, new_z, new_dx, new_dy, new_dz = device_euler_step(
266
+ x, y, z, dir_x, dir_y, dir_z, n, grad_x, grad_y, grad_z, step_size
267
+ )
268
+
269
+ return new_x, new_y, new_z, new_dx, new_dy, new_dz, n
270
+
271
+
272
+ @cuda.jit(device=True)
273
+ def _device_grid_rk4_step(
274
+ x: float,
275
+ y: float,
276
+ z: float,
277
+ dir_x: float,
278
+ dir_y: float,
279
+ dir_z: float,
280
+ step_size: float,
281
+ n_grid,
282
+ grad_grid,
283
+ x_min: float,
284
+ y_min: float,
285
+ z_min: float,
286
+ grid_dx: float,
287
+ grid_dy: float,
288
+ grid_dz: float,
289
+ nx: int,
290
+ ny: int,
291
+ nz: int,
292
+ ) -> tuple[float, float, float, float, float, float, float]:
293
+ """RK4 step for GridInhomogeneousModel."""
294
+ h = step_size
295
+ h2 = h / 2.0
296
+
297
+ def get_n_and_kappa(px, py, pz, dx, dy, dz):
298
+ n, gx, gy, gz = _device_grid_n_and_gradient(
299
+ px,
300
+ py,
301
+ pz,
302
+ n_grid,
303
+ grad_grid,
304
+ x_min,
305
+ y_min,
306
+ z_min,
307
+ grid_dx,
308
+ grid_dy,
309
+ grid_dz,
310
+ nx,
311
+ ny,
312
+ nz,
313
+ )
314
+ dot = dx * gx + dy * gy + dz * gz
315
+ kx = (gx - dot * dx) / n
316
+ ky = (gy - dot * dy) / n
317
+ kz = (gz - dot * dz) / n
318
+ return n, kx, ky, kz
319
+
320
+ def normalize(dx, dy, dz):
321
+ norm = math.sqrt(dx * dx + dy * dy + dz * dz)
322
+ if norm < 1e-12:
323
+ norm = 1.0
324
+ return dx / norm, dy / norm, dz / norm
325
+
326
+ # k1
327
+ n0, kx1, ky1, kz1 = get_n_and_kappa(x, y, z, dir_x, dir_y, dir_z)
328
+ k1_rx, k1_ry, k1_rz = dir_x, dir_y, dir_z
329
+ k1_dx, k1_dy, k1_dz = kx1, ky1, kz1
330
+
331
+ # k2
332
+ px = x + h2 * k1_rx
333
+ py = y + h2 * k1_ry
334
+ pz = z + h2 * k1_rz
335
+ dx, dy, dz = normalize(dir_x + h2 * k1_dx, dir_y + h2 * k1_dy, dir_z + h2 * k1_dz)
336
+ n1, kx2, ky2, kz2 = get_n_and_kappa(px, py, pz, dx, dy, dz)
337
+ k2_rx, k2_ry, k2_rz = dx, dy, dz
338
+ k2_dx, k2_dy, k2_dz = kx2, ky2, kz2
339
+
340
+ # k3
341
+ px = x + h2 * k2_rx
342
+ py = y + h2 * k2_ry
343
+ pz = z + h2 * k2_rz
344
+ dx, dy, dz = normalize(dir_x + h2 * k2_dx, dir_y + h2 * k2_dy, dir_z + h2 * k2_dz)
345
+ n2, kx3, ky3, kz3 = get_n_and_kappa(px, py, pz, dx, dy, dz)
346
+ k3_rx, k3_ry, k3_rz = dx, dy, dz
347
+ k3_dx, k3_dy, k3_dz = kx3, ky3, kz3
348
+
349
+ # k4
350
+ px = x + h * k3_rx
351
+ py = y + h * k3_ry
352
+ pz = z + h * k3_rz
353
+ dx, dy, dz = normalize(dir_x + h * k3_dx, dir_y + h * k3_dy, dir_z + h * k3_dz)
354
+ n3, kx4, ky4, kz4 = get_n_and_kappa(px, py, pz, dx, dy, dz)
355
+
356
+ # Final RK4 combination
357
+ new_x = x + (h / 6.0) * (k1_rx + 2 * k2_rx + 2 * k3_rx + dx)
358
+ new_y = y + (h / 6.0) * (k1_ry + 2 * k2_ry + 2 * k3_ry + dy)
359
+ new_z = z + (h / 6.0) * (k1_rz + 2 * k2_rz + 2 * k3_rz + dz)
360
+
361
+ new_dx = dir_x + (h / 6.0) * (k1_dx + 2 * k2_dx + 2 * k3_dx + kx4)
362
+ new_dy = dir_y + (h / 6.0) * (k1_dy + 2 * k2_dy + 2 * k3_dy + ky4)
363
+ new_dz = dir_z + (h / 6.0) * (k1_dz + 2 * k2_dz + 2 * k3_dz + kz4)
364
+
365
+ new_dx, new_dy, new_dz = normalize(new_dx, new_dy, new_dz)
366
+
367
+ n_avg = (n0 + 4 * n1 + n2) / 6.0
368
+
369
+ return new_x, new_y, new_z, new_dx, new_dy, new_dz, n_avg
370
+
371
+
372
+ @register_kernel(PropagationKernelID.GRID_EULER)
373
+ @cuda.jit
374
+ def _kernel_grid_inhomogeneous_euler(
375
+ positions,
376
+ directions,
377
+ active,
378
+ geo_path,
379
+ opt_path,
380
+ acc_time,
381
+ step_size: float,
382
+ num_steps: int,
383
+ x_min: float,
384
+ y_min: float,
385
+ z_min: float,
386
+ grid_dx: float,
387
+ grid_dy: float,
388
+ grid_dz: float,
389
+ nx: int,
390
+ ny: int,
391
+ nz: int,
392
+ n_grid,
393
+ grad_grid,
394
+ wavelength: float,
395
+ ):
396
+ """GPU kernel for GridInhomogeneousModel using Euler integration."""
397
+ c = SPEED_OF_LIGHT
398
+
399
+ idx = cuda.grid(1)
400
+ if idx >= positions.shape[0]:
401
+ return
402
+ if not active[idx]:
403
+ return
404
+
405
+ x = positions[idx, 0]
406
+ y = positions[idx, 1]
407
+ z = positions[idx, 2]
408
+ dx = directions[idx, 0]
409
+ dy = directions[idx, 1]
410
+ dz = directions[idx, 2]
411
+ gp = geo_path[idx]
412
+ op = opt_path[idx]
413
+ at = acc_time[idx]
414
+
415
+ for _ in range(num_steps):
416
+ x, y, z, dx, dy, dz, n = _device_grid_euler_step(
417
+ x,
418
+ y,
419
+ z,
420
+ dx,
421
+ dy,
422
+ dz,
423
+ step_size,
424
+ n_grid,
425
+ grad_grid,
426
+ x_min,
427
+ y_min,
428
+ z_min,
429
+ grid_dx,
430
+ grid_dy,
431
+ grid_dz,
432
+ nx,
433
+ ny,
434
+ nz,
435
+ )
436
+ gp += step_size
437
+ op += n * step_size
438
+ at += n * step_size / c
439
+
440
+ positions[idx, 0] = x
441
+ positions[idx, 1] = y
442
+ positions[idx, 2] = z
443
+ directions[idx, 0] = dx
444
+ directions[idx, 1] = dy
445
+ directions[idx, 2] = dz
446
+ geo_path[idx] = gp
447
+ opt_path[idx] = op
448
+ acc_time[idx] = at
449
+
450
+
451
+ @register_kernel(PropagationKernelID.GRID_RK4)
452
+ @cuda.jit
453
+ def _kernel_grid_inhomogeneous_rk4(
454
+ positions,
455
+ directions,
456
+ active,
457
+ geo_path,
458
+ opt_path,
459
+ acc_time,
460
+ step_size: float,
461
+ num_steps: int,
462
+ x_min: float,
463
+ y_min: float,
464
+ z_min: float,
465
+ grid_dx: float,
466
+ grid_dy: float,
467
+ grid_dz: float,
468
+ nx: int,
469
+ ny: int,
470
+ nz: int,
471
+ n_grid,
472
+ grad_grid,
473
+ wavelength: float,
474
+ ):
475
+ """GPU kernel for GridInhomogeneousModel using RK4 integration."""
476
+ c = SPEED_OF_LIGHT
477
+
478
+ idx = cuda.grid(1)
479
+ if idx >= positions.shape[0]:
480
+ return
481
+ if not active[idx]:
482
+ return
483
+
484
+ x = positions[idx, 0]
485
+ y = positions[idx, 1]
486
+ z = positions[idx, 2]
487
+ dx = directions[idx, 0]
488
+ dy = directions[idx, 1]
489
+ dz = directions[idx, 2]
490
+ gp = geo_path[idx]
491
+ op = opt_path[idx]
492
+ at = acc_time[idx]
493
+
494
+ for _ in range(num_steps):
495
+ x, y, z, dx, dy, dz, n_avg = _device_grid_rk4_step(
496
+ x,
497
+ y,
498
+ z,
499
+ dx,
500
+ dy,
501
+ dz,
502
+ step_size,
503
+ n_grid,
504
+ grad_grid,
505
+ x_min,
506
+ y_min,
507
+ z_min,
508
+ grid_dx,
509
+ grid_dy,
510
+ grid_dz,
511
+ nx,
512
+ ny,
513
+ nz,
514
+ )
515
+ gp += step_size
516
+ op += n_avg * step_size
517
+ at += n_avg * step_size / c
518
+
519
+ positions[idx, 0] = x
520
+ positions[idx, 1] = y
521
+ positions[idx, 2] = z
522
+ directions[idx, 0] = dx
523
+ directions[idx, 1] = dy
524
+ directions[idx, 2] = dz
525
+ geo_path[idx] = gp
526
+ opt_path[idx] = op
527
+ acc_time[idx] = at
@@ -0,0 +1,105 @@
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
+ Propagation Kernel Protocol
36
+
37
+ Defines the interface for material propagation kernels that integrate
38
+ rays through inhomogeneous materials.
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 PropagationKernelProtocol(Protocol):
51
+ """
52
+ Protocol for material propagation kernels.
53
+
54
+ Propagation kernels integrate rays through materials with spatially-varying
55
+ refractive index. They update ray positions, directions, and accumulated
56
+ path lengths based on the material's optical properties.
57
+
58
+ The kernel signature varies depending on the material type:
59
+ - Simple: 1D LUT interpolation (altitude only)
60
+ - Spectral: 2D LUT interpolation (altitude × wavelength)
61
+ - Grid: 3D trilinear interpolation
62
+
63
+ Common Parameters
64
+ -----------------
65
+ positions : DeviceNDArray, shape (N, 3)
66
+ Ray positions in meters (in/out)
67
+ directions : DeviceNDArray, shape (N, 3)
68
+ Ray direction unit vectors (in/out)
69
+ active : DeviceNDArray, shape (N,)
70
+ Boolean mask of active rays
71
+ geo_path : DeviceNDArray, shape (N,)
72
+ Accumulated geometric path length in meters (in/out)
73
+ opt_path : DeviceNDArray, shape (N,)
74
+ Accumulated optical path length (n·ds integral) (in/out)
75
+ acc_time : DeviceNDArray, shape (N,)
76
+ Accumulated travel time in seconds (in/out)
77
+ step_size : float
78
+ Integration step size in meters
79
+ num_steps : int
80
+ Number of integration steps to perform
81
+
82
+ Notes
83
+ -----
84
+ Kernels are registered with the central registry using @register_kernel
85
+ decorator and PropagationKernelID enum values.
86
+
87
+ The actual signature includes material-specific parameters (LUT arrays,
88
+ center coordinates, etc.) that vary by kernel type.
89
+ """
90
+
91
+ def __call__(
92
+ self,
93
+ positions: DeviceNDArray,
94
+ directions: DeviceNDArray,
95
+ active: DeviceNDArray,
96
+ geo_path: DeviceNDArray,
97
+ opt_path: DeviceNDArray,
98
+ acc_time: DeviceNDArray,
99
+ step_size: float,
100
+ num_steps: int,
101
+ *args,
102
+ **kwargs,
103
+ ) -> None:
104
+ """Execute the propagation kernel."""
105
+ ...