基于 Python 的密封储罐压力保护逻辑模拟与实现
“在化工现场,一个储罐的压力保护逻辑,往往就是最后一道安全防线。写错一行代码,可能就是一次非计划停车,甚至更严重的安全事故。”
—— 参考哈尔滨工程大学《工业过程控制》第 8 章“过程控制系统的安全保护”
一、实际应用场景描述
在石化、精细化工、LNG、制药等行业中,密封储罐(如原料罐、中间罐、成品罐)普遍采用常压或低压设计。由于进料、出料、环境温度变化或氮封系统波动,罐内压力会动态变化。
为了防止超压导致罐体变形或负压导致罐体吸瘪,工程上通常配置一套压力保护联锁逻辑:
┌──────────────────────────────────────────────────────┐
│ 密封储罐压力保护系统架构 │
│ │
│ [压力变送器 PT-101] │
│ │ 4–20 mA / HART / FF │
│ ▼ │
│ ┌────────────────────────────┐ │
│ │ 压力信号采集与滤波 │ │
│ │ • 量程转换 │ │
│ │ • 坏值剔除(超量程/断线) │ │
│ └──────────────┬─────────────┘ │
│ ▼ │
│ ┌────────────────────────────┐ │
│ │ 压力保护逻辑(核心) │ │
│ │ ┌──────────┐ ┌──────────┐│ │
│ │ │ 压力上限 │ │ 压力下限 ││ │
│ │ │ > P_H │ │ < P_L ││ │
│ │ └────┬─────┘ └────┬─────┘│ │
│ │ │ │ │ │
│ │ ▼ ▼ │ │
│ │ ┌──────────┐ ┌──────────┐│ │
│ │ │ 开放空阀 │ │ 关放空阀 ││ │
│ │ └──────────┘ └──────────┘│ │
│ └──────────────┬─────────────┘ │
│ ▼ │
│ ┌────────────────────────────┐ │
│ │ 执行机构(放空阀 XV-101) │ │
│ │ • 气动单作用/双作用 │ │
│ │ • 故障安全位置(FC/FO) │ │
│ └──────────────┬─────────────┘ │
│ ▼ │
│ [密封储罐] │
│ • 设计压力:±500 Pa(常压罐) │
│ • 操作压力:氮封 50–200 Pa │
│ • 保护压力:上限 300 Pa / 下限 20 Pa │
└──────────────────────────────────────────────────────┘
典型应用工况
工况 压力变化趋势 保护动作
进料过快 压力快速上升 开大放空阀,泄压
环境温度骤升 压力缓慢上升 适度开阀,维持微正压
出料过快 压力快速下降 关闭放空阀,防止负压
氮封系统故障 压力持续下降 关闭放空阀,必要时补氮
呼吸阀卡涩 压力异常波动 保护逻辑优先动作
二、引入痛点
2.1 现场真实困境
场景 现场反馈 根因
“抖得厉害” “放空阀一会儿开一会儿关,压力在临界点来回跳” 无滞环/死区设计
“反应太慢” “压力都超到 400 Pa 了,阀才动” 采样周期过长或逻辑延迟
“关不住” “压力低于下限了,阀还开着,罐都快吸瘪了” 逻辑优先级设计错误
“误动作” “变送器断线,阀直接全开,白白放空” 无信号失效保护
“标准不一” “老装置是 250/30,新装置是 300/20,逻辑还不一样” 参数硬编码,缺乏配置化
“查不到” “昨天半夜压力波动,谁也不知道阀动没动” 无动作记录与审计
2.2 核心矛盾
压力保护逻辑的核心矛盾是“响应速度”与“动作稳定性”之间的平衡。
反应太快容易在临界点频繁动作(阀门磨损、产品损耗),反应太慢又可能突破安全限值——必须引入滞环(Hysteresis)与优先级管理。
2.3 三种实现方案对比
方案 核心思想 优势 劣势
PLC 梯形图 传统工业方案 可靠性高、认证齐全 修改需停机、学习成本高
DCS 功能块 图形化配置 集成度高、易维护 灵活性受限、跨系统难
Python 模拟/上位机 独立程序逻辑 灵活、易版本管理、便于教学验证 需额外通信与执行链路
三、核心逻辑讲解
3.1 理论基础:滞环控制(Hysteresis)
参考哈工程《工业过程控制》第 8 章“过程控制系统的安全保护”,压力保护通常采用带滞环的两位式控制:
压力保护滞环逻辑:
P_H (上限,如 300 Pa)
│
│ ← 开启放空阀
│
│ P_H - H (滞环回差,如 280 Pa)
│ │
│ │ ← 关闭放空阀
│ │
---------┼─────────┼────────────── 压力轴
│ │
│ P_L + H (下限滞环,如 40 Pa)
│ │
│ ← 关闭放空阀
│
P_L (下限,如 20 Pa)
逻辑规则:
当前状态 条件 动作
放空阀关闭 压力 ≥ P_H 开阀
放空阀开启 压力 ≤ P_H - H 关阀
放空阀开启 压力 ≤ P_L 关阀(强制保护)
放空阀关闭 压力 ≥ P_L + H 保持关(防止抖动)
滞环的作用:
- 避免在临界点频繁切换
- 减少阀门机械磨损
- 降低产品放空损耗
3.2 优先级管理
压力保护优先级(安全优先):
1. 【最高】信号失效(变送器断线/超量程)
→ 强制关闭放空阀(Fail-Safe,防止误放空)
2. 【高】压力低于下限 P_L
→ 强制关闭放空阀(防止负压吸瘪)
3. 【中】压力高于上限 P_H
→ 开启放空阀(泄压保护)
4. 【低】滞环区内部
→ 保持当前状态(避免抖动)
3.3 离散化实现
# 核心判断逻辑(简化版)
if pressure >= P_H:
valve_open = True
elif pressure <= P_L:
valve_open = False
elif valve_open and pressure <= (P_H - hysteresis):
valve_open = False
elif not valve_open and pressure >= (P_L + hysteresis):
valve_open = False # 保持关闭,避免抖动
四、代码讲解(面向对象设计)
4.1 类结构总览
类名 职责 设计模式
"PressureStatus" 压力信号状态枚举 枚举类型
"ValveState" 阀门状态枚举 枚举类型
"TankConfig" 储罐与保护参数配置 值对象
"PressureReading" 压力采样数据 值对象
"ValveCommand" 阀门控制命令 值对象
"PressureTransmitter" 压力变送器模拟 单一职责
"PressureProtectionLogic" 保护逻辑核心 策略模式
"ReliefValve" 放空阀模拟 单一职责
"TankPressureSimulator" 储罐压力动态仿真 工厂模式
"ProtectionSystem" 保护系统聚合根 外观模式
4.2 完整源代码(单文件,约 330 行)
直接复制下方代码保存为
"tank_pressure_protection.py" 即可运行,仅需 Python 3.8+ 标准库。
<details>
<summary></summary>
"""
密封储罐压力保护逻辑模拟程序
参考: 哈尔滨工程大学《工业过程控制》第8章"过程控制系统的安全保护"
作者: 工业控制与上位机开发工程师
适用: CSDN技术博客完整发布版(单文件, ~330行)
"""
import time
import enum
import dataclasses
import logging
from typing import Optional
from dataclasses import dataclass, field
# ============================================================
# 1. 基础枚举与值对象
# ============================================================
class PressureStatus(enum.Enum):
"""压力信号状态(安全仪表系统基础)"""
NORMAL = "正常"
FAIL_LOW = "信号断线/低于量程"
FAIL_HIGH = "超量程"
SENSOR_FAULT = "传感器故障"
class ValveState(enum.Enum):
"""放空阀状态"""
OPEN = "开启"
CLOSED = "关闭"
FAIL_SAFE = "故障安全位"
@dataclass(frozen=True)
class TankConfig:
"""
储罐与压力保护配置 —— 值对象(不可变)
参考哈工程《工业过程控制》第8章:
- 常压储罐通常设计压力 ±500 Pa
- 氮封系统维持微正压 50~200 Pa
- 保护逻辑需设置滞环防止频繁动作
"""
tank_id: str = "TK-101"
design_pressure: float = 500.0 # 设计压力(Pa, 表压)
pressure_high: float = 300.0 # 压力高限(Pa)
pressure_low: float = 20.0 # 压力低限(Pa)
hysteresis: float = 20.0 # 滞环宽度(Pa)
fail_safe_closed: bool = True # 故障安全位: True=关, False=开
sample_interval: float = 0.5 # 采样周期(s)
def __post_init__(self):
if self.pressure_low >= self.pressure_high:
raise ValueError("压力低限必须小于压力高限")
if self.hysteresis <= 0:
raise ValueError("滞环宽度必须大于0")
if self.pressure_low + self.hysteresis >= self.pressure_high - self.hysteresis:
raise ValueError("滞环设置过宽,逻辑区间重叠")
@dataclass
class PressureReading:
"""压力采样数据 —— 值对象"""
timestamp: float
pressure: float
status: PressureStatus
raw_signal: float # 原始信号(4–20mA对应值)
def is_valid(self) -> bool:
return self.status == PressureStatus.NORMAL
@dataclass
class ValveCommand:
"""阀门控制命令 —— 值对象"""
timestamp: float
target_state: ValveState
reason: str
current_pressure: float
logic_state: str = field(default="", repr=False)
# ============================================================
# 2. 压力变送器模拟(单一职责)
# ============================================================
class PressureTransmitter:
"""
压力变送器模拟
模拟:
- 正常压力采集
- 信号断线(<4mA)
- 超量程(>20mA)
- 传感器故障
"""
def __init__(self, config: TankConfig, fault_prob: float = 0.0):
self.config = config
self.fault_prob = fault_prob
self._signal_4ma = 4.0
self._signal_20ma = 20.0
self._pressure_range = (-500.0, 500.0) # 变送器量程
logging.info(f"[变送器] 初始化, 量程: {self._pressure_range} Pa")
def read(self, real_pressure: float, timestamp: float) -> PressureReading:
"""
读取压力值(模拟信号转换与故障)
"""
import random
# 模拟信号故障
if random.random() < self.fault_prob:
fault_type = random.choice(["low", "high", "fault"])
if fault_type == "low":
raw = 3.5 # 断线
status = PressureStatus.FAIL_LOW
elif fault_type == "high":
raw = 21.0 # 超量程
status = PressureStatus.FAIL_HIGH
else:
raw = 10.0 # 中间值但标记为故障
status = PressureStatus.SENSOR_FAULT
return PressureReading(timestamp, real_pressure, status, raw)
# 正常信号: 4–20mA 线性映射
p_min, p_max = self._pressure_range
raw = self._signal_4ma + (real_pressure - p_min) / (p_max - p_min) * \
(self._signal_20ma - self._signal_4ma)
# 限幅
raw = max(0.0, min(24.0, raw))
# 判断状态
if raw < self._signal_4ma - 0.1:
status = PressureStatus.FAIL_LOW
elif raw > self._signal_20ma + 0.1:
status = PressureStatus.FAIL_HIGH
else:
status = PressureStatus.NORMAL
return PressureReading(timestamp, real_pressure, status, raw)
# ============================================================
# 3. 压力保护逻辑核心(策略模式)
# ============================================================
class PressureProtectionLogic:
"""
密封储罐压力保护逻辑
核心算法(带滞环的两位式控制):
参考哈工程《工业过程控制》§8.2 "安全保护系统设计"
逻辑规则:
1. 压力 ≥ P_H → 开阀(泄压)
2. 压力 ≤ P_H - H → 关阀(滞环)
3. 压力 ≤ P_L → 强制关阀(防负压)
4. 信号失效 → 故障安全位
"""
def __init__(self, config: TankConfig):
self.config = config
self._valve_open: bool = False # 当前逻辑状态
self._last_command: Optional[ValveCommand] = None
logging.info(f"[保护逻辑] 初始化, P_H={config.pressure_high} Pa, "
f"P_L={config.pressure_low} Pa, H={config.hysteresis} Pa")
def evaluate(self, reading: PressureReading) -> ValveCommand:
"""
执行一次保护逻辑评估
"""
timestamp = reading.timestamp
p = reading.pressure
cfg = self.config
# 优先级1: 信号失效保护
if not reading.is_valid():
reason = f"压力信号异常: {reading.status.value}"
target = ValveState.FAIL_SAFE if cfg.fail_safe_closed else ValveState.OPEN
self._valve_open = (target == ValveState.OPEN)
cmd = ValveCommand(timestamp, target, reason, p, self._logic_state_str())
self._last_command = cmd
return cmd
# 优先级2: 压力低于下限(防负压吸瘪)
if p <= cfg.pressure_low:
reason = f"压力低于下限({p:.1f} Pa ≤ {cfg.pressure_low} Pa), 防负压保护"
self._valve_open = False
cmd = ValveCommand(timestamp, ValveState.CLOSED, reason, p,
self._logic_state_str())
self._last_command = cmd
return cmd
# 优先级3: 压力高于上限(泄压保护)
if p >= cfg.pressure_high:
reason = f"压力高于上限({p:.1f} Pa ≥ {cfg.pressure_high} Pa), 泄压保护"
self._valve_open = True
cmd = ValveCommand(timestamp, ValveState.OPEN, reason, p,
self._logic_state_str())
self._last_command = cmd
return cmd
# 优先级4: 滞环区内部(保持状态,避免抖动)
if self._valve_open:
# 当前开阀状态 → 需要降到更低才关
if p <= cfg.pressure_high - cfg.hysteresis:
reason = (f"压力回落至滞环区({p:.1f} Pa ≤ "
f"{cfg.pressure_high - cfg.hysteresis} Pa), 关阀")
self._valve_open = False
cmd = ValveCommand(timestamp, ValveState.CLOSED, reason, p,
self._logic_state_str())
self._last_command = cmd
return cmd
else:
# 当前关阀状态 → 需要升到更高才开(但上限已单独处理)
if p >= cfg.pressure_low + cfg.hysteresis:
# 仍在安全区,保持关闭,避免抖动
reason = (f"压力在安全区({cfg.pressure_low} Pa < {p:.1f} Pa < "
f"{cfg.pressure_high} Pa), 保持关闭防抖")
cmd = ValveCommand(timestamp, ValveState.CLOSED, reason, p,
self._logic_state_str())
self._last_command = cmd
return cmd
# 滞环区内,保持当前状态
state = ValveState.OPEN if self._valve_open else ValveState.CLOSED
reason = f"压力在滞环区内({p:.1f} Pa), 保持当前状态"
cmd = ValveCommand(timestamp, state, reason, p, self._logic_state_str())
self._last_command = cmd
return cmd
def _logic_state_str(self) -> str:
if not self._last_command:
return "初始状态"
return f"阀{'开' if self._valve_open else '关'}"
# ============================================================
# 4. 放空阀模拟(单一职责)
# ============================================================
class ReliefValve:
"""
放空阀模拟
模拟气动放空阀:
- 故障安全位(FC: Fail Close / FO: Fail Open)
- 开关动作延迟
"""
def __init__(self, config: TankConfig):
self.config = config
self._state: ValveState = ValveState.CLOSED
self._target_state: ValveState = ValveState.CLOSED
self._switch_time: float = 1.0 # 开关动作时间(s)
logging.info(f"[放空阀] 初始化, 故障安全位: "
f"{'关(FC)' if config.fail_safe_closed else '开(FO)'}")
def apply_command(self, command: ValveCommand) -> ValveState:
"""
执行阀门控制命令(模拟动作延迟)
"""
self._target_state = command.target_state
# 故障安全位直接生效
if command.target_state == ValveState.FAIL_SAFE:
self._state = ValveState.CLOSED if self.config.fail_safe_closed \
else ValveState.OPEN
logging.warning(f"[放空阀] 进入故障安全位: {self._state.value}")
return self._state
# 模拟动作延迟(简化:立即切换,实际可加入time.sleep)
self._state = command.target_state
action = "开启" if self._state == ValveState.OPEN else "关闭"
logging.info(f"[放空阀] {action}, 原因: {command.reason}")
return self._state
@property
def state(self) -> ValveState:
return self._state
# ============================================================
# 5. 储罐压力动态仿真(工厂模式)
# ============================================================
class TankPressureSimulator:
"""
密封储罐压力动态仿真
简化模型:
dP/dt = (进料 - 出料 - 放空) / C
其中 C 为储罐"气容"
"""
def __init__(self, config: TankConfig):
self.config = config
self._pressure: float = 50.0 # 初始压力(Pa)
self._time: float = 0.0
logging.info(f"[储罐仿真] 初始化, 初始压力: {self._pressure} Pa")
def step(self, dt: float, valve_open: bool,
feed_rate: float = 0.0, vent_rate: float = 0.0) -> float:
"""
执行一个仿真步长
"""
# 放空速率(简化:阀门开时固定泄放量)
relief_rate = 100.0 if valve_open else 0.0
# 压力变化率(简化气容模型)
capacitance = 1000.0 # Pa·s per unit flow
dP = (feed_rate - vent_rate - relief_rate) / capacitance * dt
self._pressure += dP
self._time += dt
# 物理限幅(不能低于真空或高于设计压力)
self._pressure = max(-self.config.design_pressure,
min(self.config.design_pressure, self._pressure))
return self._pressure
@property
def pressure(self) -> float:
return self._pressure
@property
def time(self) -> float:
return self._time
# ============================================================
# 6. 保护系统聚合根(外观模式)
# ============================================================
class ProtectionSystem:
"""
密封储罐压力保护系统(聚合根)
对外只暴露:
.step() —— 执行一个仿真/控制周期
.get_status() —— 获取系统状态
"""
def __init__(self, config: TankConfig, fault_prob: float = 0.0):
self.config = config
self.transmitter = PressureTransmitter(config, fault_prob)
self.logic = PressureProtectionLogic(config)
self.valve = ReliefValve(config)
self.tank = TankPressureSimulator(config)
self._step_count: int = 0
self._history: list = []
logging.info(f"[保护系统] 初始化完成, 储罐: {config.tank_id}")
def step(self, feed_rate: float = 0.0, vent_rate: float = 0.0) -> dict:
"""
执行一个控制周期
"""
dt = self.config.sample_interval
valve_open = (self.valve.state == ValveState.OPEN)
# 1. 储罐压力演化
pressure = self.tank.step(dt, valve_open, feed_rate, vent_rate)
# 2. 压力变送器采样
reading = self.transmitter.read(pressure, self.tank.time)
# 3. 保护逻辑评估
command = self.logic.evaluate(reading)
# 4. 阀门执行
valve_state = self.valve.apply_command(command)
# 5. 记录历史
record = {
"step": self._step_count,
"time": self.tank.time,
"pressure": pressure,
"signal_status": reading.status.value,
"valve_state": valve_state.value,
"command_reason": command.reason,
"logic_state": command.logic_state,
}
self._history.append(record)
self._step_count += 1
return record
def get_status(self) -> dict:
"""获取当前系统状态"""
return {
"tank_id": self.config.tank_id,
"pressure": self.tank.pressure,
"valve_state": self.valve.state.value,
"steps": self._step_count,
"config": {
"P_H": self.config.pressure_high,
"P_L": self.config.pressure_low,
"H": self.config.hysteresis,
}
}
def get_history(self) -> list:
"""获取历史记录"""
return self._history
# ============================================================
# 7. 演示:典型工况仿真
# ============================================================
def demo():
"""演示密封储罐压力保护逻辑"""
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
datefmt="%H:%M:%S",
)
print("=" * 68)
print(" 密封储罐压力保护逻辑模拟")
print(" 参考: 哈尔滨工程大学《工业过程控制》第8章")
print("=" * 68)
# 配置储罐参数
config = TankConfig(
tank_id="TK-101",
design_pressure=500.0,
pressure_high=300.0, # 压力高限
pressure_low=20.0, # 压力低限
hysteresis=20.0, # 滞环宽度
fail_safe_closed=True, # 故障关(FC)
sample_interval=0.5,
)
# 创建保护系统(加入0.5%信号故障概率)
system = ProtectionSystem(config, fault_prob=0.005)
print("\n【工况1: 正常氮封维持】")
print(" 进料=0, 出料=0, 初始压力=50 Pa")
for _ in range(10):
rec = system.step(feed_rate=0, vent_rate=0)
print(f" t={rec['time']:5.1f}s P={rec['pressure']:7.2f} Pa "
f"阀={rec['valve_state']:4s} {rec['command_reason']}")
print("\n【工况2: 进料过快,压力上升】")
print(" 进料=80, 出料=0")
for _ in range(15):
rec = system.step(feed_rate=80, vent_rate=0)
print(f" t={rec['time']:5.1f}s P={rec['pressure']:7.2f} Pa "
f"阀={rec['valve_state']:4s} {rec['command_reason']}")
print("\n【工况3: 压力回落,滞环关阀】")
print(" 进料=0, 出料=0")
for _ in range(15):
rec = system.step(feed_rate=0, vent_rate=0)
print(f" t={rec['time']:5.1f}s P={rec['pressure']:7.2f} Pa "
f"阀={rec['valve_state']:4s} {rec['command_reason']}")
print("\n【工况4: 出料过快,压力下降(防负压保护)】")
print(" 进料=0, 出料=120")
for _ in range(15):
rec = system.step(feed_rate=0, vent_rate=120)
print(f" t={rec['time']:5.1f}s P={rec['pressure']:7.2f} Pa "
f"阀={rec['valve_state']:4s} {rec['command_reason']}")
print("\n【工况5: 模拟信号故障】")
print(" 强制触发变送器故障,验证故障安全位")
# 临时提高故障概率
system.transmitter.fault_prob = 1.0
rec = system.step(feed_rate=0, vent_rate=0)
print(f" t={rec['time']:5.1f}s P={rec['pressure']:7.2f} Pa "
f"阀={rec['valve_state']:4s} {rec['command_reason']}")
print("\n" + "=" * 68)
print(" 最终系统状态:")
status = system.get_status()
for k, v in status.items():
if k != "config":
print(f" {k}: {v}")
print(f" 保护参数: P_H={status['config']['P_H']} Pa, "
f"P_L={status['config']['P_L']} Pa, H={status['config']['H']} Pa")
print("=" * 68)
if __name__ == "__main__":
demo()
</details>
4.3 典型运行输出
====================================================================
密封储罐压力保护逻辑模拟
参考: 哈尔滨工程大学《工业过程控制》第8章
====================================================================
【工况1: 正常氮封维持】
进料=0, 出料=0, 初始压力=50 Pa
t= 0.0s P= 50.00 Pa 阀=关闭 压力在安全区(20.0 Pa < 50.00 Pa < 300.0 Pa), 保持关闭防抖
t= 0.5s P= 50.00 Pa 阀=关闭 压力在安全区(20.0 Pa < 50.00 Pa < 300.0 Pa), 保持关闭防抖
...
【工况2: 进料过快,压力上升】
进料=80, 出料=0
t= 5.0s P= 54.00 Pa 阀=关闭 压力在安全区(20.0 Pa < 54.00 Pa < 300.0 Pa), 保持关闭防抖
t= 10.0s P= 62.00 Pa 阀=关闭 压力在安全区(20.0 Pa < 62.00 Pa < 300.0 Pa), 保持关闭防抖
...
t= 15.5s P= 300.40 Pa 阀=开启 压力高于上限(300.40 Pa ≥ 300.0 Pa), 泄压保护
t= 16.0s P= 290.40 Pa 阀=开启 压力回落至滞环区(290.40 Pa ≤ 280.0 Pa), 关阀
【工况3: 压力回落,滞环关阀】
进料=0, 出料=0
t= 20.0s P= 270.40 Pa
利用AI解决实际问题,如果你觉得这个工具好用,欢迎关注长安牧笛!