Commit 7d925a1b authored by Turner, Sean's avatar Turner, Sean
Browse files

clean

parent efc7c457
Loading
Loading
Loading
Loading

.claude/settings.local.json

deleted100644 → 0
+0 −82
Original line number Diff line number Diff line
{
  "permissions": {
    "allow": [
      "Bash(dir:*)",
      "Bash(git -C \"C:\\\\Users\\\\snk\\\\powersheds\" log --oneline -20)",
      "Bash(git -C \"C:\\\\Users\\\\snk\\\\powersheds\" config --list)",
      "Bash(git -C \"C:\\\\Users\\\\snk\\\\powersheds\" status)",
      "Bash(git -C \"C:\\\\Users\\\\snk\\\\powersheds\" diff)",
      "Bash(git -C \"C:\\\\Users\\\\snk\\\\powersheds\" log --oneline -5)",
      "Bash(git -C \"C:\\\\Users\\\\snk\\\\powersheds\" add CLAUDE.md)",
      "Bash(git -C \"C:\\\\Users\\\\snk\\\\powersheds\" commit -m \"$\\(cat <<''EOF''\nadd CLAUDE.md for Claude Code guidance\n\nProvides build commands, architecture overview, and key concepts\nfor future Claude Code instances working in this repository.\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
      "Bash(.venv/Scripts/activate)",
      "Bash(maturin develop:*)",
      "Bash(.venv/Scripts/python:*)",
      "Bash(.venv/Scripts/pip install:*)",
      "Bash(.venv/Scripts/maturin develop:*)",
      "Bash(uv run maturin:*)",
      "Bash(uv run python:*)",
      "Bash(uv run pytest:*)",
      "Bash(uv add:*)",
      "Bash(uv sync)",
      "Bash(uv pip install:*)",
      "Bash(git stash:*)",
      "Bash(git checkout:*)",
      "Bash(git add:*)",
      "Bash(git commit:*)",
      "Bash(cargo clean:*)",
      "Bash(ls:*)",
      "Bash(cargo build:*)",
      "Bash(git -C C:/Users/snk/powersheds status)",
      "Bash(git -C C:/Users/snk/powersheds log --oneline -5)",
      "Bash(git -C C:/Users/snk/powersheds diff src/helpers.rs)",
      "Bash(git -C C:/Users/snk/powersheds diff src/lib.rs)",
      "Bash(git -C C:/Users/snk/powersheds add src/helpers.rs src/lib.rs tests/test_hpf.py)",
      "Bash(git -C C:/Users/snk/powersheds commit -m \"$\\(cat <<''EOF''\nHandle infeasible power targets with symmetric clamping\n\nWhen target power falls outside the feasible range at a given head,\nthe HPF interpolation now clamps to the nearest feasible boundary\n\\(min or max\\) rather than returning NaN. This maintains roundtrip\nconsistency between P→Q and Q→P functions.\n\nChanges:\n- src/helpers.rs: Implement symmetric clamping for infeasible power,\n  use linear scan for P index lookup \\(binary search unreliable with\n  approx_eq\\), remove duplicate actual_power calculation in lib.rs\n- tests/test_hpf.py: Update tolerances for flat regions \\(~2 MW corner,\n  ~1.5 MW edge\\), filter clamping cases from roundtrip tests, increase\n  performance thresholds for linear scan algorithm\n\nFixes NaN values in target_release for Cumberland example simulation.\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
      "Bash(git -C C:/Users/snk/powersheds push)",
      "Bash(python:*)",
      "Bash(python3:*)",
      "Bash(find:*)",
      "Bash(.venvScriptsactivate.ps1)",
      "Bash(.\\\\.venv\\\\Scripts\\\\python.exe:*)",
      "Bash(.venv/Scripts/python.exe:*)",
      "Bash(git rm:*)",
      "Bash(git push:*)",
      "Bash(.venv\\\\Scripts\\\\python.exe:*)",
      "Bash(C:Userssnkpowersheds.venvScriptspython.exe examples/plot_network.py examples/Cumberland/cascade_config.yaml)",
      "Bash(cmd /c \"C:\\\\Users\\\\snk\\\\powersheds\\\\.venv\\\\Scripts\\\\activate.bat && maturin develop\")",
      "Bash(cmd /c \"C:\\\\Users\\\\snk\\\\powersheds\\\\.venv\\\\Scripts\\\\activate.bat && maturin develop 2>&1\")",
      "Bash(C:Userssnkpowersheds.venvScriptsmaturin.exe develop)",
      "Bash(powershell -Command:*)",
      "WebSearch",
      "Bash(..venvScriptsactivate.ps1)",
      "Bash(source:*)",
      "Bash(jupyter nbconvert:*)",
      "Bash(head:*)",
      "Bash(. .venv/Scripts/activate)",
      "Bash(done)",
      "Bash(grep:*)",
      "Bash(\"../../../.venv/Scripts/python.exe\" -c:*)",
      "Bash(.venv/Scripts/maturin.exe:*)",
      "Bash(tasklist:*)",
      "Bash(taskkill:*)",
      "Bash(cmd //c \"taskkill /F /PID 48996\")",
      "Bash(cmd //c:*)",
      "Bash(wc:*)",
      "Bash(for f in /c/Users/snk/powersheds/examples/private/Kennebec/storage_HWelevation_tables/*.csv)",
      "Bash(do)",
      "Bash(basename=\"$f##*/\")",
      "Bash(basename=\"$basename%.csv\")",
      "Bash(if [ ! -f \"/c/Users/snk/powersheds/examples/private/Kennebec/outflow_TWelevation_tables/$basename.csv\" ])",
      "Bash(then)",
      "Bash(echo:*)",
      "Bash(fi:*)",
      "Bash(md5sum:*)",
      "Bash(xargs:*)",
      "Bash(\"C:/Users/snk/powersheds/.venv/Scripts/python\" -m jupyter nbconvert --to notebook --execute experiments.ipynb --output executed_experiments.ipynb --ExecutePreprocessor.timeout=600)",
      "Bash(\"C:/Users/snk/powersheds/.venv/Scripts/python\" \"C:/Users/snk/powersheds/examples/private/Kennebec/_test_exp.py\")",
      "Bash(\"C:/Users/snk/powersheds/.venv/Scripts/python\" -c \"import powersheds; print\\(powersheds.__file__\\)\")",
      "Bash(Rscript:*)"
    ]
  }
}
+5 −1
Original line number Diff line number Diff line
@@ -69,6 +69,10 @@ docs/_build/
# VSCode
.vscode/

# Local assistant tooling
.claude/
CLAUDE.md

# Pyenv
.python-version

CLAUDE.md

deleted100644 → 0
+0 −142
Original line number Diff line number Diff line
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

Powersheds is a hydropower cascade simulator combining Rust performance with a Python interface. It simulates river-reservoir cascades at hourly resolution, computing storage, pool elevation, head, releases, spills, routing lags, and power generation driven by plant-level power schedules. The simulation is driven by hourly inflows (total inflows for headwater reservoirs; incremental/side inflows for in-network reservoirs) and target hourly power generation time series for each hydropower dam. The purpose of the model is computation of infeasible power generation, as measured by violations in power generation after accounting for water-related constraints in the simulation.

**Status**: v0.1 beta; v1.0 scheduled for September 2026.

## Confidentiality

The `examples/private/` directory contains proprietary data for private hydropower systems. This directory is excluded from version control via `.gitignore`. **Never** commit, push, or include any data, file paths, reservoir names, parameter values, or other details from `examples/private/` in the public repository — this includes CLAUDE.md, test fixtures, regression baselines, commit messages, PR descriptions, generated outputs (diagnostics HTML, plots, notebook cell outputs), and any other artifacts.

## Build Commands

```bash
# Create virtual environment (recommended)
uv venv .venv --python 3.12
uv sync --frozen
# Activate: .\.venv\Scripts\Activate.ps1 (Windows) or source .venv/bin/activate (Unix)

# Build and install in editable mode (after activating venv)
maturin develop

# Build release wheel
maturin build --release
pip install target/wheels/powersheds-*.whl
```

After any Rust code changes, run `maturin develop` to recompile.

## Architecture

**Rust-Python Bridge**: Core simulator written in Rust for speed, exposed to Python via PyO3.

**Entry Point**: `powersheds.simulate_cascade(cascade_data: dict) -> dict[str, Result]`

**Mixed Rust+Python Package**: Uses maturin's `python-source = "python"` layout. Rust compiles to `powersheds._lib`, re-exported by `python/powersheds/__init__.py`.

**Source Structure**:
- `src/lib.rs` - Main module: data structures (`ReservoirData`, `RiverData`, `ConfluenceData`), `simulate_cascade()` orchestration, per-timestep reservoir simulation
- `src/helpers.rs` - Interpolation functions: `interpolate_elevation()`, `interpolate_storage()`, `interpolate_tailwater()`, bilinear HPF interpolation for power/release computation
- `python/powersheds/__init__.py` - Package init, re-exports from `_lib`
- `python/powersheds/diagnostics.py` - Interactive diagnostic visualization (see Diagnostics section)

**Three Object Types**:
- **Reservoir**: Pool elevation from storage-elevation curves, head calculation (with optional dynamic tailwater), release/power via bilinear HPF interpolation, storage/capacity constraints
- **River**: Lag-based flow routing with legacy flows for initialization
- **Confluence**: Instantaneous flow merging (no lag)

**Simulation Order**: Objects execute in user-specified order (lower `simulation_order` runs first), enabling flexible cascade topologies.

**Key Algorithm Flow** (per timestep):
1. Sort objects by simulation order
2. For each reservoir: interpolate pool elevation → compute head (with dynamic tailwater iteration if TW table provided) → bilinear HPF lookup for target release → constrain release → update storage → compute actual power
3. Rivers delay flows by `lag` hours; confluences pass through immediately
4. Upstream releases/spills become downstream inflows

**Dynamic Tailwater**: When `set_tw_outflow`/`set_tw_elevation` are provided, tailwater elevation is computed from total outflow via a rating curve, using fixed-point iteration (tolerance=0.001m, max_iter=10, damping=0.5). When empty, the static `tailwater_elevation` scalar is used (legacy behavior).

## Units

- Storage: Mm³ (Million Cubic Meters)
- Releases: Mm³/hr
- Head/Elevations: meters
- Power: MW
- HPF flows (`hpf_q`): m³/s (cumecs)
- TW outflow (`set_tw_outflow`): m³/s (cumecs)

## Diagnostics

The `powersheds.diagnostics` module provides interactive visualization for simulation results. Requires the `viz` optional dependency: `uv pip install -e ".[viz]"`

**Public API** (`from powersheds.diagnostics import ...`):
- `cascade_diagnostics(results, cascade_data=None, ...)` → Plotly `go.Figure` with dropdown reservoir selector, three linked time series panels (flows with release constraint band, elevation with pool/tailwater/head, power with infeasible shading). Pass `cascade_data` to enable min/max release bands on the flows panel.
- `network_diagram(cascade_data, ...)` → matplotlib `Figure` with publication-quality static network diagram (trapezoid reservoir nodes, Bezier edges, lag labels)
- `save_diagnostics_html(results, output_path, ...)` → writes standalone HTML file

**Internal helpers** (prefixed with `_`):
- `_build_topology(cascade_data)` - Extract nodes/edges from CascadeData
- `_compute_network_layout(nodes, edges)` - Topological-sort layered layout
- `_detect_reservoirs(results)` - Filter result keys to reservoir names
- `_build_flow_traces`, `_build_elevation_traces`, `_build_power_traces` - Per-panel Plotly trace builders

## Example

The `examples/Cumberland/` directory contains a full working example with 8 reservoirs.

**Python notebook** (recommended):
```bash
# Run examples/Cumberland/notebook.ipynb in Jupyter
# Produces network diagram + interactive Plotly diagnostics
```

**R/Quarto notebook**:
```r
reticulate::use_virtualenv(".venv", required = TRUE)
# quarto render examples/Cumberland/script.qmd
```

## Testing

After any major code changes to `src/helpers.rs` or `src/lib.rs`, run the test suite:

```bash
# Rebuild the module first
uv pip install -e .

# Run all tests (158 tests)
uv run python -m pytest tests/ -v

# Run a specific test file
uv run python -m pytest tests/test_cascade.py -v
```

**Test Coverage** (158 tests across 9 files):
- `tests/test_hpf.py` - HPF bilinear interpolation (74 tests): grid corner/edge/interior roundtrips, statistical robustness, performance benchmarks, edge cases
- `tests/test_diagnostics.py` - Diagnostics visualization (29 tests): topology extraction, network layout, axis ranges, network_diagram matplotlib output, cascade_diagnostics Plotly output (incl. release constraint bands), HTML export, Cumberland integration, edge cases
- `tests/test_cascade.py` - Full cascade integration (13 tests): output structure, multi-reservoir routing, simulation ordering, mass balance, physical invariants, Cumberland NaN check
- `tests/test_simulate_timestep.py` - Per-timestep reservoir constraints (10 tests): below power pool, min/max release, insufficient water, spill, mass balance, unconstrained power equality
- `tests/test_interpolation.py` - Storage-elevation interpolation (10 tests): breakpoints, midpoints, clamping, roundtrip, monotonicity, Cumberland validation
- `tests/test_dynamic_tailwater.py` - Dynamic tailwater (9 tests): flat TW matches static, higher TW reduces head, TW output in range, convergence on steep curve, mass balance, Cumberland integration (bounds, Barkley flat, no NaN, head positive)
- `tests/test_river_confluence.py` - Flow routing (6 tests): river lag, legacy flows, lag=0 passthrough, legacy length mismatch, confluence summation
- `tests/test_regression.py` - Regression tests (2 tests): Cumberland golden baseline, CLAUDE.md smoke test
- `tests/test_unit_conversion.py` - Unit conversion (2 tests): cumecs to Mm3/hr at known grid points

**Test Infrastructure**:
- `tests/helpers.py` - Shared dataclasses (`ReservoirData`, `RiverData`, `ConfluenceData`, `CascadeData`), factory functions (`make_reservoir`, `make_cascade`, `run_single_reservoir`), analytical helpers
- `tests/conftest.py` - Session-scoped Cumberland cascade fixtures
- `tests/fixtures/cumberland_baseline.json` - Golden baseline for regression testing
- `tests/TEST_DOCUMENTATION.md` - Detailed documentation of every test

**Known Limitations**:
- Some HPF tables have flat regions where multiple P values produce the same Q. In these regions, the reverse function (Q->P) cannot uniquely determine P, resulting in roundtrip errors up to ~1 MW. This is a physical limitation of the data, not a bug.

## Dependencies

- **Build**: maturin 1.9.4 (Rust-to-Python bridge)
- **Rust**: PyO3 0.23.3
- **Python**: pandas, numpy, scipy, matplotlib, PyYAML, pyarrow (pinned in `uv.lock`)
- **Optional** (`viz`): plotly >=5.18 (for diagnostics module)