夜雨聆风学习资料网

ARTICLE · 1112494

《先懂控制,再懂AI》第 2 课 为什么液位必须自动,流量却可以手动?

《先懂控制,再懂AI》第 2 课 为什么液位必须自动,流量却可以手动?
点击蓝字 | 关注我们

各位工友,周五好,我是小马。

先道一声:国庆节快乐! 今天是假期第二天,别人在朋友圈晒景区、晒高速,咱们晒的是 DCS 画面和巡检记录——假期的厂子一秒不停,总得有人盯屏。 正好,这一课讲的就是一个"没人盯屏会发生什么"的故事——值班间隙读它,再合适不过。

🔧开读前先说一句:本片文末的[阅读原文]挂着一个能亲手玩的仿真实验室——手机直接打开,不用装python。
先别急着点。这篇文章里的每一个数字(71分钟报警、4.50米、联锁早6分钟动手),实验室里都能自己跑出来,一位不差;但只有把文章先读完,你才知道该点哪个按钮,该盯哪条曲线。
一路读到文末,那里有三个作业和一道[加联锁]的选做题等你——“液位必须自动,流量可以手动”这句话,你就不是记住了,而是做出来了。

周二第 1 课咱们搞明白了一件事:阀门"自己会动",是因为背后有个回路在替你盯、替你拧。

这一课回答一个更扎心的反差——

"同样是回路,为什么现场液位几乎没有手动的,流量却一堆手动阀?液位投自动是常识,流量投手动是常态。凭什么?"

这不是习惯问题,不是"老厂就这么弄",是物理。今天用一个储罐,把它讲透。

01

先讲个真事:操作工去巡检的那一个半小时

一个储罐,正常液位 3.00 米,罐高 5 米,高报警 4.20 米。

进料 100 m³/h,出料也是 100 m³/h——出料是泵打的,不随液位变。

这个罐的液位是手动的。早上 8 点整,操作工把进料阀调好,液位稳稳地停在 3 米。然后他拿了对讲机去现场巡检了。

8 点半,上游装置提量,进料多了 8%,变成 108 m³/h。

操作工 9 点半才回到中控。

你猜这时候液位是多少?

02

答案:4.5米,离满罐只剩半米

先把结果放这儿,下面第 05 节真跑验证:

  • 液位手动:9 点 11 分(扰动后 71 分钟)就触发了高报警。操作工回来时,液位 4.50 米,离满罐只剩 0.50 米。

  • 液位自动:同样的扰动,液位全程最高 3.16 米,稳稳的。

再看旁边那条流量管线——上游是同一个扰动,工况也是变了 8%:

  • 阀位没动,流量从 100 变成了 108 m³/h

  • 1.5 小时过去,还是 108

  • 工艺允许波动 ±10,没超

同样的扰动、同样没人管一个半小时,一个报了警差点满罐,一个屁事没有。

凭什么?

03

为什么:一个会“攒”,一个不会

一句话:

"液位的偏差会攒,流量的偏差不会。"

流量:阀位定,流量就定。 阀开 50%,上游压力变了 8%,流量就从 100 跳到 108——然后停在那儿。它没有地方"存"东西,它是个"过程量",过去的事不影响现在。

液位:罐是个"存量"。 进来 108、出去 100,每小时多出来的 8 立方米没地方去,全变成了液位。它不会自己消失,只会一直堆。

这就是控制里最重要的一个分类:

  • 自衡对象:被控变量自己变了之后,会反过来"帮自己"找回平衡

  • 无自衡对象:被控变量变了,什么都不会跟着变——偏差一路走到黑

现场判断方法就一句:问它"你自己会往回找吗?"

变量它自己会往回找吗类型手动行不行
流量
阀位不动它就不动,永远不会自己跑
自衡
多数场合行
温度
温度高了散热就大,自己会凉下来
自衡
短时间行
压力
压力高了排出就多,自己会泄
自衡(多数)
看场合
液位
进出量不随液位变,没人管就一路走
无自衡不行

⚠ 注意液位那一行有个前提:出料得是泵

泵的排量不看你液位多高,它只管打。如果出料是靠液位自己压出去的(重力阀),液位一高出料也变大,它就有自衡了——那是另一种很有意思的回路,咱们第 7 课专门讲。

04

更值钱的一句:你调的不是液位,而是“差”

这堂课真正想让你带走的是这句:

"液位控制的本质,不是'控制液位',是'控制进出料的平衡'。"

液位只是那杆秤。

  • 液位在涨 = 进大于出

  • 液位在掉 = 出大于进

  • 液位稳 = 平衡

你动液位阀,其实是在消那个"差"。 而只要"差"存在,液位就一直在变——所以它不能等,必须时时刻刻有人(或自动)盯着。

流量不用管什么平衡,它只需要跟住设定值。跟住之后,它自己就站住了。

这就是为什么液位回路出事是事故(溢罐、抽空、泵汽蚀、憋压),而流量回路出事多数只是质量偏差——一个碰安全,一个碰指标。

05

真跑一遍:一张表看懂

同样的扰动(+8%),同样的操作工(第 30 分钟去现场,1.5 小时后回来),四种组合一起跑:

回路方式扰动后30分钟扰动后60分钟扰动后90分钟
液位
自动
+0.14 m
-0.01 m
-0.03 m
液位
手动+0.50 m+1.00 m+1.50 m
流量
自动
+0.06 m³/h
-0.14 m³/h
+0.01 m³/h
流量
手动
+8.00 m³/h
+8.00 m³/h+8.00 m³/h

盯着看两张表的差别:

  • 液位手动那行:0.50 → 1.00 → 1.50。每小时正好涨 1 米,一条直线,不停。

  • 流量手动那行:8.00 → 8.00 → 8.00。一步到位,然后就不动了。

这就是"攒"和"不攒"的区别,写在数字里。

再看其余几行:

  • 液位自动:扰动后 71 分钟报警 vs 全程最高 3.16 米——自动把那条直线的斜率干成了 0

  • 流量自动:全程最大偏差 4.7 m³/h,比手动的 8 还大?对——因为流量测量有噪声,自动在"追着噪声跑"。但它在 ±10 以内,而且不会跑飞。

📷 配图:跑完第 07 节的画图代码会自动生成 level_flow_compare.png。上图液位那条红线一路涨过报警线,下图流量那条红线跳上去就平了——两张图放一起,这一课就讲完了。

06

报警喊人,联锁动手:还有第三道防线

第 05 节那个罐,从头到尾其实站在三道防线后面:

防线谁在干活动作值(本例)动作之后
第一道 · 自动控制
PID 回路
偏离设定 3.00 m
把偏差按回 0
第二道 · 报警
喊人
4.20 m
 高报警
屏上响一声,等你去处理
第三道 · 联锁
自己动手4.40 m
 高高联锁
自动启动备用出料泵

第 05 节咱们只跑到了第二道:报警响了,人不在,液位接着涨。

现在把第三道接上——液位一到 4.40,自动启动备用出料泵(出料从 100 提到 160 m³/h),把料抽到 3.60 再停泵。同一个扰动、同一串噪声,两条命运:

不投联锁投上联锁
高报警(4.20 m)
扰动后 71 分钟
扰动后 71 分钟(一样)
全程最高液位
4.55 m
4.40 m
联锁动作
—
扰动后 84 分钟,启动备用出料泵
备用泵
—
只跑了 7.4 分钟(抽到 3.60 m 停)
操作工回来时
4.50 m,离满罐 0.50 m
3.75 m,安全

看最后一行:联锁在第 84 分钟动手,比操作工回来早了 6 分钟。 就这 6 分钟,罐从"离满罐剩半米"变成了"3.75 米,没事"。

三个要记住的点:

  1. 联锁值比报警值高一档。 4.20 喊人、4.40 动手——中间那 0.20 米,就是留给"人"的时间。两个值要是一样,等于没给操作工机会。

  2. 联锁不是"调节",是"保命"。 看它的曲线:液位在 3.60~4.40 之间来回跑——因为它只有"开泵 / 停泵"两档,不会像 PID 那样细调。联锁一动作,就说明前两道防线全都失守了。

  3. 方向永远朝安全。 液位高高 → 启动备用出料泵(让料变少);液位低低 → 启动备用进料泵(让料变多)。常说的"高报警开出口备泵、低报警开入口备泵",就是这个规矩的两个方向。

⚠ 一条现场铁律

联锁不能随便旁路。旁路要审批、要限时、要挂牌;联锁动作之后,必须先查明原因、确认工艺正常,才能复位——它是保险,不是工具。

严格讲,真正碰安全的那一层联锁(SIS / ESD)是独立于 DCS 的另一套系统:自己的检测元件、自己的执行机构、自己的逻辑,不跟 DCS 共用。这一课先用"高液位启动备用泵"这种软联锁把概念讲明白;SIS 那套、联锁台账、旁路管理,咱们后面专门开课。

07

想动手的往这儿看

不想敲代码的工友,看到这儿结论已经完整了,可以直接跳到第 08 节。

新建文件 level_vs_flow.py:

import numpy as npDT = 1.0T_END = 9000          # 2.5 小时T_DIST = 1800         # 30 分钟:上游工况变化T_GONE = 1800         # 操作工去现场T_BACK = 7200         # 1.5 小时后回中控DIST = 0.08           # 上游扰动 +8%CHECK = [T_DIST + 1800, T_DIST + 3600, T_DIST + 5400]   # 扰动后 30/60/90 分钟V0 = 50.0# ================= 液位对象(积分 / 无自衡)=================A_TANK = 8.0        # 罐截面积 m²L_SP = 3.00         # 设定液位 mL_HI = 4.20         # 高报警 mTANK_H = 5.00       # 罐高(满罐)mKV_L = 2.0          # 进料阀增益:1% 阀位 = 2 m³/hdef run_level(kind, Kp=12.0, Ti=900.0, seed=42):    """kind: 'auto' / 'manual'  ->  (时间序列液位, 阀位)"""    rng = np.random.default_rng(seed)    n = int(T_END / DT)    L = np.zeros(n)    V = np.zeros(n)    L[0] = L_SP    V[0] = V0    integ = 0.0    Lmf = L_SP    for k in range(1, n):        t = k * DT        dist = DIST if t >= T_DIST else 0.0        # --- 测量(液位计噪声很小)---        Lmf += (L[k - 1] + rng.normal(0, 0.01) - Lmf) * DT / 5.0        e = L_SP - Lmf                       # 液位低了 e>0 → 开大进料阀        # --- 控制方式 ---        if kind == 'auto':            integ += e * DT            V[k] = np.clip(V0 + Kp * e + Kp / Ti * integ, 0, 100)        else:            # 操作工:在线时每 120 秒看一次,一次最多动 1%            V[k] = V[k - 1]            online = (t < T_GONE) or (t >= T_BACK)            if online and k % 120 == 1 and abs(e) > 0.05:                V[k] = np.clip(V[k - 1] + np.clip(0.8 * e, -1.0, 1.0), 0, 100)        # --- 对象:进料(泵出料,不随液位变) → 液位积分 ---        F_in = (100.0 + KV_L * (V[k] - V0)) * (1.0 + dist) / 3600.0   # m³/s        F_out = 100.0 / 3600.0        L[k] = L[k - 1] + (F_in - F_out) / A_TANK * DT    return L, V# ================= 流量对象(静态 / 自衡)=================F_SP = 100.0        # 设定流量 m³/hF_TOL = 10.0        # 工艺允许波动 ±10 m³/hKV_F = 2.0          # 阀位 → 流量增益(同 2 m³/h per %)TAU_F = 3.0         # 流量对象时间常数(秒级,很快)def run_flow(kind, Kp=0.4, Ti=60.0, seed=42):    rng = np.random.default_rng(seed)    n = int(T_END / DT)    F = np.zeros(n)    V = np.zeros(n)    F[0] = F_SP    V[0] = V0    integ = 0.0    Fmf = F_SP    for k in range(1, n):        t = k * DT        dist = DIST if t >= T_DIST else 0.0        Fmf += (F[k - 1] + rng.normal(0, 1.0) - Fmf) * DT / 2.0        e = F_SP - Fmf                       # 流量低了 e>0 → 开大阀        if kind == 'auto':            integ += e * DT            V[k] = np.clip(V0 + Kp * e + Kp / Ti * integ, 0, 100)        else:            # 操作工把阀固定在一个开度就走了(流量不需要人盯)            V[k] = V[k - 1]        # --- 对象:阀位定,流量就定(一阶,很快到位)---        F[k] = F[k - 1] + (KV_F * V[k] * (1.0 + dist) - F[k - 1]) / TAU_F * DT    return F, V# ================= 汇总输出 =================print(f'{"回路":<6}{"方式":<6}{"30分钟":>16}{"60分钟":>16}{"90分钟":>16}{"单位":>8}')rows = []for name, runner, unit in [    ('液位', run_level, 'm'),    ('流量', run_flow, 'm³/h'),]:    base = L_SP if name == '液位' else F_SP    for kind, cn in [('auto', '自动'), ('manual', '手动')]:        y, _ = runner(kind)        devs = [y[t] - base for t in CHECK]        rows.append((name, cn, devs, unit, y))        print(f'{name:<6}{cn:<6}' + ''.join(f'{d:>+16.2f}' for d in devs) + f'{unit:>8}')print()Lm, _ = run_level('manual')La, _ = run_level('auto')over = np.where(Lm > L_HI)[0]print(f'液位手动:扰动后 {int((over[0] - T_DIST) / 60)} 分钟触发高报警({L_HI} m),'      f'到操作工回来时液位 {Lm[T_BACK]:.2f} m(离满罐只剩 {5.0 - Lm[T_BACK]:.2f} m)')print(f'液位自动:全程最高 {La[T_DIST:].max():.2f} m,最低 {La[T_DIST:].min():.2f} m')Fm, _ = run_flow('manual')Fa, _ = run_flow('auto')print(f'流量手动:扰动后稳定在 {Fm[T_DIST:].mean():.1f} m³/h(偏离设定 {Fm[T_DIST:].mean() - F_SP:+.1f}),'      f'最大 {Fm[T_DIST:].max():.1f},全程未超工艺允许 ±{F_TOL:.0f}')print(f'流量自动:全程最大偏差 {np.abs(Fa[T_DIST:] - F_SP).max():.1f} m³/h')

跑完,你的输出和我一个字不差:

回路    方式                30分钟            60分钟            90分钟      单位液位    自动               +0.14           -0.01           -0.03       m液位    手动               +0.50           +1.00           +1.50       m流量    自动               +0.06           -0.14           +0.01    m³/h流量    手动               +8.00           +8.00           +8.00    m³/h液位手动:扰动后 71 分钟触发高报警(4.2 m),到操作工回来时液位 4.50 m(离满罐只剩 0.50 m)液位自动:全程最高 3.16 m,最低 2.97 m流量手动:扰动后稳定在 108.0 m³/h(偏离设定 +8.0),最大 108.0,全程未超工艺允许 ±10流量自动:全程最大偏差 4.7 m³/h

再把第 06 节说的第三道防线(联锁)接上,跑一次对比:

# ============ 再加一道联锁:液位高高 → 自动启动备用出料泵 ============L_HH = 4.40        # 高高联锁值:比高报警 4.20 再高一档(先喊人,后动手)L_RST = 3.60       # 备用泵停泵值:留 0.8 m 回差,防止泵在临界点反复启停B_PUMP = 60.0      # 备用出料泵容量 m³/h(启动后总出料 100 → 160)def run_level_interlock(interlock=True, seed=42):    """和第 05 节同模型、同噪声,只是多了一个联锁开关。"""    rng = np.random.default_rng(seed)    n = int(T_END / DT)    L = np.zeros(n)    V = np.zeros(n)    L[0] = L_SP    V[0] = V0    integ = 0.0    Lmf = L_SP    pump = False    starts = []    stops = []    for k in range(1, n):        t = k * DT        dist = DIST if t >= T_DIST else 0.0        Lmf += (L[k - 1] + rng.normal(0, 0.01) - Lmf) * DT / 5.0        e = L_SP - Lmf        V[k] = V[k - 1]        online = (t < T_GONE) or (t >= T_BACK)        if online and k % 120 == 1 and abs(e) > 0.05:            V[k] = np.clip(V[k - 1] + np.clip(0.8 * e, -1.0, 1.0), 0, 100)        # ---- 第三道防线:联锁(报警喊人,联锁动手)----        if interlock and (not pump) and L[k - 1] >= L_HH:            pump = True            starts.append(t)        elif pump and L[k - 1] <= L_RST:            pump = False            stops.append(t)        F_in = (100.0 + KV_L * (V[k] - V0)) * (1.0 + dist) / 3600.0        F_out = (100.0 + (B_PUMP if pump else 0.0)) / 3600.0        L[k] = L[k - 1] + (F_in - F_out) / A_TANK * DT    return L, V, starts, stopsfor name, il in [('不投联锁', False), ('投上联锁', True)]:    L, V, starts, stops = run_level_interlock(il)    print(f'{name}:全程最高 {L.max():.2f} m,操作工回来时 {L[7200]:.2f} m,'          f'150 分钟时 {L[-1]:.2f} m')    if starts:        print(f'  联锁第 {starts[0] / 60:.0f} 分钟动作(扰动后第 {(starts[0] - T_DIST) / 60:.0f} 分钟):'              f'液位到 {L_HH} m,启动备用出料泵')        print(f'  第 {stops[0] / 60:.1f} 分钟抽回 {L_RST} m 停泵:泵只跑了 '              f'{(stops[0] - starts[0]) / 60:.1f} 分钟')

输出:

不投联锁:全程最高 4.55 m,操作工回来时 4.50 m,150 分钟时 3.93 m投上联锁:全程最高 4.40 m,操作工回来时 3.75 m,150 分钟时 3.51 m  联锁第 114 分钟动作(扰动后第 84 分钟):液位到 4.4 m,启动备用出料泵  第 121.4 分钟抽回 3.6 m 停泵:泵只跑了 7.4 分钟

想出图的话,接着跑这段:

import matplotlib.pyplot as pltplt.rcParams['font.sans-serif'] = ['Microsoft YaHei', 'SimHei']   # 中文字体plt.rcParams['axes.unicode_minus'] = False                        # 负号正常显示La, _ = run_level('auto')Lm, _ = run_level('manual')Fa, _ = run_flow('auto')Fm, _ = run_flow('manual')t = np.arange(len(La)) * DTfig, axes = plt.subplots(2, 1, figsize=(11, 8), sharex=True)ax = axes[0]ax.axvspan(T_GONE, T_BACK, color='#f0f0f0', zorder=0, label='操作工不在(现场巡检)')ax.plot(t, La, color='#2f6fb3', lw=1.6, label='液位 · 自动')ax.plot(t, Lm, color='#c0392b', lw=1.6, label='液位 · 手动')ax.axhline(L_SP, color='#444', ls=':', lw=1)ax.axhline(L_HI, color='#e08b1f', ls='--', lw=1.2, label=f'高报警 {L_HI} m')ax.axhline(TANK_H, color='#a32d2d', ls='--', lw=1.2, label=f'满罐 {TANK_H} m')ax.axvline(T_DIST, color='#888', ls=':', lw=1)ax.set_ylabel('储罐液位 (m)')ax.set_ylim(2.8, 5.3)ax.legend(loc='upper left', fontsize=9, ncol=2)ax.grid(True, alpha=0.3)ax = axes[1]ax.axvspan(T_GONE, T_BACK, color='#f0f0f0', zorder=0, label='操作工不在(现场巡检)')ax.plot(t, Fa, color='#2f6fb3', lw=1.6, label='流量 · 自动')ax.plot(t, Fm, color='#c0392b', lw=1.6, label='流量 · 手动')ax.axhline(F_SP, color='#444', ls=':', lw=1)ax.axhspan(F_SP - F_TOL, F_SP + F_TOL, color='#eaf5ef', zorder=0, label='工艺允许 ±10')ax.axvline(T_DIST, color='#888', ls=':', lw=1)ax.set_ylabel('管道流量 (m³/h)')ax.set_xlabel('时间 (秒)')ax.set_ylim(85, 115)ax.legend(loc='upper left', fontsize=9, ncol=2)ax.grid(True, alpha=0.3)plt.tight_layout()plt.savefig('level_flow_compare.png', dpi=130)plt.show()
08

【🤖AI视角】液位的趋势里,藏着比液位更之前的东西

🤖 AI 视角

第 1 课说过一句话:被闭环控制的变量,拿去做纯趋势外推必失效——因为回路会把它拉回设定值,历史趋势里没有可外推的规律。

这一课再补一刀,而且这一刀更狠:

液位这种积分对象,连"均值回归"都没有。

这意味着什么?

对流量、温度这种自衡变量,历史数据是"围绕均值波动"的——平均值有意义,它就是正常工况。所以拿它们做异常检测、做预测,模型有得学。

对液位,历史数据是"一路漂移"的——它没有"正常水平"可回归,平均值毫无意义。你直接拿液位历史数据去训练预测模型,基本是白干。

那 AI 在液位上还有用吗?有,而且更有用——但要看对东西。

  • 看液位曲线,AI 能看出的"在涨",人也能看出,没有超额价值

  • 真正值钱的是进料流量 − 出料流量的累积差。这个东西,AI 可以 7×24 小时、对几十个罐同时算,人做不到

这就是"机理 + 数据"结合的最小案例:给模型一个"积分"的机理,它只要盯两条流量曲线,就能提前半小时告诉你哪个罐要报警。

一句话总结:

"看液位是'现在',看流量的差是'未来'。"

这也是为什么厂里做 AI 预警,液位、压力这类"存量"变量的提前量,普遍比流量大得多——因为它们背后有一个可以算的平衡。

而不懂这个平衡的人做 AI,只会得出"液位预测不准,AI 没用"的结论。 和第 1 课那个趋势外推的坑,是同一个坑,换了个姿势再踩一次。

09

留个作业

📝 作业 A(基础):把 DIST 从 0.08 改成 0.02,重跑。对比表里三列数字——全变成了原来的 1/4,但方向没变,还在涨。 扰动小了,"攒"这件事没有停。

📝 作业 B(进阶):把 A_TANK 从 8.0 改成 2.0(小罐),重跑。同样的 8% 扰动,小罐 17 分钟就报警、29 分钟就满罐。 你就明白为什么现场喜欢用大罐"扛"扰动——罐就是缓冲,容量就是时间。

📝 作业 C(挑战):把出料从"泵"改成"靠液位自己压出去"——把 run_level 里这一行 F_out = 100.0 / 3600.0 改成 F_out = 57.74 * np.sqrt(max(L[k - 1], 0.0)) / 3600.0,重跑。你会看到液位偏差 +0.31 → +0.43 → +0.47,越涨越慢,最后自己稳住了。 这就是"自衡"——第 7 课的主题,咱们到时候细说。

📝 选做 · 联锁:把联锁回差改小——L_RST 从 3.60 改成 4.35(回差只剩 0.05 m),重跑。备用泵会在 4.35 和 4.40 之间来回启停 3 次,总共只跑了 1.4 分钟,液位根本没压下去(操作工回来时还是 4.38 m)。 现场为什么怕"泵频繁启停"?回差到底是干什么的?跑一遍就懂了。

这几个作业,不用敲代码也能做。 打开「仿真实验室」(文末阅读原文直达,打开就是本课模块),右侧有现成按钮——「作业 A:扰动小一点」「作业 B:换成小罐」「作业 C:重力出料」,点一下、按开始,现象直接画出来。液位和流量两个回路的手/自动是分开切的,四种组合随便试——表里那四行文章数字,你跑出来会一位不差。联锁那道也进实验室了:右侧「第三道防线」开关一开(或直接点「作业 D:加一道联锁」),液位一到 4.40 m,备用出料泵就自己动手——你亲眼看看它比操作工早了几分钟。

做完把结果发评论区,下周挑典型的讲。


老规矩:跑代码遇到任何报错,把完整报错信息贴评论区,我一个个回。你的问题,就是下一篇选题。

下期预告

下周二 10 月 6 日,《先懂控制,再懂AI》第 3 课:《同一台泵的两个回路,为啥一个好调一个调不住?》——同一个泵,两条管线,一个阀好调得像玩具,一个怎么调都振。答案在"对象特性"里。

有疑问或岗位困惑也欢迎评论区留言,你的问题

就是下一篇选题。

扫码关注
化工仪表学AI

点击"阅读原文" 进入《仿真实验室》

相关学习资料