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:
@@ -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 ≈ 20–30 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 1–2 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
|
||||||
|
`τ = (1−beta)/beta · dt` is 2–3× 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.
|
||||||
|
|||||||
Reference in New Issue
Block a user