Skip to content

About

Hydrosense is a physics-informed water-quality forecasting project for Alameda Creek, California. It combines live stream and weather data, satellite imagery, and a PINN-based research model to predict nitrate risk and track accuracy over time in a transparent public demo.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

27 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Hydrosense

An AI-powered water-quality intelligence platform, in two layers:

  1. Hydrosense National (hydrosense/) — nationwide, research-grade forecasting across USGS/WQP stations in any US state: normalized database, pooled generalized models (no station memorization), leave-one-station-out / cross-state validation, conformal uncertainty, continuous accuracy tracking, paper-grade figures. Dashboard: results/national.html. Docs: docs/NATIONAL_ARCHITECTURE.md, docs/NATIONAL_METHODOLOGY.md, docs/NATIONAL_ROADMAP.md, docs/IMPROVEMENT_AUDIT.md, docs/AUDIT_IEEE_REVIEW.md (independent IEEE-style review with trustworthiness scorecard).

  2. Alameda Creek regional demo — the original physics-informed nitrate-risk system for one watershed, with a PINN solving the 1-D advection-diffusion PDE, LiDAR channel geometry, and live operator views. Demo site: results/pinn-demo.html (15 sections, all interactive).

Repo: https://github.com/a6nkay-source/Hydrosense

# national pipeline (any states/parameters)
/usr/bin/python3 scripts/national_pipeline.py \
    --states CA TX FL NY WA \
    --parameters temperature conductivity turbidity nitrate

Reproduce our experiments

One-command reproduction via the Makefile — see REPRODUCIBILITY_GUIDE.md:

make test        # 58 offline tests (no network)
make national    # full ingest + train + evaluate
make hardening   # adaptive calibration + validation + leave-one-state-out
make registry    # regenerate EXPERIMENT_REGISTRY.md from the DB
make all         # everything

Publication infrastructure: EXPERIMENT_REGISTRY.md (auto-generated, provenance-complete) · PAPER_RESULTS.md (every claim → experiment + figure + statistical evidence, unsupported claims flagged) · PRODUCTION_HARDENING_REPORT.md.

Manual equivalents (or docker build -t hydrosense . && docker run --rm hydrosense for the test suite):

pip install -r requirements.txt              # or use the Dockerfile
python -m pytest tests/ -q                   # 58 offline tests, no network

# 1. ingest + train + evaluate (writes experiments/, leaderboard, dashboards)
python scripts/national_pipeline.py --states CA TX FL NY WA --max-stations 6

# 2. improvement benchmark (delta-mode, tuned PINN, ensembles, storm workflow)
python scripts/improve_and_benchmark.py

# 3. physics evidence (with/without-physics ablation, residuals, gradients)
python scripts/physics_diagnostics.py

# 4. statistics + calibration fixes (BH, bootstrap CIs, adaptive conformal)
python scripts/rigor_fixes.py

# 5. audits + reports + papers, then publish (tests gate the push)
python scripts/audit_report.py && python scripts/hros_cycle.py
python scripts/post_experiment.py --message "reproduction run" --no-push

Seeds are fixed (42); every run records its git commit and full config in the experiments table and results/experiments/. Key artifacts: results/leaderboard.csv · docs/PHYSICS.md · docs/AUDIT_IEEE_REVIEW.md · docs/IJCNN_REVIEW_2026-07.md · FINAL_IJCNN_READINESS_REPORT.md · results/physics_diagnostics/ · docs/paper/.

Scientific trustworthiness, not only skill

Hydrosense evaluates every feature against the category it serves — see docs/FEATURE_EVIDENCE_TABLE.md:

  • Skill features (models, engineered inputs) are judged on leakage-safe RMSE and significance vs persistence. Satellite, spatial, hydrologic, regime, and ENSO features were all proposed, tested, and rejected on evidence.
  • Trustworthiness features (uncertainty, OOD detection, confidence, explainability, monitoring) are judged on reliability and transparency and are kept even when they do not lower RMSE. Adaptive conformal calibration, an out-of-distribution detector, analog-event retrieval, the model-health board, and TreeSHAP explanations are all kept on this basis.

Distribution-shift robustness (docs/DISTRIBUTION_SHIFT.md): an OOD detector recognizes unfamiliar conditions (novelty tracks turbidity error at r=0.27) and widens uncertainty during storms rather than being confidently wrong. The wet-season weakness itself is unfixed — the model now flags it instead of hiding it.

IEEE research paper

Formal IEEE-style paper with all current data: PDF results/paper/hydrosense_ieee.pdf (8-page two-column, figures + tables + references) and compilable IEEEtran source results/paper/hydrosense_ieee.tex (pdflatex hydrosense_ieee.tex for camera-ready). Regenerate with python3 scripts/build_ieee_paper.py. Headline honest findings inside: temperature skill is significant but does not survive a wet-season holdout (persistence wins out-of-distribution); conductance/turbidity deploy persistence; physics helps turbidity only.

Research showcase

A polished, figure-rich overview renders at results/paper/index.html (open locally, or via the server at /paper/index.html) — hero, geographic coverage, method with code, results tables, physics validation, and honest limitations, all built from verified figures.

Architecture

Hydrosense architecture

Model & data pipeline: leakage-free feature encoder → pooled model zoo (no station IDs) → physics regularizer → delta-mode prediction → hierarchical conformal uncertainty → exact-TreeSHAP explanation + risk index → continuous verification. Governing equations and the physics-regularization derivation are in docs/PHYSICS.md.

┌─ 4 USGS gauges        ──┐
├─ Open-Meteo rainfall   ─┤
├─ NASA GIBS satellite   ─┼─→  feature pipeline  →  3 models  →  daily log  →  demo site
├─ USGS 3DEP LiDAR       ─┤                                          ↑
└─ NLCD / PRISM (static) ─┘                                  launchd cron, no human in the loop

What's measured · modeled · estimated

This section is the single most important page of the README. Every claim the demo makes should be traceable to one of these three buckets.

MEASURED (real sensors, real APIs)

Signal Source Cadence Where in demo
Discharge (4 gauges) USGS NWIS instantaneous 15 min Sections 0, 0.5 (live)
Rainfall (Sunol watershed) Open-Meteo Archive daily Sections 5, 12
Rainfall forecast (Sunol, 7 d) Open-Meteo Forecast hourly refresh Section 5
Turbidity (Coyote regional proxy) USGS NWIS instantaneous 15 min Section 10
Satellite RGB + NDVI NASA GIBS MODIS Terra daily / 8-day Sections 0, 9
Map base layer ESRI World Imagery (Sentinel-2 / Maxar) seasonal Section 0

MODELED (computed from measurements + physics)

Output Inputs Method
Peak nitrate prediction rainfall + discharge closed-form physics kernel peak = 2.0 + 0.45·rain + 0.02·max(0, Q-5)
Spatial concentration C(x,t) velocity, diffusion PINN solving ∂C/∂t + u·∂C/∂x - D·∂²C/∂x² = S (research artifact, not in live calendar)
Channel velocity LiDAR bed slope Manning equation u = R^(2/3)·√S/n (Section 7)
Confidence intervals residuals stratified by rain band split conformal, per-day (Section 5)
Validation regression predicted peak vs measured turbidity Pearson / Spearman r (Section 10)
Operator action ETAs velocity × distance LiDAR-derived 7.9 h headwater → intake (Section 8)

ESTIMATED / PLACEHOLDER (be careful with these)

Output Why it's not solid Where labeled
Kernel coefficients (0.45 mg/L/mm, 0.02 mg/L/cfs, 1.6 d decay) Literature estimates, not regressed against measured nitrate Sections 5, 8 footnotes
Spatial PDE → station concentration Currently exp(-x/80) placeholder, not the trained PINN Section 8 footnote
Operator action recommendations Role-keyed templates, not learned policies Section 8 footnote
NDWI proxy Derived from rendered NDVI tile, not Sentinel-2 band math Section 7 footnote
Cloud detection Brightness threshold, not the official MODIS cloud mask Section 7 footnote
Watershed land cover (data/processed/watershed_features.json) Static summary, doesn't refresh JSON honest_note
Operator role labels (release point / calibration gauge / TREATMENT INTAKE) Descriptive only; no operational partnership exists Section 8

NOT CLAIMED (judges, please note)

  • Satellite imagery does NOT directly measure nitrate. It's a proxy for turbidity / watershed wetness / cloud cover.
  • The "98.2 % safety status correct" is over a 60-day dry-season window where the trivial baseline guess (always safe) wins by default.
  • The PINN's R² ≈ 0 on the synthetic Gaussian benchmark — a known limitation of sparse-data PINN training, not a deception.

Repo layout

hydrosense/
├── README.md                         this file
├── PINN_ROADMAP.md                   historical: how the PINN was built (long)
├── docs/PRESS_RELEASE_ALPHA.md       press release prose
│
├── alameda_creek_pinn/               PYTHON SIDE — training, ingestion, modeling
│   ├── src/
│   │   ├── data/satellite.py             NASA GIBS daily-feature pipeline
│   │   ├── data/sentinel2.py             Sentinel-2 spectral indices (NDWI, NDTI, etc.)
│   │   ├── data/lidar.py                 USGS 3DEP point-cloud → channel profile
│   │   ├── features.py                   33-feature unified daily frame
│   │   ├── physics/advection_diffusion.py   PDE residual
│   │   └── train/trainer.py              PINN trainer
│   ├── scripts/
│   │   ├── prediction_tracker.py         DAILY  06:00 cron — fetch + predict + log
│   │   ├── train_ml.py                   WEEKLY 03:00 — RF + GradientBoost + kernel
│   │   ├── conformal_intervals.py        WEEKLY — per-rain-band ±half-widths
│   │   ├── turbidity_validation.py       WEEKLY — kernel vs measured turbidity r
│   │   ├── evaluate_daily.py             DAILY  — rolling-window metrics + CSV
│   │   ├── run_research_pipeline.py      research — full 9-step evaluation pipeline
│   │   ├── backfill_predictions.py       once — seed 60 days of history
│   │   ├── backfill_accuracy_log.py      once — seed the trend chart
│   │   ├── export_lidar_artifacts.py     once — write profile + metrics for demo
│   │   └── train.py                      research — PINN training (LiDAR / no-LiDAR)
│   ├── experiments/
│   │   ├── base.yaml                     PINN config (lidar.enabled: true)
│   │   └── baseline_no_lidar.yaml        ablation config
│   ├── data/
│   │   ├── raw/                          USGS + Open-Meteo + LiDAR sample CSVs
│   │   ├── processed/                    watershed_features.json
│   │   ├── satellite/                    cached daily GIBS features
│   │   └── runs/<timestamp>/             versioned ML run dirs
│   └── models/
│       ├── pinn_lidar_v1/best.pt         trained PINN (LiDAR velocity)
│       └── pinn_no_lidar_v1/best.pt      trained PINN (default velocity)
│
└── results/                          DEMO SITE
    ├── index.html                        cinematic homepage
    ├── pinn-demo.html                    12-section live demo
    ├── accuracy.html                     legacy dashboard
    ├── press-release.html                styled press release
    ├── data/                             served as JSON / CSV
    │   ├── predictions.json              399+ entries, rolling
    │   ├── accuracy_log.{json,csv}       46+ daily metric snapshots
    │   ├── lidar_profile.json            channel bed + slope per site
    │   ├── lidar_metrics.json            LiDAR vs no-LiDAR PINN comparison
    │   ├── ml_metrics.json               latest train_ml.py run
    │   ├── conformal_intervals.json      per-rain-band ±half-widths
    │   ├── turbidity_validation.json     external sensor correlation
    │   └── research/                     research pipeline outputs
    │       ├── dashboard_data.json       combined dashboard feed
    │       ├── regime_analysis.json      regime + storm event metrics
    │       ├── event_evaluation.json     event-based scoring
    │       ├── adaptive_conformal.json   rolling conformal calibration
    │       ├── changepoints.json         detected regime shifts
    │       ├── ablation.json             feature group ablation
    │       └── validation.json           cross-validation results
    └── scripts/
        ├── prediction_tracker.py         (older copy, kept for the live demo)
        ├── backfill_predictions.py       (older copy)
        └── rivertree_proxy.py            NVIDIA Integrate proxy for the AI agent

How the pieces fit

Live forecast path (every browser load)

USGS + Open-Meteo  →  feature assembly (rain/lag/doy) ──┬──→ ONNX MLP        →  Section 5 (primary)
                                                        └──→ kernel fallback →  Section 5 (if ONNX fails)
                                                              peak = 2.0 + 0.45·rain + 0.02·max(0, Q-5)
                                                        ↳ ±half-width from conformal_intervals.json

The trained neural network — a small PyTorch MLP (7 features → 32 → 32 → 16 → 1, ~1.3 K params, 8.6 KB ONNX) — is the primary inference path. It loads via onnxruntime-web 1.20.1 in the browser and runs entirely in WASM. If the runtime is missing, the ONNX file 404s, or inference throws, Section 5 silently falls back to the closed-form kernel — visible via the badge above the forecast cards (mint = ONNX, amber = fallback).

The PyTorch ↔ ONNX parity is asserted on every export: max |Δ| ≈ 1e-6 (float32 round-trip tolerance) — see alameda_creek_pinn/scripts/test_onnx_parity.py.

Important distinction. The PINN (Section 7) is a separate spatial-PDE artifact that takes (x, t) and outputs C(x, t) — it has no rainfall input and cannot drive a rainfall→peak forecast. It exports to pinn.onnx (68 KB) as a parity demo, not as live inference. The forecast MLP is what Section 5 actually calls.

Automated path (no human in the loop)

06:00 launchd cron  →  prediction_tracker.py     →  predictions.json gains 7 entries
06:00 launchd cron  →  evaluate_daily.py         →  accuracy_log gains 1 row
03:00 Sunday cron   →  train_ml.py              →  data/runs/<ts>/ + ml_metrics.json
03:00 Sunday cron   →  conformal_intervals.py   →  refreshed ±half-widths
03:00 Sunday cron   →  turbidity_validation.py  →  Section 10 r refreshes

Research path (manual)

PINN training:     python -m scripts.train experiments/base.yaml   (LiDAR-derived u)
                   python -m scripts.train experiments/baseline_no_lidar.yaml
                   python -m scripts.export_lidar_artifacts        (writes to results/data/)

Research pipeline: python alameda_creek_pinn/scripts/run_research_pipeline.py
                   → regime classification + storm event detection
                   → event-based evaluation (rainfall, discharge, nitrate events)
                   → adaptive conformal prediction (rolling 60-day window)
                   → change-point detection (CUSUM + sliding-window tests)
                   → feature ablation (6 groups, permutation importance)
                   → advanced validation (rolling-origin CV, LOSO, temporal holdout)
                   → automated reports (results/reports/)
                   → dashboard data (results/data/research/dashboard_data.json)

Demo site sections

# Section Anchor Live signal
0 Watershed map + 4 gauges #map discharge values update every 60 s
0.5 5 live data streams #data turbidity + 3 discharges + rainfall
1 Channel simulation #sim client-side advection-diffusion
2 Model comparison (analytical vs PINN vs MLP) #compare research output
3 Impact dashboard #impact static numbers
4 Project explainer #explainer static
5 7-day forecast calendar #forecast per-day rain × Q → peak ± conformal band
6 "What if?" scenario explorer #scenario slider-driven storm hypothetical
7 LiDAR channel profile #lidar bed elevation + slope + Manning u
8 Spread + operator actions #spread 4-station table with real ETAs
9 ML model comparison #ml RF / GB / kernel test metrics
10 External turbidity validation #validation Pearson r vs measured sensor
11 Accuracy trend over time #trend 14-day rolling MAE × 3 models × 46 days
12 Unified time-series #timeseries rain + Q + peak on shared timeline
13 Model Lab #modellab 4-solver live compare + XAI via FastAPI
14 Research Dashboard #research regime metrics, events, conformal, ablation, change-points

Data sources

Hydrology

  • USGS NWIS REST API — discharge (00060), turbidity (63680), temp (00010), specific conductance (00095)
  • USGS 3DEP — 10 m DEM, used for channel slope via LiDAR-Manning velocity
  • USGS Alameda Creek near Niles — 11179000, the primary calibration gauge
  • USGS Alameda Creek at Sunol — 11173200, headwater + the only Alameda gauge that registers turbidity (currently offline)
  • USGS Coyote Creek at Hwy 237 — 11172175, regional turbidity fallback (different watershed)
  • USGS Water Quality Portal — discrete nitrate samples (sparse, ~monthly)

Weather

  • Open-Meteo Forecast API — 7-day rainfall outlook, hourly refresh
  • Open-Meteo Archive API — historical daily rainfall for verification

Remote sensing

  • NASA GIBS — daily MODIS Terra true-color + 8-day NDVI composite, no key
  • ESRI World Imagery — Sentinel-2 / Maxar mosaic, no key
  • Watershed climatology: PRISM 30-year normals (1991–2020), summarized once

Land cover / topography

  • MRLC NLCD 2021 30 m land cover, summarized once into data/processed/watershed_features.json

Models

Model Type Trainable params Used for
Physics kernel Closed form 0 Live calendar prediction (Sections 5, 8, 12)
Random Forest sklearn 1.6 300 trees, depth 10 Compared in Section 9, contributes feature importances
Gradient Boost sklearn 1.6 (XGBoost stand-in) 400 trees, depth 3 Compared in Section 9
Persistence y_today = y_yesterday 0 Baseline floor
Rain-only kernel minus runoff term 0 Ablation in Section 9
PINN (LiDAR-u) PyTorch, Fourier features 16,701 Research artifact (Sections 2, 7), NOT in live inference
PINN (no-LiDAR) same 16,701 Ablation for Section 7 metrics

Splits are chronological only (train_ml.py uses 70/15/15 by date). No shuffling — that would leak future info into training.


Daily automation

Both launchd jobs are installed and active:

launchctl list | grep hydrosense
-   2   com.hydrosense.tracker      # daily 06:00 PT — fetch + predict + log
-   0   com.hydrosense.retrain      # Sundays 03:00  — retrain + recalibrate

Logs at results/data/tracker.log and results/data/retrain.log. plist files at ~/Library/LaunchAgents/com.hydrosense.{tracker,retrain}.plist.


Limitations — the honest version

  1. Self-consistent target. ML models in Section 9 score MAE 0 against the kernel by definition — the target column is the kernel's own output. The interesting comparison is against measured nitrate, which we don't have continuously.

  2. No winter testing. Calibration data is April–June 2026 (dry season). Only 6 of 60 days had measurable rain (≥0.5 mm). The "heavy" conformal band has n=0 calibration days — we cannot bound uncertainty during atmospheric rivers yet.

  3. PINN not in the live inference path (intentional). The calendar uses an ONNX-deployed trained MLP — that's the trained network running live. The PINN itself is a spatial-PDE artifact that takes (x, t) → C and has no rainfall input, so it can't drive a forecast. It is exported to ONNX (pinn.onnx, 68 KB) for parity/demo, not for production inference. R² ≈ 0 on the synthetic Gaussian benchmark limits its standalone ceiling regardless.

  4. Turbidity validation Pearson r = +0.056, p = 0.67. Statistically insignificant. The reason is in #2 — there's nothing to correlate against in a dry-season window. Section 10 says this explicitly.

  5. Niles turbidity sensor doesn't exist. Section 0.5 displays a fallback constant (4.2 FNU) when the real-time fetch fails — which it always does, because gauge 11179000 doesn't measure turbidity. Sunol (11173200) registers a turbidity sensor but it's currently offline.

  6. Sample LiDAR data. The 2 LiDAR CSVs (~20 points/site) are sample-size for the format spec, not training data. The slope estimate is statistically meaningless; real Manning velocity needs the full USGS 3DEP tile.

  7. Operator action recommendations are templates, not learned policies. The "+7.9 h lead time" and "18 antecedent-risk days" are real (computed), but "switch to reservoir intake" is descriptive.

  8. Land cover percentages don't refresh. NLCD updates every 3-5 years; we summarized once and stored statically.


Future work (ranked by leverage)

  1. PINN → live inference ✅ Done. The trained MLP runs in-browser via ONNX Runtime Web. PINN itself exported as parity demo.
  2. Re-add discharge_cfs as a model feature once 30+ days of in-distribution training data accumulate (it was dropped because backfilled training rows had it NaN-filled).
  3. Sentinel-2 band math via Copernicus Data Space Ecosystem (key required, free). Real NDWI / NDTI for in-channel pixels at 10 m.
  4. Continuous nitrate sensor (~$5k hardware). Eliminates the turbidity-proxy detour.
  5. Wet-season testing (Nov–Mar). Calibrates the "heavy" conformal band against real first-flush events.
  6. GitHub Pages deploy so the demo is publicly viewable without local serve.
  7. dataretrieval-python swap-in. Replaces hand-rolled USGS URL fetches with the maintained Python package.

Build/run

# 1. Install deps
pip install -r requirements.txt   # torch numpy pandas scikit-learn scipy pillow pyyaml

# 2. Bootstrap the prediction log (once)
cd alameda_creek_pinn
python -m scripts.backfill_predictions --days 60

# 3. Train ML models (chronological splits)
python -m scripts.train_ml
python -m scripts.conformal_intervals
python -m scripts.turbidity_validation

# 4. Seed the accuracy-trend chart (once)
python -m scripts.backfill_accuracy_log --window 14 --step 1

# 5. (optional) Install the daily/weekly launchd jobs — see ~/Library/LaunchAgents/

# 6. Serve the demo locally
cd results && python3 -m http.server 8000
# → http://localhost:8000/pinn-demo.html

Credits

USGS for the water data and 3DEP LiDAR. Open-Meteo for free weather forecasts. NASA for GIBS satellite imagery. ESRI for the World Imagery basemap. MRLC for NLCD land cover. PRISM Climate Group at Oregon State. Developed by: • Anshuman Kumar • Arjun Khurana Development assistance provided by Claude for model implementation, debugging, and training workflows.

About

Hydrosense is a physics-informed water-quality forecasting project for Alameda Creek, California. It combines live stream and weather data, satellite imagery, and a PINN-based research model to predict nitrate risk and track accuracy over time in a transparent public demo.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages