Skip to content

Commit fd8b71e

Browse files
committed
feat(fluid): ΔP model + pump power + stoich water flow (v0.5 Phase 1)
Physik: - Darcy-Friction-Factor f(α, Re) via Shah & London (1978) Table 42, linear interpolation zwischen 12 Stützstellen α ∈ [0.01, 1.0] - Water density + viscosity als T-abhängige Fits (NIST / Seeton 2006) - Flow-Pattern bestimmt Pfadlänge: serpentine (L ≈ H · (W/pitch)), parallel (L ≈ H), interdigitated v0.5 wie parallel approximiert - Laminar only (Re < 2000); ValueError bei Turbulenz - Pumpenleistung P = ΔP · Q / η_pump - Stöchiometrische Wasserzufuhr ṁ = I/(2F) · M_H2O · λ / ρ(T) Tests: 23 neue (total 144 grün), inkl. f·Re-Stützstellen-Check, Serpentine > Parallel, realistic magnitude (~O(10²-10⁴) Pa), Faraday- Skalierung, Input-Validation. Docs: v0.5-plan.md updated — Q1-4 + Q6 confirmed, Q5 (paper choice) bleibt offen, blockiert aber nicht Phase 1/2.
1 parent 7fcf6f2 commit fd8b71e

3 files changed

Lines changed: 741 additions & 0 deletions

File tree

docs/planning/v0.5-plan.md

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
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

Comments
 (0)