Introduction

The ordinary Kalman filter is a beautiful piece of math, but it comes with a strict requirement: the system it tracks must be linear — its next state must be a straight-line combination of its current state. A satellite drifting in empty space is close enough. A pendulum swinging under gravity is not: its restoring force depends on the sine of the angle, not the angle itself. A drone banking through the air, a chemical reaction, a car turning a corner — nearly everything interesting in the real world curves.

So what do you do when your system is nonlinear but you still want the Kalman filter's optimal blend of model and measurement? The Extended Kalman Filter (EKF), developed in the early 1960s for the Apollo program's guidance computer, offers a pragmatic answer: at every single time step, approximate the nonlinear system with a straight line — its best local linear approximation — and then run the ordinary Kalman filter machinery on that approximation.

The tool that builds this local straight-line approximation is the Jacobian matrix, the multivariable generalization of a derivative. The EKF's whole trick is to recompute the Jacobian fresh at every step, so the "straight line" always matches the curve exactly where the estimate currently sits — like a tangent line that keeps repositioning itself as a car climbs a hill.

Try It

Below, a pendulum swings under gravity and friction — a genuinely nonlinear system, since its acceleration depends on sin(θ)\sin(\theta). The gray arm shows the true motion. Only a noisy reading of the angle is available at every step (the scattered red marks) — the angular velocity is never measured directly.

<p class="hint">{{hint_para}}</p>
<div class="stage">
  <svg id="pendulum-svg" viewBox="0 0 300 220" class="pend-svg">
    <line x1="150" y1="20" x2="150" y2="26" stroke="#8a94a3" stroke-width="2"/>
    <circle cx="150" cy="20" r="4" fill="#8a94a3"/>
    <line id="arm-true" x1="150" y1="20" x2="150" y2="150" class="arm true-arm"/>
    <circle id="bob-true" cx="150" cy="150" r="7" class="bob true-bob"/>
    <line id="arm-est" x1="150" y1="20" x2="150" y2="150" class="arm est-arm"/>
    <circle id="bob-est" cx="150" cy="150" r="7" class="bob est-bob"/>
    <g id="meas-marks"></g>
  </svg>
  <div class="legend">
    <span><i class="swatch true-swatch"></i>{{legend_true}}</span>
    <span><i class="swatch meas-swatch"></i>{{legend_meas}}</span>
    <span><i class="swatch est-swatch"></i>{{legend_est}}</span>
  </div>
</div>
<div class="status" id="status">{{status_ready}}</div>
<div class="btns">
  <button id="run" type="button">{{btn_run}}</button>
  <button id="disturb" type="button">{{btn_disturb}}</button>
  <button id="reset" type="button" class="ghost">{{btn_reset}}</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .6rem; line-height: 1.45; }
.stage { display: flex; flex-direction: column; align-items: center; }
.pend-svg { width: 260px; height: 190px; background: #f4f6f8; border-radius: 10px; }
.arm { stroke-width: 3; stroke-linecap: round; }
.true-arm { stroke: #9aa3af; }
.est-arm { stroke: #1d3557; }
.bob { stroke: #fff; stroke-width: 1.5; }
.true-bob { fill: #9aa3af; }
.est-bob { fill: #1d3557; }
.meas-tick { fill: #e63946; opacity: .85; }
.legend { display: flex; gap: 1rem; font-size: .78rem; color: #444; margin: .35rem 0 .2rem; flex-wrap: wrap; justify-content: center; }
.legend span { display: flex; align-items: center; gap: .3rem; }
.swatch { width: 10px; height: 10px; border-radius: 50%; display: inline-block; }
.true-swatch { background: #9aa3af; }
.meas-swatch { background: #e63946; }
.est-swatch { background: #1d3557; }
.status { font-size: .95rem; font-weight: 600; margin: .5rem 0; min-height: 1.4em; text-align: center; }
.status.ok { color: #0a7d33; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; justify-content: center; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
         background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
button:disabled { opacity: .55; cursor: default; }
// Code not found

Press Run and watch the blue arm — the EKF's estimate — lock onto the true swing even though it only ever sees noisy angle snapshots. Internally, at every step the filter recomputes the Jacobian of the pendulum's dynamics at the current estimate, uses it to predict how uncertainty should grow, and then blends in the next noisy reading with a Kalman gain — exactly as the linear Kalman filter would, just against a constantly refreshed straight-line stand-in for gravity's curve. Try Add Disturbance to kick the pendulum and see the filter recover.

The Real Complexity

The EKF keeps the ordinary Kalman filter's two-step loop — predict, then update — but replaces every matrix that assumed linearity with a freshly computed Jacobian.

Predict. Instead of a fixed linear transition matrix, the state moves forward through the true nonlinear function ff:

x^k=f(x^k1)\hat{x}_{k}^{-} = f(\hat{x}_{k-1})

But the uncertainty still needs a linear operator to propagate, so the filter computes the Jacobian Fk=fxx^k1F_k = \left.\dfrac{\partial f}{\partial x}\right|_{\hat{x}_{k-1}} — the matrix of partial derivatives evaluated at the current estimate — and uses it exactly where the linear filter would use its fixed transition matrix:

Pk=FkPk1Fk+QP_{k}^{-} = F_k P_{k-1} F_k^\top + Q

Update. If the sensor itself measures a nonlinear function hh of the state, the same trick applies to the observation model: compute the Jacobian Hk=hxx^kH_k = \left.\dfrac{\partial h}{\partial x}\right|_{\hat{x}_{k}^{-}}, then reuse the ordinary Kalman gain formula unchanged:

Kk=PkHk(HkPkHk+R)1K_k = P_{k}^{-} H_k^\top \left(H_k P_{k}^{-} H_k^\top + R\right)^{-1}

The gain still slides between "trust the model" and "trust the sensor," exactly as in the linear filter — the only difference is that FkF_k and HkH_k are recomputed at every single step instead of staying fixed forever.

Why "extended," and why it can fail. This is a well-motivated approximation, not a proof. Because the Jacobian is only accurate very close to the point where it was evaluated, the EKF can drift or even diverge if the system is highly nonlinear, the time step is too large, or the initial estimate starts too far from the truth — the tangent line stops resembling the curve. Engineers manage this with smaller time steps, careful initialization, or alternatives like the Unscented Kalman Filter, which propagates a small set of sample points through the true nonlinear function instead of linearizing it at all, trading a cheap derivative for a handful of extra function evaluations.

Where It Matters

The moment a system's motion or its sensors are nonlinear — which is almost always — the EKF (or a close cousin) tends to show up:

  • Spacecraft and aircraft navigation: the Apollo Guidance Computer used an early Extended Kalman Filter to combine star sightings with inertial measurements while the spacecraft's orbital mechanics are thoroughly nonlinear.
  • Robot localization and SLAM: a robot estimating its position and heading while building a map fuses noisy distance and bearing sensors — both nonlinear functions of position — using EKF-based SLAM.
  • Sensor fusion in phones and drones: combining a gyroscope, accelerometer, and magnetometer to estimate orientation (which lives on a nonlinear rotation manifold) is a textbook EKF application.
  • Battery and process monitoring: estimating a battery's hidden internal charge state from voltage and current readings uses an EKF because battery chemistry is nonlinear.
  • Economics and epidemiology: models where a hidden quantity evolves nonlinearly (an infection rate, a volatility regime) are tracked from noisy observed data with EKF-style estimators.

All of these share the same shape as the pendulum above: a state that curves, and only noisy, partial glimpses of it. The particle filter offers a sampling-based alternative when the nonlinearity is too severe even for repeated linearization.

Conclusion

The Extended Kalman Filter is not a new theory of estimation — it is a disciplined act of faith in the old one. It takes the Kalman filter's provably optimal linear machinery and reapplies it, moment by moment, to whatever straight line best imitates the true curve right here, right now. The Jacobian is the tool that finds that straight line; the predict-update loop is unchanged underneath.

That combination is not perfect — the EKF can be fooled by sharp nonlinearities or bad starting guesses, unlike its provably optimal linear ancestor. But for six decades it has been good enough to fly to the Moon, keep robots localized, and let a phone know which way is up. The pendulum in the demo above swings the same way a spacecraft tumbles or a drone banks: a curve tamed, one tangent line at a time.

Share this article

Pick a channel — or use your device's native share sheet.

Comments

Loading comments...

https://www.kipuhub.com/en/article/extended-kalman-filter/Content licensed under CC BY-NC 4.0.