docs: add heat rate noise section to temp control calibration

Documents the stirring-induced rate noise problem, its root cause
(post-differentiation filtering), and three mitigation options: pre-filter
before differentiating, sliding-window regression, and model-based rate
from Pot.p_pot (Smith only).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NSo8R6GjoBQdStB3j67zSs
This commit is contained in:
2026-06-29 22:16:10 +02:00
co-authored by Claude Sonnet 4.6
parent abcd011a9f
commit dd331c164f
+85
View File
@@ -117,3 +117,88 @@ A replay panel added to `utils/analyze_log.py` would show:
- `temp_ist` (blue) vs `sim_temp` (red dashed) — model accuracy at a glance - `temp_ist` (blue) vs `sim_temp` (red dashed) — model accuracy at a glance
- Residual `temp_ist sim_temp` in a separate subplot — reveals systematic - Residual `temp_ist sim_temp` in a separate subplot — reveals systematic
parameter errors vs noise floor parameter errors vs noise floor
## 5. Heat rate noise from stirring
Stirring creates low-frequency temperature fluctuations at the sensor. The heat
rate is computed in both controllers (`temp_controller_smith.py:71-74`,
identical in `temp_controller.py`) as:
```python
heatrate = (self.theta_ist - self.last_theta_ist) / self.dt * 60
alpha = 0.1
self.heatrate_ist = (1-alpha) * self.heatrate_ist + alpha * heatrate
```
Two problems compound:
1. **Differentiation amplifies noise.** A temperature fluctuation δT at
frequency f becomes a rate fluctuation of `2π·f·δT·60` K/min. Low-frequency
stirrer noise (e.g. 0.05 Hz, δT = 0.05 °C) → ≈ 0.9 K/min rate noise, the
same order as a typical `rate_soll`.
2. **The IIR filter comes after differentiation.** With α=0.1 and dt=1 s the
time constant is τ = 9 s (cutoff ≈ 0.018 Hz). Filtering *after*
differentiating is the least effective arrangement: noise has already been
amplified before the filter sees it.
The noisy `heatrate_err` feeds into `pid_heat.process()` as the proportional
error. With `kp=0.08`, ±1 K/min rate ripple produces ±8% power output noise.
### Options
#### Option A — Pre-filter theta_ist before differentiating (best return for effort)
Apply a separate, heavier IIR to the raw temperature *before* the derivative:
```python
beta = 0.05 # smaller = more smoothing, more lag
self.theta_ist_filtered = (1-beta)*self.theta_ist_filtered + beta*self.theta_ist
heatrate = (self.theta_ist_filtered - self.last_theta_ist_filtered) / self.dt * 60
```
This is fundamentally better than post-filtering: the derivative of a smooth
signal stays smooth. `beta` can be tuned independently of `alpha`.
#### Option B — Sliding-window linear regression
Fit a line through the last N samples of `theta_ist` and use the slope:
```python
slope, _ = np.polyfit(t_window, theta_window, 1)
heatrate_ist = slope * 60 # K/s → K/min
```
Gives the minimum-variance rate estimate for additive white noise, with a clean
constant lag of N/2 seconds. N ≈ 2030 s is a reasonable starting point.
#### Option C — Model-based rate (Smith predictor only)
`Pot.process()` already computes `p_pot = delayed_power_in L·M·(temp theta_amb)`
and integrates `d(temp)/dt = p_pot / (M·C)`. This can be used directly:
```python
rate_model = self.model.get_p_pot() / (self.model.M * self.model.C) * 60
```
(`get_p_pot()` is not yet public on `Pot` but trivially added.) The rate is
completely noise-free because it is derived from the heater power signal, not
from differentiating the sensor. Accuracy depends on model calibration — see
sections 12 above.
#### Option D — Reduce kp of the Heat/Cool PID
Halving `kp` from 0.08 to 0.04 halves the power noise at the cost of slower
rate convergence. Treats the symptom rather than the source.
### Recommendation
Start with **option A**: one extra IIR state, localised to the rate
computation, no other structural changes. Set `beta` so that
`τ = (1beta)/beta · dt` is 23× the dominant stirrer-noise period (visible
as the ripple frequency in `rate_ist` in the log).
For the Smith predictor, **option C** is the most principled long-term
solution — it replaces a noisy numerical derivative with the model's own
analytical rate, which the Smith predictor is already tracking.