Score the full model against the base model; compile the search step
Validation: the walk-forward evaluator now replays all zones together with the full model (electrical heat, neighbour exchange, ventilation and recovery) using the parameters frozen at the forecast start and the electrical heat and window openings that actually happened, and scores the base 3-parameter model on the same spans. Forecast Error sensors add base_model_mae and improvement_vs_base (share of the base model's error the new features remove; 0 = no gain, negative = worse). Saved 0.3 statistics load with their own error as the base column. On synthetic apartments, 60-minute error full vs base: gaming office 0.031 vs 0.182 °C (83 % better), bedroom with ventilation 0.026 vs 0.218 °C (88 %), two coupled rooms 0.024 vs 0.037 °C (34 %; gone by 120 min, where the constant-outdoor assumption dominates). The same ventilation week without window sensors shows no improvement, as it should. These check the method, not real-world accuracy. Performance: every term of a zone's 5-minute change is affine in the zone temperatures and the radiator lag, and which layer applies depends only on which inputs exist. compile_step_model reads the coefficients off the real model per zone and step, so the plan search does a few multiply-adds per step. It matches the direct model to 1e-12 and makes a 180-minute, 5-zone search 4.5x faster (0.73 s here, close to the old base-only search). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8d197fabcd
commit
b1577a0c78
7 files changed
+266
-31
No files matched your search
@@ -13,7 +13,7 @@ samples where all of its inputs are known.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Mapping
|
||||
from typing import Any, Callable, Mapping
|
||||
|
||||
from .const import READY_OFF_SAMPLES, READY_ON_SAMPLES, READY_SAMPLES
|
||||
from .learning import FeatureLearner
|
||||
@@ -117,3 +117,44 @@ class ZoneModel:
|
||||
a, b, c = self.base.theta
|
||||
return {"loss": round(12 * a * (inputs["outside"] - temp), 3),
|
||||
"heat": round(12 * b * inputs["lag"], 3), "bias": round(12 * c, 3)}
|
||||
|
||||
|
||||
def _probe(model: ZoneModel, key: str, inputs: Mapping[str, Any], state: Mapping[str, float], lag: float) -> float:
|
||||
return model.delta(state[key], {**inputs, "temps": state, "lag": lag})
|
||||
|
||||
|
||||
def compile_step_model(models: Mapping[str, ZoneModel], keys: list[str], steps: int,
|
||||
inputs_at: Callable[[str, int], Mapping[str, Any]]):
|
||||
"""A fast, exact multi-zone step for the optimizer's plan search.
|
||||
|
||||
Every term of ZoneModel.delta is affine in the zone temperatures and the
|
||||
radiator lag (loss, neighbours, ventilation and recovery all scale with
|
||||
temperature differences), and which layer applies depends only on which
|
||||
inputs exist, not on their values. So per zone and step the change is
|
||||
a + sum_j b_j T_j + h lag, with the coefficients read off the real model at
|
||||
a few probe points. The search then does a handful of multiply-adds per step
|
||||
instead of rebuilding feature vectors for every candidate plan.
|
||||
"""
|
||||
zero = {key: 0.0 for key in keys}
|
||||
terms: dict[str, list[tuple[float, list[tuple[str, float]], float]]] = {}
|
||||
for key in keys:
|
||||
model = models[key]
|
||||
related = [key] + ([name.split(":", 1)[1] for name in model.extended.features
|
||||
if name.startswith("couple:") and name.split(":", 1)[1] in zero]
|
||||
if model.extended is not None else [])
|
||||
per_step = []
|
||||
for k in range(steps):
|
||||
inputs = dict(inputs_at(key, k))
|
||||
a = _probe(model, key, inputs, zero, 0.0)
|
||||
per_step.append((a, [(j, _probe(model, key, inputs, {**zero, j: 1.0}, 0.0) - a) for j in related],
|
||||
_probe(model, key, inputs, zero, 1.0) - a))
|
||||
terms[key] = per_step
|
||||
|
||||
def step(state: dict[str, float], lag: float, k: int) -> dict[str, float]:
|
||||
k = min(k, steps - 1)
|
||||
new = {}
|
||||
for key, value in state.items():
|
||||
a, coefficients, h = terms[key][k]
|
||||
new[key] = value + a + h * lag + sum(b * state[j] for j, b in coefficients)
|
||||
return new
|
||||
return step
|
||||
Reference in new issue
Block a user