| <!doctype html> |
| <html lang="zh"> |
| <head> |
| <meta charset="utf-8"/> |
| <meta name="viewport" content="width=device-width, initial-scale=1"/> |
| <title>模块1 · Forcing 驱动数据</title> |
| <link rel="stylesheet" href="style.css"/> |
| <script src="https://cdn.plot.ly/plotly-2.35.2.min.js"></script> |
| <script src="https://cdn.jsdelivr.net/pyodide/v0.26.4/full/pyodide.js"></script> |
| <script src="data.js"></script> |
| </head> |
| <body> |
| <div class="wrap"> |
| <h1>模块 1 · Forcing(驱动数据)</h1> |
| <p class="sub">forcing = 逐时间步喂给模型的气象输入(降水/PET/温度)。模型再好,输入不对必错(garbage in, garbage out)。</p> |
|
|
| <h2>真实数据:Hesperange 三要素</h2> |
| <div class="card"> |
| <p class="sub">拖动下面窗口滑块看不同时段。注意 PET 的季节循环、流量冬春涨夏季退。</p> |
| <div class="row"> |
| <label>起始天 <span id="d0v">0</span></label><input id="d0" type="range" min="0" max="1125" step="1" value="0"/> |
| <label>结束天 <span id="d1v">1126</span></label><input id="d1" type="range" min="1" max="1126" step="1" value="1126"/> |
| </div> |
| <div id="forc" class="plot" style="height:400px"></div> |
| </div> |
|
|
| <h2>方法菜单:空间聚合(点雨量站 → 流域面平均)— 可切换 & 可改代码</h2> |
| <div class="card"> |
| <table> |
| <tr><th>版本</th><th>思想</th><th>谁用</th><th>可微性</th></tr> |
| <tr><td>arithmetic mean</td><td>算术平均</td><td>简单集总</td><td>线性→可微</td></tr> |
| <tr><td><b>Thiessen (1911)</b></td><td>面积加权(最近邻多边形)</td><td><b>HBV 等经典集总</b></td><td>线性→可微</td></tr> |
| <tr><td>IDW</td><td>反距离加权 1/d^p</td><td>插值/半分布预处理</td><td>线性→可微</td></tr> |
| <tr><td>gridded 网格</td><td>雷达MRMS/卫星GPM 逐格、不面平均</td><td><b>CREST/EF5</b> <span class="crest-tag">CREST</span></td><td>模块8(分布式)</td></tr> |
| </table> |
| <p class="status">注意:CREST 的选择(gridded)属于分布式阶段,本页先对比三种"面平均"法。Hesperange 数据已是面平均,所以用<b>合成 3 站</b>(对真实序列加偏差+噪声)演示——合成部分已在代码里标明。</p> |
|
|
| <div id="tabs"></div> |
| <textarea id="code" class="code" spellcheck="false"></textarea> |
| <div class="row" style="margin-top:10px"> |
| <label>IDW 幂次 p = <span id="pv">2.0</span></label> |
| <input id="pw" type="range" min="0.5" max="4.0" step="0.1" value="2.0"/> |
| <label>站1权重因子(Thiessen面积) <span id="a1v">120</span> km²</label> |
| <input id="a1" type="range" min="20" max="220" step="5" value="120"/> |
| <button id="run">运行 Run</button> |
| <button id="reset" class="ghost">重置代码</button> |
| <span id="status" class="status"></span> |
| </div> |
| <div id="plot" class="plot" style="height:330px;margin-top:10px"></div> |
| <div id="readout" class="readout"></div> |
| <div id="err"></div> |
| </div> |
|
|
| <h2>术语 Glossary</h2> |
| <div class="card"> |
| <details class="gl" open><summary>流域面平均降水 · Catchment-average precipitation</summary><div class="body"> |
| <div class="en">Aggregating point gauges into one basin-representative series — the standard input of lumped models.</div> |
| <div class="zh">把点雨量站汇成一个代表全流域的降水序列,集总模型的标准输入。</div></div></details> |
| <details class="gl"><summary>Thiessen 多边形 · Thiessen polygon (1911)</summary><div class="body"> |
| <div class="en">Weight each gauge by the area for which it is the nearest gauge (its polygon).</div> |
| <div class="zh">按"每站最近邻控制的面积"加权求面平均;经典、简单、常用于业务集总模型。</div></div></details> |
| <details class="gl"><summary>IDW · Inverse-distance weighting</summary><div class="body"> |
| <div class="en">Weights ∝ 1/distance^p to a target point (e.g. centroid); p controls how fast influence decays.</div> |
| <div class="zh">权重 ∝ 1/距离^p;p 越大近站影响越强。上面的滑块就是 p。</div></div></details> |
| <details class="gl"><summary>网格降水 · Gridded precipitation (CREST 的选择)</summary><div class="body"> |
| <div class="en">Per-cell precipitation fields from radar (MRMS) or satellite (TRMM/GPM); CREST/EF5 ingests these directly per grid cell — no averaging.</div> |
| <div class="zh">雷达/卫星反演的逐网格降水场;CREST/EF5 逐格直接吃、不做面平均(见模块8 分布式)。</div></div></details> |
| <details class="gl"><summary>率定/验证划分 · Calibration / Validation split & 暖机 Warm-up</summary><div class="body"> |
| <div class="en">Tune on one period, test on an independent one; discard an initial warm-up while states converge.</div> |
| <div class="zh">一段调参、另一段独立检验防过拟合;开头暖机期让状态收敛、评估时丢弃。本课划分:113/676/338 天。</div></div></details> |
| </div> |
|
|
| <div class="next"><a href="m00_overview.html">← 0 水量平衡</a><a href="m02_pet.html">下一模块:2 PET →</a></div> |
| <div class="foot">data: TU Delft CIE4431 Hesperange · anchor: CREST/EF5</div> |
| </div> |
| <script src="shared.js"></script> |
| <script> |
| renderNav("m01_forcing.html"); |
| const DAYS=[...Array(PRECIP.length).keys()]; |
| |
| |
| function drawForcing(){ |
| const a=parseInt(document.getElementById("d0").value), b=Math.max(a+1,parseInt(document.getElementById("d1").value)); |
| const xs=DAYS.slice(a,b); |
| Plotly.react("forc",[ |
| {x:xs,y:PRECIP.slice(a,b),type:"bar",name:"precip",marker:{color:"#2563eb"},yaxis:"y"}, |
| {x:xs,y:PET.slice(a,b),type:"scatter",mode:"lines",name:"PET",line:{color:"#ea580c",width:1},yaxis:"y2"}, |
| {x:xs,y:QOBS.slice(a,b),type:"scatter",mode:"lines",name:"Q_obs",line:{color:"#16a34a",width:1.2},yaxis:"y3"}, |
| ],{margin:{t:8,r:10,b:38,l:55},showlegend:false, |
| xaxis:{anchor:"y3",title:"day"}, |
| yaxis:{domain:[0.7,1],title:"P",autorange:"reversed"}, |
| yaxis2:{domain:[0.36,0.66],title:"PET"}, |
| yaxis3:{domain:[0,0.32],title:"Q_obs"}},{responsive:true,displayModeBar:false}); |
| } |
| bindSlider("d0","d0v",v=>v.toFixed(0),drawForcing); |
| bindSlider("d1","d1v",v=>v.toFixed(0),drawForcing); |
| drawForcing(); |
| |
| |
| const SNIPPETS={ |
| mean:`# V1 arithmetic mean -- USED_BY: simple lumped setups |
| def aggregate(gauges, areas, dists, p): |
| """gauges: (T,3) synthetic stations. Return catchment-average (T,).""" |
| return np.nanmean(gauges, axis=1)`, |
| thiessen:`# V2 Thiessen (1911) area-weighted -- USED_BY: HBV-era lumped (classic choice) |
| def aggregate(gauges, areas, dists, p): |
| w = areas / areas.sum() # polygon-area weights |
| return np.nansum(gauges * w[None, :], axis=1)`, |
| idw:`# V3 inverse-distance weighting -- USED_BY: interpolation / semi-distributed pre-processing |
| def aggregate(gauges, areas, dists, p): |
| w = 1.0 / dists**p # closer gauge -> larger weight |
| w = w / w.sum() |
| return np.nansum(gauges * w[None, :], axis=1)`}; |
| const codeEl=document.getElementById("code"); |
| codeEl.value=SNIPPETS.thiessen; |
| let runFn=null; |
| const getTab=makeTabs("tabs",[ |
| {key:"mean",label:"V1 mean"}, |
| {key:"thiessen",label:"V2 Thiessen (HBV)"}, |
| {key:"idw",label:"V3 IDW"}, |
| ],k=>{codeEl.value=SNIPPETS[k]; if(runFn)runFn();}); |
| |
| bindSlider("pw","pv",v=>v.toFixed(1),()=>runFn&&runFn()); |
| bindSlider("a1","a1v",v=>v.toFixed(0),()=>runFn&&runFn()); |
| |
| runFn=makeRunner({ |
| codeEl, runBtn:document.getElementById("run"), resetBtn:document.getElementById("reset"), |
| statusEl:document.getElementById("status"), errEl:document.getElementById("err"), |
| params:()=>({p:parseFloat(document.getElementById("pw").value), |
| a1:parseFloat(document.getElementById("a1").value)}), |
| buildScript:(code,prm)=>` |
| import numpy as np |
| P = np.array(${JSON.stringify(PRECIP)}) |
| rng = np.random.default_rng(0) |
| # SYNTHETIC 3 gauges: bias factors x noise around the true catchment-average |
| factors = np.array([1.15, 0.9, 0.95]) |
| gauges = np.clip(P[:,None]*factors[None,:]*(1+0.1*rng.standard_normal((len(P),3))), 0, None) |
| areas = np.array([${prm.a1}, 90.0, 82.0]) # Thiessen polygon areas (km2) - slider controls station 1 |
| dists = np.array([3.0, 5.0, 8.0]) # distance to centroid (km) |
| ${code} |
| avg = np.asarray(aggregate(gauges, areas, dists, ${prm.p}), float) |
| yrs = len(P)/365.25 |
| __out = {"avg": avg.tolist(), "annual": float(avg.sum()/yrs), "true_annual": float(P.sum()/yrs)} |
| `, |
| onResult:(r,prm,ms)=>{ |
| const a=150,b=260, xs=DAYS.slice(a,b); |
| linePlot("plot",[ |
| {x:xs,y:r.avg.slice(a,b),type:"scatter",mode:"lines",name:"aggregated",line:{color:"#2563eb",width:1.4}}, |
| {x:xs,y:PRECIP.slice(a,b),type:"scatter",mode:"lines",name:"true catchment-avg",line:{color:"#0f172a",width:1.2,dash:"dash"}}, |
| ],"precip (mm/d)","day (zoom 150-260)"); |
| document.getElementById("readout").innerHTML= |
| `年降水=<b>${r.annual.toFixed(0)} mm/yr</b>(真值 ${r.true_annual.toFixed(0)}) | `+ |
| `偏差 <b>${(100*(r.annual-r.true_annual)/r.true_annual).toFixed(1)}%</b> | 运行 <b>${ms.toFixed(0)} ms</b><br>`+ |
| `试试:切换版本看年降水如何变化;拖站1面积/IDW幂次;或直接改代码里的权重公式。`; |
| } |
| }); |
| </script> |
| </body> |
| </html> |
|
|