Skip to content

Add alpha_beta and alpha_beta_gamma trackers - #1069

Merged
baggepinnen merged 2 commits into
masterfrom
alpha-beta-trackers
Sep 8, 2026
Merged

baggepinnen merged 2 commits into
masterfrom
alpha-beta-trackers

Conversation

@baggepinnen

Copy link
Copy Markdown
Member

Adds alpha_beta and alpha_beta_gamma: fixed-gain trackers of the position and rate (and, third order, the acceleration) of a target, returned as discrete-time StateSpace systems whose input is the measured position and whose outputs are the estimates.

Both are observer_filter of an integrator chain with K = [α, β/T_s] and K = [α, β/T_s, γ/T_s^2], so the state is the a-posteriori estimate and the systems are strictly proper. The rate output is a filtered derivative of the input, available without a separate differentiator.

julia> sys = alpha_beta(0.5, 0.1);          # α alone is a complete tuning

julia> size(sys)
(2, 1)

Why the third-order one

An α-β tracker predicts with a locally constant rate, so on a signal that is genuinely accelerating its rate estimate lags by an amount proportional to the acceleration, and no choice of alpha and beta removes that bias. Predicting with the acceleration removes it. That pays when the measurement is oversampled relative to the signal, where a longer effective memory costs little lag — differentiating a quantized encoder to estimate a joint velocity is the case these were written for.

Parameterization

alpha alone is a complete tuning. beta defaults to Kalata's steady-state relation β = 2(2-α) - 4\sqrt{1-α} and gamma to β^2/(2α), which make the filters the steady-state Kalman filters for constant-velocity and constant-acceleration targets. At α = 0.5 these are β ≈ 0.1716 and γ ≈ 0.0294.

The extended help also gives the critically damped alternative, because the defaults leave a complex pole pair for every alpha — the error always decays with an oscillatory mode. To place the error poles on the real axis at a common radius s:

second order α = 1 - s^2, β = (1-s)^2
third order α = 1 - s^3, β = \tfrac{3}{2}(1-s)^2(1+s), γ = (1-s)^3

with the trap stated: setting only alpha leaves the other gains at their Kalata defaults and puts the poles straight back off the axis.

These relations are stated for the gain convention used here, in which the acceleration correction is γ/T_s^2 alongside the rate correction β/T_s. References differ by factors of two depending on whether γ or is written in that position, so the docstring says which one it means, and the tests check the relations rather than leaving them as prose. I derived rather than quoted them for exactly this reason — my first draft of the second-order relation had a spurious factor of two and the test caught it.

One-sample bookkeeping

Documented in a !!! note, since it is easy to trip over: the state is x̂(k|k), so with the usual x(k+1) = Ax(k) + Bu(k) update the estimate that absorbed y(k) appears at output index k+1 of a simulation. This is the same convention observer_filter already has.

Tests

48 assertions in test_synthesis.jl: shapes, Ts, strict properness and that B == K; the default gains; agreement with the documented recurrence under lsim (transcribed as a Julia loop, compared tick by tick); ramp tracking; equality with observer_filter of the corresponding integrator chain, which is the structural check that these really are that filter; the critical-damping recipes at five radii and three sample rates; that the defaults are not critically damped; and argument validation.

No new dependencies. lib/ControlSystemsBase/test/test_synthesis.jl passes 141 pre-existing plus the 48 new.

Happy to convert this to an issue for discussion first if you would rather — CONTRIBUTING suggests that, and I went straight to a PR because the change is self-contained and additive.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PZNSHjGe7PQ7MdJg3itmCM

Two fixed-gain estimators of the position and rate (and, for the third-order
one, the acceleration) of a target, returned as discrete-time StateSpace
systems whose input is the measured position and whose outputs are the
estimates. Both are observer_filter of an integrator chain with
K = [alpha, beta/Ts] and K = [alpha, beta/Ts, gamma/Ts^2], so the state is the
a-posteriori estimate and the systems are strictly proper.

The rate output is a filtered derivative of the input, available without a
separate differentiator, and the third-order filter removes the bias an
alpha-beta tracker's rate estimate carries on an accelerating signal -- worth
having when the measurement is oversampled relative to the signal, since a
longer effective memory then costs little lag.

`alpha` alone is a complete tuning: `beta` defaults to Kalata's steady-state
relation and `gamma` to beta^2/(2*alpha), which make the filters the
steady-state Kalman filters for constant-velocity and constant-acceleration
targets.

The extended help gives the alternative critically damped tuning, since the
defaults leave a complex pole pair for every alpha:

    2nd order: alpha = 1 - s^2, beta = (1 - s)^2
    3rd order: alpha = 1 - s^3, beta = 1.5(1 - s)^2(1 + s), gamma = (1 - s)^3

Those are stated for the gain convention used here, in which the acceleration
correction is gamma/Ts^2 alongside the rate correction beta/Ts; references
differ by factors of two depending on that choice, so the docstring says which
one it means and the tests check the relations rather than leaving them as
prose. The docstrings also note the one-sample bookkeeping these share with
observer_filter: the state is x(k|k), so the estimate that absorbed y(k)
appears at output index k+1 of a simulation.

Tests cover the shapes and defaults, agreement with the documented recurrence
under lsim, ramp tracking, equality with observer_filter of the corresponding
integrator chain, the critical-damping recipes at several radii and sample
rates, that the defaults are not critically damped, and argument validation.
No new dependencies.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PZNSHjGe7PQ7MdJg3itmCM
@JuliaControlBot

Copy link
Copy Markdown

This is an automated message.
Plots were compared to references. 4/11 images have changed, see differences below.
After pulling this PR, please update the reference images by creating a PR to ControlExamplePlots.jl here.

Difference Reference Image New Image
✔️ 0.0 Reference New
✔️ 0.0 Reference New
✔️ 0.0 Reference New
✔️ 0.0 Reference New

Inspired by ASD-STE100: one idea per sentence, active voice, no metaphor and
no idiom, and consistent terms for the same thing throughout.

Removed: "noise rejection is bought rather than paid for in phase", "the extra
state is what it buys", "the speed knob", "sibling", "puts the poles back off
the axis", "settles somewhat slower than the radius alone suggests". Long
sentences with semicolons or em-dash asides are split. Statements that are
really instructions are written as instructions -- "Setting only `alpha` is
not enough" becomes "Do not give only `alpha`".

Terms are now used consistently: "gains" rather than "triple"/"defaults"/
"values", "the filter", "the target". The note title "One-sample bookkeeping"
becomes "The sample index", which is also how `alpha_beta_gamma` refers to it,
and "has absorbed" becomes "includes".

The technical content is unchanged, as are the doctests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PZNSHjGe7PQ7MdJg3itmCM
@codecov

codecov Bot commented Sep 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.74%. Comparing base (4784e94) to head (554fc26).

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1069      +/-   ##
==========================================
+ Coverage   87.94%   91.74%   +3.80%     
==========================================
  Files          42       42              
  Lines        5649     5730      +81     
==========================================
+ Hits         4968     5257     +289     
+ Misses        681      473     -208     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@baggepinnen
baggepinnen merged commit 145a91b into master Sep 8, 2026
4 of 5 checks passed
@baggepinnen
baggepinnen deleted the alpha-beta-trackers branch September 8, 2026 05:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants