供水管网埋在地下,漏水看不见。这套系统只用几只压力表的读数,反推出「最可能在漏的是哪几根管」,把开挖范围从几公里收到几十米。
一座县城的供水管网,每年漏掉的水常在总供水量的两三成。难点不在「知道在漏」——分区计量的夜间最小流量早就告诉你哪个区在漏——而在漏在哪一根管。听漏工沿街逐段听,一天走不了几公里;开挖验证一次意味着破路、断交通、再回填,挖错了这些成本全部作废。所以真正的问题是:怎么把开挖顺序排对。
| 要的是排序,不是答案 | 没有任何算法能指着地图说「就是这里」。工程上有用的输出是一份带置信度的候选清单:先挖第 1 根,没有就挖第 2 根。评价指标因此是 top-3 命中率,不是「准不准」。 |
| 候选单元是管段,不是节点 | 漏损物理上发生在管段上——腐蚀穿孔、接口脱开、爆管。节点只是模型里的交汇抽象,地上没有那个东西。所以建库时把每根管从中点劈开、插一个虚拟节点再开孔,候选集是管。真实项目里改成按管之后,top-1 命中率比按节点高约 7 个百分点。 |
| 所有表都同向降压 | 随便哪儿漏,每只压力表都往下掉,只是掉多掉少不同——任意两根管的「指纹」相似度中位数高达 0.94。常见对策是先减掉这个共同的下降量,只比响应模式的相对差异(本页实测能把 0.94 压到 0.36)。但这是笔交易不是免费午餐:换来对基线漂移的免疫,代价是丢掉幅值信息。划不划算取决于你的基线有多准,而这只能在自己的网上量、不能照抄结论——第 2 个演示区把两种口径并排跑给你看,包括去均值输掉的那些工况。 |
| 压力和用水量互相咬 | 漏水导致压降,压降又抑制居民正常出水,出水少了压力回升一点,又反过来改变漏量本身。这个耦合只有压力驱动模型(PDA)表达得了,用常规的需水驱动模型算漏损工况,算出来的压力场是错的。 |
| 有些漏点原理上就定不了 | 从水厂出来的那根总干管一旦漏,全网所有表同幅下降,相对差异为零——去均值之后信号整个归零。这类漏点靠压力反演永远排不出来,只能靠分区计量的流量数据抓。把这条写进说明书,比把命中率报高一点重要。 |
| 假数字长得和真数字一样 | 这套结果的表格、概率、排名,用假设参数跑和用实测参数跑一模一样,肉眼分不出。所以真伪不能靠人记——引擎每次把逐项数据来源(实测/派生/假设/占位/合成)随产物一起输出,任何一项不是实测,总口径就自动降级为「演示」,界面禁止标成实测定位。 |
下面这四个式子就是整套系统的全部数学。每个都用本页真实跑出来的数代一遍,结果能和演示区里的数字对上。 代的这些数出自内置示例管网(P16、P20 那几根管)—— 换成你自己的管网之后,演示区的数字会变,这四个式子一个字不变。
水从管子一头流到另一头会损失多少压力。整个水力模型的骨架就是它加节点上的水量平衡。
| hf | 水头损失(米水柱) |
| L | 管长(米) |
| Q | 流量(立方米每秒) |
| C | Hazen-Williams 糙率系数,本页取 100–110(越大越光滑) |
| D | 管内径(米,不是毫米) |
代一遍:取管 P16 被劈开后的上半段,L=115 m、D=0.200 m、C=100。 先把与流量无关的部分并成阻力系数: 10.67 × 115 ÷ (1001.852 × 0.2004.871) = 1227.05 ÷ (5023.8 × 0.0003968) = 615.95。 这一段若走 Q=0.010 m³/s,则 hf = 615.95 × 0.0101.852 = 615.95 × 0.0001977 = 0.1218 m。 ——十米出头的管子只掉十几厘米,所以漏损引起的那零点几米压降才是可辨识的信号。
压力不够时水龙头出水会变小。不写这一条,漏损工况算出来的压力场就是错的。
| d(p) | 该节点在压力 p 下的实际出水量 |
| d0 | 该节点的额定用水量(压力充足时想用多少) |
| preq | 足额供水压力,取 21 m(约 30 psi) |
| pmin | 完全断流压力,取 3.5 m |
| 0.55 | 折减指数,规范经验值 |
代一遍:某节点压力掉到 p=15 m 时, (15 − 3.5) ÷ (21 − 3.5) = 11.5 ÷ 17.5 = 0.6571,再取 0.55 次方 = 0.7938 —— 也就是只能出到额定水量的 79.4%。本页演示网的基线压力都在 26 m 以上,全部落在「足额」那一档; 只有大爆管把某处压到 21 m 以下时,这条折减才开始起作用。
漏多少水,取决于漏口有多大、以及那一点还剩多少压力。它和上一条一起构成「压力—漏量」的互相咬合。
| q | 漏量(立方米每秒) |
| Cd | 孔口流量系数,取 0.75 |
| A | 漏口面积,建库标准孔口取 0.0015 m² |
| g | 重力加速度 9.81 m/s² |
| p | 漏点处的压力水头(米)——注意它本身是被漏量拉低的,所以要迭代 |
代一遍:在管 P16 中点开标准孔口,迭代收敛后该点压力 p = 29.7217 m。 √(2 × 9.81 × 29.7217) = √583.14 = 24.1483, q = 0.75 × 0.0015 × 24.1483 = 0.0271668 m³/s = 27.167 L/s。 本页求解器独立算出的漏量是 27.1668 L/s,逐位一致 —— 这条正是水力内核的自检用例。 对照:全网正常用水一共才 56.5 L/s,这一个漏口就顶掉了近一半的供水量。
前三个式子用来建指纹库,这一个用来查库。
| rj | 第 j 只表的残差 = 实测压力 − 基线压力(都是负数) |
| Sij | 指纹库第 i 行第 j 列 = 第 i 根管漏时第 j 只表掉多少米 |
| r̄、S̄i | 各自 M 个分量的均值。减掉它就是「去均值」——把「大家一起降」的共同分量剥掉 |
| M | 启用的压力表数量,本页 6 |
| ρi | 第 i 根管的相关得分,值域 [−1, 1],越接近 1 越可疑 |
| τ | softmax 温度,取 0.10。只改概率的尖锐程度,不改排序 |
代一遍(就是反演台默认那一局:真值 P16、无噪声,所以残差 r 恰好等于指纹库 P16 那一行):
r = [−0.3148, −0.4338, −0.4427, −0.6070, −0.5315, −0.7100],均值 r̄ = −0.5066;
拿它去比候选 P20,SP20 = [−0.3149, −0.4957, −0.4999, −0.5879, −0.6976, −0.8096],均值 = −0.5676。
两边各自去均值后逐项相乘再求和,分子 = 0.1125230;两个平方和开根分别是 0.3134853 与 0.3865859。
ρP20 = 0.1125230 ÷ (0.3134853 × 0.3865859) = 0.1125230 ÷ 0.1211885 = 0.928492 —— 与演示区表格里 P20 那一行的得分逐位相同。
而真值 P16 自己和自己比,ρ = 1.000000。再过 softmax:两者概率之比 = e(1.000000−0.928492)/0.10 = e0.71508 = 2.0444,
对应表里的 35.00% 与 17.12%(35.00 ÷ 17.12 = 2.044)。
注意这个 0.9285 有多高 —— P20 完全不是真值,得分却离满分只差 0.07。这就是为什么必须给的是排序而不是单一答案,
这个逼仄的得分间隔也解释了为什么排序对误差这么敏感。
顺带说清「去均值」到底买到了什么。它等价于在比对前把 r 和 S 各自的整体升降抹掉,好处是任何全表同幅的误差都被消掉了
——水厂水位当天高了半米、当日整体用水偏离模型、基线本身标定得不准,这些都是全表同幅的。代价是幅值信息一起被扔了,
只剩下「形状」。所以这不是一条放之四海皆准的改进,而是一笔交易:基线越脏越该去均值,基线越干净越不必。
本页这张网的基线是干净的(就是模型自己算的),所以在演示区里你会看到朴素 cosine 反而更准。
把基线漂移逐档拨大,实测出来的是这样一组数(σ=0.02 m,各 2500 次,top-1 命中率):
基线不漂时 Pearson 82.7% / 朴素 91.5%;±0.2 m 时 82.4% / 76.6%;±0.5 m 时 83.4% / 51.8%;±1 m 时 83.0% / 31.8%。
去均值那条几乎是一条水平线——它对共模误差在数学上完全免疫;朴素 cosine 则一路滑下去,两条线在 ±0.17 m 附近交叉。
真实项目里网更大、指纹更像、基线是由未装齐的表和假设参数拼出来的,天平才实实在在倒向去均值。
别照抄任何一边的结论,在自己的网上量。
| 水力内核 | 稳态管网求解用全局梯度法(Todini GGA):把上面式①的管段能量方程和节点水量平衡联立,牛顿迭代到收敛。本页这版是为演示用原生 JS 重写的,生产版本跑在 EPANET/WNTR 上。本页基线工况的节点连续性残差量级 1e-15 m³/s,水源入流与用户实际用水逐位平衡。 |
| 压力驱动 | 节点出水按式②随压力折减,漏口按式③出流。用水量和漏量都随压力变、又反过来改变压力,所以要在水力迭代外面再套一层不动点迭代(本页阻尼系数 0.4,收敛判据 1e-11 m³/s)。 |
| 建指纹库 | 逐根管从中点劈开插虚拟节点、在虚拟节点上开一个标准孔口,各跑一次仿真,得灵敏度矩阵 S[24×6],第 i 行 = 第 i 根管漏时 6 只表各掉多少米。这一步是离线的、可缓存的:拓扑/需水/水源/水力参数任一项变了才需要重建。真实项目 67 根管重建一次约 12 秒,之后命中缓存。 |
| 反演匹配 | 残差(实测减基线)与 S 每一行按式④做 Pearson 相关(等价于「先去均值再算 cosine」)得 24 个得分,再过 softmax 转成概率排序。去均值把行间相似度中位数从 0.94 压到 0.36、把 top-1 概率从 0.07 抬到 0.35 —— 但区分度变好不等于命中率变好:本页实测,基线干净时朴素 cosine 的 top-1 反而高出约 9 个百分点(91.5% vs 82.7%);随着基线共模漂移变大,去均值那条几乎是水平线(82~83% 不动),朴素 cosine 一路滑到 ±1 m 时的 31.8%,两条线在 ±0.17 m 附近交叉。所以它不是「更好的相似度」,而是共模误差保险,保费是幅值信息。 |
| 概率怎么读 | softmax 概率是候选之间的相对可信度,不是「有 35% 的概率漏在这」。温度 τ 只改分布的尖锐程度,不改排序——你在反演台上换 τ 可以验证这一点。真正决定成败的是排序,所以验收指标盯 top-3 命中率。 |
| 已知失效模式 | ① 总干管漏损定不了(全表同幅降压,去均值后归零);② 糙率标定误差影响远大于加表——真实项目实验里糙率偏 20% 能让 top-3 命中率腰斩;③ 指纹库按单点漏损建,多点同时漏时残差是叠加的,排序会指向两点之间。这三条都写在交付说明书里,不藏。 |
| 数据真伪机制 | 每项输入带一个状态标签:measured/derived/assumed/placeholder/synthetic。只要有一项不是前两类,产物总口径自动落为 demo,并在终端打横幅、在产物里写明「最弱的一项是什么」。声明文件缺失或写了词表外的值,引擎直接拒绝运行——不允许靠删掉声明让警告消失。 |
| 防漂移 | 给开发的交付包是主仓算法的单文件重打包。快照式重打包最容易出的事故是「主仓改了一行常数,交付包没跟上,两边结果长得一模一样」。所以配了一道对账闸门,逐项比 5 份复制件字节、监测点列表、水力常数、反演参数、灵敏度矩阵与基线,并拿主仓算法把每个候选复算一遍;fail-closed,一项都没跑起来也算失败,拒绝在空集上报绿。 |