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,200 @@
|
|
|
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
|
+
Propagator Factory
|
|
36
|
+
|
|
37
|
+
Factory function for creating propagators with proper kernel/propagator
|
|
38
|
+
selection based on material compatibility declarations.
|
|
39
|
+
|
|
40
|
+
This module provides a unified interface for creating propagators,
|
|
41
|
+
handling the selection of the appropriate propagator and kernel based on:
|
|
42
|
+
1. Material's declared compatibility
|
|
43
|
+
2. User overrides at propagator creation time
|
|
44
|
+
3. Material instance preferences
|
|
45
|
+
4. Class defaults
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
from __future__ import annotations
|
|
49
|
+
|
|
50
|
+
from typing import TYPE_CHECKING, Any
|
|
51
|
+
|
|
52
|
+
from ..kernels.registry import PropagationKernelID, PropagatorID
|
|
53
|
+
|
|
54
|
+
if TYPE_CHECKING:
|
|
55
|
+
from ...materials.base.material_field import MaterialField
|
|
56
|
+
|
|
57
|
+
__all__ = ["create_propagator"]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def create_propagator(
|
|
61
|
+
material: MaterialField,
|
|
62
|
+
propagator_id: PropagatorID | None = None,
|
|
63
|
+
kernel_id: PropagationKernelID | None = None,
|
|
64
|
+
method: str | None = None,
|
|
65
|
+
prefer_gpu: bool = True,
|
|
66
|
+
**kwargs: Any,
|
|
67
|
+
) -> Any:
|
|
68
|
+
"""
|
|
69
|
+
Create the appropriate propagator for a material.
|
|
70
|
+
|
|
71
|
+
Selection priority (highest to lowest):
|
|
72
|
+
1. If propagator_id/kernel_id passed here → use those (override)
|
|
73
|
+
2. Else use material's stored preference (material._propagator_id, material._kernel_id)
|
|
74
|
+
3. Else use material's class defaults
|
|
75
|
+
|
|
76
|
+
Parameters
|
|
77
|
+
----------
|
|
78
|
+
material : MaterialField
|
|
79
|
+
The material to propagate through.
|
|
80
|
+
propagator_id : PropagatorID, optional
|
|
81
|
+
Override propagator selection. If None, uses material's preference.
|
|
82
|
+
kernel_id : PropagationKernelID, optional
|
|
83
|
+
Override kernel selection. If None, uses material's preference.
|
|
84
|
+
Note: Only used for GPU propagators.
|
|
85
|
+
method : str, optional
|
|
86
|
+
Integration method ("euler" or "rk4"). If None, determined from kernel_id.
|
|
87
|
+
prefer_gpu : bool, default True
|
|
88
|
+
If True and material supports GPU, use GPU propagator.
|
|
89
|
+
If False, force CPU propagator.
|
|
90
|
+
**kwargs
|
|
91
|
+
Additional propagator configuration (e.g., threads_per_block).
|
|
92
|
+
|
|
93
|
+
Returns
|
|
94
|
+
-------
|
|
95
|
+
Propagator
|
|
96
|
+
Configured propagator instance appropriate for the material.
|
|
97
|
+
|
|
98
|
+
Raises
|
|
99
|
+
------
|
|
100
|
+
ValueError
|
|
101
|
+
If propagator_id or kernel_id is not supported by the material.
|
|
102
|
+
|
|
103
|
+
Examples
|
|
104
|
+
--------
|
|
105
|
+
>>> from lsurf.materials import ExponentialAtmosphere
|
|
106
|
+
>>> from lsurf.propagation.propagators import create_propagator
|
|
107
|
+
>>> from lsurf.propagation.kernels import PropagatorID, PropagationKernelID
|
|
108
|
+
|
|
109
|
+
>>> # Use defaults (RK4 kernel, GPU propagator)
|
|
110
|
+
>>> atmo = ExponentialAtmosphere(n_sea_level=1.000293)
|
|
111
|
+
>>> propagator = create_propagator(atmo)
|
|
112
|
+
|
|
113
|
+
>>> # Override kernel at propagator creation time
|
|
114
|
+
>>> propagator_euler = create_propagator(
|
|
115
|
+
... atmo,
|
|
116
|
+
... kernel_id=PropagationKernelID.SIMPLE_EULER
|
|
117
|
+
... )
|
|
118
|
+
|
|
119
|
+
>>> # Force CPU propagator
|
|
120
|
+
>>> cpu_propagator = create_propagator(atmo, propagator_id=PropagatorID.CPU_GRADIENT)
|
|
121
|
+
|
|
122
|
+
>>> # Or use prefer_gpu=False
|
|
123
|
+
>>> cpu_propagator = create_propagator(atmo, prefer_gpu=False)
|
|
124
|
+
"""
|
|
125
|
+
# Resolve propagator: override > prefer_gpu > material instance > class default
|
|
126
|
+
resolved_propagator_id = propagator_id
|
|
127
|
+
if resolved_propagator_id is None:
|
|
128
|
+
# If prefer_gpu is False, force CPU propagator
|
|
129
|
+
if not prefer_gpu:
|
|
130
|
+
resolved_propagator_id = PropagatorID.CPU_GRADIENT
|
|
131
|
+
else:
|
|
132
|
+
resolved_propagator_id = getattr(material, "_propagator_id", None)
|
|
133
|
+
if resolved_propagator_id is None:
|
|
134
|
+
resolved_propagator_id = material.default_propagator()
|
|
135
|
+
|
|
136
|
+
# Validate propagator is supported
|
|
137
|
+
supported_propagators = material.supported_propagators()
|
|
138
|
+
if supported_propagators and resolved_propagator_id not in supported_propagators:
|
|
139
|
+
raise ValueError(
|
|
140
|
+
f"{material.__class__.__name__} does not support {resolved_propagator_id}. "
|
|
141
|
+
f"Supported: {supported_propagators}"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# Resolve kernel: override > material instance > class default
|
|
145
|
+
resolved_kernel_id = kernel_id
|
|
146
|
+
if resolved_kernel_id is None:
|
|
147
|
+
resolved_kernel_id = getattr(material, "_kernel_id", None)
|
|
148
|
+
if resolved_kernel_id is None:
|
|
149
|
+
resolved_kernel_id = material.default_kernel()
|
|
150
|
+
|
|
151
|
+
# Validate kernel is supported (if applicable)
|
|
152
|
+
supported_kernels = material.supported_kernels()
|
|
153
|
+
if supported_kernels and resolved_kernel_id is not None:
|
|
154
|
+
if resolved_kernel_id not in supported_kernels:
|
|
155
|
+
raise ValueError(
|
|
156
|
+
f"{material.__class__.__name__} does not support {resolved_kernel_id}. "
|
|
157
|
+
f"Supported: {supported_kernels}"
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
# Resolve method: parameter > kernel_id > default
|
|
161
|
+
resolved_method = method
|
|
162
|
+
if resolved_method is None:
|
|
163
|
+
resolved_method = "rk4" # Default
|
|
164
|
+
if resolved_kernel_id is not None:
|
|
165
|
+
# Map kernel ID to method string
|
|
166
|
+
if "EULER" in resolved_kernel_id.name:
|
|
167
|
+
resolved_method = "euler"
|
|
168
|
+
elif "RK4" in resolved_kernel_id.name:
|
|
169
|
+
resolved_method = "rk4"
|
|
170
|
+
|
|
171
|
+
# Create the appropriate propagator
|
|
172
|
+
if resolved_propagator_id == PropagatorID.CPU_GRADIENT:
|
|
173
|
+
from .gradient import GradientPropagator
|
|
174
|
+
|
|
175
|
+
return GradientPropagator(method=resolved_method, **kwargs)
|
|
176
|
+
|
|
177
|
+
elif resolved_propagator_id == PropagatorID.GPU_GRADIENT:
|
|
178
|
+
from .gpu_gradient import GPUGradientPropagator
|
|
179
|
+
|
|
180
|
+
return GPUGradientPropagator(
|
|
181
|
+
material=material, method=resolved_method, **kwargs
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
elif resolved_propagator_id == PropagatorID.GPU_SPECTRAL:
|
|
185
|
+
from .spectral_gpu_gradient import SpectralGPUGradientPropagator
|
|
186
|
+
|
|
187
|
+
return SpectralGPUGradientPropagator(
|
|
188
|
+
material=material, method=resolved_method, **kwargs
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
elif resolved_propagator_id == PropagatorID.GPU_SURFACE:
|
|
192
|
+
from .surface_propagator import SurfacePropagator
|
|
193
|
+
|
|
194
|
+
return SurfacePropagator(material=material, method=resolved_method, **kwargs)
|
|
195
|
+
|
|
196
|
+
else:
|
|
197
|
+
raise ValueError(
|
|
198
|
+
f"Unknown propagator ID: {resolved_propagator_id}. "
|
|
199
|
+
f"Available: {list(PropagatorID)}"
|
|
200
|
+
)
|
|
@@ -0,0 +1,305 @@
|
|
|
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
|
+
Fresnel Handler
|
|
36
|
+
|
|
37
|
+
GPU memory management for Fresnel coefficient kernels.
|
|
38
|
+
This module provides high-level interfaces that handle cuda.to_device(),
|
|
39
|
+
copy_to_host(), and kernel launching for the Fresnel kernels.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
import numpy as np
|
|
43
|
+
from numpy.typing import NDArray
|
|
44
|
+
|
|
45
|
+
from ..kernels.fresnel import (
|
|
46
|
+
kernel_fresnel_standard,
|
|
47
|
+
kernel_fresnel_polarized,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# GPU support is optional
|
|
51
|
+
try:
|
|
52
|
+
from numba import cuda
|
|
53
|
+
|
|
54
|
+
_HAS_CUDA = cuda.is_available()
|
|
55
|
+
except ImportError:
|
|
56
|
+
_HAS_CUDA = False
|
|
57
|
+
|
|
58
|
+
class _FakeCuda:
|
|
59
|
+
@staticmethod
|
|
60
|
+
def is_available():
|
|
61
|
+
return False
|
|
62
|
+
|
|
63
|
+
cuda = _FakeCuda() # type: ignore[assignment]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def fresnel_coefficients_gpu(
|
|
67
|
+
cos_theta_i: NDArray[np.float32],
|
|
68
|
+
n1: NDArray[np.float32] | float,
|
|
69
|
+
n2: NDArray[np.float32] | float,
|
|
70
|
+
polarization: str | NDArray[np.int32] = "unpolarized",
|
|
71
|
+
threads_per_block: int = 256,
|
|
72
|
+
) -> tuple[NDArray[np.float32], NDArray[np.float32]]:
|
|
73
|
+
"""
|
|
74
|
+
GPU-accelerated Fresnel coefficient calculation.
|
|
75
|
+
|
|
76
|
+
Parameters
|
|
77
|
+
----------
|
|
78
|
+
cos_theta_i : ndarray, shape (N,)
|
|
79
|
+
Cosine of incident angles
|
|
80
|
+
n1 : float or ndarray
|
|
81
|
+
Refractive index of incident medium
|
|
82
|
+
n2 : float or ndarray
|
|
83
|
+
Refractive index of transmitted medium
|
|
84
|
+
polarization : str or ndarray
|
|
85
|
+
Polarization state: 's', 'p', 'unpolarized', or per-ray int array
|
|
86
|
+
where 0=unpolarized, 1=s, 2=p
|
|
87
|
+
threads_per_block : int, optional
|
|
88
|
+
CUDA threads per block (default 256)
|
|
89
|
+
|
|
90
|
+
Returns
|
|
91
|
+
-------
|
|
92
|
+
reflectance : ndarray, shape (N,)
|
|
93
|
+
Reflection coefficient (0-1)
|
|
94
|
+
transmittance : ndarray, shape (N,)
|
|
95
|
+
Transmission coefficient (0-1)
|
|
96
|
+
"""
|
|
97
|
+
num_rays = len(cos_theta_i)
|
|
98
|
+
|
|
99
|
+
# Handle scalar n1/n2
|
|
100
|
+
n1_is_scalar = not isinstance(n1, np.ndarray) or n1.ndim == 0
|
|
101
|
+
n2_is_scalar = not isinstance(n2, np.ndarray) or n2.ndim == 0
|
|
102
|
+
|
|
103
|
+
n1_arr = np.atleast_1d(np.float32(n1) if n1_is_scalar else n1.astype(np.float32))
|
|
104
|
+
n2_arr = np.atleast_1d(np.float32(n2) if n2_is_scalar else n2.astype(np.float32))
|
|
105
|
+
|
|
106
|
+
# Handle polarization
|
|
107
|
+
if isinstance(polarization, str):
|
|
108
|
+
if polarization == "s":
|
|
109
|
+
pol_arr = np.ones(num_rays, dtype=np.int32)
|
|
110
|
+
elif polarization == "p":
|
|
111
|
+
pol_arr = np.full(num_rays, 2, dtype=np.int32)
|
|
112
|
+
else: # unpolarized
|
|
113
|
+
pol_arr = np.zeros(num_rays, dtype=np.int32)
|
|
114
|
+
use_polarized_kernel = polarization in ("s", "p")
|
|
115
|
+
else:
|
|
116
|
+
pol_arr = polarization.astype(np.int32)
|
|
117
|
+
use_polarized_kernel = True
|
|
118
|
+
|
|
119
|
+
if _HAS_CUDA:
|
|
120
|
+
# Allocate output arrays
|
|
121
|
+
reflectance = np.zeros(num_rays, dtype=np.float32)
|
|
122
|
+
transmittance = np.zeros(num_rays, dtype=np.float32)
|
|
123
|
+
|
|
124
|
+
# Transfer to GPU
|
|
125
|
+
d_cos_theta = cuda.to_device(cos_theta_i.astype(np.float32))
|
|
126
|
+
d_n1 = cuda.to_device(n1_arr)
|
|
127
|
+
d_n2 = cuda.to_device(n2_arr)
|
|
128
|
+
d_reflectance = cuda.to_device(reflectance)
|
|
129
|
+
d_transmittance = cuda.to_device(transmittance)
|
|
130
|
+
|
|
131
|
+
blocks = (num_rays + threads_per_block - 1) // threads_per_block
|
|
132
|
+
|
|
133
|
+
if use_polarized_kernel:
|
|
134
|
+
d_pol = cuda.to_device(pol_arr)
|
|
135
|
+
kernel_fresnel_polarized[blocks, threads_per_block](
|
|
136
|
+
d_cos_theta,
|
|
137
|
+
d_n1,
|
|
138
|
+
d_n2,
|
|
139
|
+
d_pol,
|
|
140
|
+
d_reflectance,
|
|
141
|
+
d_transmittance,
|
|
142
|
+
n1_is_scalar,
|
|
143
|
+
n2_is_scalar,
|
|
144
|
+
)
|
|
145
|
+
else:
|
|
146
|
+
kernel_fresnel_standard[blocks, threads_per_block](
|
|
147
|
+
d_cos_theta,
|
|
148
|
+
d_n1,
|
|
149
|
+
d_n2,
|
|
150
|
+
d_reflectance,
|
|
151
|
+
d_transmittance,
|
|
152
|
+
n1_is_scalar,
|
|
153
|
+
n2_is_scalar,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
cuda.synchronize()
|
|
157
|
+
reflectance = d_reflectance.copy_to_host()
|
|
158
|
+
transmittance = d_transmittance.copy_to_host()
|
|
159
|
+
else:
|
|
160
|
+
reflectance, transmittance = _fresnel_coefficients_cpu(
|
|
161
|
+
cos_theta_i,
|
|
162
|
+
n1,
|
|
163
|
+
n2,
|
|
164
|
+
polarization if isinstance(polarization, str) else "unpolarized",
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
return reflectance, transmittance
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def compute_reflection_direction(
|
|
171
|
+
incident: NDArray[np.float32],
|
|
172
|
+
normal: NDArray[np.float32],
|
|
173
|
+
) -> NDArray[np.float32]:
|
|
174
|
+
"""
|
|
175
|
+
Compute reflected ray direction using law of reflection.
|
|
176
|
+
|
|
177
|
+
Parameters
|
|
178
|
+
----------
|
|
179
|
+
incident : ndarray, shape (N, 3)
|
|
180
|
+
Incident ray directions (should be normalized)
|
|
181
|
+
normal : ndarray, shape (N, 3)
|
|
182
|
+
Surface normals (should be normalized, pointing toward incident side)
|
|
183
|
+
|
|
184
|
+
Returns
|
|
185
|
+
-------
|
|
186
|
+
reflected : ndarray, shape (N, 3)
|
|
187
|
+
Reflected ray directions (normalized)
|
|
188
|
+
"""
|
|
189
|
+
dot_in = np.sum(incident * normal, axis=1, keepdims=True)
|
|
190
|
+
reflected = incident - 2.0 * dot_in * normal
|
|
191
|
+
norms = np.linalg.norm(reflected, axis=1, keepdims=True)
|
|
192
|
+
return (reflected / (norms + 1e-10)).astype(np.float32)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def compute_refraction_direction(
|
|
196
|
+
incident: NDArray[np.float32],
|
|
197
|
+
normal: NDArray[np.float32],
|
|
198
|
+
n1: NDArray[np.float32] | float,
|
|
199
|
+
n2: NDArray[np.float32] | float,
|
|
200
|
+
) -> tuple[NDArray[np.float32], NDArray[np.bool_]]:
|
|
201
|
+
"""
|
|
202
|
+
Compute refracted ray direction using Snell's law.
|
|
203
|
+
|
|
204
|
+
Parameters
|
|
205
|
+
----------
|
|
206
|
+
incident : ndarray, shape (N, 3)
|
|
207
|
+
Incident ray directions (should be normalized)
|
|
208
|
+
normal : ndarray, shape (N, 3)
|
|
209
|
+
Surface normals (should be normalized)
|
|
210
|
+
n1 : float or ndarray
|
|
211
|
+
Refractive index of incident medium
|
|
212
|
+
n2 : float or ndarray
|
|
213
|
+
Refractive index of transmitted medium
|
|
214
|
+
|
|
215
|
+
Returns
|
|
216
|
+
-------
|
|
217
|
+
refracted : ndarray, shape (N, 3)
|
|
218
|
+
Refracted ray directions (normalized, zero for TIR rays)
|
|
219
|
+
tir_mask : ndarray, shape (N,)
|
|
220
|
+
Boolean mask indicating total internal reflection
|
|
221
|
+
"""
|
|
222
|
+
n1 = np.atleast_1d(n1).astype(np.float32)
|
|
223
|
+
n2 = np.atleast_1d(n2).astype(np.float32)
|
|
224
|
+
|
|
225
|
+
cos_theta_i = -np.sum(incident * normal, axis=1)
|
|
226
|
+
n_ratio = n1 / n2
|
|
227
|
+
|
|
228
|
+
sin_theta_t_sq = (n_ratio**2) * (1.0 - cos_theta_i**2)
|
|
229
|
+
tir_mask = sin_theta_t_sq > 1.0
|
|
230
|
+
|
|
231
|
+
cos_theta_t = np.sqrt(np.clip(1.0 - sin_theta_t_sq, 0, 1))
|
|
232
|
+
|
|
233
|
+
refracted = (
|
|
234
|
+
n_ratio[:, np.newaxis] * incident
|
|
235
|
+
+ (n_ratio * cos_theta_i - cos_theta_t)[:, np.newaxis] * normal
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
refracted[tir_mask] = 0.0
|
|
239
|
+
|
|
240
|
+
norms = np.linalg.norm(refracted, axis=1, keepdims=True)
|
|
241
|
+
refracted = np.where(norms > 1e-10, refracted / norms, 0.0)
|
|
242
|
+
|
|
243
|
+
return refracted.astype(np.float32), tir_mask
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
# =============================================================================
|
|
247
|
+
# CPU Fallback Implementation
|
|
248
|
+
# =============================================================================
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _fresnel_coefficients_cpu(
|
|
252
|
+
cos_theta_i: NDArray[np.float32],
|
|
253
|
+
n1: NDArray[np.float32] | float,
|
|
254
|
+
n2: NDArray[np.float32] | float,
|
|
255
|
+
polarization: str = "unpolarized",
|
|
256
|
+
) -> tuple[NDArray[np.float32], NDArray[np.float32]]:
|
|
257
|
+
"""CPU implementation of Fresnel coefficient calculation."""
|
|
258
|
+
# Ensure arrays
|
|
259
|
+
cos_theta_i = np.atleast_1d(cos_theta_i).astype(np.float32)
|
|
260
|
+
n1 = np.atleast_1d(n1).astype(np.float32)
|
|
261
|
+
n2 = np.atleast_1d(n2).astype(np.float32)
|
|
262
|
+
|
|
263
|
+
n_ratio = n1 / n2
|
|
264
|
+
sin_theta_i_sq = 1.0 - cos_theta_i**2
|
|
265
|
+
sin_theta_t_sq = (n_ratio**2) * sin_theta_i_sq
|
|
266
|
+
|
|
267
|
+
# Check for TIR
|
|
268
|
+
tir_mask = sin_theta_t_sq > 1.0
|
|
269
|
+
cos_theta_t = np.sqrt(np.clip(1.0 - sin_theta_t_sq, 0, 1))
|
|
270
|
+
|
|
271
|
+
# Fresnel amplitude coefficients
|
|
272
|
+
r_s_num = n1 * cos_theta_i - n2 * cos_theta_t
|
|
273
|
+
r_s_den = n1 * cos_theta_i + n2 * cos_theta_t
|
|
274
|
+
r_s = r_s_num / (r_s_den + 1e-10)
|
|
275
|
+
|
|
276
|
+
r_p_num = n2 * cos_theta_i - n1 * cos_theta_t
|
|
277
|
+
r_p_den = n2 * cos_theta_i + n1 * cos_theta_t
|
|
278
|
+
r_p = r_p_num / (r_p_den + 1e-10)
|
|
279
|
+
|
|
280
|
+
# Intensity coefficients
|
|
281
|
+
R_s = r_s**2
|
|
282
|
+
R_p = r_p**2
|
|
283
|
+
|
|
284
|
+
# Handle TIR
|
|
285
|
+
R_s = np.where(tir_mask, 1.0, R_s)
|
|
286
|
+
R_p = np.where(tir_mask, 1.0, R_p)
|
|
287
|
+
|
|
288
|
+
# Select based on polarization
|
|
289
|
+
if polarization == "s":
|
|
290
|
+
R = R_s
|
|
291
|
+
elif polarization == "p":
|
|
292
|
+
R = R_p
|
|
293
|
+
else:
|
|
294
|
+
R = 0.5 * (R_s + R_p)
|
|
295
|
+
|
|
296
|
+
T = 1.0 - R
|
|
297
|
+
|
|
298
|
+
return R.astype(np.float32), T.astype(np.float32)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
__all__ = [
|
|
302
|
+
"fresnel_coefficients_gpu",
|
|
303
|
+
"compute_reflection_direction",
|
|
304
|
+
"compute_refraction_direction",
|
|
305
|
+
]
|