Smithsonian / Smithsonian/layup

BK-IOD cannot be selected: it is reachable only as a fallback after every Gauss root has failed

Open
#586 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
10
Forks
2
Avg merge
1d 43m
Merged PRs (30d)
58

Description

The Bernstein-Khushalani 5-parameter linear initial orbit determination is in the package and works, but there is no way to ask for it. orbitfit accepts iod="gauss" or iod="auto", and under auto the BK seed is tried only when every Gauss root has already failed to seed a converged fit (orbitfit.py:1206-1216). For a survey of distant objects that is the wrong order, and the code's own comment at that line says so:

BK-IOD shines on distant short arcs -- exactly where Gauss's three-point geometry is ill-conditioned.

Two things block selecting it, and neither is in the fitter:

  1. It is never registered. iod.py registers only gauss (line 201). run_bk_iod is called directly inside do_fit's fallback rather than through the registry, so get_iod("bk") does not exist.
  2. _orbitfit hardcodes the accepted names: if iod.lower() in ["gauss", "auto"] (orbitfit.py:1570), with anything else routed to do_other_fit, which raises. iod_methods() already exists in iod.py and is already imported into orbitfit.py at line 87, unused for this.

So a registered method still cannot be reached from the public entry point. The same line is why PR #554 has to append "herget" to that list: every new method needs an edit in a file other than the one it registers in.

What it costs, measured on a real survey

110 trans-Neptunian objects recovered by a shift-and-stack search of DECam Deep Ecliptic Exploration Project (DEEP) data, fields DEEP001-006, by P. H. Bernardinelli, who supplied both the detections and his own orbit fits. 493 detection pairs, median heliocentric distance 42 AU, magnitudes 22.3-26.3, all with MPC designations. Each object has 8-12 observations, taken in pairs about four hours apart, across 3 nights spanning roughly 770 days with the nights 300-400 days apart. Fits are gravity-only with supplied per-observation uncertainties (median 0.13 arcsec).

Because _build_sequence splits on 90-day gaps, seq[0] is a single night -- four observations, four hours -- for every object, and do_fit screens each IOD candidate against it. The Cartesian engine is asked for six free parameters there, ndof = 2.

engine (same data, sequence, IOD and weights) flag 0
cartesian 53 / 110
bk_native 93 / 110

43 of the 57 Cartesian failures reach flag 0 under the BK engine and 3 are lost. The 57 failures are 49 at flag 3 and 8 at flag 4. The recovered fits are sound rather than merely converged: reduced chi-square p10/median/p90 of 0.18 / 0.48 / 1.44 across all BK flag-0 fits, and heliocentric distances of 35-77 AU with a median of 42.8.

The BK path succeeds because it solves five parameters with gdot pinned by the bound-orbit energy prior, which is tractable against four observations of a distant object where six free parameters are not.

Suggested change

Register a bk_iod(observations, seq) wrapper mirroring gauss_iod's contract -- run_bk_iod on seq[0] at the middle observation's epoch, matching the convention the existing fallback already uses -- and replace the hardcoded list with a registry lookup, iod.lower() == "auto" or iod.lower() in iod_methods(). run_bk_iod and MU_SUN are already importable from layup.routines and layup.constants. do_other_fit's error survives for genuinely unknown names. Tests can extend tests/layup/test_bk_iod.py and test_iod_auto.py.

Deliberately not proposed here

Whether auto should try BK-IOD first for distant objects, rather than only after Gauss has failed. This dataset argues for it, but it is one population of trans-Neptunian objects and Gauss-first is presumably right for the main belt. That is a measurement and a behaviour change, not plumbing, and it should be decided separately from making the method reachable at all.

Drafted with Claude Opus 5.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in iod.py with gauss_iod, the registration logic, and iod_methods(), then trace _orbitfit and do_fit in orbitfit.py, especially lines 1206-1216 and 1570. Run the existing tests in tests/layup/test_bk_iod.py and test_iod_auto.py before extending coverage. Done means BK can be selected through the public orbitfit entry point while unknown IOD names still fail as before.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
backend
Issue type
Feature
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
78/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.