Smithsonian / Smithsonian/layup
BK-IOD cannot be selected: it is reachable only as a fallback after every Gauss root has failed
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:
- It is never registered.
iod.pyregisters onlygauss(line 201).run_bk_iodis called directly insidedo_fit's fallback rather than through the registry, soget_iod("bk")does not exist. _orbitfithardcodes the accepted names:if iod.lower() in ["gauss", "auto"](orbitfit.py:1570), with anything else routed todo_other_fit, which raises.iod_methods()already exists iniod.pyand is already imported intoorbitfit.pyat 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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