orbitprop: initial_step_secs hint and next_step_secs warm start (numeris 0.6) - #178
Merged
Conversation
… (numeris 0.6) The adaptive integrators no longer start from numeris' Hairer starting-step heuristic, which is scale sensitive: for an orbit in metres and seconds at 1e-9 tolerance it started RKV98 at a fraction of a millisecond against a ~270 s working step and spent about half the force evaluations of a one-hour arc growing into it (satkit #175 discussion). - `PropSettings::initial_step_secs: Option<f64>` — first step to attempt. `None` (default) derives `1.5 · |r|/|v| · tol^(1/(p+1))` from the initial state, the tolerances (`tol = rel_error + abs_error/|r|`) and the integrator order `p`: the settled stride of an order-p method scales as tol^(1/(p+1)), and the constant is fit to LEO strides. Across RKTS54..RKV98 and 1e-6..1e-12 the hint lands within 0.64–2.6× of the settled stride, so the controller is on stride in a step or two. Ignored by Gauss-Jackson 8. Zero / non-finite → error. - `PropagationResult::next_step_secs` — the integrator's working stride at the end of the arc (numeris `Solution::next_step`; GJ8's fixed step; 0 for a zero-duration arc), signed like the propagation direction, so a follow-on arc can warm-start by passing it back. - Python: `propsettings.initial_step_secs` (kwarg + property, pickled) and `propresult.next_step_secs`; stubs and docstrings; integrators guide section; CHANGELOG. - numeris requirement 0.5.18 → 0.6.0. One-hour 550 km arc, RKV98 at 1e-9, 8x8 gravity: 525 evals with the old-heuristic-sized start → 315 with the default hint → 294 warm-started from the previous hour; every RK integrator at every tolerance from 1e-6 to 1e-12 is now within one step of the warm-start cost. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017VHexyxYasfatXebfxtphw
The five extra stages of the 21-stage Verner 9(8) tableau exist only to build the interpolant. With `enable_interp = false` nothing is stored to interpolate, so `Integrator::RKV98` now dispatches to `RKV98NoInterp`: same order and error control, 24% fewer force evaluations per step. The result still reports `Integrator::RKV98`; `interp` fails with "no dense output" exactly as before. Results for that combination change at the tolerance level (a different tableau takes different steps). Tests pin evals == 16 × steps without interpolation and 21 × steps with, and agreement of the two final positions to 1 cm over an hour. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017VHexyxYasfatXebfxtphw
ssmichael1
force-pushed
the
feat/ode-warm-start
branch
from
September 12, 2026 01:17
0fbf5c0 to
c5971aa
Compare
Merged
ssmichael1
added a commit
that referenced
this pull request
Sep 12, 2026
Version bump in Cargo.toml, python/Cargo.toml, pyproject.toml; changelog rolled (Unreleased -> 0.22.0 - 2026-09-12; entries added for #176 and the #178 references; 0.20.3 pruned to keep five releases). Docs index links the new starting-step / warm-start section of the integrators guide. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017VHexyxYasfatXebfxtphw
ssmichael1
added a commit
that referenced
this pull request
Sep 12, 2026
* Release 0.22.0 Version bump in Cargo.toml, python/Cargo.toml, pyproject.toml; changelog rolled (Unreleased -> 0.22.0 - 2026-09-12; entries added for #176 and the #178 references; 0.20.3 pruned to keep five releases). Docs index links the new starting-step / warm-start section of the integrators guide. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017VHexyxYasfatXebfxtphw * docs: release steps in CONTRIBUTING (cargo update, version sync, tag) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017VHexyxYasfatXebfxtphw * build: compile the extension with --locked against the committed Cargo.lock Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017VHexyxYasfatXebfxtphw * Cargo.lock: record 0.22.0 for the workspace crates Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017VHexyxYasfatXebfxtphw --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Follow-up to the initial-step discussion on #175. The adaptive integrators no longer start from numeris' Hairer starting-step heuristic, which is scale sensitive: for an orbit in metres and seconds at 1e-9 tolerance it started
rkv98at a fraction of a millisecond against a ~270 s working step, and about half the force evaluations of a one-hour arc went into growing that first step.propsettings.initial_step_secs(RustPropSettings::initial_step_secs: Option<f64>) — first step the adaptive integrators attempt. DefaultNonederives it from the initial state, the tolerances and the integrator order:The settled stride of an order-p method scales as tol^(1/(p+1)); the constant is fit to LEO strides. Across
rkts54..rkv98and tolerances 1e-6..1e-12 the hint lands within 0.64–2.6× of the settled stride, so the controller is on stride within a step or two. Ignored bygauss_jackson8. Zero / non-finite raises.propresult.next_step_secs(RustPropagationResult::next_step_secs) — the integrator's working stride at the end of the arc (numerisSolution::next_step, i.e. the controller's last unclamped proposal; GJ8's fixed step; 0 for a zero-duration arc), signed like the propagation direction. Pass it back asinitial_step_secsto warm-start a follow-on arc.Python kwarg + property (pickled), stubs and docstrings, a "Starting Step and Warm Start" section in the integrators guide, CHANGELOG entry.
numeris requirement 0.5.18 → 0.6.0 (which adds
AdaptiveSettings::initial_step/Solution::next_step).Measured against
mainSame script, same machine: one-hour arcs, 8x8 gravity. h1 = first hour from an axis-aligned state, h2 = second hour from the resulting general state, (n) = rejected steps.
For the #175 script's scenario (40x40 EGM96 + drag + third bodies,
rkv98at 1e-9) the first hour goes from 779 evaluations onmainto 441 with the default hint and 420 warm-started; final positions agree to 2 mm.Why not fix the controller instead: the ramp is bound by the error-estimate noise floor (round-off against a 1e-9 m absolute tolerance), not by the PID exponents — an undamped opening phase recovered only ~25–30% of the waste and made short-arc error ~10× worse. A step hint removes the ramp entirely and leaves the controller and tolerances untouched.
Also:
rkv98runs as the 16-stage tableau when interpolation is offAfter the ramp is gone the step controller is within a few percent of optimal (an undamped controller gained 1.5% on a one-day arc), so the remaining lever is stage count. The five extra stages of the 21-stage Verner 9(8) tableau exist only to build the interpolant. With
enable_interp=Falsenothing is stored to interpolate, sointegrator.rkv98now dispatches to the 16-stagerkv98_nointerptableau: same order and error control, 24% fewer force evaluations per step. The result still reportsrkv98andinterpfails with "no dense output" exactly as before. Results for that combination change at the tolerance level, since a different tableau takes different steps. Tests pinevals == 16 × stepswithout interpolation and21 × stepswith, and 1 cm agreement of the two final positions over an hour.Test plan
test_initial_step_and_warm_start: hint is the first accepted step, warm ≲ default < cold, same final position, backward stride sign, GJ8 reports its fixed step, invalid hint errorstest_propagation.py(default / kwarg / setter, pickle round-trip, stride sign, warm-start savings, 16-stage tableau, invalid hint) — 50/50 pass on the rebuilt extensioncargo test --release --lib(298) and doctests (44) pass; clippy clean;python/crate type-checks🤖 Generated with Claude Code
https://claude.ai/code/session_017VHexyxYasfatXebfxtphw