JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

Beyond Linear-Gaussian and Into the Loop

EKF, UKF, and particle filters for when linearity breaks — and the closed-loop lesson (ReFIT, CLDA) that offline-optimal decoders can fail on a live user.

When linear-Gaussian breaks

Three things break the Kalman filter's assumptions: nonlinear tuning (firing that is not a linear function of the state), non-Gaussian noise (Poisson spikes, heavy tails, artifacts), and multimodal posteriors (two plausible movements at once). Three families of filter relax those, at rising computational cost: the extended, the unscented, and the particle filter.

EKF and UKF

The extended Kalman filter keeps the Gaussian machinery but linearizes a nonlinear observation function h(\cdot) around the current estimate, using its Jacobian as a local C. It is cheap and works when the nonlinearity is mild, but the linearization can diverge if the estimate is far from the truth.

\hat{x}_k = \hat{x}_k^{-} + K_k\big(y_k - h(\hat{x}_k^{-})\big), \qquad C_k = \left.\frac{\partial h}{\partial x}\right|_{\hat{x}_k^{-}}

EKF: replace the linear C with the Jacobian of the true observation function, evaluated at the prediction.

When the neural-to-state relationship is curved, the plain Kalman filter breaks. The EKF fixes it by linearizing — replacing the constant C with the local slope (Jacobian) of the true function at the current best guess.

h(\cdot)
The true nonlinear observation function.
\hat{x}_k^{-}
The predicted state, used as the linearization point.
C_k
The Jacobian — the local slope of h at the prediction.
K_k
The Kalman gain, now built from C_k.

This is the extended Kalman filter; it works well when h is only mildly curved near the estimate.

The unscented Kalman filter avoids Jacobians entirely. It picks a small set of deterministic sigma points, pushes each through the true nonlinearity, and reconstructs a Gaussian from the transformed cloud. For moderate-to-strong nonlinearity it is usually more accurate than the EKF at similar cost.

\mathcal{X}^{(i)} = \hat{x} \pm \Big(\sqrt{(n+\lambda)\,P}\Big)_i, \qquad \hat{y} = \sum_i W_i\, h\big(\mathcal{X}^{(i)}\big)

The unscented transform: propagate sigma points through h directly, then reweight — no derivatives needed.

Instead of differentiating, the UKF picks a few clever sample points — sigma points — around the estimate, pushes each through the real nonlinear function, and re-averages. It captures the curvature with no derivatives.

\mathcal{X}^{(i)}
The sigma points placed around the current estimate.
\sqrt{(n+\lambda)\,P}
The spread of the sigma points, set by the uncertainty P.
W_i
The weights used to recombine the transformed points.
h(\cdot)
The nonlinear observation function each point passes through.

This is the unscented Kalman filter; it usually beats the EKF when h is strongly curved.

Particle filters

When the posterior is genuinely non-Gaussian or multimodal, represent it not by a mean and covariance but by a swarm of weighted samples (particles). Each particle is propagated through the dynamics, reweighted by how well it explains the new measurement, and the swarm is periodically resampled. This is the particle filter, the most general realization of the guide-1 Bayes recursion.

w_k^{(i)} \propto w_{k-1}^{(i)}\, p\big(y_k \mid x_k^{(i)}\big), \qquad \hat{x}_k = \sum_i w_k^{(i)}\, x_k^{(i)}

Sequential importance weighting; the posterior mean is the weighted average of the particles.

Represent the belief with a cloud of guesses (particles). Each round, weight every guess by how well it predicted the new data, and take the weighted average as your estimate. Handles any shape of distribution.

x_k^{(i)}
The i-th particle — one candidate state.
w_k^{(i)}
That particle's importance weight.
p(y_k \mid x_k^{(i)})
How well the particle explains the new measurement.
\hat{x}_k
The weighted-average estimate over all particles.

This is the particle filter — flexible but costly, since accuracy grows with the number of particles.

The closed-loop twist

Here is the single most important practical lesson of the track. Every filter above is usually fit on open-loop data — the user passively watches a moving target while you record. But the instant you close the loop and let the user drive, their neural activity changes: they now react to cursor error, correct, and adapt. The training statistics no longer match the test statistics (covariate shift), and a decoder that was statistically optimal offline can feel sluggish, biased, or unstable online.

ReFIT and closed-loop decoder adaptation

The fix is to train on data that reflects the user's intention, not their imperfect open-loop cursor. The insight behind ReFIT is that during calibration the user almost always intends to move straight toward the target. So you take the observed cursor velocities, rotate each to point at the goal (keeping its speed), and refit the Kalman filter on these intention-corrected velocities.

v_k^{\text{intended}} = \lVert v_k \rVert \cdot \frac{p_{\text{target}} - p_k}{\lVert p_{\text{target}} - p_k \rVert}

Intention estimation: keep the decoded speed, but redirect it toward the known target before retraining.

A retraining trick: assume the user always meant to head straight for the target, so keep the decoded speed but rotate its direction to point at the goal, then retrain on these corrected intentions. It sharply boosts accuracy.

\lVert v_k \rVert
The decoded speed, which is kept as-is.
p_{\text{target}}
The known goal position.
p_k
The current cursor position.
v_k^{\text{intended}}
The velocity redirected straight at the target.

This intention estimation is the heart of ReFIT closed-loop decoder adaptation.

# ReFIT: rotate observed velocity toward the target, keep the speed
def refit_intention(pos, vel, target):
    to_target = target - pos
    dirn  = to_target / (np.linalg.norm(to_target) + 1e-9)
    speed = np.linalg.norm(vel)
    return speed * dirn      # intended velocity used to refit C, Q of the KF
The whole ReFIT idea in four lines; run it over calibration data, then refit the observation model.
Revisit the cursor task with the closed loop in mind: pushing the noise sliders toward more measurement noise buys the stability that intention-corrected retraining also delivers.

Generalizing ReFIT, closed-loop decoder adaptation (CLDA) repeats this recalibration while the user drives, updating the decoder over seconds to minutes so it converges with the user rather than against them.

Two learners, adapting at once