Skip to Content

orbitforge od ekf

Synopsis

orbitforge od ekf --obs <PATH> --stations <PATH> --initial <PATH> --epoch <RFC3339> [OPTIONS]

Description

An extended Kalman filter processes observations one at a time, in order, maintaining a running state and covariance. Between measurements it propagates both forward, growing the uncertainty. At each measurement it applies a correction weighted by how much it trusts the prediction against the observation.

Unlike od bls, it never revisits an earlier observation. That makes it suitable for tracking an object as data arrives, and it means the final state reflects only information available up to that point.

Options

ParameterTypeUnitDefaultRequiredDescription
--obspathn/a—YesTracking CSV.
--stationspathn/a—YesTracking-stations JSON. Must describe every station in the observation file.
--initialpathn/a—YesKeplerian-elements JSON, the initial state at the filter start epoch.
--epochstringRFC 3339 UTC—YesFilter start epoch.
--forceenumn/aj2NoForce model: `two-body`, `j2`, or `j4`.
--sncfloatkm^2/s^31e-12NoState noise compensation: acceleration power spectral density. See below.
--sigma-pos-kmfloatkm10NoInitial position uncertainty, one sigma per axis.
--sigma-vel-km-sfloatkm/s0.01NoInitial velocity uncertainty, one sigma per axis.
--jsonpathn/a—NoWrite the full filter history: state and covariance at every epoch.

Worked example

The same 220 observations that od bls fits in batch:

orbitforge od ekf \ --obs obs.csv \ --stations stations.json \ --initial orbit.json \ --epoch 2026-01-01T00:00:00Z
EKF processed 110 epochs: 220 accepted / 0 rejected. Final position sigma [0.0362, 0.1861, 0.0256] km at 2026-01-02T00:00:00Z.

110 epochs carrying 220 observations, because each visible epoch produced both a range and a range-rate measurement.

Comparing against batch least squares

Run on identical data, the two methods report very different uncertainty:

MethodPosition sigma, km per axisAt epoch
od bls0.0006, 0.0008, 0.0009Start of arc
od ekf0.0362, 0.1861, 0.0256End of arc

The filter’s figures are one to two orders of magnitude larger, and that is correct rather than a defect. Three things drive it:

  1. Different epochs. Batch reports uncertainty at the solution epoch, constrained by observations both before and after it in the arc. The filter reports it at the final epoch, constrained only by what came before.
  2. Time since the last observation. The covariance grows during propagation. If the last pass ended well before the final epoch, the filter has been coasting.
  3. Process noise. --snc deliberately inflates the covariance to keep the filter responsive, which batch does not do at all.

The middle component, 0.1861 km, is several times larger than the other two. That is the in-track direction, and it is the expected signature: along-track uncertainty grows fastest under almost every error source, because an error in velocity magnitude integrates directly into along-track position.

If your filter reports its largest uncertainty in radial or cross-track instead, something is usually wrong with the geometry or the station model.

Process noise, and why it exists

--snc adds acceleration power spectral density to the propagation step, inflating the covariance over time. Its purpose is to stop the filter becoming overconfident.

Without process noise, a filter processing many consistent observations drives its covariance toward zero. It then effectively stops believing new measurements, because the prediction appears far more certain than any observation. That is called filter saturation or smugness, and its practical symptom is a filter that ignores a genuine maneuver or a real change in dynamics.

SettingEffectSymptom when wrong
Too smallCovariance collapses; the filter stops responding to dataResiduals grow but the covariance stays tiny
Too largeCovariance stays inflated; the filter over-trusts each new measurementEstimate is noisy and jumps between observations

The default of 1e-12 km^2/s^3 is a starting point for a well-modeled orbit. Tune it upward when the dynamics are less well known, for example a drag-dominated low-altitude object or a spacecraft that maneuvers.

Initial uncertainty

--sigma-pos-km and --sigma-vel-km-s set how much the filter trusts the initial guess. The defaults, 10 km and 0.01 km/s, are deliberately loose so a rough initial orbit does not fight the data.

Setting them too tight is the classic way to make a filter reject good observations: the prediction looks so certain that every measurement appears to be an outlier.

Validating a filter

Because od simulate-tracking starts from a known truth orbit, you can check something you never can operationally: whether the reported covariance is honest.

Run many seeds, compare the actual error against the reported sigma, and confirm the errors fall within one sigma about 68 percent of the time. A filter that reports 0.03 km while actually erring by 0.3 km is worse than one that honestly reports 0.3 km, because downstream decisions trust the number.

See also

Question? Give us feedbackDocuments Varaha Constellation Designer main (pre-release)
Last updated on