|
| 1 | +# CLAUDE.md — Venting Codebase Guide for AI Assistants |
| 2 | + |
| 3 | +This file provides context for AI assistants (Claude, Copilot, etc.) working in the |
| 4 | +`venting` repository. Read it before modifying any code. |
| 5 | + |
| 6 | +--- |
| 7 | + |
| 8 | +## Project Overview |
| 9 | + |
| 10 | +**Venting v10.0.0** is a 0D/network depressurization solver for aerospace and |
| 11 | +industrial pressure-relief analysis. It models rigid gas volumes (nodes) connected |
| 12 | +by orifices or channels (edges) and integrates the resulting stiff ODE system using |
| 13 | +SciPy's Radau solver. |
| 14 | + |
| 15 | +- **Not CFD.** No spatial fields, no shocks, no acoustics — purely lumped-parameter. |
| 16 | +- **Primary output:** maximum `|ΔP|` across each edge and whether flow is choked. |
| 17 | +- **Python ≥ 3.10**, MIT license. |
| 18 | + |
| 19 | +--- |
| 20 | + |
| 21 | +## Repository Layout |
| 22 | + |
| 23 | +``` |
| 24 | +venting/ |
| 25 | +├── src/venting/ # Main package (src-layout, installed as `venting`) |
| 26 | +│ ├── __init__.py # Version: 10.0.0 |
| 27 | +│ ├── __main__.py # `python -m venting` entry point |
| 28 | +│ ├── cli.py # Argparse CLI (subcommands: gate, sweep, sweep2d, …) |
| 29 | +│ ├── constants.py # Physical constants (γ, R, π_c, safety thresholds) |
| 30 | +│ ├── cases.py # Frozen dataclasses: CaseConfig, NetworkConfig, SolveResult |
| 31 | +│ ├── geometry.py # Unit converters (mm↔m, circle area) |
| 32 | +│ ├── profiles.py # External pressure profiles (linear/step/barometric/table) |
| 33 | +│ ├── graph.py # Network topology builder: GasNode, Orifice, ShortTube, SlotChannel |
| 34 | +│ ├── flow.py # Mass-flow functions: orifice, short_tube, slot, Fanno, Sutherland |
| 35 | +│ ├── solver.py # ODE RHS, solve_ivp (Radau), sparsity, events, streaming |
| 36 | +│ ├── diagnostics.py # Peak detection, τ_exit, pressure regime classification |
| 37 | +│ ├── validity.py # Physical validity flags (acoustic timescale, Re, Mach, thermo) |
| 38 | +│ ├── gates.py # 5 analytical gate tests (single-node, two-node, conservation) |
| 39 | +│ ├── io.py # Artifact export: run.json, summary.csv, .npz, meta.json |
| 40 | +│ ├── run.py # High-level case execution pipeline |
| 41 | +│ ├── compare.py # Run-comparison and loading utilities |
| 42 | +│ ├── plotting.py # Matplotlib ΔP-vs-time visualization (Agg backend) |
| 43 | +│ ├── montecarlo.py # Latin-hypercube parametric sampling |
| 44 | +│ ├── presets.py # Default panel geometry (volumes, wall areas) |
| 45 | +│ ├── thermo.py # NASA-7 polynomial fits: cp(T), cv(T), γ(T), h(T) |
| 46 | +│ ├── state_layout.py # ODE state-vector slicing (m, T, T_wall indices) |
| 47 | +│ └── gui/ # Optional PySide6/pyqtgraph desktop interface |
| 48 | +│ ├── main.py |
| 49 | +│ ├── app.py |
| 50 | +│ ├── config.py |
| 51 | +│ └── state_layout.py |
| 52 | +├── tests/ # pytest test suite (~900 lines, 15 files) |
| 53 | +├── docs/ # Markdown physics docs and verification criteria |
| 54 | +├── archive/ # Legacy monolithic script (reference only, not imported) |
| 55 | +├── .github/workflows/ # CI: Python 3.10–3.12 matrix, ruff, black, pytest |
| 56 | +├── pyproject.toml # Project metadata, build config, tool settings |
| 57 | +├── requirements.txt # Runtime deps (numpy, scipy, matplotlib) |
| 58 | +├── requirements-dev.txt # Dev deps (-e .[dev]) |
| 59 | +├── .pre-commit-config.yaml |
| 60 | +├── README.md # Bilingual (Russian + English) physics and CLI reference |
| 61 | +└── CONTRIBUTING.md |
| 62 | +``` |
| 63 | + |
| 64 | +--- |
| 65 | + |
| 66 | +## Development Setup |
| 67 | + |
| 68 | +```bash |
| 69 | +# Install in editable mode with dev dependencies |
| 70 | +pip install -e ".[dev]" |
| 71 | + |
| 72 | +# Install pre-commit hooks (ruff + black auto-fix on every commit) |
| 73 | +pre-commit install |
| 74 | +``` |
| 75 | + |
| 76 | +### Optional GUI |
| 77 | + |
| 78 | +```bash |
| 79 | +pip install -e ".[gui]" # adds PySide6, pyqtgraph |
| 80 | +python -m venting gui |
| 81 | +``` |
| 82 | + |
| 83 | +--- |
| 84 | + |
| 85 | +## Running Tests |
| 86 | + |
| 87 | +```bash |
| 88 | +# Fast (CI-equivalent) |
| 89 | +pytest -q |
| 90 | + |
| 91 | +# With coverage |
| 92 | +pytest -q --cov=venting --cov-report=term-missing |
| 93 | + |
| 94 | +# Single test file |
| 95 | +pytest tests/test_gates.py -v |
| 96 | +``` |
| 97 | + |
| 98 | +All tests are deterministic. Gate tests validate against hardcoded analytic solutions |
| 99 | +with tight tolerances (`< 0.5%` for physics, `< 0.1%` for mass conservation). |
| 100 | + |
| 101 | +--- |
| 102 | + |
| 103 | +## Linting and Formatting |
| 104 | + |
| 105 | +```bash |
| 106 | +ruff check . # Lint (F, E, I, B, UP rules; E501 ignored) |
| 107 | +ruff check . --fix # Auto-fix safe issues |
| 108 | +black . # Format (line-length 88) |
| 109 | +black --check . # Format check only |
| 110 | +``` |
| 111 | + |
| 112 | +Pre-commit hooks run ruff and black automatically on `git commit`. |
| 113 | + |
| 114 | +--- |
| 115 | + |
| 116 | +## CLI Commands |
| 117 | + |
| 118 | +```bash |
| 119 | +python -m venting gate # Run all 5 analytical gate tests |
| 120 | +python -m venting gate --single # Single-node validation only |
| 121 | +python -m venting gate --two # Two-node validation only |
| 122 | + |
| 123 | +python -m venting sweep # 1D parameter sweep (d_int, d_exit, Cd) |
| 124 | +python -m venting sweep2d # 2D grid sweep |
| 125 | +python -m venting thermal # Multi-h thermal sensitivity |
| 126 | +python -m venting montecarlo # Latin-hypercube parametric sampling |
| 127 | + |
| 128 | +python -m venting compare dir1 dir2 # Compare two run result directories |
| 129 | + |
| 130 | +python -m venting gui # Launch PySide6 desktop interface |
| 131 | +``` |
| 132 | + |
| 133 | +--- |
| 134 | + |
| 135 | +## Architecture and Data Flow |
| 136 | + |
| 137 | +``` |
| 138 | +NetworkConfig / CaseConfig |
| 139 | + │ |
| 140 | + ▼ |
| 141 | + graph.py ──► builds list of GasNode + edge objects |
| 142 | + │ |
| 143 | + ▼ |
| 144 | + solver.py ──► constructs ODE RHS using flow.py functions |
| 145 | + │ applies sparsity pattern for Radau Jacobian |
| 146 | + │ integrates with scipy.integrate.solve_ivp |
| 147 | + ▼ |
| 148 | + SolveResult (time array + state array) |
| 149 | + │ |
| 150 | + ├──► diagnostics.py ──► peak ΔP, τ_exit, regime (CHOKED/subsonic) |
| 151 | + ├──► validity.py ──► physical flag checks |
| 152 | + └──► io.py ──► run.json, summary.csv, .npz, meta.json |
| 153 | +``` |
| 154 | + |
| 155 | +### ODE State Vector Layout (`state_layout.py`) |
| 156 | + |
| 157 | +For a network with `N` nodes: |
| 158 | + |
| 159 | +| Slice | Variables | |
| 160 | +|-------|-----------| |
| 161 | +| `[0:N]` | mass `m[i]` (kg) for each node | |
| 162 | +| `[N:2N]` | temperature `T[i]` (K) — only if `mode != "isothermal"` | |
| 163 | +| `[2N:3N]` | wall temperature `T_wall[i]` (K) — only if `lumped_wall=True` | |
| 164 | + |
| 165 | +--- |
| 166 | + |
| 167 | +## Key Dataclasses (`cases.py`) |
| 168 | + |
| 169 | +All config objects are **frozen dataclasses** (immutable after construction). |
| 170 | + |
| 171 | +- **`CaseConfig`** — thermodynamic mode (`"isothermal"` / `"intermediate"` / `"variable"`), |
| 172 | + wall model settings, external model selection. |
| 173 | +- **`NetworkConfig`** — node volumes/temperatures, edge geometry (diameters, Cd, lengths), |
| 174 | + network topology (n_cells, n_chains, vestibule flag). |
| 175 | +- **`SolveResult`** — `t`, `y` arrays from solve_ivp, plus metadata. |
| 176 | + |
| 177 | +--- |
| 178 | + |
| 179 | +## Flow Models (`flow.py`) |
| 180 | + |
| 181 | +| Model | Function | Notes | |
| 182 | +|-------|----------|-------| |
| 183 | +| Sharp-edged orifice | `mdot_orifice(...)` | Cd-based, choked/subsonic | |
| 184 | +| Short-tube | `mdot_short_tube(...)` | Darcy friction + K_in/K_out minor losses; uses effective Cd, not Fanno | |
| 185 | +| Slot channel | `mdot_slot(...)` | Viscous laminar (Poiseuille) | |
| 186 | +| Fanno flow | `mdot_fanno(...)` | Friction-limited choked flow (available but not default in v10) | |
| 187 | + |
| 188 | +**Sentinel values** (defined in `constants.py`): |
| 189 | +- `EXT_NODE = -1` — marks external-atmosphere boundary nodes |
| 190 | +- `M_SAFE`, `T_SAFE` — minimum safe mass/temperature to avoid division by zero |
| 191 | +- `P_STOP` — solver early-stop pressure threshold |
| 192 | + |
| 193 | +--- |
| 194 | + |
| 195 | +## Thermodynamic Modes |
| 196 | + |
| 197 | +| Mode | T evolution | Notes | |
| 198 | +|------|------------|-------| |
| 199 | +| `"isothermal"` | `T = T₀` (fixed) | Fast; valid when walls are highly conductive | |
| 200 | +| `"intermediate"` | Solves `dT/dt` with wall heat transfer | Default recommendation | |
| 201 | +| `"variable"` | Same as intermediate + NASA-7 `cp(T)`, `cv(T)`, `γ(T)` | Most accurate | |
| 202 | + |
| 203 | +Use `thermo.py` functions when `mode == "variable"`. The NASA-7 fits are valid for |
| 204 | +air in the range ~200–2000 K. |
| 205 | + |
| 206 | +--- |
| 207 | + |
| 208 | +## External Pressure Profiles (`profiles.py`) |
| 209 | + |
| 210 | +Profiles define `P_ext(t)` for the environment node: |
| 211 | + |
| 212 | +| Profile | Description | |
| 213 | +|---------|-------------| |
| 214 | +| `"linear"` | Linearly drops from `P0` to `P_final` over `t_final` | |
| 215 | +| `"step"` | Instantaneous step at `t_step` | |
| 216 | +| `"barometric"` | Exponential decay (e.g., rocket ascent) | |
| 217 | +| `"table"` | Interpolated from user-supplied `(t, P)` data | |
| 218 | + |
| 219 | +--- |
| 220 | + |
| 221 | +## Output Artifacts (`io.py`) |
| 222 | + |
| 223 | +| File | Contents | |
| 224 | +|------|----------| |
| 225 | +| `run.json` | Reproducibility metadata: git commit, Python version, all input parameters | |
| 226 | +| `summary.csv` | Per-edge metrics: max ΔP, peak time, flow regime, peak type | |
| 227 | +| `*.npz` | Compressed time series: `t, m, T, P, P_ext, τ_exit` | |
| 228 | +| `*_meta.json` | Peak diagnostics and validity flags | |
| 229 | +| `*_validity.json` | Physical validity summary (Re, Mach, acoustic checks) | |
| 230 | + |
| 231 | +--- |
| 232 | + |
| 233 | +## Physics Conventions |
| 234 | + |
| 235 | +- **Ideal gas EOS:** `P = m R T / V` |
| 236 | +- **Mass balance:** `dm/dt = Σ ṁ_in − Σ ṁ_out` |
| 237 | +- **Energy balance (non-isothermal):** `m cv dT/dt = Σ ṁ_in (cp T_in − cv T) − Σ ṁ_out (R T) + h A ΔT` |
| 238 | +- **Choking condition:** `P_up / P_down ≥ π_c = ((γ+1)/2)^(γ/(γ−1))` |
| 239 | +- **Default constants:** `γ = 1.4`, `R = 287.05 J/(kg·K)` |
| 240 | +- **No state clipping:** The solver avoids aggressive clipping; safety guards apply |
| 241 | + only to denominators (`M_SAFE`, `T_SAFE`). |
| 242 | + |
| 243 | +--- |
| 244 | + |
| 245 | +## Validity Checks (`validity.py`) |
| 246 | + |
| 247 | +After each solve, `validity.py` produces flags: |
| 248 | + |
| 249 | +- **Acoustic timescale:** Is the ODE timestep >> acoustic propagation time? (0D |
| 250 | + assumption must hold.) |
| 251 | +- **Thermodynamic range:** Are temperatures within NASA-7 polynomial validity? |
| 252 | +- **Short-tube Re/Mach:** Is viscous laminar assumption self-consistent? |
| 253 | +- **Friction factor:** Is Darcy friction in the expected regime? |
| 254 | + |
| 255 | +Always inspect validity flags before trusting results. |
| 256 | + |
| 257 | +--- |
| 258 | + |
| 259 | +## Testing Conventions |
| 260 | + |
| 261 | +- **Gate tests (`test_gates.py`):** Compare solver to hardcoded analytic solutions. |
| 262 | + Tolerances: `< 0.5%` for pressures, `< 0.1%` for mass conservation. |
| 263 | +- **Validity checks are masked:** Only high-pressure region (`P > 0.01 P0`) is |
| 264 | + validated to avoid noise near equilibrium. |
| 265 | +- **No flaky tests:** All tests use deterministic inputs with fixed random seeds |
| 266 | + where sampling is involved. |
| 267 | +- **GUI tests skip gracefully** if PySide6 is not installed. |
| 268 | + |
| 269 | +--- |
| 270 | + |
| 271 | +## Code Style Rules |
| 272 | + |
| 273 | +- **Formatter:** Black (line-length 88). Run before committing. |
| 274 | +- **Linter:** Ruff with rules `F, E, I, B, UP` (no `E501`). |
| 275 | +- **Type hints:** Used throughout; mypy is available but not enforced in CI. |
| 276 | +- **Dataclasses:** Prefer `@dataclass(frozen=True)` for config objects. |
| 277 | +- **Pure functions:** Flow model functions in `flow.py` are stateless. |
| 278 | +- **No magic numbers:** Physical constants live in `constants.py`. |
| 279 | +- **Archive is read-only:** `archive/venting_v84.py` is reference only; do not |
| 280 | + import from it in production code or tests. |
| 281 | + |
| 282 | +--- |
| 283 | + |
| 284 | +## CI/CD |
| 285 | + |
| 286 | +GitHub Actions (`.github/workflows/ci.yml`) runs on every push and pull request: |
| 287 | + |
| 288 | +1. Python matrix: **3.10, 3.11, 3.12** on `ubuntu-latest` |
| 289 | +2. `pip check` — dependency consistency |
| 290 | +3. `ruff check .` — linting |
| 291 | +4. `black --check .` — formatting |
| 292 | +5. `pytest -q --cov=venting` — tests with coverage |
| 293 | + |
| 294 | +All steps must pass before merging. |
| 295 | + |
| 296 | +--- |
| 297 | + |
| 298 | +## Known Limitations |
| 299 | + |
| 300 | +- **Not CFD:** No spatial velocity or temperature gradients, no acoustic wave |
| 301 | + propagation, no shock capturing. |
| 302 | +- **C_d is the primary uncertainty:** Always run a Cd sweep to understand |
| 303 | + sensitivity before drawing conclusions. |
| 304 | +- **Short-tube model:** Lossy-nozzle via Cd_eff (v10 default). Fanno |
| 305 | + friction-choking is implemented but not the default. |
| 306 | +- **Streaming vs. batch solve:** Results agree within integrator tolerances but |
| 307 | + may differ at the last decimal place due to chunking. |
| 308 | +- **GUI packaging:** Bundling as a standalone executable is a known TODO. |
| 309 | + |
| 310 | +--- |
| 311 | + |
| 312 | +## Quick Reference: Adding a New Feature |
| 313 | + |
| 314 | +1. **New flow model** → add a function to `flow.py`; wire into `graph.py` edge |
| 315 | + dispatch; update `state_layout.py` if state vector changes. |
| 316 | +2. **New external profile** → add a class/function to `profiles.py`; register it |
| 317 | + in the CLI (`cli.py`) and in `cases.py`. |
| 318 | +3. **New CLI subcommand** → add a `add_parser` block in `cli.py` and a handler |
| 319 | + function; keep handler thin, delegate to `run.py` or domain modules. |
| 320 | +4. **New test** → add to `tests/`; gate tests go in `test_gates.py` if they have |
| 321 | + an analytic solution, otherwise create a regression test file. |
| 322 | +5. **Always run:** `ruff check . --fix && black . && pytest -q` before committing. |
0 commit comments