feat: add ComplementaryFilter, fusing a drifting rate with a noisy absolute reading - #7614
Merged
DenizAltunkapan merged 1 commit intoSep 22, 2026
Conversation
…solute reading The classic pair is an accelerometer and a gyroscope measuring the same tilt: one never drifts but picks up every vibration, the other is smooth but has to be integrated, so any bias in it walks away without limit. Their errors live in different parts of the spectrum, and the filter is a high pass on the integrated rate plus a low pass on the absolute reading whose transfer functions sum to unity, so the true signal passes through untouched and no lag is introduced. The single parameter is exposed as a time constant as well, tau = a * dt / (1 - a), which fixes the price of the trade exactly: a rate with a constant bias b leaves a steady state error of tau * b and no more. The tests check that identity, the geometric convergence onto the reference, the noise reduction of sqrt((1 - a) / (1 + a)), and that a ramp is followed without the lag a plain low pass on the reference would add. Signed-off-by: alxkm <19151554+alxkm@users.noreply.github.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #7614 +/- ##
============================================
+ Coverage 81.18% 81.21% +0.02%
- Complexity 7899 7922 +23
============================================
Files 830 831 +1
Lines 25012 25056 +44
Branches 4882 4885 +3
============================================
+ Hits 20307 20350 +43
Misses 3920 3920
- Partials 785 786 +1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
DenizAltunkapan
approved these changes
Sep 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds the complementary filter: one estimate out of two sensors that are each wrong in a different way.
The classic pair is an accelerometer and a gyroscope measuring the same tilt. The accelerometer knows where down is and never drifts, but every vibration of the frame shows up in it. The gyroscope is smooth and immune to vibration, but it measures a rate, so using it means integrating, and the smallest bias in that rate integrates into an angle that walks away without limit. Neither is usable alone, and their errors live in different parts of the spectrum, which is exactly the situation this filter is for.
Read as a pair of filters that add up to one, it is a high pass on the integrated rate and a low pass on the absolute reading: the drift of the first is cut off below the corner frequency and the noise of the second above it. The two transfer functions sum to unity at every frequency, so the true signal passes through untouched whatever
ais, and the filter cannot introduce a lag of its own the way a plain low pass on the accelerometer would.The parameter is easier to reason about as a time constant,
tau = a * dt / (1 - a), andofTimeConstant(tau, dt)sets it that way round. Belowtauthe answer comes from the gyroscope, above it from the accelerometer, and that fixes the price of the trade exactly: a rate with a constant biasbleaves a steady state error oftau * band no more, where plain integration would have grown without limit.Against the
KalmanFilteralready in the package: the Kalman filter is the right answer when the noise of both sensors is known and worth modelling, and it will beat this one when it is. The complementary filter needs no covariance, no model of the process, two multiplications per sample and one number of state, and it degrades gracefully when the noise is not what anybody assumed. That is why it is what actually runs on small flight controllers, and both Javadocs say so.ComplementaryFilterTestcovers 31 cases, and four of them check the theory rather than the behaviour:a^n, checked step by step over 200 samples;tau * biasand stays there over 5000 samples, while the same bias integrated on its own reaches 5.0;sqrt((1 - a) / (1 + a))the theory promises, measured over 20000 samples;aon the reference alone lags bytau * slope, which the test measures and compares.The rest cover the seeding of the first sample from the reference, the round trip between the coefficient and the time constant, the scaling of the integration by the elapsed time, restarting from a known estimate, and the argument checks.
Checklist
clang-format -i --style=file path/to/your/file.java