Interacting Multiple Model Filter (gnc-autonomy/estimation-filtering/interacting-multiple-model-filter)
Use when the task is maneuvering-target tracking with an interacting
multiple model (IMM) filter: a two-mode bank of Kalman filters, one
constant-velocity (CV) and one constant-acceleration (CA), that interact
through the Markov mode-transition probabilities each cycle. This leaf
implements the IMM cycle (mix, predict, update, mode-probability refresh,
combine) in pure Python, stdlib only, with the standard per-axis planar
simplification: each planar axis runs its own 2-state CV or 3-state CA
filter and both axes share one mode-probability vector. It pairs with
gnc-autonomy/navigation/kalman-filter-design and the single-model filters
of the estimation-filtering pack for the mode-conditioned core.
Domain quick reference
- Mode 1 CV, state [p, v]: F_cv = [[1, DT], [0, 1]], Q_cv = q_cv *
[[DT^3/3, DT^2/2], [DT^2/2, DT]] with q_cv = 1.0 m2/s3.
- Mode 2 CA, state [p, v, a]: F_ca = [[1, DT, 0.5 DT^2], [0, 1, DT],
[0, 0, 1]], Q_ca = q_ca * [[DT^5/20, DT^4/8, DT^3/6], [DT^4/8,
DT^3/3, DT^2/2], [DT^3/6, DT^2/2, DT]] with q_ca = 2.0. DT = 1.0 s.
- Measurement: position only, H = [1, 0] or [1, 0, 0], r = 25.0 m2
(sigma 5 m). Markov transition Pi = [[0.95, 0.05], [0.05, 0.95]].
- Mode prediction: c_j = sum_i pi_ij * mu_i and mixing weights
mu_ij = pi_ij * mu_i / c_j; the mixed prior of mode j is
x0_j = sum_i mu_ij * x_i with the interaction spread term in the
covariance.
- Mode likelihood: L = exp(-0.5 * innovation^2 / s) / sqrt(2 pi s) with
s = H P H^T + r; mode update mu_new_j = L_j c_j / sum_k L_k c_k.
- Combined estimate: x_combined = sum_j mu_j * x_j per axis (CV padded
with zero acceleration); both planar axes multiply their per-mode
likelihoods into one joint per-mode likelihood per cycle.
- All matrix arithmetic is hand coded stdlib, 2x2 and 3x3 only; no numpy.
Workflow
- Generate the deterministic maneuvering truth track with
make_maneuvering_track (100 m/s along x, then ay = 20 m/s2 for 25 s
from t = 50 s) or pass any scripted position list.
- Start the bank with run_imm_track: both axes anchor on the first
measurement with MU0 = [0.95, 0.05] and the documented initial
covariances.
- When a distinct prior per mode exists, form the common interacting
prior with mix_initial(mu, x_ests, p_ests, pi).
- Inside each cycle imm_step mixes the mode-conditioned priors per mode
(CV projected from CA, CA padded from CV), calls kalman_predict and
kalman_update on each axis, refreshes the shared probabilities with
mode_update, and returns the combined estimate.
- Read mode_probability_ca = mu_hist[t][1] to gate maneuver detection:
a rise above 0.8 within a few seconds of onset flags the maneuver.
- Compare the combined estimate against the truth positions; a
single-mode CV run on the same track quantifies the tracking loss the
CA mode removes.
- Confirm the deterministic checks with the contract test
scripts/test_interacting_multiple_model_filter.py.
Worked example
Scripted 100 s track, lateral acceleration 20 m/s2 on y from t = 50 s.
Module outputs (real, deterministic):
- Mode probability of the CA mode: 0.108 before the maneuver (t = 50),
rising to 0.972 at t = 52, 0.859 at t = 53 (above 0.8 within 3 s of
onset, the detection gate) and 0.999 at t = 54. After the coast starts
(t > 75) it relaxes back toward the CV mode (0.108 at t = 99).
- IMM combined position RMS error over the maneuver window t in [50, 75]:
4.44 m (gate: below 15 m). The largest combined error is 12.91 m at
t = 53, during the CA-mode takeover transient.
- A single CV-only filter on the same track peaks at 76.04 m position
error (t = 60), well above the 60 m gate: the accel model is what the
IMM adds.
- The CV velocity estimate on the straight x leg converges to the true
100 m/s within 1e-3 by t = 16 s.
- Mode probabilities sum to 1.0 at every step (max deviation 2e-16) and
the combined estimate equals the mu-weighted sum of the per-mode
estimates at the final step to 1e-12.
Verification
- Confirm kalman_update returns the documented 5-tuple and that a
non-positive r, a malformed matrix dimension, or a mode-probability
vector that does not sum to 1 raises ValueError.
- Confirm the mode likelihood is positive, mode_update keeps the
probabilities in [0, 1] summing to 1, and mix_initial recovers the
input prior when the two mode-conditioned priors are identical.
- Confirm run_imm_track is deterministic (two runs, identical outputs),
mu_ca > 0.8 by t = 53, IMM RMS position error below 15 m in the
maneuver window, CV-only error above 60 m somewhere in the window, and
the combined estimate matches the mu-weighted per-mode sum.
- Run the contract test offline: python3
scripts/test_interacting_multiple_model_filter.py (35 tests,
deterministic, exit 0).
Pitfalls
- Gating maneuver detection on the wrong window: mode_probability_ca rises
above 0.8 within about 3 s of maneuver onset (0.972 at t = 52 for the
worked example) and relaxes back after the coast starts; sample mu_hist
around the onset, not at the end of the track.
- Comparing a single-mode CV filter against the IMM without the same track:
the CV-only filter peaks at 76.04 m error on the maneuvering track while
the IMM combined RMS stays below 15 m in the window - the accel model is
what the IMM adds.
- Forgetting the mode probability normalization: mode probabilities must sum
to 1.0 at every step (max deviation 2e-16 in the test) and a
mode-probability vector that does not sum to 1 raises ValueError.
- Reading the combined estimate without the per-mode split: the combined
estimate equals the mu-weighted sum of the per-mode estimates (CV padded
with zero acceleration), verified to 1e-12 at the final step.
- Per-axis filters share one mode-probability vector per cycle, and both
planar axes multiply their per-mode likelihoods into one joint likelihood;
do not refresh the probabilities per axis independently.
Related leaves
- gnc-autonomy/navigation/kalman-filter-design: the single-model Kalman
filter that is the mode-conditioned core of each bank member.
- gnc-autonomy/estimation-filtering/alpha-beta-filter: the fixed-gain
single-model tracker, a cheaper alternative for benign targets.
- gnc-autonomy/estimation-filtering/extended-kalman-filter: nonlinear
measurement extension when the position-only linear model is not
enough.
- gnc-autonomy/navigation/inertial-navigation: the propagation context
(state transition and process noise) the bank filters assume.
Behavior contract (gate 3)
Run the deterministic contract test (stdlib unittest, offline):
python3 scripts/test_interacting_multiple_model_filter.py
The test covers the mixing equations and their normalization, the CV and
CA prediction kinematics, the scalar position update and likelihood, the
mode update formula, the single-cycle dict contract, the analytic track
points, and the full-track worked example: mu_ca above 0.8 within 3 s of
maneuver onset, IMM RMS position error below 15 m in the maneuver window,
CV-only error above 60 m, probability normalization at every step, the
final mu-weighted combination identity, straight-leg velocity convergence
within 1e-3 by t = 20 s, determinism, and ValueError rejection of
malformed dimensions, non-positive r and invalid probabilities.
Compliance
- Standards referenced, not reproduced: ARP4754A frames the
certification context for GNC estimation functions; the IMM relations
above are standard engineering methodology (Bar-Shalom style
interacting multiple model), summary-only per standards-map.yaml.
- compliance: STANDARDS-REF, gated: false.
1---2name: interacting-multiple-model-filter3description: Use when you must track a maneuvering target with an interacting multiple model filter: run a two-mode IMM bank of constant velocity (CV) and constant acceleration (CA) Kalman filters per planar axis, mix the mode-conditioned estimates through the Markov mode-transition probabilities, refresh mode probabilities from innovation likelihoods, and combine the per-mode estimates into the mixed state estimate. Produces mode probabilities that gate maneuver detection, per-mode estimates, the combined estimate and position error history on a scripted maneuvering track. Hand-coded stdlib matrices, deterministic, offline. Trigger: interacting multiple model filter, imm filter, mode probability, Markov mode switching, maneuvering target tracking.4license: Apache-2.05---67# Interacting Multiple Model Filter (gnc-autonomy/estimation-filtering/interacting-multiple-model-filter)89Use when the task is maneuvering-target tracking with an interacting10multiple model (IMM) filter: a two-mode bank of Kalman filters, one11constant-velocity (CV) and one constant-acceleration (CA), that interact12through the Markov mode-transition probabilities each cycle. This leaf13implements the IMM cycle (mix, predict, update, mode-probability refresh,14combine) in pure Python, stdlib only, with the standard per-axis planar15simplification: each planar axis runs its own 2-state CV or 3-state CA16filter and both axes share one mode-probability vector. It pairs with17gnc-autonomy/navigation/kalman-filter-design and the single-model filters18of the estimation-filtering pack for the mode-conditioned core.1920## Domain quick reference2122- Mode 1 CV, state [p, v]: F_cv = [[1, DT], [0, 1]], Q_cv = q_cv *23 [[DT^3/3, DT^2/2], [DT^2/2, DT]] with q_cv = 1.0 m2/s3.24- Mode 2 CA, state [p, v, a]: F_ca = [[1, DT, 0.5 DT^2], [0, 1, DT],25 [0, 0, 1]], Q_ca = q_ca * [[DT^5/20, DT^4/8, DT^3/6], [DT^4/8,26 DT^3/3, DT^2/2], [DT^3/6, DT^2/2, DT]] with q_ca = 2.0. DT = 1.0 s.27- Measurement: position only, H = [1, 0] or [1, 0, 0], r = 25.0 m228 (sigma 5 m). Markov transition Pi = [[0.95, 0.05], [0.05, 0.95]].29- Mode prediction: c_j = sum_i pi_ij * mu_i and mixing weights30 mu_ij = pi_ij * mu_i / c_j; the mixed prior of mode j is31 x0_j = sum_i mu_ij * x_i with the interaction spread term in the32 covariance.33- Mode likelihood: L = exp(-0.5 * innovation^2 / s) / sqrt(2 pi s) with34 s = H P H^T + r; mode update mu_new_j = L_j c_j / sum_k L_k c_k.35- Combined estimate: x_combined = sum_j mu_j * x_j per axis (CV padded36 with zero acceleration); both planar axes multiply their per-mode37 likelihoods into one joint per-mode likelihood per cycle.38- All matrix arithmetic is hand coded stdlib, 2x2 and 3x3 only; no numpy.3940## Workflow41421. Generate the deterministic maneuvering truth track with43 make_maneuvering_track (100 m/s along x, then ay = 20 m/s2 for 25 s44 from t = 50 s) or pass any scripted position list.452. Start the bank with run_imm_track: both axes anchor on the first46 measurement with MU0 = [0.95, 0.05] and the documented initial47 covariances.483. When a distinct prior per mode exists, form the common interacting49 prior with mix_initial(mu, x_ests, p_ests, pi).504. Inside each cycle imm_step mixes the mode-conditioned priors per mode51 (CV projected from CA, CA padded from CV), calls kalman_predict and52 kalman_update on each axis, refreshes the shared probabilities with53 mode_update, and returns the combined estimate.545. Read mode_probability_ca = mu_hist[t][1] to gate maneuver detection:55 a rise above 0.8 within a few seconds of onset flags the maneuver.566. Compare the combined estimate against the truth positions; a57 single-mode CV run on the same track quantifies the tracking loss the58 CA mode removes.597. Confirm the deterministic checks with the contract test60 scripts/test_interacting_multiple_model_filter.py.6162## Worked example6364Scripted 100 s track, lateral acceleration 20 m/s2 on y from t = 50 s.65Module outputs (real, deterministic):6667- Mode probability of the CA mode: 0.108 before the maneuver (t = 50),68 rising to 0.972 at t = 52, 0.859 at t = 53 (above 0.8 within 3 s of69 onset, the detection gate) and 0.999 at t = 54. After the coast starts70 (t > 75) it relaxes back toward the CV mode (0.108 at t = 99).71- IMM combined position RMS error over the maneuver window t in [50, 75]:72 4.44 m (gate: below 15 m). The largest combined error is 12.91 m at73 t = 53, during the CA-mode takeover transient.74- A single CV-only filter on the same track peaks at 76.04 m position75 error (t = 60), well above the 60 m gate: the accel model is what the76 IMM adds.77- The CV velocity estimate on the straight x leg converges to the true78 100 m/s within 1e-3 by t = 16 s.79- Mode probabilities sum to 1.0 at every step (max deviation 2e-16) and80 the combined estimate equals the mu-weighted sum of the per-mode81 estimates at the final step to 1e-12.8283## Verification8485- Confirm kalman_update returns the documented 5-tuple and that a86 non-positive r, a malformed matrix dimension, or a mode-probability87 vector that does not sum to 1 raises ValueError.88- Confirm the mode likelihood is positive, mode_update keeps the89 probabilities in [0, 1] summing to 1, and mix_initial recovers the90 input prior when the two mode-conditioned priors are identical.91- Confirm run_imm_track is deterministic (two runs, identical outputs),92 mu_ca > 0.8 by t = 53, IMM RMS position error below 15 m in the93 maneuver window, CV-only error above 60 m somewhere in the window, and94 the combined estimate matches the mu-weighted per-mode sum.95- Run the contract test offline: python396 scripts/test_interacting_multiple_model_filter.py (35 tests,97 deterministic, exit 0).9899## Pitfalls100101- Gating maneuver detection on the wrong window: mode_probability_ca rises102 above 0.8 within about 3 s of maneuver onset (0.972 at t = 52 for the103 worked example) and relaxes back after the coast starts; sample mu_hist104 around the onset, not at the end of the track.105- Comparing a single-mode CV filter against the IMM without the same track:106 the CV-only filter peaks at 76.04 m error on the maneuvering track while107 the IMM combined RMS stays below 15 m in the window - the accel model is108 what the IMM adds.109- Forgetting the mode probability normalization: mode probabilities must sum110 to 1.0 at every step (max deviation 2e-16 in the test) and a111 mode-probability vector that does not sum to 1 raises ValueError.112- Reading the combined estimate without the per-mode split: the combined113 estimate equals the mu-weighted sum of the per-mode estimates (CV padded114 with zero acceleration), verified to 1e-12 at the final step.115- Per-axis filters share one mode-probability vector per cycle, and both116 planar axes multiply their per-mode likelihoods into one joint likelihood;117 do not refresh the probabilities per axis independently.118119## Related leaves120121- gnc-autonomy/navigation/kalman-filter-design: the single-model Kalman122 filter that is the mode-conditioned core of each bank member.123- gnc-autonomy/estimation-filtering/alpha-beta-filter: the fixed-gain124 single-model tracker, a cheaper alternative for benign targets.125- gnc-autonomy/estimation-filtering/extended-kalman-filter: nonlinear126 measurement extension when the position-only linear model is not127 enough.128- gnc-autonomy/navigation/inertial-navigation: the propagation context129 (state transition and process noise) the bank filters assume.130131## Behavior contract (gate 3)132133Run the deterministic contract test (stdlib unittest, offline):134135 python3 scripts/test_interacting_multiple_model_filter.py136137The test covers the mixing equations and their normalization, the CV and138CA prediction kinematics, the scalar position update and likelihood, the139mode update formula, the single-cycle dict contract, the analytic track140points, and the full-track worked example: mu_ca above 0.8 within 3 s of141maneuver onset, IMM RMS position error below 15 m in the maneuver window,142CV-only error above 60 m, probability normalization at every step, the143final mu-weighted combination identity, straight-leg velocity convergence144within 1e-3 by t = 20 s, determinism, and ValueError rejection of145malformed dimensions, non-positive r and invalid probabilities.146147## Compliance148149- Standards referenced, not reproduced: ARP4754A frames the150 certification context for GNC estimation functions; the IMM relations151 above are standard engineering methodology (Bar-Shalom style152 interacting multiple model), summary-only per standards-map.yaml.153- compliance: STANDARDS-REF, gated: false.