Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@ the project follows [Semantic Versioning](https://semver.org/).

## [Unreleased]

### Tooling

- CI: GitHub Actions workflow at `.github/workflows/ci.yml` covering
MSRV (1.85) build × four feature combos, full build/test/doctest
on stable × four feature combos, clippy, docs (with
`RUSTDOCFLAGS: -D warnings`), wasm32 build for `lambert_izzo` and
`lambert_izzo_wasm`, and the `stress` example.
- Benches: `multi_rev` parametrized across `M ∈ {1, 3, 5}` via
Criterion's `bench_with_input`; new `multi_rev_battin` group exercising
the Battin near-parabolic regime (`|x − 1| < BATTIN_THRESHOLD`) via a
near-180° geometry with TOF jittered around the parabolic time-of-flight.

## [1.0.0] — 2026-04-30

Initial public release. `lambert_izzo` is a `no_std`-friendly Rust
Expand Down
5 changes: 4 additions & 1 deletion crates/lambert_izzo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,10 @@ for the parallel batch row).
| Workload | Throughput | Per call |
| ----------------------------------------- | -------------- | -------- |
| Single-rev (random Earth orbits) | ~3.1 M calls/s | ~320 ns |
| Multi-rev `M=3` (Earth orbits) | ~775 K calls/s | ~1.3 µs |
| Multi-rev `M=1` (Earth orbits) | ~1.5 M calls/s | ~650 ns |
| Multi-rev `M=3` (Earth orbits) | ~770 K calls/s | ~1.3 µs |
| Multi-rev `M=5` (Earth orbits) | ~520 K calls/s | ~1.9 µs |
| Battin near-parabolic (177°, single-rev) | ~4.2 M calls/s | ~240 ns |
| Sequential batch (loop over `lambert`) | ~1.2 M calls/s | ~830 ns |
| Parallel batch via `lambert_par` (rayon) | ~8.8 M calls/s | ~114 ns |

Expand Down
5 changes: 3 additions & 2 deletions crates/lambert_izzo/benches/batch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use criterion::{Criterion, criterion_group, criterion_main};
use lambert_izzo::{RevolutionBudget, lambert};
use lambert_izzo_test_support::bodies::MU_EARTH;
use lambert_izzo_test_support::elements_u64;
use lambert_izzo_test_support::random_inputs::{Spec, WayStrategy, generate};
use std::hint::black_box;

Expand All @@ -26,7 +27,7 @@ fn batch_sequential(c: &mut Criterion) {
let inputs = generate(&spec());

let mut group = c.benchmark_group("batch_sequential");
group.throughput(criterion::Throughput::Elements(inputs.len() as u64));
group.throughput(criterion::Throughput::Elements(elements_u64(inputs.len())));
group.sample_size(20);
group.bench_function("lambert_seq_x10000", |b| {
b.iter(|| {
Expand All @@ -46,7 +47,7 @@ fn batch_parallel(c: &mut Criterion) {
let inputs = generate(&spec());

let mut group = c.benchmark_group("batch_parallel");
group.throughput(criterion::Throughput::Elements(inputs.len() as u64));
group.throughput(criterion::Throughput::Elements(elements_u64(inputs.len())));
group.sample_size(20);
group.bench_function("lambert_par_x10000", |b| {
b.iter(|| {
Expand Down
96 changes: 80 additions & 16 deletions crates/lambert_izzo/benches/multi_rev.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,59 @@
//! Multi-revolution Lambert throughput (`M = 3`).
//! Multi-revolution Lambert throughput across `M ∈ {1, 3, 5}` plus a
//! Battin near-parabolic regime stress.
//!
//! Two benchmark groups:
//! - `multi_rev`: random Earth-orbit inputs parametrized over the
//! revolution budget via Criterion's `bench_with_input`.
//! - `multi_rev_battin`: a deterministic near-180° geometry whose
//! solutions sit in the Battin regime (`|x − 1| < BATTIN_THRESHOLD`),
//! constructed by jittering TOF around the parabolic time-of-flight.
//!
//! Run: `cargo bench --bench multi_rev`.

use criterion::{Criterion, criterion_group, criterion_main};
use lambert_izzo::{RevolutionBudget, lambert};
use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
use lambert_izzo::{LambertInput, RevolutionBudget, TransferWay, lambert};
use lambert_izzo_test_support::bodies::MU_EARTH;
use lambert_izzo_test_support::elements_u64;
use lambert_izzo_test_support::random_inputs::{Spec, WayStrategy, generate};
use rand::SeedableRng;
use rand::distributions::{Distribution, Uniform};
use rand_chacha::ChaCha20Rng;
use std::hint::black_box;

fn multi_rev_throughput(c: &mut Criterion) {
let inputs = generate(&Spec {
n: 1_000,
seed: 0xBEEF_DEAD,
radius_range: (5600.0, 10_500.0),
tof_range: (10_000.0, 250_000.0),
mu: MU_EARTH,
way: WayStrategy::Short,
revolutions: RevolutionBudget::try_up_to(3).expect("3 is within BoundedRevs::MAX"),
});
let mut group = c.benchmark_group("multi_rev");
group.sample_size(20);

for &m in &[1u32, 3, 5] {
let inputs = generate(&Spec {
n: 1_000,
seed: 0xBEEF_DEAD ^ u64::from(m),
radius_range: (5_600.0, 10_500.0),
tof_range: (10_000.0, 250_000.0),
mu: MU_EARTH,
way: WayStrategy::Short,
revolutions: RevolutionBudget::try_up_to(m).expect("m ≤ BoundedRevs::MAX"),
});

group.throughput(criterion::Throughput::Elements(elements_u64(inputs.len())));
group.bench_with_input(BenchmarkId::new("M", m), &inputs, |b, inputs| {
b.iter(|| {
for input in inputs {
let _ = black_box(lambert(input));
}
});
});
}
group.finish();
}

let mut group = c.benchmark_group("multi_rev_M3");
group.throughput(criterion::Throughput::Elements(inputs.len() as u64));
fn battin_near_parabolic(c: &mut Criterion) {
let inputs = generate_battin_inputs(1_000, MU_EARTH);

let mut group = c.benchmark_group("multi_rev_battin");
group.throughput(criterion::Throughput::Elements(elements_u64(inputs.len())));
group.sample_size(20);
group.bench_function("lambert_x1000_random_earth", |b| {
group.bench_function("near_parabolic_x1000", |b| {
b.iter(|| {
for input in &inputs {
let _ = black_box(lambert(input));
Expand All @@ -32,5 +63,38 @@ fn multi_rev_throughput(c: &mut Criterion) {
group.finish();
}

criterion_group!(benches, multi_rev_throughput);
/// Deterministic batch whose solutions land in `tof.rs`'s Battin regime
/// (`|x − 1| < BATTIN_THRESHOLD = 0.01`). Achieved via a fixed near-180°
/// transfer geometry with TOF jittered around the parabolic time-of-flight
/// (Lancaster's closed form `T_p = (√2 / (3√μ)) · (s^(3/2) − (s−c)^(3/2))`
/// for the short-way branch).
fn generate_battin_inputs(n: usize, mu: f64) -> Vec<LambertInput> {
let r_norm = 7_000.0_f64;
// ~177° transfer — close enough to parabolic to land in Battin,
// far enough from 180° to clear `COLINEARITY_TOL = 1e-15`.
let theta = std::f64::consts::PI - 0.05;
let r1 = [r_norm, 0.0, 0.0];
let r2 = [r_norm * theta.cos(), r_norm * theta.sin(), 0.0];

let dx = r2[0] - r1[0];
let dy = r2[1] - r1[1];
let chord = (dx * dx + dy * dy).sqrt();
let s = (2.0 * r_norm + chord) / 2.0;
let t_par = (2.0 / mu).sqrt() / 3.0 * (s.powf(1.5) - (s - chord).powf(1.5));

let mut rng = ChaCha20Rng::seed_from_u64(0xBA77_171F);
let dist = Uniform::new(0.995 * t_par, 1.005 * t_par);
(0..n)
.map(|_| LambertInput {
r1,
r2,
tof: dist.sample(&mut rng),
mu,
way: TransferWay::Short,
revolutions: RevolutionBudget::SingleOnly,
})
.collect()
}

criterion_group!(benches, multi_rev_throughput, battin_near_parabolic);
criterion_main!(benches);
3 changes: 2 additions & 1 deletion crates/lambert_izzo/benches/single_rev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use criterion::{Criterion, criterion_group, criterion_main};
use lambert_izzo::{RevolutionBudget, lambert};
use lambert_izzo_test_support::bodies::MU_EARTH;
use lambert_izzo_test_support::elements_u64;
use lambert_izzo_test_support::random_inputs::{Spec, WayStrategy, generate};
use std::hint::black_box;

Expand All @@ -20,7 +21,7 @@ fn single_rev_throughput(c: &mut Criterion) {
});

let mut group = c.benchmark_group("single_rev");
group.throughput(criterion::Throughput::Elements(inputs.len() as u64));
group.throughput(criterion::Throughput::Elements(elements_u64(inputs.len())));
group.sample_size(20);
group.bench_function("lambert_x10000_random_earth", |b| {
b.iter(|| {
Expand Down
12 changes: 12 additions & 0 deletions crates/lambert_izzo_test_support/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@ pub mod bodies {
pub const AU: f64 = 1.495_978_707e8;
}

/// `usize → u64` for `criterion::Throughput::Elements` callers.
///
/// Uses `u64::try_from` rather than `as` to comply with the workspace
/// casting policy (no silent truncation). The conversion is lossless on
/// every supported target (native 64-bit + `wasm32`); the `expect` only
/// trips on hypothetical >64-bit targets that the workspace doesn't
/// compile for.
#[must_use]
pub fn elements_u64(n: usize) -> u64 {
u64::try_from(n).expect("len fits in u64 on supported targets")
}

/// Generate a uniformly distributed unit vector by rejection sampling on
/// the unit cube. Used to seed Lambert stress / bench inputs.
pub fn rand_unit_vec<R: Rng + ?Sized>(rng: &mut R) -> [f64; 3] {
Expand Down