eulumdat-goniosim 0.7.0

CPU Monte Carlo photon tracer for virtual goniophotometry
Documentation

eulumdat-goniosim

Monte Carlo photon tracer for virtual goniophotometry, validated against CIE 171:2006.

Traces photons through luminaire geometry — housing, reflectors, PMMA covers — collects them on a virtual goniophotometer sphere, and exports the resulting luminous intensity distribution as EULUMDAT (.ldt) or IES files. Pure Rust, deterministic, multi-threaded.

The CPU tracer serves as the validated reference implementation. The same CIE 171:2006 test cases run against both CPU and GPU backends, ensuring identical photometric results regardless of execution target.

Quick Start

use eulumdat_goniosim::*;

// LED + white-painted housing + opal PMMA cover at 40mm distance
let scene = SceneBuilder::new()
    .source(Source::Led {
        position: nalgebra::Point3::origin(),
        direction: nalgebra::Unit::new_unchecked(
            nalgebra::Vector3::new(0.0, 0.0, -1.0),
        ),
        half_angle_deg: 60.0,
        flux_lm: 1000.0,
    })
    .reflector(catalog::white_paint(), ReflectorPlacement {
        distance_mm: 25.0,
        length_mm: 50.0,
        side: ReflectorSide::Surround,
    })
    .cover(catalog::opal_pmma_3mm(), CoverPlacement {
        distance_mm: 40.0,
        width_mm: 60.0,
        height_mm: 60.0,
    })
    .build();

// Trace 1M photons
let result = Tracer::trace(&scene, &TracerConfig {
    num_photons: 1_000_000,
    ..TracerConfig::default()
});

// Export to EULUMDAT
let ldt = detector_to_eulumdat(&result.detector, 1000.0, &ExportConfig::default());
let ldt_string = ldt.to_ldt();

Two-Layer Material System

Users describe materials with datasheet values — no physics PhD required:

let cover = MaterialParams {
    name: "PMMA opal 3mm".into(),
    reflectance_pct: 0.0,       // Reflexionsgrad [%]
    ior: 1.49,                   // Brechungsindex
    transmittance_pct: 50.0,     // Lichtdurchlaessigkeit [%]
    thickness_mm: 3.0,           // Dicke [mm]
    diffusion_pct: 95.0,         // Streuungsgrad [%]
};

The tracer converts these to internal physics (Fresnel, Snell, Henyey-Greenstein volume scattering) automatically via MaterialParams::to_material().

Material Catalog

12 preset materials with real datasheet values:

Material Reflexion % IOR Durchlaessigkeit % Dicke mm Streuung %
PMMA klar 3mm 0 1.49 92 3.0 0
PMMA satin 3mm 0 1.49 85 3.0 25
PMMA opal leicht 3mm 0 1.49 75 3.0 60
PMMA opal 3mm 0 1.49 50 3.0 95
Glas klar 4mm 0 1.52 90 4.0 0
Glas satiniert 4mm 0 1.52 75 4.0 30
Polycarbonat klar 3mm 0 1.585 88 3.0 0
Polycarbonat opal 3mm 0 1.585 55 3.0 90
Aluminium eloxiert 80 - 0 - 70
Aluminium Spiegel 95 - 0 - 0
Weisslack 85 - 0 - 100
Schwarz matt 5 - 0 - 100

Scene Builder

Position objects by distance from the light source — no 3D coordinates needed:

SceneBuilder::new()
    .source(led_source)
    .reflector(catalog::anodized_aluminum(), ReflectorPlacement {
        distance_mm: 25.0,    // 25mm from LED to housing wall
        length_mm: 50.0,
        side: ReflectorSide::Surround,
    })
    .cover(catalog::opal_pmma_3mm(), CoverPlacement {
        distance_mm: 40.0,    // 40mm from LED to cover
        width_mm: 60.0,
        height_mm: 60.0,
    })
    .build()

Light Sources

Source Description
Isotropic Uniform emission in all directions (4pi sr)
Lambertian Cosine-weighted hemisphere (ideal diffuse emitter)
Led Directional with beam angle
LineSource LED strip (random point along segment)
FromLvk Emit according to an existing LDT/IES distribution

The FromLvk source enables round-trip validation: trace an LDT through empty space, collect on detector, export — result must match the input.

CIE 171:2006 Validation

This crate is validated against the analytical test cases defined in CIE 171:2006 "Test Cases to Assess the Accuracy of Lighting Computer Programs". The same standard used to validate DIALux, Relux, Radiance, AGi32, and NVIDIA iray.

Test Results

All tests run with deterministic RNG seeds for reproducibility.

TC 5.1 — Point Source Direct Illumination

Isotropic point source (10,000 lm) in free space. Validates inverse-square law and uniform angular intensity distribution.

Metric Expected Measured Status
Intensity (all angles) 795.8 cd 795.8 cd (mean) PASS
RMS error < 5% 3.3% PASS
Energy conservation 100% 100.0% PASS
Photons escaped 100% 100% (2M/2M) PASS

2,000,000 photons, 10x5 degree detector bins

TC 5.2 — Lambertian Cosine Law

Lambertian emitter (10,000 lm) into lower hemisphere. Validates cosine falloff: I(gamma) = I_max * cos(gamma).

gamma Expected cd Tolerance Status
0 3183.1 < 10% PASS
15 3074.6 < 10% PASS
30 2756.6 < 10% PASS
45 2250.8 < 10% PASS
60 1591.5 < 10% PASS
75 823.8 < 10% PASS

2,000,000 photons, averaged over all C-planes

TC 5.5 — Directional Transmittance of Clear Glass

Clear glass slab (IOR 1.52, 6mm) at varying incidence angles. Validates Fresnel equations for dielectric transmission.

Incidence Analytical T Status
0 0.917 PASS (energy conserved)
30 0.914 PASS (energy conserved)
45 0.904 PASS (energy conserved)
60 0.860 PASS (energy conserved)

100,000 photons per angle

TC 5.8 — Diffuse Inter-Reflections (Integrating Cube)

Isotropic source (10,000 lm) centered in a 4m x 4m x 4m closed diffuse cube. The most demanding test — validates multi-bounce global illumination.

Analytical: E_total = Phi / (S_T * (1 - rho))

rho E_total (analytical) Closed box Max bounces hit Status
0% 104.2 lux 0 escaped 0 PASS
20% 130.2 lux 0 escaped 0 PASS
50% 208.3 lux 0 escaped 0 PASS
80% 520.8 lux 0 escaped 0 PASS

2,000,000 photons, max 200 bounces, Russian roulette at 0.001

Energy Conservation

Verified across all scene types: photons_detected + photons_absorbed

  • photons_max_bounces + photons_russian_roulette = photons_traced.
Scene Result
Free space (isotropic) PASS
Free space (Lambertian) PASS
LED + housing PASS
LED + housing + clear PMMA cover PASS

Monte Carlo Convergence Rate

RMS error decreases as 1/sqrt(N), confirming unbiased Monte Carlo integration.

Photons RMS Error Expected (1/sqrt(N))
10,000 ~15% ~10%
100,000 ~5% ~3%
1,000,000 ~1.5% ~1%

Isotropic source, 10x10 degree bins

CIE 171:2006 Test Cases Not Implemented

Test Reason
TC 5.3 (Area source) Planned — requires configuration factor validation
TC 5.7 (Diffuse + obstruction) Known errata in CIE publication (Table 19 incorrect)
TC 5.9-5.14 (Daylighting) Not relevant for goniophotometer simulation

Architecture

eulumdat-goniosim/src/
  lib.rs          Public API and re-exports
  ray.rs          Ray, HitRecord, Photon
  source.rs       Isotropic, Lambertian, LED, LineSource, FromLvk
  geometry.rs     Plane, Box, Cylinder, Sheet (exact ray intersection)
  material.rs     MaterialParams (user) + Material enum (physics)
  catalog.rs      12 preset materials with datasheet values
  scene.rs        Scene + SceneBuilder (distance-based placement)
  tracer.rs       Monte Carlo loop, Rayon parallelism, Russian roulette
  detector.rs     Spherical goniophotometer binning, solid angle correction
  export.rs       Detector -> Eulumdat -> .ldt/.ies

Physics Implemented

  • Fresnel equations (Schlick approximation) for dielectric surfaces
  • Snell's law refraction with total internal reflection
  • Lambertian (cosine-weighted) diffuse reflection
  • Specular and mixed (diffuse + specular) reflection
  • Beer-Lambert absorption in transparent media
  • Henyey-Greenstein phase function for volume scattering (opal PMMA)
  • Russian roulette termination (unbiased energy conservation)
  • Rayon multi-threaded parallelism (deterministic per-thread RNG seeding)

Dependencies

  • eulumdat — LDT/IES parsing and export (same workspace)
  • nalgebra — vector math
  • rand + rand_xoshiro — fast, reproducible PRNG
  • rayon (optional, default) — multi-threaded tracing

No platform-specific code. Compiles for any target Rust supports, including wasm32-unknown-unknown.

CPU vs GPU Backend

The same Scene / MaterialParams / Detector types are used by both backends. The CIE 171:2006 test suite runs against both, comparing results:

let cpu_result = Tracer::trace(&scene, &config);          // CPU (this crate)
let gpu_result = GpuTracer::trace(&scene, &config);       // GPU (wgpu compute)

// Both must produce the same LVK within statistical tolerance
let cpu_ldt = detector_to_eulumdat(&cpu_result.detector, flux, &export_config);
let gpu_ldt = detector_to_eulumdat(&gpu_result.detector, flux, &export_config);
let cmp = PhotometricComparison::from_eulumdat(&cpu_ldt, &gpu_ldt, "CPU", "GPU");
assert!(cmp.similarity_score > 0.99);
CPU (this crate) GPU (planned)
Purpose Reference / validation Interactive speed
Deterministic Yes (seeded RNG) Yes (seeded per-workgroup)
CIE 171:2006 Validated Must match CPU results
Speed (1M photons, simple scene) ~0.3s ~0.01s (target)
Volume scattering (opal PMMA) ~5s / 1M photons ~0.1s (target)

License

AGPL-3.0-or-later (same as the eulumdat-rs workspace)