Add docs/temp_control_calibration.md
Covers Smith predictor parameter identification (C, M, L, Td) from log data, model-plant replay verification, and controller performance metrics. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NSo8R6GjoBQdStB3j67zSs
This commit is contained in:
@@ -0,0 +1,119 @@
|
|||||||
|
# Temperature controller calibration
|
||||||
|
|
||||||
|
## Controller overview
|
||||||
|
|
||||||
|
`components/pid/temp_controller_smith.py` implements a Smith predictor. Each tick it computes:
|
||||||
|
|
||||||
|
```
|
||||||
|
theta_ist = theta_ist_model + (theta_ist_plant − theta_ist_model_delay)
|
||||||
|
```
|
||||||
|
|
||||||
|
`theta_ist_model` comes from a fast internal `Pot` (transport delay Td = 0);
|
||||||
|
`theta_ist_model_delay` from a second `Pot` with the full configured Td.
|
||||||
|
The correction term `(theta_ist_plant − theta_ist_model_delay)` removes the dead
|
||||||
|
time from the feedback path.
|
||||||
|
|
||||||
|
The plant model (`components/plant/pot.py`) integrates:
|
||||||
|
|
||||||
|
```
|
||||||
|
temp += (delayed_power_in − L·M·(temp − theta_amb)) / (M·C) · dt
|
||||||
|
```
|
||||||
|
|
||||||
|
Four parameters must be calibrated:
|
||||||
|
|
||||||
|
| Parameter | Unit | Meaning |
|
||||||
|
|---|---|---|
|
||||||
|
| C | J/(kg·K) | Specific thermal capacity |
|
||||||
|
| M | kg | Mass of pot + water + grain |
|
||||||
|
| L | W/(kg·K) | Heat loss coefficient |
|
||||||
|
| Td | s | Transport (heater-to-sensor) delay |
|
||||||
|
|
||||||
|
|
||||||
|
## 1. Verify model-plant match: replay simulation
|
||||||
|
|
||||||
|
Feed the **recorded `power_eff`** from a log file into a fresh `Pot` instance
|
||||||
|
with the same parameters and compare the simulated temperature to `temp_ist`.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from components.plant.pot import Pot
|
||||||
|
|
||||||
|
pot = Pot(dt=1.0)
|
||||||
|
pot.set_plant_params({'C': C, 'M': M, 'L': L, 'Td': Td})
|
||||||
|
pot.set_ambient_temperature(theta_amb)
|
||||||
|
pot.initial(samples[0]['temp_ist'])
|
||||||
|
|
||||||
|
sim_temp = []
|
||||||
|
for s in samples:
|
||||||
|
pot.set_power(s['power_eff'])
|
||||||
|
pot.process()
|
||||||
|
sim_temp.append(pot.get_temperature())
|
||||||
|
```
|
||||||
|
|
||||||
|
Plot `sim_temp` (model) against `temp_ist` (real) and the residual
|
||||||
|
`temp_ist − sim_temp`. What divergence tells you:
|
||||||
|
|
||||||
|
| Symptom | Likely cause |
|
||||||
|
|---|---|
|
||||||
|
| Ramp slopes differ | C·M wrong |
|
||||||
|
| Phase shift between power step and temperature rise | Td wrong |
|
||||||
|
| Wrong equilibrium temperature during HOLD | L wrong |
|
||||||
|
| Residual grows over a long run | Model drift / L temperature-dependent |
|
||||||
|
|
||||||
|
|
||||||
|
## 2. Parameter identification from log data
|
||||||
|
|
||||||
|
### Td — transport delay
|
||||||
|
|
||||||
|
Cross-correlate `power_eff` with `temp_ist`. The lag at peak correlation is the
|
||||||
|
actual Td. Alternatively, find a sharp power step (start of a ramp) and measure
|
||||||
|
the visible delay before `temp_ist` begins rising.
|
||||||
|
|
||||||
|
### L — heat loss coefficient
|
||||||
|
|
||||||
|
At steady-state HOLD, `d(temp)/dt ≈ 0`, so all input power compensates losses:
|
||||||
|
|
||||||
|
```
|
||||||
|
P_hold = L · M · (T − theta_amb)
|
||||||
|
→ L = P_hold / (M · (T − theta_amb))
|
||||||
|
```
|
||||||
|
|
||||||
|
Repeat across hold phases at different temperatures and fit a line through
|
||||||
|
`(T − theta_amb)` vs `P_hold` for a more robust estimate.
|
||||||
|
|
||||||
|
### C — specific heat capacity
|
||||||
|
|
||||||
|
During a ramp where heat loss is small relative to input power:
|
||||||
|
|
||||||
|
```
|
||||||
|
C ≈ power_eff / (M · rate_ist_K_per_s)
|
||||||
|
```
|
||||||
|
|
||||||
|
`rate_ist` in the log is K/min; divide by 60 to get K/s. Use a mid-ramp window
|
||||||
|
where `power_eff` and `rate_ist` are both stable.
|
||||||
|
|
||||||
|
|
||||||
|
## 3. Controller performance metrics
|
||||||
|
|
||||||
|
These can be read directly from the log without re-simulation:
|
||||||
|
|
||||||
|
| Metric | How to compute |
|
||||||
|
|---|---|
|
||||||
|
| Overshoot | `max(temp_ist) − temp_soll` after each setpoint step |
|
||||||
|
| Settling time | First time `|temp_ist − temp_soll| < threshold` is sustained after a step |
|
||||||
|
| Steady-state error | Mean of `temp_soll − temp_ist` during HOLD phase |
|
||||||
|
| Rate tracking error | `rate_ist − rate_soll` during RAMP |
|
||||||
|
|
||||||
|
### Smith predictor correction signal
|
||||||
|
|
||||||
|
The term `theta_ist_plant − theta_ist_model_delay` is the Smith correction; a
|
||||||
|
growing correction signal indicates model drift. This is not currently written to
|
||||||
|
the log. To observe it, log `theta_ist_model` and `theta_ist_model_delay` from
|
||||||
|
`temp_controller_smith.py` alongside the existing fields.
|
||||||
|
|
||||||
|
|
||||||
|
## 4. Suggested addition to analyze_log.py
|
||||||
|
|
||||||
|
A replay panel added to `utils/analyze_log.py` would show:
|
||||||
|
- `temp_ist` (blue) vs `sim_temp` (red dashed) — model accuracy at a glance
|
||||||
|
- Residual `temp_ist − sim_temp` in a separate subplot — reveals systematic
|
||||||
|
parameter errors vs noise floor
|
||||||
Reference in New Issue
Block a user