diff --git a/docs/temp_control_calibration.md b/docs/temp_control_calibration.md new file mode 100644 index 0000000..6c7ef32 --- /dev/null +++ b/docs/temp_control_calibration.md @@ -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