Note
AI-generated code. This repository — code, models, experiments and
documentation — was developed with Claude Code (Anthropic) acting as a
coding agent under human direction. The researcher set the objectives,
supplied domain knowledge and field observations (which drove several model
calibrations), reviewed intermediate results and made the design decisions;
the agent implemented, ran and documented the work. Model parameters and
behavior are backed by reproducible verification scripts (see the docs and
results/), but AI-generated content may contain errors — independent
review is advised before relying on quantitative results. Commit-level
attribution is recorded via Co-Authored-By trailers.
buildingsimulator runs a multi-tenant building with hydronic radiator heating — the full path central heat generation → distribution (pump, risers) → individual radiators with thermostatic valves — as a Modelica/FMU plant under a Python software-in-the-loop (SIL) harness, and turns it into a testbed for the control strategies of electronic radiator thermostats (eTRVs): adaptive control of individual devices and distributed control of all thermostats in a building.
The experimental core is the three-layer view of a heated room:
- Reality — the room-resolved plant truth: a field-calibrated 1980s
German multi-family house (
Building80s) with verified heat loads, cooldown dynamics, riser hydraulics and oscillation signatures. - What the device senses — an eTRV knows the room only through its valve-mounted sensor: warm-biased by the radiator below it, sampled, quantized, noisy. Real low-cost devices are almost blind, and the simulator shows exactly what that costs (2.1× the ideal-PI discomfort).
- What the firmware does about it — a cumulative strategy ladder (night-anchor bias estimation, battery-aware actuation, adaptive optimal start, distributed coordination) recovers the device penalty in firmware alone — including one documented negative result and the identifiability limits found on the way.
Intended use: control engineering teaching and algorithm development. The plant is a verified nonlinear multivariable system with realistic actuator and sensor imperfections — a working testbed for the classic topics:
- Parameter estimation / system identification — grey-box RC identification, the night-anchor bias estimator (Prony-style partial-decay identification, with documented under- and over-identification failure modes)
- Adaptive control — self-calibrating thermostat firmware, learned lead times, the full strategy ladder in docs/phase3-adaptive-strategies.md
- Nonlinear control — quick-opening valve characteristics, EN 442 radiator power law, coupled hydraulic networks, relay-driven plants and limit cycles
- Model predictive control / optimal start — multi-time-constant recovery dynamics, schedule anticipation, energy-comfort trade-offs
- Reinforcement learning — a validated Gymnasium interface
(
sil/gym_env.py) with a device-realistic partial-observability mode
Exercises can attack the plant at every level: FMU inputs directly, the
eTRV device firmware (sil/thermostat.py / sil/strategies.py), the Gym
environment, or the BOPTEST
reference benchmark. Parameter derivations and physics decisions live in
docs/ (building80s-parameters.md,
radiator-modeling.md,
valve-modeling.md,
heatup-dynamics.md); the claim-by-claim
verification report with graphical evidence is generated by
scripts/make_verification_report.py into
results/verification-report.html.
| Layer | What it is | Where |
|---|---|---|
| Plant | Modelica models (Buildings library) compiled to FMI 2.0 CS FMUs with embedded CVODE | modelica/ → build/ |
| SIL harness + device firmware | FMPy co-simulation loop, eTRV device models, strategy ladder, KPI module, Gym env | sil/ |
| Dashboard | React (Vite + Recharts) experiment workbench + FastAPI run-store API — run catalog, building view, KPI leaderboard, device inspector, run launcher | ui/ + server/ (ports 5173 / 8010) |
| Grafana | free-form time-series exploration, live monitoring of long batches, later field-measurement data | provisioned dashboard (port 3001) |
Simulation runs persist to runs/<id>/ (manifest + series); the dashboard
lists them, replays them with a time scrubber, and polls live while a run
is in progress.
- Parameterizable multi-tenant building (
MultiTenantBuilding): N floors × M apartments (compile-time), setpoint-tracking boiler + constant speed pump feeding a vertical two-pipe riser; per apartment an EN 442-2 radiator with dynamic water/steel storage behind a quick-opening TRV insert and a 2R2C zone. - Verified 1980s German MFH (
Building80s): room-resolved (living/bedroom/kitchen/bath + hall), IWU-typology envelope, 90/70 system, per-stack risers, manual presetting/balancing valves — design-day verified at 65 W/m², overnight cooldown calibrated to the field corridor (−0.2…−0.4 K/h). - Effects central to distributed TRV control, built in: riser hydraulics (upper floors starve when ground-floor valves open), inter-apartment coupling (an unheated apartment steals heat), facade asymmetry (kW-scale south solar vs diffuse north light).
- Oscillation realism: cycling two-point boiler (era on/off burner) with Schnellaufheizung morning boost, riser water columns, stochastic internal gains and window events — burner starts, supply sawtooth, room ripple and flow chatter land inside published field ranges.
- Valve realism (German M30×1.5 inserts, 1.5 mm stroke): table-based quick-opening characteristic anchored to Danfoss RA-N data, sealing dead zone, leakage floor; device-side 0.1 mm motor-pin backlash; 60 s motor stroke as a harness-side rate limit.
- eTRV device model: sampled control, valve-mounted sensor bias/noise/quantization fed by radiator heat, actuation deadband, motor current signature with stall-based zero referencing, battery KPIs.
- The Phase 3 strategy ladder: sensor-bias compensation, battery-aware limit-cycle suppression, per-room adaptive optimal start, and a documented negative on distributed considerate recovery (results).
- Gymnasium interface (
sil/gym_env.py): valve-vector actions through the real motor rate limit, leaderboard-consistent reward, plant- and device-observation modes; an RL postscript shows the bias compensation is learnable from reward alone. - BOPTEST adapter (
sil/boptest_adapter.py): the same firmware objects run unmodified againstmultizone_residential_hydronic.
.\scripts\build_image.ps1 # once: toolchain image (OpenModelica 1.27 + Buildings 13 + FMPy)
# multi-tenant building (default 3 floors x 2 apartments)
.\scripts\build_multitenant_fmu.ps1 -Floors 3 -ApartmentsPerFloor 2
docker run --rm -v "${PWD}:/work" -w /work/sil buildingsimulator:dev python3 run_multitenant.py
# two-room prototype
.\scripts\build_fmu.ps1 # compile modelica/PrototypeTwoRooms.mo
.\scripts\run_prototype.ps1 # run both prototype scenarios
# dashboard stack: run-store API (8010) + Grafana (3001)
docker compose -p buildingsim up -d
cd ui; npm install; npm run dev # React dashboard at http://localhost:5173If Docker Desktop is unavailable (e.g. its Windows service needs an admin start), the same toolchain runs directly in a per-user WSL distro — no admin rights needed:
wsl --install -d Ubuntu-24.04 --no-launch
wsl -d Ubuntu-24.04 -u root -- bash -c "tr -d '\r' < /mnt/c/<repo-path>/scripts/wsl_toolchain_setup.sh | bash"
# then build/run exactly like the docker variant, e.g.:
wsl -d Ubuntu-24.04 -u root -- bash -c "cd /work/build && omc /work/modelica/build_80s.mos"
wsl -d Ubuntu-24.04 -u root -- bash -c "cd /work/sil && /opt/silenv/bin/python3 run_design_day.py"The setup script installs OpenModelica (apt stable), Modelica Buildings
13.0.0 and a Python venv at /opt/silenv, and links the repo at /work so
the .mos build scripts work unchanged. If WSL has no network (campus NAT
policies), put networkingMode=mirrored under [wsl2] in
%UserProfile%\.wslconfig and run wsl --shutdown once. The
Grafana/BOPTEST stack still requires Docker.
Experiments can also be launched from the dashboard (thermostat type,
duration, cloudiness, vacant apartments) via POST /api/launch; running
experiments can be stopped, and the leaderboard ranks all runs by KPI.
modelica/*.mo ─► omc (Docker or WSL) ─► build/*.fmu (FMI 2.0 CS, embedded CVODE)
│
sil/harness.py (FMPy fixed-step loop, 60 s valve-stroke rate limit)
│ outputs: TRoom, mFlow, QRad, dpVal, TSup, TRet, QBoi …
▼
controllers per radiator: PIThermostat │ ElectronicThermostat │ strategy ladder
supervisory: TwoPointBoiler + Schnellaufheizung (firmware in the loop)
│ records
▼
runs/<id>/ (runstore) ─► FastAPI :8010 ─► React dashboard :5173
└────────► Grafana :3001 (Infinity)
| Path | Content |
|---|---|
modelica/BuildingSimulator/ |
ApartmentBranch (valve + radiator + 2R2C zone), MultiTenantBuilding, Building80s |
sil/harness.py |
Generic FMU co-simulation loop |
sil/thermostat.py / sil/strategies.py |
eTRV device model + Phase 3 firmware ladder |
sil/controllers.py / sil/boiler.py |
Baseline PI, supervisory boiler logic |
sil/gym_env.py |
Gymnasium environment (plant/device observation modes) |
sil/boptest_adapter.py |
BOPTEST REST adapter (same firmware objects) |
sil/kpi.py / sil/runstore.py |
KPIs; run persistence for dashboard/Grafana |
scripts/ |
FMU builds, WSL setup, data fetchers, verification report |
docs/ / results/ / data/ |
Documentation; committed evidence; weather + VDI case data |
MultiTenantBuilding — setpoint-tracking boiler (+80 l water mass) and
constant-speed pump feed a vertical two-pipe riser; on every floor
nApeFlo apartment branches tap off. Zones are 2R2C (fast air node + slow
structural mass): the air responds to solar bursts and radiator action
within minutes while the building mass stays slow. Weather comes from
Python: synthetic sinusoidal TOut plus facade solar gains via
pvlib (clear-sky Ineichen with
cloudiness factor, per-apartment facade orientation); measured weather
(DWD, EPW) can replace the synthetic model without touching the FMU — the
TABULA season validation does exactly that.
FMU inputs: yVal[i], QGain[i], TOut, TSupSet · outputs: TRoom[i],
mFlow[i], QRad[i], dpVal[i], TSup, TRet, QBoi, PPum.
Building80s — the verified research plant: IWU class MFH_G
(1979–1983), 3 floors × 2 apartments × 4 rooms, 90/70 °C two-pipe system,
radiators 1.3× design load (era sizing) with dynamic water/steel storage,
interior coupling per ISO 13790, night-accessible structural mass
(DIN V 18599-2-backed), manual presetting rings + riser balancing valves as
FMU inputs. Parameter derivation with sources:
docs/building80s-parameters.md.
PrototypeTwoRooms — the minimal two-room plant: shared riser
resistances behind a constant-speed pump, so closing one valve shifts flow
to the other branch — the hydraulic coupling that distributed thermostat
control has to deal with, in its smallest reproducible form.
Every headline claim is measured by a reproducible script and documented next to its evidence; the full graphical report is results/verification-report.html.
| Result | Evidence |
|---|---|
| Design-day verification: 65.0 W/m² specific heat load (literature 58–70), all 24 rooms on setpoint, unbalanced-system flow signature | building80s-parameters.md |
| Field-calibrated dynamics: overnight free-cool tail −0.25 K/h (field corridor −0.2…−0.4), boost recovery ≈ 1 h — the fast-up/slow-down asymmetry as a power phenomenon | heatup-dynamics.md |
| Device pathology: stock eTRV firmware on its biased valve sensor costs 2.1× the ideal-PI discomfort for a ~4 % energy saving | sil/run_thermostat_comparison.py |
| Strategy ladder: firmware-only recovery of the device penalty (854 → 378 K·h discomfort), valve moves cut to a third, plus a documented negative on distributed coordination | phase3-adaptive-strategies.md |
| RL under partial observability: 12-episode policy search learns the bias compensation from reward alone, recovering ≈ 90 % of the observability gap | phase3-adaptive-strategies.md §7 |
External benchmark (BOPTEST): pathology (2.7×) and ladder recovery (40 % of the gap at PI-equal energy) reproduce on the independent multizone_residential_hydronic plant, on its two-sided KPIs |
boptest-benchmark.md |
| Archetype statistics (TABULA/IWU): a full season with measured DWD weather and era operation lands at 95 % of the MFH_G measured-consumption level (net 110 vs 115 kWh/(m²·a) at reference climate), 21 % below the standard calculation — the prebound effect emerges untuned; energy signature 719 W/K, heating limit 17.0 °C | tabula-season-validation.md |
| Zone methodology (VDI 6007-1, TC1–TC7, diagnostic): steady-state transmission exact by construction; 0.55–1.7 K on the realistic mixed-excitation case, 0.6–3 K pure topology cost elsewhere; the larger square-wave deviations are the documented furnished-room fast node (VDI prescribes massless air) | vdi6007-zone-tests.md |
Numerics: communication-step convergence (30 s vs 10 s) is part of the verification report. Open validation rungs: the ASHRAE 140 envelope subset and an empirical replay of the IEA EBC Annex 58 Twin-House dataset.
The source code, models and documentation are licensed under the MIT License.
Committed data carries its own attributions: the weather year under
data/weather/ is DWD Climate Data Center open data
(CC BY 4.0, "Quelle:
Deutscher Wetterdienst"); the VDI 6007 test-case tables under
data/vdi6007/ are extracted from
RWTH-EBC/AixLib (BSD-3-Clause). The
Modelica Buildings library
(LBNL) is a build-time dependency fetched by the toolchain, not
redistributed here. TABULA/IWU comparison values are cited from the public
typology brochures.