Zones, electrical heat sources, windows with ventilation, wind exposure and recovery, and exchange between adjacent zones, all observational. docs/MODEL.md documents the equations, units, coefficient meanings, priors and bounds, learning conditions, forecast assumptions, validation method and results, and limitations. README gains a configuration guide for zones, heat sources and windows, the new diagnostics and the accuracy comparison against the base model. CHANGELOG has upgrade notes (config entry version 3, rollback needs the backup, renamed Status attributes). ROADMAP marks the Milestone 2 items now covered; AUDIT records the design decisions and evidence. AGENTS.md lists the new modules and the convention that new model terms start at zero and must beat the base model out of sample before being relied on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
219 lines
11 KiB
Markdown
219 lines
11 KiB
Markdown
# Heating MPC thermal model
|
||
|
||
This document describes the model used from version 0.4.0: what it computes, the
|
||
units and meaning of every coefficient, when each part learns, what the forecasts
|
||
assume, and how the model is validated. It is observational: nothing here controls
|
||
the boiler.
|
||
|
||
## Time step and inputs
|
||
|
||
The model works in 5-minute steps, the polling interval. For each zone *i*, each
|
||
poll provides:
|
||
|
||
| Symbol | Meaning | Unit | Source |
|
||
|---|---|---|---|
|
||
| T_i | Zone temperature: sensor-weighted mean of its usable sensors | °C | zone sensors |
|
||
| T_out | Outdoor temperature (first fresh, valid source) | °C | weather sources |
|
||
| lag | Radiator-lag proxy, 0 (cold) to 1 (fully warm); 20-minute first-order response to the relay, integrated at the relay's actual switch times | – | relay state + read-only listener |
|
||
| q_i | Electrical heat above baseline after its thermal response | kW | heat sources |
|
||
| O_i | Sum of open windows' opening factors, weighted by the share of the interval they were open | – | window sensors |
|
||
| F_i | Same, each window also weighted by cos(angle between wind direction and the window's facing) | – | windows + wind direction |
|
||
| v, g | Wind speed and gust speed | m/s | weather sources |
|
||
| R_i | Recovery weight after the zone's last window closed: exp(−t / τ_rec), 0 below 0.05 | – | window sensors |
|
||
| T_ref,i | Zone temperature at the poll before the window opened | °C | zone sensors |
|
||
|
||
## Equations
|
||
|
||
Per 5-minute step, the predicted change of zone *i* is the sum of layers:
|
||
|
||
```text
|
||
ΔT_i = loss × (T_out − T_i) envelope heat loss
|
||
+ heat × lag boiler, through the radiators
|
||
+ bias constant gains and losses
|
||
+ internal × q_i electrical heat above baseline (extended layer)
|
||
+ Σ_j couple:j × (T_j − T_i) exchange with adjacent zones (extended layer)
|
||
+ vent_natural × O_i (T_out − T_i) (ventilation layer)
|
||
+ vent_wind × O_i v (T_out − T_i)
|
||
+ vent_wind_dir × F_i v (T_out − T_i)
|
||
+ vent_gust × O_i max(0, g − v) (T_out − T_i) (optional)
|
||
+ vent_cross × O_i O_other (T_out − T_i) (optional)
|
||
+ recovery × R_i (T_ref,i − T_i)
|
||
```
|
||
|
||
All coefficients are in °C per 5 minutes per unit of their term. They are
|
||
statistical estimates, not measured physical properties: `heat` is not boiler
|
||
efficiency, and a relay that is on does not prove the burner is firing.
|
||
|
||
In conceptual continuous-time form this corresponds to
|
||
|
||
```text
|
||
C_i dT_i/dt = UA_i (T_out − T_i) + Q_boiler + Q_internal + Q_ventilation + Σ_j K_ij (T_j − T_i) + Q_other
|
||
```
|
||
|
||
divided through by the zone's effective heat capacity C_i and discretized to 5
|
||
minutes. The model is first-order (one temperature per zone). Thermal inertia
|
||
enters through the radiator lag, the electrical response filter and the recovery
|
||
term; a separate wall-mass state is not modeled (see Limitations).
|
||
|
||
### Base layer
|
||
|
||
The base layer is the 3-parameter model of earlier versions (loss, heat, bias),
|
||
learned per zone. It is always kept: it is the fallback whenever another layer's
|
||
inputs are unknown, and the comparison that shows whether the other layers help.
|
||
|
||
### Extended layer
|
||
|
||
Present when a zone has an enabled heat source or an adjacent zone. It learns all
|
||
base terms plus `internal` and `couple:<zone>` and replaces the base layer in
|
||
forecasts once it is as trained as the base layer needs to be (200 samples, 30 with
|
||
the relay on, 30 off). If any of its inputs is unknown, the base layer is used.
|
||
|
||
### Electrical heat
|
||
|
||
For each enabled source:
|
||
|
||
```text
|
||
effective power = measured W − measured W of enabled sources included in it (≥ 0)
|
||
baseline = 20th percentile of the last 24 h of effective power, or the manual value
|
||
variable heat = heat fraction × (effective power − baseline) / 1000 (kW)
|
||
thermal response : r ← r + (1 − exp(−5 min / τ)) × (variable heat held during the interval − r)
|
||
```
|
||
|
||
q_i is the sum of the responses of the zone's sources. Centring on the baseline
|
||
matters: the persistent load (for example a UPS drawing 200 W all day) is already
|
||
part of the bias term, so feeding it again would count it twice. The baseline
|
||
needs one hour of data; after startup or an outage a source's response must run
|
||
for two time constants before it is used for learning.
|
||
|
||
Only active power counts (W, kW, mW). VA (apparent power) and % (UPS load) are
|
||
rejected. A UPS output sensor does not include the UPS's own losses; if they matter,
|
||
measure the input side or lower the heat fraction accordingly.
|
||
|
||
### Ventilation layer
|
||
|
||
Present when a zone has an enabled window. Learned separately, from the part of
|
||
the measured change the closed-window layers cannot explain, only while a window is
|
||
open, its state is unknown, or the zone is recovering. During those periods the
|
||
base and extended layers do **not** learn, so window events cannot inflate `loss`
|
||
or shift `bias`.
|
||
|
||
`vent_wind_dir` is bounded by `vent_wind` (|vent_wind_dir| ≤ vent_wind), so the
|
||
combined wind term never goes negative: a leeward window still ventilates.
|
||
|
||
Wind direction is meteorological: the direction the wind comes **from**, degrees
|
||
clockwise from north. A window's azimuth is the direction its outside faces. Wind
|
||
from the azimuth blows straight at the window (cos = 1); wind from behind the
|
||
building gives −1. Angle differences wrap around north correctly.
|
||
|
||
## Priors, bounds and confidence
|
||
|
||
| Coefficient | Prior | Bounds | Prior worth |
|
||
|---|---|---|---|
|
||
| loss | 0.002 | 0 … 0.04 | as in earlier versions |
|
||
| heat | 0.045 | 0 … 0.25 | as in earlier versions |
|
||
| bias | 0 | −0.06 … 0.06 | as in earlier versions |
|
||
| internal | 0 | 0 … 0.5 | 20 samples at 0.4 kW |
|
||
| couple:j | 0 | 0 … 0.05 | 200 samples at 1 °C difference |
|
||
| vent_natural | 0 | 0 … 0.2 | 20 samples at opening 1, 15 °C difference |
|
||
| vent_wind, vent_wind_dir | 0 | 0 … 0.03 / ±vent_wind | 20 samples at 4 m/s |
|
||
| vent_gust | 0 | 0 … 0.03 | 20 samples |
|
||
| vent_cross | 0 | 0 … 0.1 | 100 samples |
|
||
| recovery | 0 | 0 … 0.5 | 20 samples at 0.5 °C deficit |
|
||
|
||
New effects start at 0: no effect is assumed until the data shows one. Learning is
|
||
recursive least squares with exponential forgetting (about 3.5 days of samples),
|
||
a covariance bound that stops windup during long periods without excitation, and
|
||
outlier rejection above 0.8 °C per 5 minutes.
|
||
|
||
Each coefficient reports:
|
||
|
||
* `confidence`: the share of its information that comes from data rather than the
|
||
prior (0 = prior only);
|
||
* `std`: its standard error, using a running estimate of the unexplained noise;
|
||
* `samples`: how many accepted samples actually exercised it;
|
||
* `identified`: at least 30 exercising samples and confidence ≥ 0.8.
|
||
|
||
A coefficient can move without being identified. Example from the tests: two rooms
|
||
whose temperature difference stays fairly steady cannot separate exchange between
|
||
them from each room's bias; the learner finds a positive coupling but correctly
|
||
reports it as not identified.
|
||
|
||
## Learning conditions
|
||
|
||
A zone learns from the interval between two polls only when:
|
||
|
||
* the polls are 4.5–5.5 minutes apart, the relay held one state throughout and the
|
||
radiator-lag estimate is known within 0.1 (for example ~32 minutes after an HA
|
||
restart);
|
||
* the outdoor source did not change and the zone used the same sensors at both
|
||
polls;
|
||
* for the extended layer, every source in the zone is settled and every adjacent
|
||
zone was usable;
|
||
* for the ventilation layer, every window state in the zone is known.
|
||
|
||
No experiments are run and the boiler is never switched to gather data: everything
|
||
is learned passively.
|
||
|
||
## Forecasts
|
||
|
||
Forecasts simulate all zones together, step by step, over the MPC horizon (60–180
|
||
minutes):
|
||
|
||
* **Weather:** held at current values.
|
||
* **Electrical heat:** the variable part fades towards baseline with a configurable
|
||
half-life (default 30 minutes; 0 = stops now), and the room keeps following it with
|
||
its response time. A gaming PC drawing 600 W now is not assumed to run on for two
|
||
hours.
|
||
* **Windows:** an open window is assumed to stay open for a configurable time
|
||
(default 15 minutes), then close; the zone then recovers.
|
||
* **Relay:** the candidate plan being evaluated.
|
||
|
||
For the plan search the step is compiled into exact per-zone coefficients (every
|
||
term is linear in the temperatures and the lag), which keeps the 180-minute search
|
||
fast on a Raspberry Pi.
|
||
|
||
Plan forecasts are **counterfactual** while Generic Thermostat controls the relay:
|
||
they describe what would happen under the MPC's proposed plan.
|
||
|
||
## Validation
|
||
|
||
The Forecast Error sensors score forecasts that are valid for comparison with
|
||
measured temperatures. At every poll the model parameters are frozen; 15, 30, 60
|
||
and 120 minutes later all zones are replayed from that poll using the relay,
|
||
electrical heat and window history that actually followed (outdoor temperature held
|
||
at its start value), and compared with the readings. The base layer and a
|
||
no-change forecast are scored on exactly the same spans:
|
||
|
||
* `mae`, `bias`: the full model's error;
|
||
* `persistence_mae`, `skill`: against assuming no change;
|
||
* `base_model_mae`, `improvement_vs_base`: against the base layer alone. A feature
|
||
that does not improve this should be switched off (for example the gust term).
|
||
|
||
Results on synthetic apartments (the tests; these check the method, not real-world
|
||
accuracy):
|
||
|
||
| Scenario | 60-min error, full | base | no change |
|
||
|---|---|---|---|
|
||
| Office, UPS 200 W + 400 W gaming sessions, thermostat cycling | 0.031 °C | 0.182 °C | 0.40 °C |
|
||
| Bedroom, 15–20 min ventilation every 4 h, wind and recovery | 0.026 °C | 0.218 °C | 0.52 °C |
|
||
| Two adjacent rooms, true exchange 0.01 | 0.024 °C | 0.037 °C | 0.29 °C |
|
||
|
||
In the ventilation scenario, the same data without the window configured produced
|
||
a −0.013 °C/5 min cooling bias and an 11 % inflated boiler coefficient, which is
|
||
the contamination the separate ventilation layer prevents.
|
||
|
||
## Limitations
|
||
|
||
* One temperature per zone: walls and air are not separate states. The recovery
|
||
term approximates the air warming back after ventilation.
|
||
* Response times (electrical heat, recovery) are configured, not learned. Compare
|
||
settings with `improvement_vs_base`.
|
||
* Coupling between zones heated by one boiler is weakly identifiable; it is shrunk
|
||
towards 0 and usually reported as not identified.
|
||
* Natural and wind ventilation terms both scale with the opening; with little wind
|
||
variation their split is uncertain even when their combined effect is accurate.
|
||
* Forecasts hold weather constant and use one scenario for electrical heat and
|
||
windows; no forecast uncertainty band is computed yet.
|
||
* Window sensors with a permanently unknown state keep their zone out of learning;
|
||
disable such a window.
|