# The Clear BSD License
#
# Copyright (c) 2026 Tobias Heibges
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted (subject to the limitations in the disclaimer
# below) provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from this
# software without specific prior written permission.
#
# NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY
# THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
"""
Kernel and Propagator Registry
Central registry for all kernel types in the ray tracing system:
- PropagationKernelID: Material propagation kernels
- IntersectionKernelID: Surface intersection kernels
- DetectionKernelID: Ray detection kernels
- FresnelKernelID: Fresnel reflection/refraction kernels
- PropagatorID: Propagator implementations
This module provides:
- Kernel ID enums for each category
- Registration decorator and lookup functions
- Unified registry for all kernel types
"""
from enum import Enum, auto
from typing import Callable
__all__ = [
# Enums
"PropagationKernelID",
"IntersectionKernelID",
"DetectionKernelID",
"FresnelKernelID",
"PropagatorID",
"KernelID",
# Registration
"register_kernel",
"get_kernel",
"get_registered_kernels",
"list_kernels",
]
# =============================================================================
# Kernel Identifier Enums
# =============================================================================
[docs]
class PropagationKernelID(Enum):
"""
Identifiers for material propagation kernels.
Each kernel corresponds to a specific integration method and material type.
Materials declare which kernels they support, and propagators use these
IDs to select the appropriate CUDA kernel.
Naming convention: {MATERIAL_TYPE}_{INTEGRATION_METHOD}
Examples
--------
>>> from lsurf.propagation.kernels import PropagationKernelID
>>> PropagationKernelID.SIMPLE_RK4
<PropagationKernelID.SIMPLE_RK4: ...>
"""
# Simple inhomogeneous (1D LUT - altitude only)
SIMPLE_EULER = auto()
SIMPLE_RK4 = auto()
# Spectral inhomogeneous (2D LUT - altitude × wavelength)
SPECTRAL_EULER = auto()
SPECTRAL_RK4 = auto()
SPECTRAL_EULER_PERRAY = auto()
SPECTRAL_RK4_PERRAY = auto()
# Grid inhomogeneous (3D grid with trilinear interpolation)
GRID_EULER = auto()
GRID_RK4 = auto()
# Duct atmosphere (analytical, no LUT)
DUCT_EULER = auto()
DUCT_RK4 = auto()
DUCT_EULER_ADAPTIVE = auto()
DUCT_RK4_ADAPTIVE = auto()
# US Standard duct atmosphere (analytical, no LUT)
US_DUCT_EULER = auto()
US_DUCT_RK4 = auto()
US_DUCT_EULER_ADAPTIVE = auto()
US_DUCT_RK4_ADAPTIVE = auto()
[docs]
class IntersectionKernelID(Enum):
"""
Identifiers for surface intersection kernels.
Each kernel corresponds to a specific geometry type and intersection method.
Surfaces declare which kernels they support for ray-surface intersection.
Note: Complex surfaces (waves, curved surfaces) use the generic signed
distance kernels which dispatch to device functions based on geometry_id.
Naming convention: {GEOMETRY_TYPE}_{METHOD}
Examples
--------
>>> from lsurf.propagation.kernels import IntersectionKernelID
>>> IntersectionKernelID.PLANE_ANALYTICAL
<IntersectionKernelID.PLANE_ANALYTICAL: ...>
"""
# Analytical (GPU-capable, fast) - specific geometry kernels
PLANE_ANALYTICAL = auto() # Ray-plane intersection
SPHERE_ANALYTICAL = auto() # Ray-sphere intersection
BOUNDED_PLANE_ANALYTICAL = auto() # Ray-bounded-plane intersection
ANNULAR_PLANE_ANALYTICAL = auto() # Ray-annular-plane intersection
# Generic dispatch kernels (GPU-capable, all geometry types)
SIGNED_DISTANCE_GENERIC = auto() # Signed distance for any surface via geometry_id
SURFACE_NORMAL_GENERIC = auto() # Surface normal for any surface via geometry_id
[docs]
class DetectionKernelID(Enum):
"""
Identifiers for ray detection kernels.
Each kernel corresponds to a specific detector geometry.
Naming convention: {GEOMETRY_TYPE}_{VARIANT}
Examples
--------
>>> from lsurf.propagation.kernels import DetectionKernelID
>>> DetectionKernelID.SPHERICAL_SINGLE
<DetectionKernelID.SPHERICAL_SINGLE: ...>
"""
SPHERICAL_SINGLE = auto() # Single spherical detector
SPHERICAL_MULTI = auto() # Multiple spherical detectors (scan mode)
PLANAR_SINGLE = auto() # Single planar detector
[docs]
class FresnelKernelID(Enum):
"""
Identifiers for Fresnel reflection/refraction kernels.
Each kernel implements a specific approach to computing Fresnel coefficients.
Examples
--------
>>> from lsurf.propagation.kernels import FresnelKernelID
>>> FresnelKernelID.STANDARD
<FresnelKernelID.STANDARD: ...>
"""
STANDARD = auto() # Standard Fresnel equations (unpolarized)
POLARIZED = auto() # Polarization-aware Fresnel equations
[docs]
class PropagatorID(Enum):
"""
Identifiers for available propagator implementations.
Each propagator ID corresponds to a specific propagator class that handles
ray propagation through materials.
Examples
--------
>>> from lsurf.propagation.kernels import PropagatorID
>>> PropagatorID.GPU_GRADIENT
<PropagatorID.GPU_GRADIENT: ...>
"""
CPU_GRADIENT = auto() # GradientPropagator (CPU, any material)
GPU_GRADIENT = auto() # GPUGradientPropagator (GPU, scalar wavelength)
GPU_SPECTRAL = auto() # SpectralGPUGradientPropagator (GPU, per-ray wavelength)
GPU_SURFACE = auto() # SurfacePropagator (GPU, with intersection detection)
# =============================================================================
# Type Aliases
# =============================================================================
# Union type for any kernel ID
KernelID = (
PropagationKernelID | IntersectionKernelID | DetectionKernelID | FresnelKernelID
)
# =============================================================================
# Kernel Registration System
# =============================================================================
# Global registry mapping kernel ID → kernel function
_KERNEL_REGISTRY: dict[KernelID, Callable] = {}
[docs]
def register_kernel(kernel_id: KernelID):
"""
Decorator to register a kernel function with the global registry.
Parameters
----------
kernel_id : KernelID
The unique identifier for this kernel. Can be any kernel ID type:
PropagationKernelID, IntersectionKernelID, DetectionKernelID,
or FresnelKernelID.
Returns
-------
decorator
A decorator that registers the function and returns it unchanged.
Examples
--------
>>> @register_kernel(PropagationKernelID.SIMPLE_EULER)
... def my_euler_kernel(positions, directions, ...):
... ...
>>> @register_kernel(DetectionKernelID.SPHERICAL_SINGLE)
... def my_detection_kernel(positions, directions, ...):
... ...
Notes
-----
Registration happens at import time. The registered function can be
retrieved later using get_kernel().
"""
def decorator(func: Callable) -> Callable:
if kernel_id in _KERNEL_REGISTRY:
existing = _KERNEL_REGISTRY[kernel_id]
raise ValueError(
f"Kernel {kernel_id} already registered by {existing.__name__}. "
f"Cannot register {func.__name__}."
)
_KERNEL_REGISTRY[kernel_id] = func
return func
return decorator
[docs]
def get_kernel(kernel_id: KernelID) -> Callable:
"""
Retrieve a registered kernel function by ID.
Parameters
----------
kernel_id : KernelID
The identifier of the kernel to retrieve. Can be any kernel ID type.
Returns
-------
Callable
The registered kernel function.
Raises
------
KeyError
If the kernel ID is not registered.
Examples
--------
>>> kernel = get_kernel(PropagationKernelID.SIMPLE_RK4)
>>> kernel(positions, directions, ...)
>>> detect = get_kernel(DetectionKernelID.SPHERICAL_SINGLE)
>>> detect(positions, directions, ...)
"""
if kernel_id not in _KERNEL_REGISTRY:
registered = list(_KERNEL_REGISTRY.keys())
raise KeyError(
f"Kernel {kernel_id} not registered. " f"Available kernels: {registered}"
)
return _KERNEL_REGISTRY[kernel_id]
[docs]
def get_registered_kernels() -> dict[KernelID, Callable]:
"""
Return a copy of all registered kernels.
Returns
-------
dict
Dictionary mapping kernel IDs to kernel functions.
"""
return _KERNEL_REGISTRY.copy()
[docs]
def list_kernels(kernel_type: type[Enum] | None = None) -> list[KernelID]:
"""
List all registered kernels, optionally filtered by type.
Parameters
----------
kernel_type : type[Enum], optional
If provided, only return kernels of this enum type.
For example, PropagationKernelID to list only propagation kernels.
Returns
-------
list[KernelID]
List of registered kernel IDs.
Examples
--------
>>> # List all registered kernels
>>> all_kernels = list_kernels()
>>> # List only propagation kernels
>>> prop_kernels = list_kernels(PropagationKernelID)
>>> # List only detection kernels
>>> detect_kernels = list_kernels(DetectionKernelID)
"""
if kernel_type is None:
return list(_KERNEL_REGISTRY.keys())
return [k for k in _KERNEL_REGISTRY.keys() if isinstance(k, kernel_type)]