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,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
|
+
Material Field Base Class
|
|
36
|
+
|
|
37
|
+
Defines the abstract base class for all material types. Materials provide
|
|
38
|
+
spatially-varying optical properties including refractive index, absorption,
|
|
39
|
+
and scattering coefficients.
|
|
40
|
+
|
|
41
|
+
Design Notes
|
|
42
|
+
------------
|
|
43
|
+
- Materials are position-dependent by default (field concept)
|
|
44
|
+
- Homogeneous materials can optimize for constant properties
|
|
45
|
+
- GPU-compatible interface for raytracing kernels
|
|
46
|
+
- Materials declare supported kernels and propagators for compatibility checking
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
from __future__ import annotations
|
|
50
|
+
|
|
51
|
+
import math
|
|
52
|
+
from abc import ABC, abstractmethod
|
|
53
|
+
from typing import TYPE_CHECKING
|
|
54
|
+
|
|
55
|
+
import numpy as np
|
|
56
|
+
from numpy.typing import NDArray
|
|
57
|
+
|
|
58
|
+
if TYPE_CHECKING:
|
|
59
|
+
from ...propagation.kernels.registry import PropagationKernelID, PropagatorID
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class MaterialField(ABC):
|
|
63
|
+
"""
|
|
64
|
+
Abstract base class for spatially-varying material properties.
|
|
65
|
+
|
|
66
|
+
All material properties are functions of position (x, y, z) and wavelength.
|
|
67
|
+
Subclasses must implement methods that can be called from both CPU and GPU.
|
|
68
|
+
|
|
69
|
+
Parameters
|
|
70
|
+
----------
|
|
71
|
+
name : str, optional
|
|
72
|
+
Descriptive name for this material. Default is "Material".
|
|
73
|
+
|
|
74
|
+
Attributes
|
|
75
|
+
----------
|
|
76
|
+
name : str
|
|
77
|
+
Material identifier.
|
|
78
|
+
|
|
79
|
+
Notes
|
|
80
|
+
-----
|
|
81
|
+
Material properties modeled:
|
|
82
|
+
- Refractive index n: Real part of complex refractive index
|
|
83
|
+
- Absorption coefficient α: Controls intensity decay
|
|
84
|
+
- Scattering coefficient μ_s: Controls scattering mean free path
|
|
85
|
+
- Anisotropy factor g: Henyey-Greenstein scattering parameter
|
|
86
|
+
|
|
87
|
+
References
|
|
88
|
+
----------
|
|
89
|
+
.. [1] Born, M., & Wolf, E. (1999). Principles of Optics (7th ed.).
|
|
90
|
+
Cambridge University Press.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
# =========================================================================
|
|
94
|
+
# CLASS-LEVEL COMPATIBILITY DECLARATIONS
|
|
95
|
+
# =========================================================================
|
|
96
|
+
# Subclasses should override these to declare supported kernels/propagators
|
|
97
|
+
_supported_kernels: list[PropagationKernelID] = []
|
|
98
|
+
_default_kernel: PropagationKernelID | None = None
|
|
99
|
+
_supported_propagators: list[PropagatorID] = []
|
|
100
|
+
_default_propagator: PropagatorID | None = None
|
|
101
|
+
|
|
102
|
+
def __init__(
|
|
103
|
+
self,
|
|
104
|
+
name: str = "Material",
|
|
105
|
+
kernel: PropagationKernelID | None = None,
|
|
106
|
+
propagator: PropagatorID | None = None,
|
|
107
|
+
):
|
|
108
|
+
"""
|
|
109
|
+
Initialize material field.
|
|
110
|
+
|
|
111
|
+
Parameters
|
|
112
|
+
----------
|
|
113
|
+
name : str, optional
|
|
114
|
+
Descriptive name for this material. Default is "Material".
|
|
115
|
+
kernel : PropagationKernelID, optional
|
|
116
|
+
Override the default propagation kernel. Must be in supported_kernels().
|
|
117
|
+
If None, uses the class default.
|
|
118
|
+
propagator : PropagatorID, optional
|
|
119
|
+
Override the default propagator. Must be in supported_propagators().
|
|
120
|
+
If None, uses the class default.
|
|
121
|
+
|
|
122
|
+
Raises
|
|
123
|
+
------
|
|
124
|
+
ValueError
|
|
125
|
+
If kernel or propagator is not supported by this material type.
|
|
126
|
+
"""
|
|
127
|
+
self.name = name
|
|
128
|
+
self._is_homogeneous = False
|
|
129
|
+
|
|
130
|
+
# Resolve kernel preference
|
|
131
|
+
if kernel is None:
|
|
132
|
+
self._kernel_id = self._default_kernel
|
|
133
|
+
else:
|
|
134
|
+
if self._supported_kernels and kernel not in self._supported_kernels:
|
|
135
|
+
raise ValueError(
|
|
136
|
+
f"{self.__class__.__name__} does not support kernel {kernel}. "
|
|
137
|
+
f"Supported: {self._supported_kernels}"
|
|
138
|
+
)
|
|
139
|
+
self._kernel_id = kernel
|
|
140
|
+
|
|
141
|
+
# Resolve propagator preference
|
|
142
|
+
if propagator is None:
|
|
143
|
+
self._propagator_id = self._default_propagator
|
|
144
|
+
else:
|
|
145
|
+
if (
|
|
146
|
+
self._supported_propagators
|
|
147
|
+
and propagator not in self._supported_propagators
|
|
148
|
+
):
|
|
149
|
+
raise ValueError(
|
|
150
|
+
f"{self.__class__.__name__} does not support propagator {propagator}. "
|
|
151
|
+
f"Supported: {self._supported_propagators}"
|
|
152
|
+
)
|
|
153
|
+
self._propagator_id = propagator
|
|
154
|
+
|
|
155
|
+
@abstractmethod
|
|
156
|
+
def get_refractive_index(
|
|
157
|
+
self,
|
|
158
|
+
x: float | NDArray[np.float64],
|
|
159
|
+
y: float | NDArray[np.float64],
|
|
160
|
+
z: float | NDArray[np.float64],
|
|
161
|
+
wavelength: float | NDArray[np.float64],
|
|
162
|
+
) -> float | NDArray[np.float64]:
|
|
163
|
+
"""
|
|
164
|
+
Get refractive index at position (x, y, z) for given wavelength.
|
|
165
|
+
|
|
166
|
+
Parameters
|
|
167
|
+
----------
|
|
168
|
+
x, y, z : float or ndarray
|
|
169
|
+
Position coordinates in meters.
|
|
170
|
+
wavelength : float or ndarray
|
|
171
|
+
Wavelength in meters.
|
|
172
|
+
|
|
173
|
+
Returns
|
|
174
|
+
-------
|
|
175
|
+
n : float or ndarray
|
|
176
|
+
Real part of refractive index (dimensionless).
|
|
177
|
+
|
|
178
|
+
Notes
|
|
179
|
+
-----
|
|
180
|
+
For absorbing materials, this returns only Re(n). Use
|
|
181
|
+
get_absorption_coefficient() for the imaginary part.
|
|
182
|
+
"""
|
|
183
|
+
pass
|
|
184
|
+
|
|
185
|
+
@abstractmethod
|
|
186
|
+
def get_refractive_index_gradient(
|
|
187
|
+
self,
|
|
188
|
+
x: float | NDArray[np.float64],
|
|
189
|
+
y: float | NDArray[np.float64],
|
|
190
|
+
z: float | NDArray[np.float64],
|
|
191
|
+
wavelength: float | NDArray[np.float64],
|
|
192
|
+
) -> tuple[
|
|
193
|
+
float | NDArray[np.float64],
|
|
194
|
+
float | NDArray[np.float64],
|
|
195
|
+
float | NDArray[np.float64],
|
|
196
|
+
]:
|
|
197
|
+
"""
|
|
198
|
+
Get gradient of refractive index ∇n at position (x, y, z).
|
|
199
|
+
|
|
200
|
+
Parameters
|
|
201
|
+
----------
|
|
202
|
+
x, y, z : float or ndarray
|
|
203
|
+
Position coordinates in meters.
|
|
204
|
+
wavelength : float or ndarray
|
|
205
|
+
Wavelength in meters.
|
|
206
|
+
|
|
207
|
+
Returns
|
|
208
|
+
-------
|
|
209
|
+
grad_n : tuple of (float or ndarray)
|
|
210
|
+
(∂n/∂x, ∂n/∂y, ∂n/∂z) in units of m⁻¹.
|
|
211
|
+
|
|
212
|
+
Notes
|
|
213
|
+
-----
|
|
214
|
+
For homogeneous materials, this returns (0, 0, 0).
|
|
215
|
+
Gradient drives ray curvature via the eikonal equation:
|
|
216
|
+
d²r/ds² = ∇n(r)/n(r)
|
|
217
|
+
|
|
218
|
+
References
|
|
219
|
+
----------
|
|
220
|
+
.. [1] Born & Wolf (1999), Section 3.1.
|
|
221
|
+
"""
|
|
222
|
+
pass
|
|
223
|
+
|
|
224
|
+
@abstractmethod
|
|
225
|
+
def get_absorption_coefficient(
|
|
226
|
+
self,
|
|
227
|
+
x: float | NDArray[np.float64],
|
|
228
|
+
y: float | NDArray[np.float64],
|
|
229
|
+
z: float | NDArray[np.float64],
|
|
230
|
+
wavelength: float | NDArray[np.float64],
|
|
231
|
+
) -> float | NDArray[np.float64]:
|
|
232
|
+
"""
|
|
233
|
+
Get absorption coefficient α at position (x, y, z).
|
|
234
|
+
|
|
235
|
+
Parameters
|
|
236
|
+
----------
|
|
237
|
+
x, y, z : float or ndarray
|
|
238
|
+
Position coordinates in meters.
|
|
239
|
+
wavelength : float or ndarray
|
|
240
|
+
Wavelength in meters.
|
|
241
|
+
|
|
242
|
+
Returns
|
|
243
|
+
-------
|
|
244
|
+
alpha : float or ndarray
|
|
245
|
+
Absorption coefficient in m⁻¹.
|
|
246
|
+
|
|
247
|
+
Notes
|
|
248
|
+
-----
|
|
249
|
+
Intensity decays as I(d) = I₀ exp(-αd) (Beer-Lambert law).
|
|
250
|
+
|
|
251
|
+
References
|
|
252
|
+
----------
|
|
253
|
+
.. [1] https://en.wikipedia.org/wiki/Beer%E2%80%93Lambert_law
|
|
254
|
+
"""
|
|
255
|
+
pass
|
|
256
|
+
|
|
257
|
+
@abstractmethod
|
|
258
|
+
def get_scattering_coefficient(
|
|
259
|
+
self,
|
|
260
|
+
x: float | NDArray[np.float64],
|
|
261
|
+
y: float | NDArray[np.float64],
|
|
262
|
+
z: float | NDArray[np.float64],
|
|
263
|
+
wavelength: float | NDArray[np.float64],
|
|
264
|
+
) -> float | NDArray[np.float64]:
|
|
265
|
+
"""
|
|
266
|
+
Get scattering coefficient μ_s at position (x, y, z).
|
|
267
|
+
|
|
268
|
+
Parameters
|
|
269
|
+
----------
|
|
270
|
+
x, y, z : float or ndarray
|
|
271
|
+
Position coordinates in meters.
|
|
272
|
+
wavelength : float or ndarray
|
|
273
|
+
Wavelength in meters.
|
|
274
|
+
|
|
275
|
+
Returns
|
|
276
|
+
-------
|
|
277
|
+
mu_s : float or ndarray
|
|
278
|
+
Scattering coefficient in m⁻¹.
|
|
279
|
+
|
|
280
|
+
Notes
|
|
281
|
+
-----
|
|
282
|
+
Mean free path between scattering events: ℓ_s = 1/μ_s.
|
|
283
|
+
"""
|
|
284
|
+
pass
|
|
285
|
+
|
|
286
|
+
def get_extinction_coefficient(
|
|
287
|
+
self,
|
|
288
|
+
x: float | NDArray[np.float64],
|
|
289
|
+
y: float | NDArray[np.float64],
|
|
290
|
+
z: float | NDArray[np.float64],
|
|
291
|
+
wavelength: float | NDArray[np.float64],
|
|
292
|
+
) -> float | NDArray[np.float64]:
|
|
293
|
+
"""
|
|
294
|
+
Get total extinction coefficient (absorption + scattering).
|
|
295
|
+
|
|
296
|
+
Parameters
|
|
297
|
+
----------
|
|
298
|
+
x, y, z : float or ndarray
|
|
299
|
+
Position coordinates in meters.
|
|
300
|
+
wavelength : float or ndarray
|
|
301
|
+
Wavelength in meters.
|
|
302
|
+
|
|
303
|
+
Returns
|
|
304
|
+
-------
|
|
305
|
+
mu_t : float or ndarray
|
|
306
|
+
Total extinction coefficient in m⁻¹.
|
|
307
|
+
"""
|
|
308
|
+
alpha = self.get_absorption_coefficient(x, y, z, wavelength)
|
|
309
|
+
mu_s = self.get_scattering_coefficient(x, y, z, wavelength)
|
|
310
|
+
return alpha + mu_s
|
|
311
|
+
|
|
312
|
+
def get_anisotropy_factor(
|
|
313
|
+
self,
|
|
314
|
+
x: float | NDArray[np.float64],
|
|
315
|
+
y: float | NDArray[np.float64],
|
|
316
|
+
z: float | NDArray[np.float64],
|
|
317
|
+
wavelength: float | NDArray[np.float64],
|
|
318
|
+
) -> float | NDArray[np.float64]:
|
|
319
|
+
"""
|
|
320
|
+
Get scattering anisotropy factor g (Henyey-Greenstein parameter).
|
|
321
|
+
|
|
322
|
+
Parameters
|
|
323
|
+
----------
|
|
324
|
+
x, y, z : float or ndarray
|
|
325
|
+
Position coordinates in meters.
|
|
326
|
+
wavelength : float or ndarray
|
|
327
|
+
Wavelength in meters.
|
|
328
|
+
|
|
329
|
+
Returns
|
|
330
|
+
-------
|
|
331
|
+
g : float or ndarray
|
|
332
|
+
Anisotropy factor, range [-1, 1].
|
|
333
|
+
- g = 0: isotropic scattering
|
|
334
|
+
- g > 0: forward scattering
|
|
335
|
+
- g < 0: backward scattering
|
|
336
|
+
|
|
337
|
+
Notes
|
|
338
|
+
-----
|
|
339
|
+
Default implementation returns 0 (isotropic).
|
|
340
|
+
Used in Henyey-Greenstein phase function:
|
|
341
|
+
p(cos θ) = (1-g²) / (1 + g² - 2g cos θ)^(3/2)
|
|
342
|
+
|
|
343
|
+
References
|
|
344
|
+
----------
|
|
345
|
+
.. [1] Henyey & Greenstein (1941). Astrophysical Journal, 93, 70-83.
|
|
346
|
+
"""
|
|
347
|
+
if isinstance(x, np.ndarray):
|
|
348
|
+
return np.zeros_like(x)
|
|
349
|
+
return 0.0
|
|
350
|
+
|
|
351
|
+
def is_homogeneous(self) -> bool:
|
|
352
|
+
"""
|
|
353
|
+
Check if material has uniform properties (no spatial variation).
|
|
354
|
+
|
|
355
|
+
Returns
|
|
356
|
+
-------
|
|
357
|
+
bool
|
|
358
|
+
True if material is homogeneous, False if inhomogeneous.
|
|
359
|
+
|
|
360
|
+
Notes
|
|
361
|
+
-----
|
|
362
|
+
Homogeneous materials enable optimizations (straight-line propagation).
|
|
363
|
+
"""
|
|
364
|
+
return self._is_homogeneous
|
|
365
|
+
|
|
366
|
+
# =========================================================================
|
|
367
|
+
# COMPATIBILITY QUERY METHODS
|
|
368
|
+
# =========================================================================
|
|
369
|
+
|
|
370
|
+
@classmethod
|
|
371
|
+
def supported_kernels(cls) -> list[PropagationKernelID]:
|
|
372
|
+
"""
|
|
373
|
+
Return list of propagation kernels supported by this material type.
|
|
374
|
+
|
|
375
|
+
Returns
|
|
376
|
+
-------
|
|
377
|
+
list[PropagationKernelID]
|
|
378
|
+
List of kernel IDs this material supports.
|
|
379
|
+
|
|
380
|
+
Examples
|
|
381
|
+
--------
|
|
382
|
+
>>> from lsurf.materials import ExponentialAtmosphere
|
|
383
|
+
>>> ExponentialAtmosphere.supported_kernels()
|
|
384
|
+
[<PropagationKernelID.SIMPLE_EULER: ...>, <PropagationKernelID.SIMPLE_RK4: ...>]
|
|
385
|
+
"""
|
|
386
|
+
|
|
387
|
+
# Return a copy to prevent modification
|
|
388
|
+
return list(cls._supported_kernels)
|
|
389
|
+
|
|
390
|
+
@classmethod
|
|
391
|
+
def default_kernel(cls) -> PropagationKernelID | None:
|
|
392
|
+
"""
|
|
393
|
+
Return the default propagation kernel for this material type.
|
|
394
|
+
|
|
395
|
+
Returns
|
|
396
|
+
-------
|
|
397
|
+
PropagationKernelID or None
|
|
398
|
+
The default kernel ID, or None if no GPU kernels are supported.
|
|
399
|
+
|
|
400
|
+
Examples
|
|
401
|
+
--------
|
|
402
|
+
>>> from lsurf.materials import ExponentialAtmosphere
|
|
403
|
+
>>> ExponentialAtmosphere.default_kernel()
|
|
404
|
+
<PropagationKernelID.SIMPLE_RK4: ...>
|
|
405
|
+
"""
|
|
406
|
+
return cls._default_kernel
|
|
407
|
+
|
|
408
|
+
@classmethod
|
|
409
|
+
def supported_propagators(cls) -> list[PropagatorID]:
|
|
410
|
+
"""
|
|
411
|
+
Return list of propagators supported by this material type.
|
|
412
|
+
|
|
413
|
+
Returns
|
|
414
|
+
-------
|
|
415
|
+
list[PropagatorID]
|
|
416
|
+
List of propagator IDs this material supports.
|
|
417
|
+
|
|
418
|
+
Examples
|
|
419
|
+
--------
|
|
420
|
+
>>> from lsurf.materials import ExponentialAtmosphere
|
|
421
|
+
>>> ExponentialAtmosphere.supported_propagators()
|
|
422
|
+
[<PropagatorID.GPU_GRADIENT: ...>, <PropagatorID.CPU_GRADIENT: ...>]
|
|
423
|
+
"""
|
|
424
|
+
|
|
425
|
+
return list(cls._supported_propagators)
|
|
426
|
+
|
|
427
|
+
@classmethod
|
|
428
|
+
def default_propagator(cls) -> PropagatorID | None:
|
|
429
|
+
"""
|
|
430
|
+
Return the default propagator for this material type.
|
|
431
|
+
|
|
432
|
+
Returns
|
|
433
|
+
-------
|
|
434
|
+
PropagatorID or None
|
|
435
|
+
The default propagator ID, or None if not specified.
|
|
436
|
+
|
|
437
|
+
Examples
|
|
438
|
+
--------
|
|
439
|
+
>>> from lsurf.materials import ExponentialAtmosphere
|
|
440
|
+
>>> ExponentialAtmosphere.default_propagator()
|
|
441
|
+
<PropagatorID.GPU_GRADIENT: ...>
|
|
442
|
+
"""
|
|
443
|
+
return cls._default_propagator
|
|
444
|
+
|
|
445
|
+
@property
|
|
446
|
+
def kernel_id(self) -> PropagationKernelID | None:
|
|
447
|
+
"""
|
|
448
|
+
Return the kernel ID configured for this material instance.
|
|
449
|
+
|
|
450
|
+
Returns
|
|
451
|
+
-------
|
|
452
|
+
PropagationKernelID or None
|
|
453
|
+
The kernel ID selected for this instance, or None if not applicable.
|
|
454
|
+
"""
|
|
455
|
+
return self._kernel_id
|
|
456
|
+
|
|
457
|
+
@property
|
|
458
|
+
def propagator_id(self) -> PropagatorID | None:
|
|
459
|
+
"""
|
|
460
|
+
Return the propagator ID configured for this material instance.
|
|
461
|
+
|
|
462
|
+
Returns
|
|
463
|
+
-------
|
|
464
|
+
PropagatorID or None
|
|
465
|
+
The propagator ID selected for this instance, or None if not applicable.
|
|
466
|
+
"""
|
|
467
|
+
return self._propagator_id
|
|
468
|
+
|
|
469
|
+
def get_refractive_index_gradient_magnitude(
|
|
470
|
+
self,
|
|
471
|
+
x: float | NDArray[np.float64],
|
|
472
|
+
y: float | NDArray[np.float64],
|
|
473
|
+
z: float | NDArray[np.float64],
|
|
474
|
+
wavelength: float | NDArray[np.float64],
|
|
475
|
+
) -> float | NDArray[np.float64]:
|
|
476
|
+
"""
|
|
477
|
+
Get magnitude of refractive index gradient |∇n|.
|
|
478
|
+
|
|
479
|
+
Parameters
|
|
480
|
+
----------
|
|
481
|
+
x, y, z : float or ndarray
|
|
482
|
+
Position coordinates in meters.
|
|
483
|
+
wavelength : float or ndarray
|
|
484
|
+
Wavelength in meters.
|
|
485
|
+
|
|
486
|
+
Returns
|
|
487
|
+
-------
|
|
488
|
+
grad_mag : float or ndarray
|
|
489
|
+
Magnitude of gradient |∇n| in m⁻¹.
|
|
490
|
+
"""
|
|
491
|
+
grad_x, grad_y, grad_z = self.get_refractive_index_gradient(x, y, z, wavelength)
|
|
492
|
+
|
|
493
|
+
if isinstance(grad_x, np.ndarray):
|
|
494
|
+
return np.sqrt(grad_x**2 + grad_y**2 + grad_z**2)
|
|
495
|
+
else:
|
|
496
|
+
return math.sqrt(grad_x**2 + grad_y**2 + grad_z**2)
|
|
497
|
+
|
|
498
|
+
def compute_phase_velocity(
|
|
499
|
+
self,
|
|
500
|
+
x: float | NDArray[np.float64],
|
|
501
|
+
y: float | NDArray[np.float64],
|
|
502
|
+
z: float | NDArray[np.float64],
|
|
503
|
+
wavelength: float | NDArray[np.float64],
|
|
504
|
+
) -> float | NDArray[np.float64]:
|
|
505
|
+
"""
|
|
506
|
+
Compute phase velocity v_p = c/n at position.
|
|
507
|
+
|
|
508
|
+
Parameters
|
|
509
|
+
----------
|
|
510
|
+
x, y, z : float or ndarray
|
|
511
|
+
Position coordinates in meters.
|
|
512
|
+
wavelength : float or ndarray
|
|
513
|
+
Wavelength in meters.
|
|
514
|
+
|
|
515
|
+
Returns
|
|
516
|
+
-------
|
|
517
|
+
v_p : float or ndarray
|
|
518
|
+
Phase velocity in m/s.
|
|
519
|
+
"""
|
|
520
|
+
c = 299792458.0 # Speed of light in vacuum (m/s)
|
|
521
|
+
n = self.get_refractive_index(x, y, z, wavelength)
|
|
522
|
+
return c / n
|
|
523
|
+
|
|
524
|
+
def __repr__(self) -> str:
|
|
525
|
+
"""Return string representation."""
|
|
526
|
+
homo_str = "homogeneous" if self._is_homogeneous else "inhomogeneous"
|
|
527
|
+
return f"<{self.__class__.__name__}(name='{self.name}', {homo_str})>"
|