Quick Start

This guide will get you started with L-SURF in minutes.

Full Simulation Pipeline

The recommended approach uses GeometryBuilder to define your simulation geometry and Simulation to run the ray tracing:

import lsurf as sr
from lsurf.geometry import GeometryBuilder
from lsurf.simulation import Simulation, SimulationConfig
from lsurf.surfaces import SphereSurface, PlaneSurface, SurfaceRole

# Constants
EARTH_RADIUS = 6.371e6  # meters

# Create materials
atmosphere = sr.ExponentialAtmosphere(n_sea_level=1.000293)

# Create surfaces with roles
ocean = SphereSurface(
    center=(0, 0, -EARTH_RADIUS),
    radius=EARTH_RADIUS,
    role=SurfaceRole.OPTICAL,  # Reflects/refracts rays
    name="ocean",
)

detector = PlaneSurface(
    point=(0, 0, 35000),
    normal=(0, 0, 1),
    role=SurfaceRole.DETECTOR,  # Records rays
    name="detector_35km",
)

# Build geometry with named media
geometry = (
    GeometryBuilder()
    .register_medium("atmosphere", atmosphere)
    .register_medium("ocean", sr.WATER)
    .set_background("atmosphere")
    .add_surface(ocean, front="atmosphere", back="ocean")
    .add_detector(detector)
    .build()
)

# Configure simulation
config = SimulationConfig(
    step_size=100.0,        # Maximum step size (meters)
    max_bounces=5,          # Maximum surface interactions
    min_intensity=1e-10,    # Terminate weak rays
)

# Create and run simulation
sim = Simulation(geometry, config)
source = sr.CollimatedBeam(
    center=(0, 0, 1000),
    direction=(0.17, 0, -0.98),
    radius=10.0,
    num_rays=10000,
    wavelength=532e-9,
)
result = sim.run(source.generate())

print(f"Detected: {result.statistics.rays_detected}")
print(f"Absorbed: {result.statistics.rays_absorbed}")

Simple Surface Interaction

For quick tests without the full simulation framework:

import lsurf as sr

# Create a flat water surface at z=0
surface = sr.PlanarSurface(
    point=(0, 0, 0),
    normal=(0, 0, 1),
    material_front=sr.AIR_STP,
    material_back=sr.WATER,
    role=sr.SurfaceRole.OPTICAL,
    name="water",
)

# Create a collimated beam at 45° incidence
source = sr.CollimatedBeam(
    center=(0, 0, 0.1),
    direction=(0.707, 0, -0.707),
    radius=0.01,
    num_rays=1000,
    wavelength=532e-9,
)
rays = source.generate()

# Process surface interaction
reflected, refracted = sr.process_surface_interaction(
    rays, surface,
    wavelength=532e-9,
    generate_reflected=True,
    generate_refracted=True,
)

print(f"Reflected rays: {reflected.num_rays}")
print(f"Refracted rays: {refracted.num_rays}")

Key Concepts

GeometryBuilder

The GeometryBuilder provides a fluent interface for constructing simulations:

  1. Register media - Name your materials for consistency across surfaces

  2. Set background - Define the ambient propagation medium

  3. Add surfaces - Add optical surfaces with front/back media

  4. Add detectors - Add detection surfaces

  5. Build - Create the immutable Geometry object

geometry = (
    GeometryBuilder()
    .register_medium("air", sr.AIR_STP)
    .register_medium("glass", sr.BK7_GLASS)
    .set_background("air")
    .add_surface(lens, front="air", back="glass")
    .add_detector(screen)
    .build()
)

SimulationConfig

Configure simulation behavior with SimulationConfig:

config = SimulationConfig(
    step_size=100.0,              # Max step size (meters)
    min_step_size=3e-4,           # Min step (0.3mm → ~1ps resolution)
    adaptive_stepping=True,       # Reduce step near surfaces
    max_bounces=10,               # Max surface interactions
    min_intensity=1e-10,          # Terminate weak rays
    bounding_radius=500_000.0,    # Terminate rays outside this
    polarization="unpolarized",   # 's', 'p', or 'unpolarized'
    use_gpu=True,                 # Use GPU if available
)

Surface Roles

Surfaces have roles that determine their behavior:

  • SurfaceRole.OPTICAL - Reflects and/or refracts rays (Fresnel)

  • SurfaceRole.DETECTOR - Records rays that hit (terminates them)

  • SurfaceRole.ABSORBER - Terminates rays without recording

Surfaces

L-SURF supports several surface types:

  • PlaneSurface - Flat surfaces (mirrors, windows, detectors)

  • SphereSurface - Curved spherical surfaces (lenses, curved Earth)

  • GerstnerWaveSurface - Ocean wave surfaces (CPU)

  • CurvedWaveSurface - Large-scale curved ocean surfaces (CPU)

  • GPUCurvedWaveSurface - GPU-accelerated curved wave surfaces

  • LocalRecordingSphereSurface - Spherical detector at altitude

Materials

Built-in materials:

  • AIR_STP - Standard temperature/pressure air (n ≈ 1.000293)

  • WATER - Pure water (n ≈ 1.33)

  • BK7_GLASS - Common optical glass (n ≈ 1.52)

  • VACUUM - Perfect vacuum (n = 1.0)

  • ExponentialAtmosphere - Height-dependent refractive index

Ray Sources

Generate rays from various sources:

  • PointSource - Point source with spherical emission

  • CollimatedBeam - Parallel beam of rays

  • DivergingBeam - Diverging conical beam

  • GaussianBeam - Gaussian intensity profile

Simulation Results

The SimulationResult contains:

result = sim.run(rays)

# Access detected rays
detected = result.detected  # RecordedRays object
print(f"Detected {detected.num_rays} rays")
print(f"Positions: {detected.positions.shape}")
print(f"Times: {detected.times}")

# Statistics
stats = result.statistics
print(f"Total created: {stats.total_rays_created}")
print(f"Detected: {stats.rays_detected}")
print(f"Absorbed: {stats.rays_absorbed}")
print(f"Bounces: {stats.bounces_completed}")

# Per-detector counts
for name, count in result.detections_per_surface.items():
    print(f"  {name}: {count} hits")

Logging

Enable logging to see simulation progress:

import lsurf as sr

# INFO level shows simulation summaries
sr.configure_logging("INFO")

# DEBUG level shows per-bounce details
sr.configure_logging("DEBUG")

Next Steps