Navette - Weaving thin-film systems that perform
Navette is a high-performance, physically rigorous 1D optical engine designed for the simulation of light propagation in stratified media. Built on a modern Scattering Matrix (S-matrix) architecture, it offers a numerically stable and vectorized alternative to traditional Transfer Matrix Methods (TMM).
1. Unconditional Numerical Stability
Traditional TMM suffers from numerical divergence (exponentially growing evanescent waves) when dealing with thick layers or highly absorbing materials. Navette utilizes the Redheffer Star Product to propagate scattering matrices, ensuring that all matrix elements remain bounded and physically meaningful, regardless of layer thickness.
2. High-Concurrency Performance
As a Principal Performance Engineer, you need tools that scale. Navette is built for speed:
-
Parallel Execution: Utilizes Rust + rayon data-parallelism across wavelengths/angles to saturate all available CPU cores.
-
Vectorized Engine: Operations are performed across the entire (wavelength × angle) coordinate space in a single pass, eliminating Python's loop overhead.
-
Memory Efficiency: Collapses multi-layer stacks into a compact global S-matrix to minimize cache misses.
Measured, not claimed. All numbers below are --release builds on a
32-core Windows box, taken by the scripts named beside them under
validation/benches/; each of those scripts refuses to run against a debug
build (see the profile note under Getting started). "vs numba" compares
against the numba reference implementation this engine replaced.
| What | Measured | Script |
|---|---|---|
| Full observable mask, 40 lambda x 3 theta, 5 layers (complex amplitudes + dispersion) | 0.164 ms median -- ~1.4 us per point for every channel | bench_backside_speed |
| Rigorous 12-channel request, 6 layers, 20 000 / 60 000 points | 1.68 ms / 4.02 ms, i.e. 1.1-1.8x the numba kernel | bench_core_engine_scaling.py |
| Photometric 4-channel request, same grid sizes | 1.50 ms / 3.70 ms | bench_core_engine_scaling.py |
| pchip interpolation, 1 M points | 1.22 ms (1.2 ns/pt) vs 1.59 ms numba; accuracy identical (~1e-14 vs analytic) on both sides | 1dinterpol_test_bench |
| dE76 / dE94 / CMC / DIN99 / dE2000 batches | 23-36x faster than the reference, at exact parity with colour-science including black/white/near-black rows |
bench_validate_color |
Weaver set_data / get_weaved / unweave_cached |
1.5-7.8x the Python reference across small and mid grids | navette_spectral_bench |
Batch unweave_collection |
1.15-2.29x faster than before R5.1; still the one path that can trail the reference at extreme key counts | navette_spectral_bench |
| One LM thickness optimize (synthesis), with needle re-fold | 2.1 ms, re-fold 9.1 % overhead | bench_refold |
| Structure grid assert | 0.9 us | bench_grid_assert |
Two caveats kept deliberately visible: small grids (under ~2 000 points) are
dispatch-bound and still behind the numba kernel, and bench_refold does not
exercise the needle insertion path, which is the expensive part of synthesis.
3. Partial Coherence Support
Real-world systems often involve thick substrates (like a 1mm glass slide) where phase information is lost. Navette features a Hybrid Coherence Engine:
-
Coherent Blocks: Preserves phase for thin-film interference.
-
Incoherent Interfaces: Switches to intensity-based propagation for thick layers, preventing the "unphysical ringing" caused by assuming perfect coherence across a macroscopic substrate.
4. Advanced Physics Modeling
Navette goes beyond simple Fresnel equations to provide research-grade accuracy:
-
Interface Roughness: Implements the Névot-Croce model, providing superior accuracy for high-frequency or X-ray reflectometry compared to standard Gaussian approximations.
-
Ellipsometric Rigor: Outputs (Ψ,Δ) parameters that strictly follow the Azzam & Bashara convention, ensuring direct compatibility with commercial ellipsometers (e.g., Woollam, Horiba).
5. Automated Coating Design
Navette doesn't just simulate — it synthesizes, with the classic needle method running natively on the same engine:
-
Needle Insertion: Probes every candidate position with an infinitesimal test layer and inserts real material where the merit function improves most — the Tikhonravov needle algorithm, merit-driven and target-aware.
-
Thickness Optimization: Levenberg-Marquardt refinement over free layers with bounds and clamping, interleaved with insertion passes and impact-ranked cleanup (merge, thin-layer removal, re-optimization).
-
Multi-domain Targets: One joint merit over spectral, angular, and CIE color demands — multiple angles, illuminants with own-white metamerism control, and per-target wavelength windows — all folded into the needle gradient with analytic chain-rule terms, so a single run designs for daylight and showroom light at once.
-
Graded Media: Gradient-index profiles expand natively for simulation and serve as pinned background (substrate diffusion gradients, rugate foundations) while the needle designs around them.
Technical Specifications
| Feature | Implementation & Engineering Benefit |
|---|---|
| Core Algorithm | 1D Scattering Matrix ($S$-matrix): Utilizes the Redheffer Star Product to eliminate numerical divergence and precision loss in thick or highly absorbing layers. |
| Propagation Logic | Hybrid Mixed Coherence: Sophisticated dual-stage engine supporting phase-accurate (coherent) and intensity-only (incoherent) layers within a single pass. |
| Coherent Blocks | $2 \times 2$ Complex Field Matrices: Maintains full phase and amplitude information, ensuring rigorous calculation of thin-film interference and ellipsometric parameters. |
| Incoherent Blocks | Stokes-Mueller / Intensity Redheffer: Prevents unphysical interference artifacts in macroscopic substrates by utilizing intensity-based propagation. |
| Roughness Model | Névot-Croce (Exact Wavevector): Achieves research-grade accuracy for X-ray and UV interfaces by modeling exact wavevector correlations across boundaries. |
| Optimization | Rust / rayon + PyO3: Native multi-threaded kernels (GIL released) with a thin Python API, optimized for high-concurrency simulation and real-time GUI responsiveness. |
| Polarization | Full $s$ and $p$ Support: Comprehensive Jones and Stokes calculus integration, following standard commercial ellipsometry conventions (Azzam & Bashara). |
| Complexity | $O(N)$ Scaling: Optimized linear time complexity relative to the number of layers, ensuring stable performance for complex multi-stack architectures. |
Project layout
Navette/
├── Cargo.toml # Rust workspace (cargo check/test --workspace)
├── pyproject.toml # maturin project: builds the `navette` wheel (src layout)
├── src/navette/ # unified Python package
│ ├── __init__.py # version + public surface
│ ├── color/ # wrapper over native `navette._color`
│ ├── interpolate/ # wrapper over native `navette._interpolate`
│ ├── smatrix/ # ScatterMatrix + needle (native `navette._smatrix`)
│ ├── spectralweave/ # weavers + merit (native `navette._spectralweave`)
│ ├── materials/ # dispersion models (native `navette._materials`)
│ ├── _*.py # shims re-exporting the `navette._navette` submodules
│ ├── structure/ # stacks, architect (native model + thin wrappers)
│ ├── synthesis/ # needle pipeline driver (native DesignStack)
│ ├── config/ # native-validated holders, program documents
│ └── data/CIE/ # bundled reference spectra
├── rust/ # Rust sources: one engine crate + bindings
│ ├── navette/ # pure-Rust engine (color/interpolate/materials/
│ │ # smatrix/spectralweave/structure modules;
│ │ # published as `navette` on crates.io)
│ └── navette-py/ # PyO3 aggregator -> navette._navette (one wheel)
├── validation/ # tests, parity, benches, goldens + references (see validation/README.md)
├── tools/check_exposure.py # bidirectional exposure lint (CI)
├── examples/ docs/plans/ benchmarks/
Install & build
# Single aggregated native extension (navette._navette, all engines):
maturin develop --release
# checks
cargo check --workspace
cargo test --workspace # everything (needs Python for binding crates)
cargo test-pure # pure-Rust gate (no Python needed)
cargo fmt --all # rustfmt defaults; CI fails on any diff
python tools/check_toolchain.py # is your clippy as new as CI's?
pytest validation
Lint on the toolchain CI uses.
cargo clippyonly reports the lints its own version knows. Between 0.6.13 and 0.6.30 the local toolchain was one minor version behind CI'sstable, the local run was clean, and CI was red for 17 consecutive pushes on a lint the local clippy did not have.tools/check_toolchain.pyfails when that gap reopens.
Run this once per clone so git blame skips the tree-wide reformat commit
(0.6.32) and points at whoever actually wrote each line:
git config blame.ignoreRevsFile .git-blame-ignore-revs
Always pass
--release. Plainmaturin developbuilds with thedevprofile: the extension imports and computes correctly, but runs several times slower, so every timing taken against it is meaningless. This is not hypothetical — a whole round of committed benchmark results (and the conclusions drawn from them) had to be discarded for exactly this reason.navette.build_profile()reports which profile is installed, and the benches undervalidation/benches/exit rather than time a"debug"one.
Architecture: Rust core, Python addon
All logic and all validation live in the navette Rust crate — it runs
fully standalone (file → design → solve → report, no interpreter).
The Python package is a thin addon: validated config holders, YAML→dict
parsing, result reshapes, and re-exports. Conversely every feature-level
Rust function is exposed via PyO3, so Python can drive the whole engine.
tools/check_exposure.py enforces this both ways in CI (see
docs/plans/exposure_audit.md).
CI
.github/workflows/ci.yml runs on every push and pull request:
cargo test --workspace, a zero-compiler-warnings check (-D warnings),
pytest validation on Windows and Linux, the exposure and CIE-sync lints,
and an assertion that the installed extension is a release build.
cargo clippy -D warnings (since 0.6.6) and cargo fmt --all --check
(since 0.6.32) are blocking; nothing in the workflow is advisory any more.
Layout notes
rust/holds the Cargo workspace (the singlenavetteengine crate plus thenavette-pyPyO3 aggregator) — the idiomatic Rust layout, publishable to crates.io.src/navette/is the Python package in src-layout — the idiomatic Python layout, which maturin detects automatically for mixed projects.
Release & publish
Release automation: tag vX.Y.Z (must match pyproject.toml, workspace
Cargo.toml, its internal navette dependency, __about__.py and both
Cargo.lock entries — all six enforced by CI) →
.github/workflows/release.yml
builds wheels (Linux/Windows/macOS) and publishes to PyPI (trusted
publisher) + crates.io (token), leaf crates first.
maturin build --release # -> target/wheels/navette-0.6.32-*.whl (single wheel, all engines)
Optimizer backends
LmConfig(optimizer=...) chooses which least-squares solver runs.
navette._smatrix.available_optimizers() reports what the installed wheel
actually has; a name it lacks is refused with the rebuild command, never
quietly replaced by a different solver.
| Name | What it is |
|---|---|
"builtin" (default) |
This crate's bounded Levenberg-Marquardt: QR step solve, gain-ratio damping, analytic Jacobian. Bounds are enforced by vetoing and clamping the solved step, so a thickness may finish exactly on a bound — which is how the synthesis loop learns a film wants removing. |
"trf" |
Trust-region reflective (Branch-Coleman-Li), the reference method for bounded least squares and the same algorithm as scipy.optimize.least_squares(method="trf"). Hand-rolled, no dependency, always available. Bounds enter the subproblem rather than clipping its answer, so a boundary optimum is handled by construction — but its iterates are strictly interior, so it stops one ULP short of a bound instead of on it. lambda_* and damping do nothing here. |
Optional cargo features
Off by default, so a standard wheel pulls no extra dependencies.
| Feature | What it adds |
|---|---|
opt-minpack-lm |
LmConfig(optimizer="minpack_lm") — the levenberg-marquardt crate (MINPACK lmdif-derived, MIT), as a reference to compare the built-in LM against. Unbounded, so it runs on an interior reparametrization: its optima are strictly inside the thickness box, where the built-in's may sit exactly on it. |
opt-argmin |
LmConfig(optimizer="argmin_gauss_newton") and "argmin_trust_region" — two solvers from the argmin ecosystem (MIT/Apache-2.0), as baselines, not as candidates. Both unbounded. The Gauss-Newton one is undamped, so it raises as soon as JᵀJ is singular — a film driven toward zero thickness is enough — and it refuses two of the three refold starts in validation/review/lm_check.py. The trust region finds the right optimum but has no convergence test of its own, so it always runs the full max_iterations: 218–393 residual evaluations where "trf" takes 8–32. Shares nalgebra with opt-minpack-lm. |
maturin develop --release --features opt-minpack-lm
maturin develop --release --features opt-argmin
Manual fallback: cargo publish -p navette;
maturin upload target/wheels/navette-0.5.0-*.whl.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters