Source code for lsurf.simulation.config

# 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.

"""
Simulation Configuration

Contains the SimulationConfig dataclass for configuring ray tracing simulations.
"""

from dataclasses import dataclass


[docs] @dataclass class SimulationConfig: """ Configuration for ray tracing simulation. Attributes ---------- step_size : float Maximum integration step size in meters (default 100.0). min_step_size : float Minimum step size in meters for adaptive stepping (default 3e-4 = 0.3mm). This provides ~1ps time resolution near surfaces. adaptive_stepping : bool Whether to use adaptive step sizing near surfaces (default True). When enabled, steps decrease as rays approach surfaces for precise timing. surface_proximity_factor : float Step size = distance * factor when within proximity threshold (default 0.5). surface_proximity_threshold : float Distance (in meters) within which adaptive stepping activates (default 10.0). max_steps_per_leg : int Maximum steps before forcing surface check (default 10000). max_bounces : int Maximum surface interactions before termination (default 10). min_intensity : float Intensity threshold below which rays are terminated (default 1e-10). bounding_radius : float Radius of bounding sphere in meters (default 500_000.0). bounding_center : tuple[float, float, float] Center of bounding sphere (default (0.0, 0.0, -6.371e6) for Earth center). apply_absorption : bool Whether to apply Beer-Lambert absorption (default True). polarization : str Polarization state: 's', 'p', or 'unpolarized' (default 'unpolarized'). track_polarization_vector : bool Whether to track 3D polarization vectors through interactions (default False). track_surface_hits : bool Whether to store intermediate surface hit positions (default False). When enabled, SimulationResult.surface_hits will contain hit positions for all optical surfaces, useful for visualization. track_refracted_rays : bool Whether to continue propagating refracted rays from optical surfaces (default False). When False, only reflected rays continue; refracted rays are discarded. Set to False for simulations where only reflected light is of interest (e.g., ocean surface reflection where underwater propagation is not needed). use_gpu : bool Whether to use GPU acceleration if available (default True). Examples -------- >>> config = SimulationConfig(step_size=50.0, max_bounces=5) >>> sim = Simulation(geometry, config) Notes ----- Adaptive stepping provides sub-nanosecond timing precision near surfaces: | Distance to Surface | Step Size | Time Resolution | |---------------------|-----------|-----------------| | > 10m | 100m | ~333ns | | 5m | 2.5m | ~8ns | | 1m | 0.5m | ~1.7ns | | 0.1m | 0.05m | ~167ps | | < 0.6mm | 0.3mm | ~1ps (minimum) | """ step_size: float = 100.0 min_step_size: float = 3e-4 # 0.3mm → ~1ps time resolution adaptive_stepping: bool = True surface_proximity_factor: float = 0.5 # Step = distance * factor when near surface_proximity_threshold: float = 10.0 # Start adapting within this distance (m) max_steps_per_leg: int = 10000 max_bounces: int = 10 min_intensity: float = 1e-10 bounding_radius: float = 500_000.0 bounding_center: tuple[float, float, float] = (0.0, 0.0, -6.371e6) apply_absorption: bool = True polarization: str = "unpolarized" track_polarization_vector: bool = False track_surface_hits: bool = False track_refracted_rays: bool = False use_gpu: bool = True gradient_adaptive_stepping: bool = False target_dn_frac: float = 0.01
[docs] def __post_init__(self) -> None: """Validate configuration.""" if self.step_size <= 0: raise ValueError(f"step_size must be positive, got {self.step_size}") if self.min_step_size <= 0: raise ValueError( f"min_step_size must be positive, got {self.min_step_size}" ) if self.min_step_size > self.step_size: raise ValueError( f"min_step_size ({self.min_step_size}) must be <= step_size ({self.step_size})" ) if self.surface_proximity_factor <= 0 or self.surface_proximity_factor > 1: raise ValueError( f"surface_proximity_factor must be in (0, 1], got {self.surface_proximity_factor}" ) if self.surface_proximity_threshold <= 0: raise ValueError( f"surface_proximity_threshold must be positive, got {self.surface_proximity_threshold}" ) if self.max_steps_per_leg <= 0: raise ValueError( f"max_steps_per_leg must be positive, got {self.max_steps_per_leg}" ) if self.max_bounces < 0: raise ValueError( f"max_bounces must be non-negative, got {self.max_bounces}" ) if self.min_intensity < 0: raise ValueError( f"min_intensity must be non-negative, got {self.min_intensity}" ) if self.bounding_radius <= 0: raise ValueError( f"bounding_radius must be positive, got {self.bounding_radius}" ) if self.polarization not in ("s", "p", "unpolarized"): raise ValueError( f"polarization must be 's', 'p', or 'unpolarized', got '{self.polarization}'" ) if self.target_dn_frac <= 0: raise ValueError( f"target_dn_frac must be positive, got {self.target_dn_frac}" )