Alpha Filter Initialization
Written 2025-11-17
Tags:filters
My home has mostly been converted to LED lightbulbs. With a variety of needs of brightness and socket bases, we have a variety of bulbs on switched dimmers, with a variety of turn-on behaviours. Some start from zero and ramp up. Some start from the minimum turn-on level of the bulb. Some seemingly start randomly then approach the expected output level. This has got me thinking about the topic of filter-initialization.
AC Dimmer Introduction
Wall power here is 120-Volt, AC/sine, 60Hz. Typical home dimmer systems operate by chopping the sine-wave at a specified phase(point in time relative to the cycle). This leads to a rather jumpy voltage delivery to light bulbs. In the incandescent past, the thermal mass of bulb filaments was enough to filter out the voltage spikes from the dimmer. With the advent of LED bulbs, "dimmable" bulbs usually include a decoder or filter of sorts to handle this.
Alpha-Filter Introduction
An alpha filter, or exponential moving average filter, is one of the simplest low-pass IIR filters. Generally, the filter is represented by two values. The first value is a state-variable that is updated each time we process a new sample. The first value is a constant, named alpha, that relates to how much smoothing occurs. For each sample the math is simply:state = sample * alpha + prevState * (1 - alpha)
When alpha=1.0, the filter simply passes input to output, and can be considered to have unlimited bandwidth. When alpha=0.0, the filter blocks any changes as has zero bandwidth. Everything in-between provides a trade-off between responsiveness and smoothness. Typically, alpha is an application-specific constant.
I have no idea if light-bulbs use alpha-filters, but we'll use them as a way to discuss a more general problem.
Initialization
What should we use for prevState for the first sample? In some applications turn-on transients may not matter too much, but here are a few approaches:Option1: zero
prevState = 0
Starting at zero means the filter is going to under-estimate the target value for a while, but in the case of a light-bulb this can give a slow but not unpleasant ramp-up, especially while carrying a newborn infant around the house wondering about how my lightbulbs handle filter initialization. One downside is that there is a noticeable delay getting a useful level of light output.
Option2: minimum bulb output
prevState = minOutput
Similar to starting at zero, starting from the minimum bulb steady-state output also gives us a small amount of light immediately, then ramp-up to the target. One odd thing about this when the dimmer is set to the minimum level when switched on, the bulb turns on immediately but then doesn't ramp up at all since it's already reached its target output. It feels inconsistent and find myself sometimes expecting it to brighten further.
Option3: first sample
prevState = anyPossibleInputValue
Some of my lightbulbs strike on to a seemingly random value, then ramp up or down as needed to the target dimming. Due to the chopped AC waveform, this is almost certainly the wrong choice for a light bulb, but may not be a bad choice for other applications.
But this approach has one advantage - on average, it'll at least start somewhere in the expected range.
This is equivalent to overriding alpha=1.0, just for the first sample.
Proposal: variable filter bandwidth initialization
What if we ramped alpha from 1.0 toward during the first few samples? It turns out we can get both quick initialization and a variety of filter responses from this. One possibility - substitute alpha for 1/N (where N is the sample-number, starting with 1):
state = sample * 1/N + prevState * (1 - 1/N)
This happens to generate a sliding-window filter. After 1/N decreases below some minimum-alpha, we can transition to traditional alpha-filter behaviour, like so:
tmpAlpha = MAX(alpha, 1/N) state = sample * tmpAlpha + prevState * (1 - tmpAlpha)
The end result of this is that we take the first sample as truth, then several subsequent samples are averaged together with decreasing filter bandwidth for each, before reaching our final minimum bandwidth steady-state alpha. Early during startup this can lead to very noisy samples, so we might want to clip tmpAlpha, like so:
tmpAlpha = CLAMP(alpha, 1/N, 10 * alpha) state = sample * tmpAlpha + prevState * (1 - tmpAlpha)
Here's what that looks like. 1500 random samples, alpha=0.002 for the fixed-bandwidth filters, but ranges 0.002 to 0.02 for the variable filter:
The variable-bandwidth filter approaches the target value quickly, perhaps a little too quickly and overshoots slightly, then smoothly approaches much quicker than constant alpha filters.
