Skip to content

Commit 8bb3e97

Browse files
tomquistclaude
andauthored
fix: don't slow EMA smoother across zero-crossings for high alpha (#373)
The sign-flip "catchup" branch in SmoothedPowermeter was meant to react faster when raw power crosses zero by boosting the effective alpha to `alpha * 4`, capped at 0.5. But `min(0.5, alpha * 4)` is only a boost while `alpha < 0.125`; for any larger configured alpha it just clamps to 0.5, and for alpha > 0.5 it actively reduces the step. Users running near self-consumption with a high `SMOOTH_TARGET_ALPHA` (e.g. 1.0 for instant tracking) therefore saw randomly halved deltas whenever the raw reading happened to straddle zero, often dragging the smoothed value back into the deadband and snapping it to 0. Raise the catchup alpha to at least the configured alpha so the branch can only ever speed the smoother up, never slow it down. Refs #371 Co-authored-by: Claude <noreply@anthropic.com>
1 parent de053d3 commit 8bb3e97

3 files changed

Lines changed: 20 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## Next
44

5+
- **Fixed** EMA smoother slowing down across zero-crossings when `SMOOTH_TARGET_ALPHA` was configured above 0.5: the sign-flip "catchup" branch capped the effective alpha at 0.5, which actually reduced responsiveness for users who picked a larger alpha (e.g. 1.0 for near-instant tracking). The catchup boost now never drops below the configured alpha ([#371](https://github.com/tomquist/astrameter/issues/371)).
56

67
## 2.0.1
78

src/astrameter/powermeter/wrappers/smoothing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ async def get_powermeter_watts(self) -> list[float]:
7474

7575
catchup_alpha = self._alpha
7676
if (raw_total > 0) != (self._value > 0):
77-
catchup_alpha = min(0.5, self._alpha * 4)
77+
catchup_alpha = max(self._alpha, min(0.5, self._alpha * 4))
7878
delta = catchup_alpha * (raw_total - self._value)
7979

8080
if self._max_step > 0:

src/astrameter/powermeter/wrappers/smoothing_test.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,27 @@ async def test_sign_change_catchup(self):
8282
# Sign flip: raw goes negative
8383
fake.set([-100.0])
8484
await sm.get_powermeter_watts()
85-
# catchup_alpha = min(0.5, 0.1 * 4) = 0.4
85+
# catchup_alpha = max(0.1, min(0.5, 0.1 * 4)) = 0.4
8686
# delta = 0.4 * (-100 - 100) = -80 → new = 20
8787
assert sm.smoothed_value == pytest.approx(20.0)
8888

89+
@pytest.mark.asyncio
90+
async def test_sign_change_does_not_slow_high_alpha(self):
91+
fake = FakePowermeter([1.0])
92+
sm = SmoothedPowermeter(fake, alpha=1.0)
93+
94+
await sm.get_powermeter_watts()
95+
assert sm.smoothed_value == 1.0
96+
97+
# Sign flip: the catchup branch must not reduce alpha below the
98+
# configured value, otherwise large user-chosen alphas (e.g. 1.0)
99+
# respond slower across zero-crossings than they do anywhere else.
100+
fake.set([-99.0])
101+
await sm.get_powermeter_watts()
102+
# catchup_alpha = max(1.0, min(0.5, 4.0)) = 1.0
103+
# delta = 1.0 * (-99 - 1) = -100 → new = -99
104+
assert sm.smoothed_value == pytest.approx(-99.0)
105+
89106
@pytest.mark.asyncio
90107
async def test_max_step_limits_delta(self):
91108
fake = FakePowermeter([100.0])

0 commit comments

Comments
 (0)