Reading the implementation — follow a minimal 1D version
Match the minimal Python / C implementation line by line against the formulas and variable names, and follow a single loop through end to end.
When you put everything so far into code, a 1-dimensional Kalman filter turns out to be extremely short. What matters is not the line count but being able to trace which formula each line corresponds to.
Minimal Python implementation
The state (x_hat, P) and the parameters (Q, R) are bundled into a class. We avoid using global to mutate module-level variables, because that would make it impossible to run multiple filter instances in parallel and would also make the code harder to test.
class KalmanFilter1D:
def __init__(self, x_hat=0.0, P=10.0, Q=0.05, R=2.0):
self.x_hat = x_hat
self.P = P
self.Q = Q
self.R = R
def step(self, z):
x_pred = self.x_hat
P_pred = self.P + self.Q
K = P_pred / (P_pred + self.R)
self.x_hat = x_pred + K * (z - x_pred)
self.P = (1.0 - K) * P_pred
return self.x_hat, self.P
We write (1.0 - K) rather than (1 - K) to make the arithmetic clearly float, matching the C version's behavior. In Python the result is the same as long as K is a float, but writing 1.0 explicitly keeps the behavior consistent when the code is ported to other languages such as C.
Minimal C implementation
The C version also bundles the state and parameters into a struct, and the function takes a pointer to that struct and updates it in place. No global variables are used.
typedef struct {
double x_hat;
double P;
double Q;
double R;
} KalmanFilter1D;
double kf_step(KalmanFilter1D *kf, double z) {
double x_pred = kf->x_hat;
double P_pred = kf->P + kf->Q;
double K = P_pred / (P_pred + kf->R);
kf->x_hat = x_pred + K * (z - x_pred);
kf->P = (1.0 - K) * P_pred;
return kf->x_hat;
}
Usage: initialize with KalmanFilter1D kf = { .x_hat = 0.0, .P = 10.0, .Q = 0.05, .R = 2.0 }; and then call kf_step(&kf, z) for every new observation.
Formulas and variables
x̂⁻ (local variable inside the method)P⁻ (local variable inside the method)x̂ (filter state)P (filter state)Check 1 — Trace one loop by hand
With Q = 0.5, R = 2, previous x̂ = 4, P = 1, and observation z = 7, follow one loop in the order the implementation runs.
Q1. After executing line 1: x_pred = self.x_hat, what is the predicted value x̂⁻?
Under the steady-state model, x̂⁻ = x̂ = 4.
Q2. After executing line 2: P_pred = self.P + self.Q, what is the predicted variance P⁻?
P⁻ = 1 + 0.5 = 1.5.
Q3. After executing line 3: K = P_pred / (P_pred + self.R), what is the Kalman gain?
K = 1.5 / (1.5 + 2) ≈ 0.4286.
Q4. After executing line 4: self.x_hat = x_pred + K * (z - x_pred), what is the updated estimate x̂?
The difference between observation and prediction is 7 − 4 = 3, so x̂ ≈ 4 + 0.4286 × 3 ≈ 5.2857.
Q5. After executing line 5: self.P = (1.0 - K) * P_pred, what is the updated variance P?
P ≈ (1 − 0.4286) × 1.5 ≈ 0.8571.
Points to keep in mind when reading this code
- First split the two prediction lines from the three update lines.
- Check what
Kdepends on. - Finally, confirm that the variance
Pis properly updated at the end.
Check 2 — Read what the code means
Translate each line of the implementation back into the part of the formula it represents.
Q1. Which of the following lines corresponds to "reducing the uncertainty after taking in the observation"?
self.P = (1.0 - K) * P_pred is the line that reduces the uncertainty after the observation is taken in.
Q2. If you mistakenly wrote K = self.R / (P_pred + self.R) in the implementation, what problem occurs?
Normally a larger R should make us distrust the observation, but swapping the formula makes a larger R lean more strongly toward the observation.
Check 3 — When the innovation is zero
Confirm how the filter behaves in the special case where the observation matches the prediction exactly.
Q1. With predicted value x̂⁻ = 5, observation z = 5, and K = 0.6, what is the updated x̂?
The innovation is 5 − 5 = 0, so the estimate stays at x̂⁻ = 5 and does not move.
Q2. With predicted variance P⁻ = 4 as well, what is the updated variance P?
P = (1 − 0.6) × 4 = 1.6. The estimate does not move, but because the observation was taken in, the uncertainty still decreases.