Skip to content

ntpstats.filters

Clock-state estimation from noisy offset measurements.

Replaces the four ad-hoc Kalman variants of the 2012 prototype (which assumed a fixed 32 s poll, mixed filtered output back into the raw timestamps and never modelled the time step) with one standard two-state (phase, frequency) Kalman filter supporting irregular sampling, plus an optional Rauch-Tung-Striebel smoother for offline analysis.

State model (continuous-time random-walk FM + white FM clock)::

x_k   = [phase (s), frequency (s/s)]
x_k+1 = F(dt) x_k + w,  F = [[1, dt], [0, 1]]
Q(dt) = q_phase * [[dt, 0], [0, 0]]
      + q_freq  * [[dt^3/3, dt^2/2], [dt^2/2, dt]]
z_k   = phase + v,  v ~ N(0, r)

q_phase (s^2/s) is the white-FM diffusion and q_freq (1/s) the random-walk-FM diffusion; they relate to the Allan variance by AVAR(tau) ~= q_phase/tau + q_freq*tau/3. :func:fit_noise estimates them (and r) from an OADEV curve so the filter can be tuned from data.

fit_noise(series)

Rough (r, q_phase, q_freq) from the series' OADEV.

Least-squares fit of AVAR(tau) = 3 r / tau^2 + q_phase / tau + q_freq tau / 3 (white PM + white FM + RW FM) on the octave-spaced OADEV, with non-negative coefficients.

kalman(series, r=None, q_phase=None, q_freq=None, smooth=False, gate=5.0, delay_weighting=True)

Run the two-state Kalman filter over series.

Parameters default to :func:fit_noise estimates. gate rejects measurements whose normalised innovation exceeds gate sigmas (popcorn spikes from queueing delay); None disables gating. With smooth=True an RTS backward pass gives the offline optimum.

If the series has a delay column and delay_weighting is true, each measurement's variance is inflated by ((delay - min_delay)/2)^2, the square of the worst-case error its queueing could cause, so low-delay samples dominate (a soft version of NTP's clock filter).

kalman_series(series, **kw)

Return a new :class:TimeSeries holding the filtered offset.