The price of filtering
Every filter delays. Measuring the delay, and what it does to a loop closed around it.
Do this lesson in the simulatorEvery filter delays. There is no exception and no clever way around it: to know that a change is real rather than noise, you have to wait and see more data, and waiting is delay.
How much delay
For the exponential filter, the delay is about
(1 - alpha) / alpha steps
so alpha 0.5 costs one step, alpha 0.2 costs four, alpha 0.05 costs nineteen. At a 0.1 s loop that is 0.1 s, 0.4 s and 1.9 s.
Two seconds of delay on a robot that crosses its own length in a third of a second is not a filter, it is a blindfold.
Measure it
Drive straight at the wall so the true distance falls in a nearly straight line, and compare when each signal passes a given value.
from bugbot import *
connect()
ALPHA = 0.15
filtered = distance()
forward(50)
for i in range(50):
raw = distance()
filtered = ALPHA * raw + (1 - ALPHA) * filtered
plot("raw", raw)
plot("filtered", filtered)
if raw < 30:
break
wait(0.1)
stop()
print("theory says", round((1 - ALPHA) / ALPHA * 0.1, 2), "s of lag")
On the chart the filtered line sits to the right of the raw one, by a constant gap while the signal is changing steadily. That gap is the delay, and it is measurable to a tenth of a second by eye.
Why it matters so much in a loop
U1.4 said it already: delay turns negative feedback into positive feedback. A filter in the feedback path adds its delay straight into that budget.
This is the trap that catches everyone once. A controller is twitchy, so the sensor gets filtered, so the controller is smoother, so the filter is tightened, so the delay grows, and now the controller oscillates for a completely different reason than it started with. Two knobs, the same symptom, opposite fixes.
The rule of thumb: the filter's delay should be small compared with the time constant of the thing you are controlling. With tau of 0.25 s, a filter delay of 0.1 s is acceptable and 1 s is not.
What to do instead
- Filter as little as you can get away with, not as much as looks nice.
- Filter the measurement, not the error. Filtering the error delays your response to the target changing as well.
- Filter only the noisy term. In a PID controller, the D term needs the filter and the P term usually does not.
- Predict. A filter that also carries a velocity can extrapolate forward by its own delay and hand back an estimate that is not late. That is what U6 buys you, and it is the honest answer to this whole problem.
Task: measure the lag
Drive steadily at the wall, filter the distance, plot raw and filtered, and print lag:, the seconds by which the filtered signal trails the raw one.
from bugbot import *
connect()
ALPHA = 0.2
DT = 0.1
filtered = distance()
Challenges
- Measure the lag at three values of alpha and plot lag against alpha. Does it match the formula?
- Put the filter inside a controller that holds 25 cm from the wall, and find the alpha at which it starts to oscillate.
- Extrapolate: add
lag * rate_of_changeto the filtered value and see how much of the delay you can buy back.