A Rust port of Tom Marsh's C++ lcurve and associated packages (trm.roche, trm.subs) for computing synthetic light curves of eclipsing white-dwarf binaries. All Roche geometry, eclipse computation, grid generation, and model-parameter handling are faithful ports of the original C++ code, rewritten in Rust for memory safety, multi-core parallelism, and easy installation.
Includes a drop-in CLI replacement (lroche) and Python bindings (lcurve_rs).
| Feature | Description |
|---|---|
| Roche geometry | Roche-lobe-filling stars with gravity darkening, limb darkening (polynomial + Claret 4-coeff) |
| Accretion disc | Opaque/transparent disc with power-law temperature profile, bright spot, disc edge |
| Star spots | Up to 3 Gaussian spots on the primary, 2 on the secondary, plus uniform equatorial spots |
| Irradiation | Mutual irradiation between stars with absorption efficiency |
| Eclipses | Primary/secondary eclipses with disc occultation |
| Exposure smearing | Trapezoid-rule integration over finite exposures |
| Gravitational lensing | Optional lensing magnification from the primary |
| Parallel computation | Rayon-based multithreading across time points and grid elements |
| Python bindings | PyO3/maturin bindings with numpy integration |
| Parameter estimation | Fitting via emcee (MCMC), UltraNest, or dynesty (nested sampling) |
Requires a Rust toolchain:
cd lcurve-rs
cargo build --release
# Binary at target/release/lrocheRequires Rust and maturin:
pip install maturin numpy
cd lcurve-rs/python
maturin develop --releaseThis builds the lcurve_rs Python package with Rayon multithreading.
# Generate a light curve from a model file (500 evenly-spaced phases)
lroche model.dat none --time1=-0.2 --time2=1.2 --ntime=500 --output=lc.dat
# Fit to observed data with autoscaling
lroche model.dat data.dat --scale --output=fit.dat
# Custom exposure smearing
lroche model.dat none --ntime=1000 --expose=0.01 --ndivide=5 --output=lc.dat| Option | Default | Description |
|---|---|---|
--time1 |
-0.2 | Start time (phase units) |
--time2 |
0.8 | End time (phase units) |
--ntime |
500 | Number of time points |
--expose |
0.001 | Exposure time per point |
--ndivide |
1 | Sub-divisions for exposure smearing |
--output |
stdout | Output file path |
--scale |
off | Autoscale to minimize chi-squared |
import lcurve_rs
import numpy as np
# Load a model from a parameter file
model = lcurve_rs.Model("model.dat")
print(model) # Model(q=0.5, iangle=82, r1=0.015, r2=-1, t1=15000, t2=3500)
# Compute a light curve with evenly-spaced times
result = model.light_curve(time1=-0.2, time2=1.2, ntime=1000)
# Access results as numpy arrays
result.times # (1000,) array of times
result.flux # (1000,) array of computed fluxes
result.wdwarf # white dwarf contribution at phase 0.5
result.sfac # scale factors [star1, disc, edge, spot, star2]
# Use custom time arrays
times = np.linspace(-0.5, 1.5, 2000)
result = model.light_curve(times=times, expose=0.01, ndiv=3)
# Fit to observed data
result = model.light_curve(data="observed.dat", scale=True)
result.chisq # weighted chi-squared
# Modify parameters on the fly
model.q = 0.3
model.iangle = 85.0
model.set_param("t1", 20000.0)
print(model.get_param("t1")) # 20000.0Direct attribute access for common parameters: q, iangle, r1, r2, t1, t2, period, t0, velocity_scale.
All 65 physical parameters are accessible via model.get_param(name) / model.set_param(name, value).
Full Pparam metadata (value, range, dstep, vary, defined) is available via model.get_pparam(name) and model.set_pparam(name, ...).
Fit model parameters to observed data using MCMC or nested sampling. Install optional backends:
pip install lcurve_rs[emcee] # MCMC
pip install lcurve_rs[ultranest] # nested sampling
pip install lcurve_rs[all] # all backendsfrom lcurve_rs.fitting import Fitter, Prior
model = lcurve_rs.Model("model.dat")
# Mark parameters as free
model.set_pparam("q", vary=True, range=0.1)
model.set_pparam("iangle", vary=True, range=5.0)
# Optional: override a prior
priors = {"iangle": Prior.gaussian("iangle", mean=82.0, sigma=1.5, low=70, high=90)}
# Fit
fitter = Fitter(model, data="observed.dat", scale=True, priors=priors)
result = fitter.run_emcee(nwalkers=32, nsteps=5000, burn=1000, progress=True)
print(result.summary()) # median + credible intervals
print(result.best_fit) # max-likelihood parametersUltraNest and dynesty are also supported — see the fitting guide for details.
The main time-point loop is parallelised with Rayon. On multi-core hardware, wall-clock time scales near-linearly with core count.
Grid setup (star continuum, disc eclipses) is also parallelised.
Release-profile optimizations include LTO (lto = "fat"), single codegen unit, and #[inline] hints on hot-path functions (visibility checks, limb darkening, grid interpolation).
Set RAYON_NUM_THREADS=N to control thread count.
cargo testRequires the original C++ lroche binary and pgplot:
module load gcccore/12.3.0 pgplot/5.2.2
python3 test_data/run_regression100.pyRuns 100 randomly-generated models through both C++ and Rust, verifying max relative error < 1e-5. Current worst-case error is ~4e-8.
cd python
source .venv/bin/activate
python -c "import lcurve_rs; m = lcurve_rs.Model('../test_data/test_model.dat'); print(m.light_curve(ntime=10).flux)"GitHub Actions runs tests automatically on every push and PR. See .github/workflows/tests.yml.
lcurve-rs/
├── src/main.rs # CLI binary (lroche)
├── crates/
│ ├── lcurve/ # Core light curve engine
│ │ ├── orchestration.rs # Main computation loop (parallelised)
│ │ ├── flux.rs # Flux calculations per component
│ │ ├── grid.rs # Roche geometry grid generation
│ │ ├── brightness.rs # Continuum brightness setup
│ │ ├── model.rs # Model parameter parsing
│ │ └── types.rs # Data types (Point, Datum, LDC)
│ ├── roche/ # Roche geometry library
│ └── subs/ # Numerical utilities (Vec3, Planck, root-finding)
├── python/ # Python bindings (PyO3 + maturin)
│ ├── src/lib.rs # PyO3 module
│ ├── lcurve_rs/ # Python package
│ └── pyproject.toml
├── test_data/ # Test models and regression suite
└── docs/ # MkDocs documentation
This is a port of Tom Marsh's C++ lcurve package and its dependencies (cpp-roche, cpp-subs). The Roche geometry, eclipse computation, disc/spot models, and model parameter file format are all faithful ports of the original code. Numerical results agree with the C++ version to ~4e-8 relative error across 100 randomised test models.
See the original lcurve license terms.