Orbit Determination from Linked Observations

Problem

You have linked detections and need fitted orbits with quantitative quality metrics and outlier-aware membership outputs.

What You Get Back

The orbit-determination APIs return two synchronized tables:

  • FittedOrbits: one row per solved candidate with quality metrics (orbit_id, arc_length, num_obs, chi2, reduced_chi2, convergence/status fields, and best-fit Cartesian state).

  • FittedOrbitMembers: one row per observation assignment with orbit_id + obs_id plus per-observation residuals and outlier flags.

You use them together to rank candidates, inspect residual structure, remove poor fits, and feed validated solutions downstream.

Implementation Options and Tradeoffs

  • initial_orbit_determination only: Fast triage and coarse fits for many candidates.

  • initial_orbit_determination + fit_least_squares: Higher-quality solutions and covariance updates, more compute and tuning.

Runnable Example

import pyarrow.compute as pc
from adam_assist import ASSISTPropagator
from adam_core.orbit_determination import (
    FittedOrbitMembers,
    FittedOrbits,
    OrbitDeterminationObservations,
    evaluate_orbits,
    fit_least_squares,
    initial_orbit_determination,
)

observations: OrbitDeterminationObservations
linkage_members: FittedOrbitMembers
# These are typically produced by your detection association pipeline.

propagator_cls: type[ASSISTPropagator] = ASSISTPropagator

iod_orbits: FittedOrbits
iod_members: FittedOrbitMembers
iod_orbits, iod_members = initial_orbit_determination(
    observations=observations,
    linkage_members=linkage_members,
    propagator=propagator_cls,
    min_obs=6,
    min_arc_length=1.0,
    chunk_size=1,
    max_processes=4,
)

propagator = ASSISTPropagator()

# Inspect top candidates by quality metric.
ranked_orbits = iod_orbits.sort_by([("reduced_chi2", "ascending")])

# Refine one candidate orbit with differential correction.
# (Converts one fitted row to Orbits for the least-squares fitter.)
fitted_orbit, fitted_members = fit_least_squares(
    orbit=ranked_orbits.take([0]).to_orbits(),
    observations=observations,
    propagator=propagator,
)

# Evaluate an orbit set against the same observation bundle.
evaluated_orbits, evaluated_members = evaluate_orbits(
    orbits=iod_orbits.to_orbits(),
    observations=observations,
    propagator=propagator,
)

# Example: count non-outlier members by orbit_id.
non_outlier = evaluated_members.apply_mask(pc.invert(evaluated_members.outlier))
print(non_outlier.group_by("orbit_id").aggregate([("obs_id", "count")]))

When to Use This Pattern

Use this for candidate confirmation, orbit quality scoring, and iterative cleanup of observation-to-orbit assignments.