Skip to content

Commit dfa7c5b

Browse files
authored
feat: update benchmarks and add CI workflow details in changelog (#2)
1 parent e497786 commit dfa7c5b

6 files changed

Lines changed: 113 additions & 20 deletions

File tree

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,18 @@ the project follows [Semantic Versioning](https://semver.org/).
66

77
## [Unreleased]
88

9+
### Tooling
10+
11+
- CI: GitHub Actions workflow at `.github/workflows/ci.yml` covering
12+
MSRV (1.85) build × four feature combos, full build/test/doctest
13+
on stable × four feature combos, clippy, docs (with
14+
`RUSTDOCFLAGS: -D warnings`), wasm32 build for `lambert_izzo` and
15+
`lambert_izzo_wasm`, and the `stress` example.
16+
- Benches: `multi_rev` parametrized across `M ∈ {1, 3, 5}` via
17+
Criterion's `bench_with_input`; new `multi_rev_battin` group exercising
18+
the Battin near-parabolic regime (`|x − 1| < BATTIN_THRESHOLD`) via a
19+
near-180° geometry with TOF jittered around the parabolic time-of-flight.
20+
921
## [1.0.0] — 2026-04-30
1022

1123
Initial public release. `lambert_izzo` is a `no_std`-friendly Rust

crates/lambert_izzo/README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,10 @@ for the parallel batch row).
290290
| Workload | Throughput | Per call |
291291
| ----------------------------------------- | -------------- | -------- |
292292
| Single-rev (random Earth orbits) | ~3.1 M calls/s | ~320 ns |
293-
| Multi-rev `M=3` (Earth orbits) | ~775 K calls/s | ~1.3 µs |
293+
| Multi-rev `M=1` (Earth orbits) | ~1.5 M calls/s | ~650 ns |
294+
| Multi-rev `M=3` (Earth orbits) | ~770 K calls/s | ~1.3 µs |
295+
| Multi-rev `M=5` (Earth orbits) | ~520 K calls/s | ~1.9 µs |
296+
| Battin near-parabolic (177°, single-rev) | ~4.2 M calls/s | ~240 ns |
294297
| Sequential batch (loop over `lambert`) | ~1.2 M calls/s | ~830 ns |
295298
| Parallel batch via `lambert_par` (rayon) | ~8.8 M calls/s | ~114 ns |
296299

crates/lambert_izzo/benches/batch.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use criterion::{Criterion, criterion_group, criterion_main};
88
use lambert_izzo::{RevolutionBudget, lambert};
99
use lambert_izzo_test_support::bodies::MU_EARTH;
10+
use lambert_izzo_test_support::elements_u64;
1011
use lambert_izzo_test_support::random_inputs::{Spec, WayStrategy, generate};
1112
use std::hint::black_box;
1213

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

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

4849
let mut group = c.benchmark_group("batch_parallel");
49-
group.throughput(criterion::Throughput::Elements(inputs.len() as u64));
50+
group.throughput(criterion::Throughput::Elements(elements_u64(inputs.len())));
5051
group.sample_size(20);
5152
group.bench_function("lambert_par_x10000", |b| {
5253
b.iter(|| {
Lines changed: 80 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,59 @@
1-
//! Multi-revolution Lambert throughput (`M = 3`).
1+
//! Multi-revolution Lambert throughput across `M ∈ {1, 3, 5}` plus a
2+
//! Battin near-parabolic regime stress.
3+
//!
4+
//! Two benchmark groups:
5+
//! - `multi_rev`: random Earth-orbit inputs parametrized over the
6+
//! revolution budget via Criterion's `bench_with_input`.
7+
//! - `multi_rev_battin`: a deterministic near-180° geometry whose
8+
//! solutions sit in the Battin regime (`|x − 1| < BATTIN_THRESHOLD`),
9+
//! constructed by jittering TOF around the parabolic time-of-flight.
210
//!
311
//! Run: `cargo bench --bench multi_rev`.
412
5-
use criterion::{Criterion, criterion_group, criterion_main};
6-
use lambert_izzo::{RevolutionBudget, lambert};
13+
use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
14+
use lambert_izzo::{LambertInput, RevolutionBudget, TransferWay, lambert};
715
use lambert_izzo_test_support::bodies::MU_EARTH;
16+
use lambert_izzo_test_support::elements_u64;
817
use lambert_izzo_test_support::random_inputs::{Spec, WayStrategy, generate};
18+
use rand::SeedableRng;
19+
use rand::distributions::{Distribution, Uniform};
20+
use rand_chacha::ChaCha20Rng;
921
use std::hint::black_box;
1022

1123
fn multi_rev_throughput(c: &mut Criterion) {
12-
let inputs = generate(&Spec {
13-
n: 1_000,
14-
seed: 0xBEEF_DEAD,
15-
radius_range: (5600.0, 10_500.0),
16-
tof_range: (10_000.0, 250_000.0),
17-
mu: MU_EARTH,
18-
way: WayStrategy::Short,
19-
revolutions: RevolutionBudget::try_up_to(3).expect("3 is within BoundedRevs::MAX"),
20-
});
24+
let mut group = c.benchmark_group("multi_rev");
25+
group.sample_size(20);
26+
27+
for &m in &[1u32, 3, 5] {
28+
let inputs = generate(&Spec {
29+
n: 1_000,
30+
seed: 0xBEEF_DEAD ^ u64::from(m),
31+
radius_range: (5_600.0, 10_500.0),
32+
tof_range: (10_000.0, 250_000.0),
33+
mu: MU_EARTH,
34+
way: WayStrategy::Short,
35+
revolutions: RevolutionBudget::try_up_to(m).expect("m ≤ BoundedRevs::MAX"),
36+
});
37+
38+
group.throughput(criterion::Throughput::Elements(elements_u64(inputs.len())));
39+
group.bench_with_input(BenchmarkId::new("M", m), &inputs, |b, inputs| {
40+
b.iter(|| {
41+
for input in inputs {
42+
let _ = black_box(lambert(input));
43+
}
44+
});
45+
});
46+
}
47+
group.finish();
48+
}
2149

22-
let mut group = c.benchmark_group("multi_rev_M3");
23-
group.throughput(criterion::Throughput::Elements(inputs.len() as u64));
50+
fn battin_near_parabolic(c: &mut Criterion) {
51+
let inputs = generate_battin_inputs(1_000, MU_EARTH);
52+
53+
let mut group = c.benchmark_group("multi_rev_battin");
54+
group.throughput(criterion::Throughput::Elements(elements_u64(inputs.len())));
2455
group.sample_size(20);
25-
group.bench_function("lambert_x1000_random_earth", |b| {
56+
group.bench_function("near_parabolic_x1000", |b| {
2657
b.iter(|| {
2758
for input in &inputs {
2859
let _ = black_box(lambert(input));
@@ -32,5 +63,38 @@ fn multi_rev_throughput(c: &mut Criterion) {
3263
group.finish();
3364
}
3465

35-
criterion_group!(benches, multi_rev_throughput);
66+
/// Deterministic batch whose solutions land in `tof.rs`'s Battin regime
67+
/// (`|x − 1| < BATTIN_THRESHOLD = 0.01`). Achieved via a fixed near-180°
68+
/// transfer geometry with TOF jittered around the parabolic time-of-flight
69+
/// (Lancaster's closed form `T_p = (√2 / (3√μ)) · (s^(3/2) − (s−c)^(3/2))`
70+
/// for the short-way branch).
71+
fn generate_battin_inputs(n: usize, mu: f64) -> Vec<LambertInput> {
72+
let r_norm = 7_000.0_f64;
73+
// ~177° transfer — close enough to parabolic to land in Battin,
74+
// far enough from 180° to clear `COLINEARITY_TOL = 1e-15`.
75+
let theta = std::f64::consts::PI - 0.05;
76+
let r1 = [r_norm, 0.0, 0.0];
77+
let r2 = [r_norm * theta.cos(), r_norm * theta.sin(), 0.0];
78+
79+
let dx = r2[0] - r1[0];
80+
let dy = r2[1] - r1[1];
81+
let chord = (dx * dx + dy * dy).sqrt();
82+
let s = (2.0 * r_norm + chord) / 2.0;
83+
let t_par = (2.0 / mu).sqrt() / 3.0 * (s.powf(1.5) - (s - chord).powf(1.5));
84+
85+
let mut rng = ChaCha20Rng::seed_from_u64(0xBA77_171F);
86+
let dist = Uniform::new(0.995 * t_par, 1.005 * t_par);
87+
(0..n)
88+
.map(|_| LambertInput {
89+
r1,
90+
r2,
91+
tof: dist.sample(&mut rng),
92+
mu,
93+
way: TransferWay::Short,
94+
revolutions: RevolutionBudget::SingleOnly,
95+
})
96+
.collect()
97+
}
98+
99+
criterion_group!(benches, multi_rev_throughput, battin_near_parabolic);
36100
criterion_main!(benches);

crates/lambert_izzo/benches/single_rev.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use criterion::{Criterion, criterion_group, criterion_main};
66
use lambert_izzo::{RevolutionBudget, lambert};
77
use lambert_izzo_test_support::bodies::MU_EARTH;
8+
use lambert_izzo_test_support::elements_u64;
89
use lambert_izzo_test_support::random_inputs::{Spec, WayStrategy, generate};
910
use std::hint::black_box;
1011

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

2223
let mut group = c.benchmark_group("single_rev");
23-
group.throughput(criterion::Throughput::Elements(inputs.len() as u64));
24+
group.throughput(criterion::Throughput::Elements(elements_u64(inputs.len())));
2425
group.sample_size(20);
2526
group.bench_function("lambert_x10000_random_earth", |b| {
2627
b.iter(|| {

crates/lambert_izzo_test_support/src/lib.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,18 @@ pub mod bodies {
1717
pub const AU: f64 = 1.495_978_707e8;
1818
}
1919

20+
/// `usize → u64` for `criterion::Throughput::Elements` callers.
21+
///
22+
/// Uses `u64::try_from` rather than `as` to comply with the workspace
23+
/// casting policy (no silent truncation). The conversion is lossless on
24+
/// every supported target (native 64-bit + `wasm32`); the `expect` only
25+
/// trips on hypothetical >64-bit targets that the workspace doesn't
26+
/// compile for.
27+
#[must_use]
28+
pub fn elements_u64(n: usize) -> u64 {
29+
u64::try_from(n).expect("len fits in u64 on supported targets")
30+
}
31+
2032
/// Generate a uniformly distributed unit vector by rejection sampling on
2133
/// the unit cube. Used to seed Lambert stress / bench inputs.
2234
pub fn rand_unit_vec<R: Rng + ?Sized>(rng: &mut R) -> [f64; 3] {

0 commit comments

Comments
 (0)