File size: 18,766 Bytes
90d096d
 
 
 
 
10cb075
90d096d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10cb075
90d096d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10cb075
 
 
90d096d
10cb075
 
 
 
90d096d
10cb075
 
 
 
 
90d096d
 
 
 
 
 
10cb075
90d096d
10cb075
90d096d
 
10cb075
 
 
 
 
 
 
 
 
 
 
 
90d096d
10cb075
 
 
90d096d
 
10cb075
90d096d
10cb075
90d096d
 
 
 
10cb075
90d096d
 
 
10cb075
90d096d
 
 
 
 
 
 
10cb075
90d096d
10cb075
90d096d
10cb075
 
90d096d
f1bc725
 
 
 
90d096d
 
 
 
 
 
 
 
6db8870
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10cb075
90d096d
10cb075
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90d096d
 
 
 
 
 
10cb075
 
 
 
90d096d
10cb075
 
 
 
90d096d
 
10cb075
90d096d
10cb075
90d096d
 
10cb075
90d096d
10cb075
 
 
90d096d
 
10cb075
 
 
 
90d096d
10cb075
 
 
 
 
 
 
90d096d
 
 
10cb075
 
 
90d096d
 
10cb075
 
 
 
 
 
 
 
 
 
90d096d
 
 
 
 
10cb075
90d096d
10cb075
90d096d
 
10cb075
 
 
 
 
 
 
 
 
 
90d096d
 
10cb075
 
 
 
 
 
 
 
 
 
 
90d096d
 
 
 
 
 
10cb075
90d096d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a942e45
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>CAMELS Benchmark Experiments — 2026-07-16</title>
<style>
  :root{
    --bg:#f7f9fc; --card:#ffffff; --ink:#1c2733; --muted:#5b6b7b;
    --blue:#0b6bb8; --orange:#c07d00; --green:#00795a; --line:#dde5ee;
    --accent:#e8f1fa;
  }
  *{box-sizing:border-box; margin:0; padding:0}
  html,body{height:100%; background:var(--bg); color:var(--ink);
    font-family:"Segoe UI",-apple-system,"Helvetica Neue",Arial,sans-serif}
  .slide{display:none; height:100vh; padding:46px 64px 84px; overflow:auto}
  .slide.active{display:block}
  h1{font-size:2.3em; margin-bottom:.35em; color:var(--blue)}
  h2{font-size:1.55em; margin-bottom:.6em; color:var(--blue);
     border-bottom:2px solid var(--line); padding-bottom:.25em}
  h3{font-size:1.08em; margin:.7em 0 .35em; color:var(--ink)}
  p,li{font-size:1.0em; line-height:1.55; color:var(--ink)}
  ul{margin:.3em 0 .6em 1.3em}
  .muted{color:var(--muted)}
  .tag{display:inline-block; background:var(--accent); color:var(--blue);
    border:1px solid #bcd6ec; border-radius:20px; padding:2px 12px;
    font-size:.82em; margin:0 6px 6px 0}
  .cols{display:flex; gap:26px}
  .col{flex:1; min-width:0}
  .card{background:var(--card); border:1px solid var(--line); border-radius:10px;
    padding:16px 20px; margin:10px 0; box-shadow:0 1px 3px rgba(20,40,70,.06)}
  table{border-collapse:collapse; width:100%; margin:.5em 0; background:var(--card)}
  th,td{border:1px solid var(--line); padding:6px 10px; font-size:.92em; text-align:left}
  th{background:var(--accent); color:var(--ink)}
  td.num{font-variant-numeric:tabular-nums}
  tr.hl td{background:#fff6e6; font-weight:600}
  .good{color:var(--green); font-weight:600}
  .flow{display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin:.6em 0}
  .fbox{background:var(--card); border:1.5px solid var(--blue); border-radius:8px;
    padding:8px 14px; font-size:.95em}
  .fbox.or{border-color:var(--orange)} .fbox.gr{border-color:var(--green)}
  .arr{color:var(--muted); font-size:1.3em}
  img{max-width:100%; border:1px solid var(--line); border-radius:8px; background:#fff}
  .cap{font-size:.85em; color:var(--muted); margin-top:4px}
  .kbd{background:#eef2f7; border:1px solid var(--line); border-radius:4px;
    padding:1px 7px; font-family:monospace}
  #nav{position:fixed; right:18px; bottom:16px; display:flex; gap:10px;
    align-items:center; z-index:50}
  #nav button{width:52px; height:52px; font-size:24px; border-radius:10px;
    border:1px solid var(--line); background:var(--card); color:var(--blue);
    cursor:pointer; box-shadow:0 2px 6px rgba(20,40,70,.12)}
  #nav button:hover{background:var(--accent)}
  #ctr{font-size:.95em; color:var(--muted); min-width:64px; text-align:center}
</style>
</head>
<body>

<!-- 1 ▸ title -->
<section class="slide active">
  <div style="margin-top:12vh">
  <h1>Differentiable CREST on CAMELS:<br>benchmark &amp; comparison experiments</h1>
  <p class="muted" style="font-size:1.2em"><b>2026-07-16</b></p>
  <div style="margin:26px 0 14px">
    <span class="tag">673 basins · daily · independent test decade</span>
    <span class="tag">same-harness model comparison</span>
    <span class="tag">optimizer verification (Adam vs SCE-UA)</span>
    <span class="tag">regionalization (dPL / PUB)</span>
  </div>
  <div class="card" style="max-width:860px">
    <b>One line:</b> a generic differentiable harness takes CREST into the classic
    conceptual-model band (NSE 0.61, KGE at HBV parity), the optimum is
    <b>verified by two independent optimizers</b>, and dPL adds the ungauged capability
    the classic models don't have.
  </div>
  <p class="muted" style="margin-top:18px">Navigate: <span class="kbd"></span>
  <span class="kbd"></span> or the corner buttons.</p>
  </div>
</section>

<!-- 2 ▸ experiment design -->
<section class="slide">
  <h2>Benchmark design — fairness first</h2>
  <table>
    <tr><th>Item</th><th>Setting</th></tr>
    <tr><td>Basins / scale</td><td>673 CAMELS basins, lumped (basin-mean forcing), daily</td></tr>
    <tr><td>Forcing</td><td>Maurer (primary, Newman convention) + Daymet (sensitivity); Oudin PET</td></tr>
    <tr><td>Split</td><td>train 1999-10→2008-09 · <b>test 1989-10→1999-09</b> (untouched decade)</td></tr>
    <tr><td>CREST config</td><td>Snow-17(9) + water balance(5) + reservoirs(5) + triangular UH(1) = 20 params/basin,
        direct sigmoid-bounded, Adam on (1−NSE), best-TRAIN-iterate selection</td></tr>
    <tr><td>Comparison models</td><td><b>HBV</b>(14p) &amp; <b>EXP-HYDRO</b>(6p) re-implemented differentiably,
        trained in the <b>identical harness</b>; <b>SAC-SMA</b> = Newman's published per-basin SCE runs
        (their own best; we only compute metrics)</td></tr>
    <tr><td>Single metric code</td><td>every row evaluated by one script (plain NSE/KGE, same window,
        ≥1000 valid days)</td></tr>
    <tr><td>Audit</td><td>independent leakage audit; obs-parity gate vs Newman r=1.0000;
        two header-area bugs + a window off-by-one found and fixed before publishing numbers</td></tr>
  </table>
  <div class="card"><b>Harness certification:</b> our same-harness HBV lands at 0.639 —
  inside its published 0.63–0.68 range — so the harness neither cheats nor handicaps.
  Differences between rows are <i>model</i> differences.</div>
</section>

<!-- 3 ▸ main table -->
<section class="slide">
  <h2>Main results (test 1989–99, n=673)</h2>
  <div class="cols">
    <div class="col" style="flex:1.25">
      <table>
        <tr><th>Model</th><th>Calib</th><th>med NSE</th><th>med KGE</th><th>β</th></tr>
        <tr><td>SAC-SMA + Snow-17 <span class="muted">(published)</span></td><td>per-basin</td>
            <td class="num">0.662</td><td class="num">0.717</td><td class="num">0.94</td></tr>
        <tr><td>HBV <span class="muted">(ours, same harness)</span></td><td>per-basin</td>
            <td class="num">0.639</td><td class="num">0.671</td><td class="num">1.00</td></tr>
        <tr class="hl"><td>CREST+UH · β-loss · Daymet</td><td>per-basin</td>
            <td class="num">0.608</td><td class="num">0.664</td><td class="num">0.946</td></tr>
        <tr><td>CREST+UH · Maurer</td><td>per-basin</td>
            <td class="num">0.600</td><td class="num">0.636</td><td class="num">0.891</td></tr>
        <tr><td>CREST no-UH · Maurer</td><td>per-basin</td>
            <td class="num">0.519</td><td class="num">0.565</td><td class="num">0.87</td></tr>
        <tr><td>EXP-HYDRO <span class="muted">(ours, same harness)</span></td><td>per-basin</td>
            <td class="num">0.523</td><td class="num">0.621</td><td class="num">1.03</td></tr>
        <tr><td>CREST-dPL <span class="muted">(regional)</span></td><td>one net</td>
            <td class="num">0.462</td><td class="num">0.436</td><td class="num">0.82</td></tr>
        <tr><td>CREST-dPL <span class="muted">(PUB, ungauged)</span></td><td>zero-shot</td>
            <td class="num">0.44–0.46</td><td class="num">0.43</td><td class="num">0.81</td></tr>
        <tr><td class="muted" colspan="5">context (Kratzert 2019): LSTM ens. 0.74 · mHM 0.66 ·
            HBV-ub 0.68 · VIC 0.55 · FUSE 0.65–0.72</td></tr>
      </table>
      <p class="muted" style="font-size:.88em">Forcing note: SAC-SMA, HBV and EXP-HYDRO rows use
      <b>Maurer</b> (Newman convention). Matched-forcing comparison: CREST+UH·Maurer
      <b>0.600</b> vs SAC-SMA <b>0.662</b>. The Daymet row's edge over Maurer is only +0.002
      once UH is present — its gain comes from the β-loss, not the forcing.</p>
    </div>
    <div class="col">
      <img src="assets/nse_cdf.png" alt="NSE CDF">
      <img src="assets/nse_map.png" alt="NSE map" style="margin-top:10px">
    </div>
  </div>
</section>


<!-- 3b ▸ controlled water-balance comparison -->
<section class="slide">
  <h2>Where the gap lives — a controlled water-balance comparison</h2>
  <p>Component-by-component, the SAC-SMA and CREST rows differ in <b>one</b> place:</p>
  <table>
    <tr><th>Component</th><th>SAC-SMA (Newman setup)</th><th>CREST+UH (ours)</th><th>Equivalent?</th></tr>
    <tr><td>Snow</td><td>Snow-17</td><td>Snow-17</td><td class="good">✅ identical</td></tr>
    <tr><td>Routing</td><td>calibrated <b>Gamma UH</b> (2 params: shape, scale)
        — SAC-SMA itself contains <b>no routing</b>; the UH is bolted on
        (CAMELS readme-verified)</td>
        <td>calibrated triangular UH (1 param: maxbas)</td>
        <td class="good">✅ same class (theirs slightly more flexible)</td></tr>
    <tr><td>PET demand</td><td>Priestley–Taylor, calibrated coefficient</td>
        <td>Oudin × calibrated ke multiplier</td>
        <td class="good">✅ same class</td></tr>
    <tr><td><b>Water balance</b></td>
        <td>dual-zone, four stores: <b>tension/free-water separation</b> in both zones,
            two nonlinear baseflow reservoirs, demand-driven percolation</td>
        <td>single VIC bucket + one linear groundwater store</td>
        <td><b>❌ the entire gap</b></td></tr>
  </table>
  <div class="cols">
    <div class="col card">
      <h3>Why daily scale makes routing a non-factor</h3>
      <p>CAMELS basins are small–medium (median ≈ 336 km²; concentration time mostly
      &lt; 1 day). Daily aggregation hides wave dynamics — a calibrated UH captures all
      the timing freedom that is visible at this scale (our Stage-3 test: daily
      KW ≈ UH). So the benchmark is effectively a <b>controlled experiment on the
      water-balance structure alone</b>.</p>
    </div>
    <div class="col card">
      <h3>Reading</h3>
      <p>Snow identical, routing &amp; PET equivalent, optimizer verified (Adam = SCE-UA),
      seeds agree ⇒ the 0.662 vs 0.608 gap has a <b>single attributable source</b>:
      SAC-SMA's five-decades-engineered vertical soil architecture
      (tension/free water, dual baseflow). That is exactly the structure the
      3-layer CREST kernel targets — pending layered soil priors to make the extra
      storages identifiable.</p>
    </div>
  </div>
</section>

<!-- 4 ▸ ablations -->
<section class="slide">
  <h2>Ablation experiments — where each point of skill comes from</h2>
  <table>
    <tr><th>Experiment</th><th>Design</th><th>Result</th><th>Reading</th></tr>
    <tr><td><b>UH routing</b></td><td>identical config ± differentiable triangular UH (maxbas)</td>
        <td class="num">0.519 → 0.600 (<b>+0.08</b>)</td>
        <td>hydrograph-shape freedom; water balance still sets the volume</td></tr>
    <tr><td><b>Forcing</b></td><td>Maurer vs Daymet, same everything</td>
        <td class="num">+0.06 without UH; ≈0 with UH</td>
        <td>UH absorbs timing differences between products</td></tr>
    <tr><td><b>Convergence control</b></td><td>warm-start + cosine LR, plain NSE loss (isolates optimization)</td>
        <td class="num">0.600 → 0.602 (+0.008 trainer-metric)</td>
        <td>baseline was ~converged; small honest gain</td></tr>
    <tr><td><b>β (bias) loss</b></td><td>NSE + 2(β−1)² penalty, selection metric matched</td>
        <td class="num">β 0.87→<b>0.946</b>, KGE +0.03, mean NSE +0.04, median ≈ flat</td>
        <td>volumetric under-bias was a <b>loss artifact</b> (loss-tolerated leak), fixed for free</td></tr>
    <tr><td><b>Multi-seed + merge</b></td><td>3 seeds; per-basin best-TRAIN merge across runs</td>
        <td class="num">seeds land 0.595–0.598; merge <b>loses</b></td>
        <td>optimization surface effectively flat across seeds; train-argmax picks noise —
            rejected honestly</td></tr>
    <tr><td><b>3-layer soil (A/B)</b></td><td>new differentiable 3-layer kernel vs single-layer,
        same harness/loss</td>
        <td class="num">0.610 vs 0.608 (tie); KGE 0.671 = HBV parity; train +0.04 didn't transfer</td>
        <td>vertical storage split <b>not identifiable from streamflow alone</b> —
            kept for the gridded stage where layered soil priors constrain it</td></tr>
  </table>
</section>

<!-- 5 ▸ optimizer comparison -->
<section class="slide">
  <h2>Optimizer comparison — Adam (backprop) vs SCE-UA</h2>
  <h3>Design</h3>
  <ul>
    <li>Identical parameter space (same sigmoid bounds), identical train window and
        plain-NSE objective, identical best-TRAIN selection — <b>only the optimizer differs</b></li>
    <li>SCE-UA (Duan 1992) GPU-vectorized <b>across basins</b>: one batched forward (17.8 s)
        evaluates one candidate per basin for all 673 → a ~673× speedup gift vs serial</li>
    <li>v1 = matched wall-clock budget; v2 = limit test (complex-hull mutation, 4 complexes,
        seeded with v1 best, 4× evaluations)</li>
  </ul>
  <table>
    <tr><th>Optimizer</th><th>Budget</th><th>Wall-clock</th><th>train NSE</th>
        <th>test NSE</th><th>test KGE</th><th>β</th></tr>
    <tr class="hl"><td>Adam (backprop)</td><td>400 iterations</td><td><b>8–13 h</b></td>
        <td class="num">~0.67</td><td class="num"><b>0.600–0.602</b></td>
        <td class="num">0.636–0.648</td><td class="num">0.89–0.92</td></tr>
    <tr><td>SCE-UA v1 <span class="muted">(same wall-clock)</span></td><td>2,542 evals</td><td>11.8 h</td>
        <td class="num">0.617</td><td class="num">0.564</td><td class="num">0.597</td><td class="num">0.874</td></tr>
    <tr><td>SCE-UA v2 <span class="muted">(limit)</span></td><td><b>10,000 evals</b></td><td><b>72 h</b></td>
        <td class="num">0.666</td><td class="num">0.597</td><td class="num">0.640</td><td class="num">0.908</td></tr>
  </table>
  <div class="card"><b>Conclusion:</b> SCE-UA eventually converges to the <b>same optimum</b>
  (0.597 ≈ 0.600, train 0.666 ≈ 0.67) — at <b>~6× the compute</b>. Two independent optimizers
  agreeing means 0.60 is the <b>model-structure ceiling</b>, not an optimizer artifact:
  the remaining 0.04 to HBV is structural, verified rather than assumed.</div>
</section>

<!-- 6 ▸ dPL -->
<section class="slide">
  <h2>Regionalization (dPL) — the capability the classic rows don't have</h2>
  <div class="cols">
    <div class="col">
      <h3>Design</h3>
      <div class="flow">
        <div class="fbox">AlphaEarth 64-d ⊕ 12 attributes</div><span class="arr"></span>
        <div class="fbox or">shared MLP</div><span class="arr"></span>
        <div class="fbox gr">20 CREST params / basin</div>
      </div>
      <ul>
        <li><b>Time-split</b>: all basins seen, test decade unseen → cost of replacing
            673 free calibrations with one network</li>
        <li><b>PUB</b>: 5-fold spatial hold-out (longitude-stratified) → true ungauged skill</li>
        <li>Trained end-to-end through the full 15-year daily simulation graph</li>
      </ul>
      <h3>Results</h3>
      <table>
        <tr><th>Mode</th><th>med NSE</th></tr>
        <tr><td>per-basin (upper ref)</td><td class="num">0.600</td></tr>
        <tr><td>dPL time-split (seen basins)</td><td class="num">0.462–0.478</td></tr>
        <tr class="hl"><td>dPL PUB (ungauged, 3 folds)</td><td class="num"><b>0.44–0.46</b></td></tr>
      </table>
    </div>
    <div class="col">
      <div class="card">
        <p><b>Key reading:</b> seen→unseen drop is tiny (0.475 → 0.44) — the
        attribute→parameter mapping <b>generalizes spatially</b>. Regional LSTM PUB
        (~0.53–0.56) stays above, as expected for 20-parameter physics.</p>
      </div>
      <div class="card">
        <h3>Identifiability findings (streamflow-only calibration)</h3>
        <ul>
          <li>groundwater-partitioning parameter: <b>50.7%</b> of basins within 1% of its
              upper bound (boundary collapse)</li>
          <li>ET-scaling parameter: spread across the range, IQR <b>22.7%</b>, no railing</li>
          <li>real 1-D objective sweep: near-optimal plateau = <b>27.5%</b> of the feasible
              range (cross-basin median); ~49% of basins have their optimum at the bound</li>
        </ul>
        <p class="muted">Two signatures of under-constraint → motivates multi-source
        constraints (soil moisture / ET / storage) in the next phase.</p>
      </div>
    </div>
  </div>
</section>

<!-- 7 ▸ takeaways -->
<section class="slide">
  <h2>Takeaways</h2>
  <div class="cols">
    <div class="col">
      <div class="card"><b>1 · CREST enters the conceptual band.</b>
        0.608 NSE / 0.664 KGE vs HBV 0.639 / 0.671 and SAC-SMA 0.662 / 0.717 —
        on their home turf (lumped, daily), with a generic harness and no
        structure-specific tuning.</div>
      <div class="card"><b>2 · Every point of skill is attributed.</b>
        UH +0.08 · forcing +0.06 (absorbed by UH) · β-loss fixes bias for free ·
        seeds/merge/3-layer honestly rejected. No unexplained gains.</div>
      <div class="card"><b>3 · The optimum is optimizer-verified.</b>
        Backprop and an exhaustive SCE-UA converge to the same score;
        backprop gets there ~6× faster. The 0.04 gap to HBV is structural.</div>
    </div>
    <div class="col">
      <div class="card"><b>4 · dPL is the differentiation.</b>
        Ungauged (PUB) NSE 0.44 with a near-zero seen/unseen gap — per-basin-calibrated
        classics have no equivalent capability. This is the mechanism that scales
        CONUS calibration to a global parameter product.</div>
      <div class="card"><b>5 · Throughput is the quiet result.</b>
        673 basins × 6 configs × multiple optimizers in days on one GPU —
        the experiment volume behind this deck is itself the case for
        differentiable calibration.</div>
      <div class="card"><b>Next:</b> soil-prior gate experiment (SoilGrids depth-layered WM),
        multi-source constraints against the identifiability findings, then the global
        0.1° parameter prior.</div>
    </div>
  </div>
</section>

<div id="nav">
  <button id="prev" aria-label="previous"></button>
  <span id="ctr">1 / 7</span>
  <button id="next" aria-label="next"></button>
</div>

<script>
  const slides = [...document.querySelectorAll(".slide")];
  let cur = 0;
  function show(i){
    cur = Math.max(0, Math.min(slides.length-1, i));
    slides.forEach((s,k)=>s.classList.toggle("active", k===cur));
    document.getElementById("ctr").textContent = (cur+1)+" / "+slides.length;
    window.scrollTo(0,0);
  }
  document.getElementById("prev").onclick = ()=>show(cur-1);
  document.getElementById("next").onclick = ()=>show(cur+1);
  document.addEventListener("keydown", e=>{
    if(e.key==="ArrowRight"||e.key==="PageDown") show(cur+1);
    if(e.key==="ArrowLeft"||e.key==="PageUp") show(cur-1);
  });
</script>
</body>
</html>