约束即法律
Constraint as Kernel Law 是 EnerOS 的安全基石:电力系统的物理约束(电压越限、支路过载、频率偏差、稳定极限)不是应用层可选的校验步骤,而是操作系统内核强制执行的法律。任何违反物理约束的 Agent 决策都会在内核态被直接拒绝,必要时自动投影到最近可行解,从架构层面杜绝”不可执行的决策”。
设计动机
传统约束校验的缺陷
传统电力软件将约束校验置于应用层,存在严重安全隐患:
| 缺陷 | 描述 | 事故案例 |
|---|---|---|
| 校验遗漏 | 部分应用未实现约束校验 | 电压越限导致设备损坏 |
| 顺序错误 | 校验在执行之后 | 误操作已下发 |
| 一致性问题 | 多应用约束冲突 | 不同应用相互覆盖 |
| 性能损耗 | 应用层重复计算 | 决策延迟增加 |
| 不可审计 | 校验日志分散 | 事故难以追溯 |
| 缺乏投影 | 拒绝即结束 | 决策无法落地 |
约束即法律的解决思路
EnerOS 将约束校验下沉到内核,所有写操作必须经过约束引擎:
Agent 决策命令
│
▼
┌─────────────┐
│ 安全网关 │ ← 实时执行域拦截
└─────┬───────┘
│
▼
┌─────────────┐ 通过 ┌──────────┐
│ 约束引擎 │ ──────────→ │ 执行命令 │
│ 校验 │ └──────────┘
└─────┬───────┘
│ 违反
▼
┌─────────────┐ 可投影 ┌──────────┐
│ 投影器 │ ──────────→ │ 投影命令 │
└─────┬───────┘ └──────────┘
│ 不可投影
▼
┌─────────────┐
│ 拒绝 + 告警 │
└─────────────┘
核心约束类型
1. 电压约束
电压必须在安全范围内,否则会损坏设备或导致保护误动:
use eneros_constraint::{Constraint, VoltageLimit};
// 全网电压约束
let voltage_constraint = Constraint::voltage()
.name("全网电压安全")
.min(0.95) // 最小电压 0.95 pu
.max(1.05) // 最大电压 1.05 pu
.buses(network.all_buses()) // 应用到所有母线
.severity(Severity::Critical) // 严重等级
.action(Action::ProjectAndWarn);
// 紧约束(特殊母线更严格)
let tight_voltage = Constraint::voltage()
.name("关键母线电压")
.min(0.97)
.max(1.03)
.buses(vec![BusId(1), BusId(5)])
.severity(Severity::Critical)
.action(Action::Reject);
constraint_engine.register(voltage_constraint)?;
constraint_engine.register(tight_voltage)?;
2. 电流/热极限约束
线路和变压器的载流不能超过热稳定极限,否则会过热损坏:
use eneros_constraint::{Constraint, ThermalLimit};
// 单条线路热极限
let thermal_constraint = Constraint::thermal()
.name("线路 L-1 热极限")
.branch(BranchId::from("L-1"))
.limit_mva(100.0) // 长期允许 100 MVA
.emergency_limit_mva(120.0) // 紧急允许 120 MVA(15 分钟)
.emergency_duration(Duration::minutes(15))
.severity(Severity::High)
.action(Action::ProjectAndWarn);
// 变压器过载约束
let transformer_constraint = Constraint::thermal()
.name("变压器 T-1 过载")
.branch(BranchId::from("T-1"))
.limit_mva(63.0)
.emergency_limit_mva(75.0)
.emergency_duration(Duration::minutes(30))
.severity(Severity::High);
constraint_engine.register(thermal_constraint)?;
constraint_engine.register(transformer_constraint)?;
3. 频率约束
系统频率必须保持在额定值附近,否则会触发低频减载或机组跳闸:
let frequency_constraint = Constraint::frequency()
.name("系统频率安全")
.min_hz(49.5) // 正常最低 49.5 Hz
.max_hz(50.5) // 正常最高 50.5 Hz
.emergency_min_hz(49.0) // 紧急最低 49.0 Hz
.emergency_max_hz(51.0) // 紧急最高 51.0 Hz
.rocof_hz_per_s(2.0) // 频率变化率限制 2 Hz/s
.severity(Severity::Critical)
.action(Action::Reject);
constraint_engine.register(frequency_constraint)?;
4. 稳定约束
包括暂态稳定、静态稳定、电压稳定等动态约束:
use eneros_constraint::{StabilityConstraint, StabilityType};
// 暂态稳定约束
let transient = Constraint::stability()
.name("暂态稳定")
.stability_type(StabilityType::Transient)
.fault_clearing_time(Duration::milliseconds(100))
.critical_clearing_time(Duration::milliseconds(150))
.damping_ratio(0.05) // 阻尼比 ≥ 5%
.severity(Severity::Critical)
.action(Action::Reject);
// 电压稳定约束
let voltage_stability = Constraint::stability()
.name("电压稳定裕度")
.stability_type(StabilityType::Voltage)
.min_margin_percent(5.0) // 最小稳定裕度 5%
.severity(Severity::High)
.action(Action::ProjectAndWarn);
constraint_engine.register(transient)?;
constraint_engine.register(voltage_stability)?;
约束配置表格
完整约束类型一览
| 约束类型 | 适用对象 | 关键参数 | 默认动作 | 严重等级 |
|---|---|---|---|---|
| 电压约束 | 母线 | min, max (pu) | Project | Critical |
| 电流约束 | 支路 | limit_a (A) | Project | High |
| 热极限约束 | 支路 | limit_mva, emergency_limit_mva | Project | High |
| 频率约束 | 系统 | min_hz, max_hz, rocof | Reject | Critical |
| 有功约束 | 发电机 | min_mw, max_mw, ramp_rate | Project | Medium |
| 无功约束 | 发电机/SVC | min_mvar, max_mvar | Project | Medium |
| 相角差约束 | 支路两端 | max_angle_diff (度) | Project | High |
| 短路容量约束 | 母线 | max_scc_mva | Reject | Critical |
| 稳定约束 | 系统/区域 | 阻尼比, 裕度 | Reject | Critical |
| 备用约束 | 系统 | min_reserve_mw | Reject | High |
| N-1 约束 | 任意支路 | 任一元件退出后满足上述约束 | Warn | High |
严重等级与处理动作
| 严重等级 | 默认动作 | 说明 |
|---|---|---|
| Critical | Reject | 直接拒绝,记录告警 |
| High | ProjectAndWarn | 投影到可行解,记录告警 |
| Medium | Project | 投影到可行解 |
| Low | Warn | 仅记录告警,允许执行 |
约束引擎工作原理
引擎架构
┌──────────────────────────────────────────┐
│ 约束引擎 (ConstraintEngine) │
├──────────────────────────────────────────┤
│ ┌─────────────┐ ┌──────────────────┐ │
│ │ 约束注册表 │ │ 约束索引(按对象)│ │
│ └─────────────┘ └──────────────────┘ │
├──────────────────────────────────────────┤
│ ┌─────────────┐ ┌──────────────────┐ │
│ │ 校验器 │ │ 投影器 │ │
│ │ (Validator) │ │ (Projector) │ │
│ └─────────────┘ └──────────────────┘ │
├──────────────────────────────────────────┤
│ ┌─────────────┐ ┌──────────────────┐ │
│ │ 审计日志 │ │ 告警通道 │ │
│ └─────────────┘ └──────────────────┘ │
└──────────────────────────────────────────┘
约束校验流程
use eneros_constraint::{ConstraintEngine, CheckResult, Violation};
let engine = ConstraintEngine::new(&network);
// 1. 加载约束(也可通过 eneros.toml 配置)
engine.load_constraints("config/constraints.toml")?;
// 2. 校验命令
let command = Command::SetGeneration { bus: 1, mw: 150.0 };
let result = engine.check(&command);
match result {
CheckResult::Ok => {
// 通过:命令满足所有约束
engine.execute(command).await?;
}
CheckResult::Violated(violations) => {
// 违反:根据约束的 action 决定后续
for v in &violations {
log::warn!("违反约束 {}: 当前={:?}, 限制={:?}",
v.constraint_name, v.actual, v.limit);
}
match violations.first().action {
Action::Reject => {
return Err(EnerOSError::ConstraintRejected(violations));
}
Action::Project => {
let feasible = engine.project(&command)?;
engine.execute(feasible).await?;
}
Action::ProjectAndWarn => {
let feasible = engine.project(&command)?;
engine.alert(AlertLevel::Warn, &violations).await?;
engine.execute(feasible).await?;
}
Action::Warn => {
engine.alert(AlertLevel::Info, &violations).await?;
engine.execute(command).await?;
}
}
}
}
约束投影算法
投影原理
当命令违反约束时,EnerOS 不是简单拒绝,而是计算满足所有约束的最近可行命令。投影问题形式化为:
minimize || x - x_orig ||_2
subject to g_i(x) ≤ 0, i = 1, ..., m
h_j(x) = 0, j = 1, ..., n
其中 x_orig 是原始命令,x 是投影后的可行命令,g_i 是不等式约束,h_j 是等式约束。
投影器实现
use eneros_constraint::{FeasibilityProjector, ProjectionMethod};
let projector = FeasibilityProjector::new(&constraints)
.method(ProjectionMethod::QuadraticProgramming) // QP 求解
.max_iter(100)
.tolerance(1e-6)
.timeout(Duration::milliseconds(10));
// 原始命令:发电 150 MW(违反最大 100 MW 约束)
let original = Command::SetGeneration { bus: 1, mw: 150.0 };
// 投影到可行解
let projected = projector.project(&original)?;
println!("原始: {:?}", original);
println!("投影: {:?}", projected);
// 输出:
// 原始: SetGeneration { bus: 1, mw: 150.0 }
// 投影: SetGeneration { bus: 1, mw: 100.0 }
多约束联合投影
实际场景中往往需要同时满足多个约束,EnerOS 使用 QP 求解器联合投影:
// 同时考虑:电压、热极限、备用约束
let command = Command::MultiDispatch {
dispatches: vec![
Dispatch { bus: 1, mw: 150.0 },
Dispatch { bus: 2, mw: 80.0 },
Dispatch { bus: 3, mw: 60.0 },
],
};
// 联合投影:调整所有发电出力,满足所有约束
let projected = projector.project(&command)?;
// projected 满足:
// - 母线电压 ∈ [0.95, 1.05]
// - 支路功率 ≤ 热极限
// - 系统备用 ≥ 50 MW
// - 总发电 = 总负荷 + 网损
投影方法对比
| 方法 | 算法 | 复杂度 | 精度 | 适用场景 |
|---|---|---|---|---|
| QuadraticProgramming | 二次规划 | O(n³) | 高 | 多约束联合 |
| ProjectionGradient | 投影梯度法 | O(n²) | 中 | 大规模问题 |
| PenaltyMethod | 罚函数法 | O(n²) | 中 | 非线性约束 |
| Heuristic | 启发式 | O(n log n) | 低 | 实时域快速投影 |
违规处理机制
三级处理策略
违反约束
│
├── Critical → Reject(拒绝执行)+ 紧急告警 + 审计记录
│
├── High → Project(投影可行解)+ 告警 + 审计记录
│
└── Medium/Low → Project 或 Warn + 审计记录
完整违规处理代码
use eneros_constraint::{Violation, ViolationHandler, AlertLevel};
let handler = ViolationHandler::new()
.enable_audit_log(true) // 启用审计日志
.enable_alert_channel(true) // 启用告警通道
.alert_topic("grid/alerts") // 告警主题
.max_alert_rate(100); // 最大告警速率(条/秒)
// 处理违规
async fn handle_violation(
handler: &ViolationHandler,
violations: &[Violation],
command: &Command,
) -> Result<Command, EnerOSError> {
// 1. 记录审计日志(不可省略)
handler.audit(AuditRecord {
timestamp: now!(),
command: command.clone(),
violations: violations.to_vec(),
source: source_agent_id(),
}).await?;
// 2. 根据严重等级处理
let max_severity = violations.iter()
.map(|v| v.severity)
.max()
.unwrap_or(Severity::Low);
match max_severity {
Severity::Critical => {
// 紧急告警
handler.alert(AlertLevel::Emergency, violations).await?;
Err(EnerOSError::ConstraintRejected(violations.to_vec()))
}
Severity::High => {
// 高级告警 + 投影
handler.alert(AlertLevel::High, violations).await?;
let projected = handler.project(command)?;
Ok(projected)
}
_ => {
// 中低级:投影或仅告警
let projected = handler.project(command)?;
Ok(projected)
}
}
}
约束配置文件
约束可通过 TOML 配置文件批量加载:
# config/constraints.toml
[[voltage]]
name = "全网电压安全"
min = 0.95
max = 1.05
buses = "all" # 或 ["bus_1", "bus_2"]
severity = "critical"
action = "project_and_warn"
[[voltage]]
name = "关键母线电压"
min = 0.97
max = 1.03
buses = ["bus_1", "bus_5"]
severity = "critical"
action = "reject"
[[thermal]]
name = "线路 L-1 热极限"
branch = "L-1"
limit_mva = 100.0
emergency_limit_mva = 120.0
emergency_duration_min = 15
severity = "high"
action = "project_and_warn"
[[frequency]]
name = "系统频率安全"
min_hz = 49.5
max_hz = 50.5
emergency_min_hz = 49.0
emergency_max_hz = 51.0
rocof_hz_per_s = 2.0
severity = "critical"
action = "reject"
[[stability]]
name = "暂态稳定"
type = "transient"
fault_clearing_ms = 100
critical_clearing_ms = 150
damping_ratio = 0.05
severity = "critical"
action = "reject"
[[reserve]]
name = "系统备用"
min_reserve_mw = 50.0
spinning_percent = 5.0
severity = "high"
action = "reject"
use eneros_constraint::ConstraintEngine;
let engine = ConstraintEngine::new(&network);
engine.load_constraints("config/constraints.toml")?;
// 启动后约束自动校验所有写操作
engine.start()?;
与 Agent 集成
Agent 决策的约束保护
use eneros_constraint::{ConstraintEngine, CheckResult};
impl Agent for DispatchAgent {
async fn run(&mut self, ctx: &mut AgentContext) -> AgentResult<()> {
// 1. Agent 生成决策命令
let command = self.optimize_dispatch().await?;
// 2. 提交到内核,约束引擎自动校验
let result = ctx.syscall(ExecuteCommand(command)).await?;
match result {
ExecuteResult::Executed => {
log::info!("调度命令已执行");
}
ExecuteResult::Projected(projected) => {
log::warn!("调度命令被投影: {:?}", projected);
self.learn_from_projection(projected); // 在线学习
}
ExecuteResult::Rejected(reason) => {
log::error!("调度命令被拒绝: {}", reason);
self.fallback_strategy().await?;
}
}
Ok(())
}
}
约束查询 API
Agent 可以主动查询约束,避免生成不可行命令:
impl Agent for DispatchAgent {
async fn run(&mut self, ctx: &mut AgentContext) -> AgentResult<()> {
// 1. 查询当前约束
let constraints = ctx.syscall(GetConstraints {
object: ConstraintObject::Bus(self.bus_id),
}).await?;
// 2. 在约束范围内优化
let voltage_limit = constraints.voltage_limit(self.bus_id);
let generation_limit = constraints.generation_limit(self.bus_id);
let target_mw = self.optimize_within(
generation_limit.min_mw,
generation_limit.max_mw,
)?;
// 3. 提交命令(不会违反约束)
ctx.syscall(SetGeneration { bus: self.bus_id, mw: target_mw }).await?;
Ok(())
}
}
性能指标
| 操作 | 延迟 | 吞吐 |
|---|---|---|
| 单约束校验 | < 1μs | 1000万次/秒 |
| 多约束校验(10 条) | < 5μs | 20万次/秒 |
| 约束投影(QP) | < 5ms | 200次/秒 |
| 约束投影(启发式) | < 100μs | 1万次/秒 |
| 约束配置加载 | < 50ms | - |
| 审计日志写入 | < 10μs | 10万次/秒 |
与传统方案对比
| 维度 | 传统约束校验 | EnerOS 约束即法律 |
|---|---|---|
| 校验位置 | 应用层 | 内核态 |
| 强制性 | 可选 | 强制 |
| 校验遗漏 | 易发生 | 不可能 |
| 约束投影 | 通常不支持 | 内置 QP 求解器 |
| 一致性 | 多应用可能冲突 | 内核统一 |
| 审计 | 分散日志 | 集中审计 |
| 性能 | 1-5ms | < 10μs |
| 配置 | 代码硬编码 | TOML 配置 + API |
| 动态更新 | 需重启 | 热加载 |
限制与权衡
| 权衡点 | 说明 | 缓解策略 |
|---|---|---|
| 内核开销 | 每条命令都需校验 | 约束索引加速,< 10μs |
| 投影可能不最优 | QP 求解局部最优 | 支持多种投影方法切换 |
| 约束冲突 | 多约束可能互相矛盾 | 启动时冲突检测 |
| 配置复杂 | 大量约束配置 | 提供标准模板与默认值 |
| 实时域限制 | 投影求解较慢 | 实时域使用启发式投影 |
下一步
- Power-Native First - 电力原生优先设计哲学
- 安全守卫 - 安全网关详解
- 实时确定性 - 约束校验在实时域的执行
- Agent-as-Grid-Node - Agent 权限边界