Incremental steady-state fit driver (capstone of #419) - #422
Merged
Conversation
Ties the three levers into one operational maintenance pass over a batch of
objects. Per object, incremental_orbitfit routes:
* skip -- observation set unchanged since the prior catalog (matching
fingerprint): carry the prior fit forward verbatim, no fit.
* sequential -- observations only appended (prior_obs given and every prior
obs still present): sequential_update() over the new obs only
(integrating just the new observations). Its nonlinearity gate
falls back to a full refit when the update is too large.
* full refit -- observations removed or changed, or no per-object prior_obs:
refit over all current obs, warm-started from the prior state.
* cold -- no prior for the object: cold IOD fit.
Obs-level diffing (_obs_row_keys / _append_only_new_obs) reuses #420's per-row
fingerprint strings, so a row's diff key equals its contribution to the object's
fingerprint. Returns the updated result catalog (same schema as orbitfit output,
carrying obs_hash/nobs_fit so it seeds the next cycle) plus a routing tally.
Warm/cold full refits go through orbitfit in two calls (warm objects filtered to
their priors, cold with no guess) so _orbitfit's per-object initial-guess lookup
is satisfied.
Stacks on #420 (fingerprint + skip) and #421 (sequential update). Validated on
real MPC data (mpc_demo/cycle_rehearsal_seq.py): sequential route matches the full
refit to state rel ~1e-8 / KEP a ~1e-9, 6.5-13.7x faster on ~1000-obs arcs (only
new obs integrated). 5 tests in test_incremental_driver.py cover all routes.
Part of #419.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts: # src/layup/orbitfit.py
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
The capstone of incremental orbit determination (issue #419):
incremental_orbitfit, a single steady-state maintenance pass over a batch of objects that ties the three levers together. For each object it routes:prior_catalog(matching fingerprint): carry the prior fit forward verbatim, no fit.prior_obsgiven and every prior observation still present): update viasequential_updateover the new observations only. Its nonlinearity gate falls back to a full refit when the update is too large.prior_obs: refit over all current obs, warm-started from the prior state.Returns the updated result catalog (same schema as
orbitfitoutput, carrying theobs_hash/nobs_fitfingerprint so it seeds the next cycle) plus a routing tally.Obs-level diffing (
_obs_row_keys/_append_only_new_obs) reuses #420's per-row fingerprint strings, so a row's diff key equals its contribution to the object's fingerprint — append-only vs modified is detected exactly.This is one feature in three parts and stacks on #420 and #421:
orbitfit(skip_unchanged=True)(the skip route + schema).run_sequential_update+sequential_update).This branch merges both, so the diff below includes them. Merge #420 and #421 first; this PR's net-new code is a single commit,
091f804(incremental_orbitfit+tests/layup/test_incremental_driver.py) — review that commit directly, and I'll rebase once the two land so the diff reduces to just the driver.Validation
test_incremental_driver.py(skip byte-identity, sequential-matches-full-refit, modified-obs→full, cold, no-prior-obs→full). Full suite of Incremental fits: obs fingerprint + skip-unchanged (Phase A of #419) #420 + Sequential / information-filter orbit update (Lever 3 of #419) #421 + this + base fit tests: 31 passed.mpc_demo/cycle_rehearsal_seq.py, a steady-state cycle on numbered asteroids): the sequential route matches a full refit to state rel ~1e-8 / KEP a ~1e-9, at 6.5–13.7× less wall-clock on ~1000-obs arcs (only the new obs are integrated; the win approaches the integration-work ratio as the arc lengthens). The nonlinearity gate never tripped on well-observed objects with small nightly additions — its job is the pathological cases.Combined with the skip route, the steady-state loop is: 95–99% of objects skip (zero cost), changed objects sequential-update at ~7–13× less work, and only non-append or nonlinear changes fall back to a full refit — the operational maintenance loop JPL/MPC run.
Follow-ups (unchanged from #421)
Outlier rejection on the new observations in the sequential path; non-grav priors; radar/occultation in the sequential path; differing-epoch priors. A CLI wrapper for
incremental_orbitfitcan follow once the API settles.Part of #419.
Closes #419