Twin-Experimente¶
End-to-End-Validierung des UKF gegen eine bekannte Wahrheit: man simuliert die Realität (truth plant) und gibt dem Filter nur verrauschte Messungen daraus. Der Vergleich zwischen Filter-Schätzung und Wahrheit sagt, ob der Filter gut kalibriert ist bevor er auf echte Anlagendaten losgelassen wird.
Grundprinzip¶
┌─────────────────────────────────────────┐
│ TRUTH SIDE (= "die echte Anlage") │
│ │
build → │ truth_plant (ADM1 ODE) │
warm-up → │ │ │
│ ↓ propagate_truth: ODE-Schritt dt=1h │
│ x_truth[k+1] │
│ │ │
│ ↓ h(x): clean truth observation │
│ obs_clean[k+1] │
│ │ │
│ ↓ truth_sensors[name].read(t) │
│ ↓ drift + lag + noise + sampling │
│ obs_noisy[k+1] ──────────────────────│─┐
└─────────────────────────────────────────┘ │
│
┌─────────────────────────────────────────┐ │
│ FILTER SIDE (UKF) │ │
│ │ │
deepcopy → │ filter_plant (= truth_plant zu t=0) │ │
│ │ │ │
│ ↓ ukf.predict(dt=1h): │ │
│ - 89 Sigma-Punkte │ │
│ - jeder Punkt: filter_plant.step() │ │
│ - gewichteter Mittelwert │ │
│ x̂[k+1] (predicted), P_pred │ │
│ │ │ │
│ ↓ ukf.update(y=obs_noisy[k+1]) ←─────│─┘
│ - 89 Sigma-Punkte durch h(x) │
│ - Innovation y − ŷ │
│ - Kalman-Gain → Posterior │
│ x̂[k+1] (posterior), P_post │
└─────────────────────────────────────────┘
In der Realität existiert kein truth_plant, die Sensoren liefern direkt die Messungen. Der filter_plant im Twin entspricht dem internen Modell des Schätzers.
Das vorgefertigte Skript¶
Im Repo liegt examples/run_twin_experiment.py, das den vollen Workflow
für die Multi-Stage-Beispielanlage durchspielt:
python examples/run_twin_experiment.py \
--warmup-days 30 \
--duration-days 5 \
--initial-perturbation-relative 0.05 \
--substrate-noise-relative 0.10
CLI-Parameter:
| Parameter | Default | Bedeutung |
|---|---|---|
--warmup-days |
30 | Vor-Simulation, bevor der Filter startet, damit die Anlage in einem Quasi-Steady-State ist |
--duration-days |
5 | Länge des eigentlichen UKF-Laufs |
--dt-hours |
1.0 | Filter-Schrittweite |
--initial-perturbation-relative |
0.05 | Relative Gauß-Perturbation des Filter-Initial-State |
--substrate-noise-relative |
0.10 | Per-Schritt-Rauschen auf der Substrat-Dosierung |
--plot-from-day |
0.0 | Burn-in für die Plots (Diagnostik wird trotzdem über vollen Lauf berechnet) |
Was passiert intern¶
- Truth-Plant bauen (
build_multi_stage_plant) und für 30 Tage
warmlaufen lassen (plant.simulate()). Dadurch erreicht die ODE einen quasi-stationären Operating Point. - Deepcopy der gewärmten Plant für den Filter. Garantiert
bit-identisches Modell zwischen Truth und Filter zut=0. - Truth-Trajektorie propagieren (
_propagate_truth_with_substrate_noise)
mit Per-Schritt-Rauschen auf den Substrat-Inputs (Operator-Lieferung ist nie exakt). - Truth-Sensoren (
build_truth_sensors) erzeugen das verrauschte
Mess-Signal. Diese benutzen diePhysicalSensor-Klassen aus PyADM1ODE für realistische Drift, Response-Lag und Sampling (siehepyadm1ode_estimation.estimation.sensors). - UKF mit
ukf.reset(x_truth0 + perturbation, P0)initialisieren. - Filter-Loop über alle Mess-Zeitstempel.
- Plots schreiben in
output/twin_experiment/.
Erzeugte Plots¶
Pro Run werden 6 Plots in output/twin_experiment/ abgelegt:
trajectories_strong.png— 6 strong-observable States
(S_ac, S_ch4, X_ac, S_hco3, p_gas_ch4, pTOTAL) mit truth,x̂und ±2σ-Band.trajectories_weak.png— 6 weak / open-loop States + 1
Substrat-Input.observations.png— alle 6 Mess-Channels mit clean truth,
noisy measurement und Filter-Vorhersage ŷ.production_estimate.png— der eigentliche
Production-Plot:- Truth Q_gas / Q_ch4 (schwarz)
- Raw sensor (rote X) — was die Messpunkte liefern
- Sensor-smoothed (rot, durchgezogen) — gleitender Mittelwert
- h(x̂) (grün) — deterministische Modell-Re-evaluation am
Filter-Posterior (Jensen-bias-frei, eine einzige Plant-Step- Auswertung statt 89 Sigma-Punkte) ±1σ-Band aus der UKF-interneny_std- Kumulative Produktion mit End-Error annotiert
nis.png— NIS-Zeitreihe in log-Skala mit Erwartungswert
als Referenz-Linie.coverage_summary.png— Per-Quality-Block 2σ-Coverage als
Bar-Chart mit Sollwerten (80 % / 40 % / 20 %).
Was die Resultate bedeuten¶
Aus einem typischen 30+5-Tage-Lauf (10 % Substrat-Rauschen, 5 % Initial-Perturbation):
| Block | Coverage | Status |
|---|---|---|
| methanogenesis | 86.7 % | strong ✓ |
| charge_balance | 94.3 % | strong ✓ |
| acidogenesis_substrates | 99.3 % | medium ✓ |
| acidogenesis_biomass | 99.8 % | weak ✓ |
| hydrolysis_sums | 100 % | weak ✓ |
| disintegration_split | 85.7 % | strukturell limitiert |
| nitrogen | 99.2 % | open-loop ✓ |
| inerts | 100 % | open-loop ✓ |
| fa_block | 100 % | open-loop ✓ |
disintegration_split bleibt strukturell unter 100 %, weil die
PS/PF-Splits aus Prozessmessungen prinzipiell nicht trennbar sind
(siehe Observability-Doku).
NIS-Mean ≈ 8.9 bei 6 Channels liegt im Idealfenster [3, 12]. Der
Filter ist gut kalibriert.
Production-Plot: Sensor vs. Modell¶
Im Production-Plot zeigt sich ein wichtiger praktischer Befund:
| Quelle | Cumulative End-Error |
|---|---|
| Sensor (smoothed) | ≈ −0.5 % |
| h(x̂) (Modell-Re-evaluation) | ≈ −3.5 % |
Der Sensor schlägt die modell-basierte Schätzung für direkt gemessene Größen. Q_gas hat 0.16 % relatives Rauschen — kein modell-basierter Schätzer kann das unterbieten, weil das Modell über 44 States aggregiert und kleine Bias-Beiträge sammelt.
Praktische Hierarchie für Operator-Reporting:
| Größe | Beste Quelle |
|---|---|
| Q_gas / Q_ch4 (direkt gemessen) | Sensor + Glättung |
| pH (direkt gemessen) | Sensor + Glättung |
| Substrat-Dosierung (direkt gemessen) | Sensor + Glättung oder Operator-Logbuch |
| S_ac, X_ac, Biomasse, Säure-Base-Spezies (nicht gemessen) | UKF x̂ |
Der UKF gibt also nicht "bessere Q_gas-Messung", er gibt dir die nicht-gemessenen 35-40 Zustände aus den gemessenen 5-6. Das ist der eigentliche Nutzen.
Acceptance-Kriterien¶
Für einen produktiven UKF gelten als Faustregel:
- Strong-observable Blöcke (methanogenesis, charge_balance,
acidogenesis_substrates): 2σ-Coverage ≥ 80 % - Weak / OU-Blöcke: 2σ-Coverage ≥ 40 %
- Open-Loop: 2σ-Coverage ≥ 20 %
- NIS-Mean über mehrere Tage in
[0.5·n, 2.0·n]
Bei einem Acceptance-Test sollten alle erfüllt sein. Wenn z.B. der NIS-Mean außerhalb des Bereichs liegt, prüfe:
- Sind die
noise_std-Werte realistisch zu den Sensoren? - Reicht der Warm-up, damit die Plant im Quasi-Steady-State ist?
- Ist die Initial-Perturbation
--initial-perturbation-relative
realistisch zur Initial-KovarianzP0? - Ist
dt_hoursklein genug für die ADM1-Nichtlinearität?