Skip to content

Add radar (delay/Doppler) Python ingest + end-to-end driver test (#146)#355

Open
matthewholman wants to merge 3 commits into
mainfrom
issue/146/radar-ingest
Open

Add radar (delay/Doppler) Python ingest + end-to-end driver test (#146)#355
matthewholman wants to merge 3 commits into
mainfrom
issue/146/radar-ingest

Conversation

@matthewholman

@matthewholman matthewholman commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator

What

Radar (delay/Doppler) Python ingest + end-to-end and real-data validation (issue #146, P2 + P3). Stacks on the C++ two-leg core PR.

Ingest_radar_observation + _is_radar in orbitfit.py: per-row dispatch; µs→days and Hz→au/day via per-obs freqTx; uncertainties from rmsDelay/rmsDoppler or defaults; Doppler without freqTx is a ValueError. orbitfit() finite-differences the barycentric observer velocity to supply the observer acceleration the two-leg model needs (only when radar columns are present). Radar forced to engine='cartesian'; _is_valid_data radar-aware; occultation/debias/Véres-weighting skip radar rows.

Validation

  • test_radar_ingest.py (6, no ASSIST): unit conventions + dispatch.
  • test_radar_end_to_end.py (2): a radar arc through the full orbitfit() driver; in-test truth mirrors the C++ two-leg model, so the noise-free fit reaches csq<1e-6 (ndof=2N−6; delay-only ndof=N−6).
  • test_radar_realdata_validation.py (1): fits the real 2013 JPL radar arc for (99942) Apophis (36 monostatic delay/Doppler rows from Goldstone DSS-14 + Arecibo) seeded from a stored JPL Horizons reference, and recovers the barycentric state to <1e-6 vs Horizons. Offline/deterministic, like test_tno_validation. A regression to the single-position model misses by orders of magnitude.

Stacking

Base is the C++ two-leg core PR. Merge order: chi2_final fix → C++ core → this.

🤖 Generated with Claude Code

matthewholman and others added 3 commits June 23, 2026 15:15
Wires the C++ RadarObservation type into the orbitfit Python pipeline.

- _radar_observation(): builds Observation.from_radar from a row, converting
  JPL units to the fitter's internal units -- delay us -> days, Doppler Hz ->
  round-trip range-rate au/day via the per-observation transmit frequency
  freqTx (doppler[au/day] = -c * doppler[Hz] / freqTx). Uncertainties from
  rmsDelay/rmsDoppler when present, else ~1 us / ~1 Hz defaults. A Doppler row
  without a usable freqTx is a ValueError, not a silent NaN.
- _is_radar(): per-row dispatch (a row is radar when delay or doppler is
  populated); added a radar branch ahead of the streak/astrometry branches.
- REQUIRED_INPUT_OBSERVATIONS_COLUMN_NAMES: delay/doppler added as one-of
  observable options alongside ra/dec and raRate/decRate.
- _is_valid_data(): radar-aware -- a radar-only file (no ra/dec columns) now
  validates, while each row must still carry a usable observable.
- The occultation / debias / Veres-weighting steps (all astrometric) skip radar
  rows; radar is forced onto engine="cartesian" (BK-native can't do the
  variable-row packing yet).

Units conversion, dispatch, and validation are locked by
tests/layup/test_radar_ingest.py (6 tests, no ASSIST needed). Full optical +
streak + radar suites: 23 passed, no regression.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Exercises the full orbitfit() Python driver path for radar observations,
which the existing radar tests skip: test_radar_validation.py calls
run_from_vector directly with observer states baked into a fixture, and
test_radar_ingest.py covers only the unit conversion in isolation. Neither
runs a radar arc through orbitfit() -> spice.str2et + obscodes_to_barycentric
station states -> _radar_observation dispatch -> variable-row Cartesian fit.

The truth delay/Doppler are generated in-test against the driver's own
obscodes_to_barycentric station states and convert_tdb_date_to_julian_date
epochs, so the observation cannot drift from the observer model the fitter
uses -- the consistency gap left when the C++ and Python cores were validated
separately. Orbit is the same ~2.6 AU MBA near opposition as the streak
fixture; truth observables are an independent ASSIST propagation at the C++
light-time convention. Radar refines a prior orbit (perturbed initial guess,
bypassing IOD), matching real radar astrometry and giving a zero-residual
fixed point the noise-free fit must return to.

Two tests: full delay+Doppler recovery (state to <1e-6, csq<1e-6, ndof=2N-6)
and a delay-only arc (has_delay/has_doppler dispatch, ndof=N-6). Verified
non-vacuous: a 10 us delay corruption raises csq from <1e-6 to ~7.6.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Completes the two-leg radar light-time model on the Python side and locks
it against real measurements.

- orbitfit() finite-differences the barycentric observer velocity from
  obscodes_to_barycentric (only when radar columns are present) to get the
  observer acceleration, appended as ax/ay/az and passed to
  Observation.from_radar_with_id. The C++ model uses it to extrapolate the
  station state to the signal transmit time.
- test_radar_end_to_end.py's in-test truth now mirrors the C++ two-leg
  model exactly (down + up leg, station Taylor-extrapolated with the same
  finite-difference acceleration), so the noise-free fit still reaches
  csq < 1e-6.
- New tests/layup/test_radar_realdata_validation.py: fits the real 2013
  JPL radar arc for (99942) Apophis (36 monostatic delay/Doppler rows from
  Goldstone DSS-14 + Arecibo, tests/data/apophis_2013_radar.json) seeded
  from a stored JPL Horizons reference, and recovers the barycentric state
  to < 1e-6 vs Horizons. Offline/deterministic, like test_tno_validation.
  A regression to the single-position model misses by orders of magnitude.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@matthewholman matthewholman force-pushed the issue/146/radar-ingest branch from 5566b9f to b813773 Compare June 23, 2026 19:16
Base automatically changed from issue/146/radar-core to main June 24, 2026 16:24
@matthewholman matthewholman reopened this Jun 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant