Source code for lsurf.propagation.kernels.registry

# 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)]