|
| 1 | +# v0.5 Plan — ΔP-Model + Rectangular Stacks + 2nd Validation |
| 2 | + |
| 3 | +**Status:** Draft for review (2026-04-20). **No code written yet.** |
| 4 | +**Scope:** 2–3 Werktage, ein Entwickler. Pure Python + Streamlit, keine neuen |
| 5 | +Runtime-Dependencies. |
| 6 | + |
| 7 | +--- |
| 8 | + |
| 9 | +## Scope |
| 10 | + |
| 11 | +### In v0.5 |
| 12 | +1. **ΔP-Modell** — Druckabfall im Flow-Field als Physik (nicht nur Display-Only), |
| 13 | + Pumpenleistung als Parasitic Power, Integration in Stack-KPIs + Polarisations-Tab. |
| 14 | +2. **Rechteckige Stacks** — BPP / Active Area mit Aspect Ratio ≠ 1. |
| 15 | +3. **2. Validation** — experimentelle Polarisationskurve aus Paper gegen Modell, |
| 16 | + RMSE-Test in `tests/test_validation.py`. |
| 17 | + |
| 18 | +### Out of v0.5 (→ v1.0) |
| 19 | +Material-DB (SQLite), Cost-Estimator, PDF-Export, DE/EN-Switch, ML-Surrogate, |
| 20 | +Water-Management-Modell, Degradation. Begründung: Aspect-Ratio und ΔP sind |
| 21 | +Physik-Erweiterungen, die anderen sind UX-/Infra-Arbeit ohne Physik-Zuwachs. |
| 22 | + |
| 23 | +--- |
| 24 | + |
| 25 | +## 1. ΔP-Modell |
| 26 | + |
| 27 | +### Physik |
| 28 | + |
| 29 | +Laminare Strömung in rechteckigen Kanälen — Reynolds im PEM-EC-Betrieb ist O(10–100), |
| 30 | +damit ist Darcy-Friction-Factor `f · Re = C(α)` eine Funktion des |
| 31 | +Aspect-Ratios α = channel_depth / channel_width (Shah & London 1978, Table 42): |
| 32 | + |
| 33 | +| α (d/w) | 1.0 | 0.8 | 0.5 | 0.25 | 0.1 | |
| 34 | +|---------|------|------|------|-------|------| |
| 35 | +| f·Re | 56.9 | 58.0 | 62.2 | 72.9 | 84.7 | |
| 36 | + |
| 37 | +Druckabfall: |
| 38 | + |
| 39 | +``` |
| 40 | +ΔP = f · (L / D_h) · (ρ · V² / 2) |
| 41 | +D_h = 4·A_channel / P_wetted = 2·w·d / (w + d) |
| 42 | +V = Q_channel / A_channel |
| 43 | +``` |
| 44 | + |
| 45 | +**Flow-Pattern bestimmt Pfadlänge L:** |
| 46 | +- `serpentine`: L ≈ edge · (edge / pitch) → lange Schlange, hoher ΔP |
| 47 | +- `parallel`: L ≈ edge → viele kurze parallele Pfade |
| 48 | +- `interdigitated`: L ≈ edge · 0.9 + (Land-Crossing) → Mischfall, Land-Druckabfall |
| 49 | + dominiert; **v0.5 Approximation:** wie parallel, Hinweis als Caption |
| 50 | + (echtes Modell → v0.6+) |
| 51 | + |
| 52 | +**Wasser-Flow-Rate** (stoichiometrisch mit User-konfigurierbarem λ): |
| 53 | + |
| 54 | +``` |
| 55 | +ṁ_H2O_stoich = I_cell / (2 · F) · M_H2O [kg/s/cell] |
| 56 | +ṁ_H2O_actual = λ · ṁ_H2O_stoich (λ default 50, slider 10–300) |
| 57 | +Q_cell = ṁ_H2O_actual / ρ_water [m³/s/cell] |
| 58 | +``` |
| 59 | + |
| 60 | +Wasser parallel auf alle Kanäle einer Zelle verteilt; Anzahl Kanäle: |
| 61 | +`n_channels = edge_width / pitch`. |
| 62 | + |
| 63 | +**Pumpenleistung:** |
| 64 | + |
| 65 | +``` |
| 66 | +P_pump,cell = ΔP · Q_cell / η_pump (η_pump default 0.6, slider 0.3–0.9) |
| 67 | +P_pump,stack = N · P_pump,cell |
| 68 | +``` |
| 69 | + |
| 70 | +Fließt als **Parasitic Load** in die Netto-Effizienz: |
| 71 | + |
| 72 | +``` |
| 73 | +η_net = η_stack · (1 − P_pump,stack / P_electric,stack) |
| 74 | +``` |
| 75 | + |
| 76 | +### Referenzen |
| 77 | +- Shah & London (1978) "Laminar flow forced convection in ducts", Adv. Heat Transfer, |
| 78 | + Supp. 1 — f·Re-Tabellen für rechteckige Kanäle. |
| 79 | +- Barbir (2012) PEM Fuel Cells, Ch. 4.3 — Serpentine-Pfadlänge-Schätzung. |
| 80 | +- Kakac, Shah, Aung (1987) Handbook of Single-Phase Convective Heat Transfer |
| 81 | + — Bestätigung f·Re-Korrelation, Textbook-Ref falls Shah&London schwer zugänglich. |
| 82 | + |
| 83 | +### Dateien |
| 84 | +- **Neu:** `src/fluid.py` — `pressure_drop_pa(flow_pattern, channel_geometry, q_per_channel)`, |
| 85 | + `pump_power_w(dp, q, eta_pump)`, `darcy_friction_factor(aspect_ratio, re)`. |
| 86 | +- **Neu:** `tests/test_fluid.py` — f·Re-Verifikation, Re-Bereichs-Check, |
| 87 | + Serpentine > Parallel bei gleicher Geometrie. |
| 88 | +- **Modifiziert:** `src/streamlit_app.py` — Sidebar: λ, η_pump; Polarization-Tab: |
| 89 | + P_pump als neue KPI, η_net neben η_energy. |
| 90 | + |
| 91 | +### Offene Fragen |
| 92 | +- **Q1:** Soll η_pump Preset-abhängig sein (centrifugal vs membrane pump) oder |
| 93 | + User-Slider? **Empfehlung:** Slider (default 0.6), Preset erst v1.0. |
| 94 | +- **Q2:** Wo zeigen wir ΔP? Im Polarization-Tab als 3. Chart, oder neu im |
| 95 | + Assembly-Tab? **Empfehlung:** Polarization-Tab (gehört zur System-Bilanz), |
| 96 | + Assembly-Tab bleibt Geometrie-Only. |
| 97 | + |
| 98 | +--- |
| 99 | + |
| 100 | +## 2. Rechteckige Stacks |
| 101 | + |
| 102 | +### Problem |
| 103 | +`StackAssembly.active_area_m2` ist skalar → Visualisierung nimmt sqrt → quadratisch. |
| 104 | +Realistische Stacks haben z. B. 100×300 mm (3:1), wichtig für ΔP (langer serpentine |
| 105 | +statt vieler kurzer parallel Pfade). |
| 106 | + |
| 107 | +### Lösungsansatz |
| 108 | +`StackAssembly` erhält ein optionales Feld `active_aspect_ratio: float = 1.0`, |
| 109 | +interpretiert als width/height. Default 1.0 → bestehende Tests + JSON-Kompatibilität |
| 110 | +bleiben. |
| 111 | + |
| 112 | +```python |
| 113 | +@dataclass(frozen=True) |
| 114 | +class StackAssembly: |
| 115 | + ... |
| 116 | + active_aspect_ratio: float = field(default=1.0) # width / height |
| 117 | +``` |
| 118 | + |
| 119 | +**Abgeleitete Größen:** |
| 120 | +```python |
| 121 | +active_width_m = sqrt(active_area_m2 * active_aspect_ratio) |
| 122 | +active_height_m = sqrt(active_area_m2 / active_aspect_ratio) |
| 123 | +bpp_width_m = active_width_m + 2 * frame_width |
| 124 | +bpp_height_m = active_height_m + 2 * frame_width |
| 125 | +``` |
| 126 | + |
| 127 | +**ΔP nutzt die Flow-Richtung:** Serpentine läuft entlang der längeren Kante |
| 128 | +(default) → L = height · (width / pitch). |
| 129 | + |
| 130 | +### Dateien |
| 131 | +- **Modifiziert:** `src/assembly.py` — neues Feld, `bpp_outer_dimensions_m` |
| 132 | + gibt (width, height) korrekt zurück. |
| 133 | +- **Modifiziert:** `src/visualization.py` — `draw_bpp_top_view` zeichnet |
| 134 | + Rechteck, Flow-Pattern folgt langer Kante. |
| 135 | +- **Modifiziert:** `src/streamlit_app.py` — Sidebar-Slider „Aspect ratio" |
| 136 | + (0.2–5.0, default 1.0). |
| 137 | +- **Neu:** `tests/test_assembly.py::test_rectangular_stack_area_preserved` — |
| 138 | + AR≠1 erhält active_area_m2 exakt. |
| 139 | + |
| 140 | +### Offene Fragen |
| 141 | +- **Q3:** Aspect ratio als Sidebar-Global oder Assembly-Tab-Lokal? **Empfehlung:** |
| 142 | + Sidebar (wie BPP), weil ΔP im Polarization-Tab davon abhängt. |
| 143 | +- **Q4:** Visualisierung: Querschnitt sollte NICHT mit AR skalieren (zeigt Dicke, |
| 144 | + nicht Grundfläche) — bestätigen. |
| 145 | + |
| 146 | +--- |
| 147 | + |
| 148 | +## 3. Zweite Validation |
| 149 | + |
| 150 | +### Kandidaten-Papers |
| 151 | + |
| 152 | +| Paper | Bedingungen | Vorteil | Nachteil | |
| 153 | +|---|---|---|---| |
| 154 | +| Lettenmeier et al. 2016 EES 9(8) | 80 °C, 1 bar, Nafion 212, IrO2/Pt-C | BPP-Preset stammt schon daher → konsistent | Kurve muss digitalisiert werden (WebPlotDigitizer); 2–3 h Arbeit | |
| 155 | +| Carmo et al. 2013 IJHE 38(12) | Review-Kurven | Breites Daten-Ensemble | Nicht eine kanonische Kurve, sondern viele Punkte | |
| 156 | +| Siracusano et al. 2011 IJHE 36(17) | 80 °C, Nafion 115, IrO2 | Klassiker, oft zitiert | Membran Nafion 115 (175 µm), Preset haben wir aber | |
| 157 | +| Bernt et al. 2020 JES 167 | 80 °C, N212, Pt/C ultra-low | Open-access, Tabellendaten im SI | Cathode catalyst loading sehr niedrig → v0.3.1-Preset passt | |
| 158 | + |
| 159 | +**Empfehlung:** **Bernt et al. 2020 JES 167** — open-access, Supplementary Info |
| 160 | +hat Tabellendaten (keine Plot-Digitalisierung), Katalysator-Preset in v0.3.1 |
| 161 | +bereits vorhanden. |
| 162 | + |
| 163 | +### Testspezifikation |
| 164 | + |
| 165 | +```python |
| 166 | +# tests/test_validation.py::test_rmse_vs_bernt2020 |
| 167 | +def test_rmse_vs_bernt2020(): |
| 168 | + data = load_csv("docs/validation/bernt_2020_fig3a.csv") |
| 169 | + # columns: j_a_per_cm2, u_cell_v |
| 170 | + predicted = [cell.cell_voltage(U.a_per_cm2_to_a_per_m2(j)) |
| 171 | + for j in data["j_a_per_cm2"]] |
| 172 | + rmse = np.sqrt(np.mean((predicted - data["u_cell_v"])**2)) |
| 173 | + assert rmse < 0.040, f"RMSE {rmse*1000:.1f} mV > 40 mV budget" |
| 174 | +``` |
| 175 | + |
| 176 | +**Pass-Kriterium:** RMSE < 40 mV über 0.05–3.0 A/cm². Ref-Modelle aus Literatur |
| 177 | +(z. B. Marangio 2009) erreichen typ. 20–60 mV → 40 mV ist realistisches Ziel. |
| 178 | + |
| 179 | +### Dateien |
| 180 | +- **Neu:** `docs/validation/bernt_2020_fig3a.csv` — rohe (j, U)-Daten aus Paper-SI. |
| 181 | +- **Neu:** `docs/validation/bernt_2020_setup.md` — Operating-Conditions + welche |
| 182 | + Preset-Kombination im Test genutzt wird (Nafion 212, Pt/C ultra-low, IrO2, …). |
| 183 | +- **Neu:** `tests/test_validation.py::test_rmse_vs_bernt2020`. |
| 184 | + |
| 185 | +### Offene Fragen |
| 186 | +- **Q5:** Paper-Zugang — liegt `Bernt 2020 JES 167.pdf` in |
| 187 | + `~/Downloads/PEMFC Claude AVL others/`? Wenn nein, welches Paper steht sicher |
| 188 | + zur Verfügung? |
| 189 | +- **Q6:** Falls RMSE > 40 mV: Parameter-Fit (α, j₀) oder Budget auf 60 mV |
| 190 | + anheben? **Empfehlung:** Erst Budget anheben + als Known-Issue dokumentieren, |
| 191 | + kein Fit (würde Physik-first-Anspruch unterlaufen). |
| 192 | + |
| 193 | +--- |
| 194 | + |
| 195 | +## Risiken & Trade-offs |
| 196 | + |
| 197 | +| Risiko | Wahrscheinlichkeit | Mitigation | |
| 198 | +|---|---|---| |
| 199 | +| ΔP-Modell bei interdigitated zu grob | Hoch | Als Caption „Approximation"; v0.6 für echtes Modell | |
| 200 | +| AR bricht Visualization-Layout (zu breit/hoch) | Mittel | Canvas-Sizing mit scaleanchor, max-Breite clampen | |
| 201 | +| Bernt-2020 nicht zugänglich → Fallback nötig | Mittel | Lettenmeier 2016 als Plan B, WebPlotDigitizer-Aufwand einplanen | |
| 202 | +| RMSE > 60 mV → Modell-Überarbeitung | Niedrig | ADR-007 dokumentiert, v0.5 verzögert sich um 1 Tag | |
| 203 | + |
| 204 | +--- |
| 205 | + |
| 206 | +## Ablauf (2–3 Werktage) |
| 207 | + |
| 208 | +| Tag | Arbeit | |
| 209 | +|---|---| |
| 210 | +| 1 | `src/fluid.py` + Tests; Streamlit-Integration mit λ- und η_pump-Slider; ΔP-KPI | |
| 211 | +| 2 | Aspect-Ratio-Feld in StackAssembly; Visualization anpassen; Sidebar-Slider | |
| 212 | +| 3 | Validation-CSV + Test + Setup-Doc; ADR-007; CHANGELOG v0.5.0; Release | |
| 213 | + |
| 214 | +--- |
| 215 | + |
| 216 | +## Was ich vom User brauche (bevor gebaut wird) |
| 217 | + |
| 218 | +### Resolved (User 2026-04-20) |
| 219 | + |
| 220 | +- **Q1:** η_pump als Slider (default 0.6). ✓ |
| 221 | +- **Q2:** ΔP-KPI im Polarization-Tab. ✓ |
| 222 | +- **Q3:** Aspect-Ratio in Sidebar (globale Quelle). ✓ |
| 223 | +- **Q4:** Querschnitt bleibt AR-unabhängig (zeigt nur Layer-Dicken). ✓ |
| 224 | +- **Q6:** RMSE-Ziel **40 mV hart**. Bei Miss Dokumentation als Known-Issue |
| 225 | + mit weichem Budget 60 mV, **kein Parameter-Fit** (würde Physik-first-Anspruch |
| 226 | + unterlaufen). |
| 227 | + |
| 228 | +### Offen |
| 229 | + |
| 230 | +- **Q5: Paper** — User möchte neuere Arbeit statt Bernt 2020. |
| 231 | + Shortlist neuerer Kandidaten (Entscheidung vor Phase 3, blockiert Phase 1/2 nicht): |
| 232 | + |
| 233 | + | Paper | Conditions | Warum | Zugang | |
| 234 | + |---|---|---|---| |
| 235 | + | Goswami et al. 2023 JPS 578 | Gore-Select M820, 80 °C | Preset schon im Projekt (v0.3.1); Ziel-Paper ist dünne reinforced Membran | wahrscheinlich im PDF-Ordner | |
| 236 | + | Möller-Gulland et al. 2024 JES 171 | N212 / kommerziell | neueste JES-Arbeit mit Full-Curve + SI-Tabellen | muss geprüft werden | |
| 237 | + | Stiber et al. 2022 AEM 12(29) | Ti-PTL-Fokus, IrO2/Pt-C | Materials passen 1:1 zu unseren Ti-BPP + IrO2-Presets | open-access | |
| 238 | + |
| 239 | + **User-Pick nötig**, sobald Phase 1 abgeschlossen ist. |
| 240 | + |
| 241 | +--- |
| 242 | + |
| 243 | +## Nicht-Ziele (explizit) |
| 244 | + |
| 245 | +- Keine 2D/3D-Flow-Simulation (CFD ist PEMFC-Phase-2-Scope, CLAUDE.md). |
| 246 | +- Kein Turbulenz-Modell (Re < 500 durchweg, laminar reicht). |
| 247 | +- Kein Two-Phase-Flow (Gas-Blasen), obwohl physikalisch präsent — v1.0-Scope. |
| 248 | +- Keine Änderung bestehender Preset-Werte in `materials.py` / `components.py`. |
0 commit comments