物理约束决策
EnerOS 遵循「约束即法律」原则:任何 Agent 决策在落地前都必须通过内核约束引擎的校验,违反物理规律的命令会被直接拦截并投影到最近可行解。这一机制确保 Agent 智能永远在电力系统物理可行域内运行。
这是 EnerOS 区别于通用 AgentOS 的根本特征之一。在通用 AI Agent 系统中,模型输出的「不可行」决策最多导致业务失败;而在电力系统中,一次违反物理规律的开关操作可能引发大面积停电、设备损毁甚至人身事故。约束引擎以微秒级开销为代价,把所有 Agent 决策锁进物理可行域。
设计哲学
「约束即法律」包含三层含义:
- 强制执行:约束校验不是可选步骤,而是内核命令通道的必经环节,Agent 无法绕过
- 物理可行:所有约束基于电力系统物理规律(潮流方程、热稳定极限、稳定边界),而非业务规则
- 可行投影:违反约束的命令不会被简单拒绝,而是投影到最近可行解,避免 Agent 陷入死循环
约束引擎架构
约束引擎位于内核态,分为三层:声明层(DSL)、校验层(同步)、投影层(异步)。
┌──────────────────────────────────────────────────┐
│ 声明层 ConstraintBuilder (DSL) │
│ - 类型化约束构造器 │
│ - 编译期类型检查 │
└───────────────────┬──────────────────────────────┘
│ register()
┌───────────────────┴──────────────────────────────┐
│ 校验层 ConstraintEngine.validate() (同步) │
│ - 单约束 < 5μs │
│ - 全量约束 < 200μs │
│ - 返回 ValidationResult │
└───────────────────┬──────────────────────────────┘
│ 若违反
┌───────────────────┴──────────────────────────────┐
│ 投影层 ConstraintEngine.project() (异步) │
│ - QP/LP 求解器 │
│ - 加权最小二乘 │
│ - 返回最近可行解 │
└──────────────────────────────────────────────────┘
约束引擎初始化
use eneros_constraint::{ConstraintEngine, Constraint, ConstraintBuilder};
let mut engine = ConstraintEngine::new();
// 声明约束
engine.register(ConstraintBuilder::voltage_limit(0.95, 1.05));
engine.register(ConstraintBuilder::thermal_limit(bus_id, 100.0)); // MW
engine.register(ConstraintBuilder::line_flow_limit(branch_id, 50.0));
engine.register(ConstraintBuilder::frequency_band(49.5, 50.5));
engine.register(ConstraintBuilder::power_balance(tolerance: 0.1));
engine.register(ConstraintBuilder::reserve_requirement(0.05)); // 5% 负荷
ConstraintBuilder 方法一览
| 方法 | 入参 | 含义 | 严格度 |
|---|---|---|---|
| voltage_limit | min, max (p.u.) | 母线电压幅值上下限 | 硬约束 |
| thermal_limit | element_id, MVA | 元件热稳定极限 | 硬约束 |
| line_flow_limit | branch_id, MW | 支路有功上限 | 硬约束 |
| frequency_band | min, max (Hz) | 系统频率允许范围 | 硬约束 |
| power_balance | tolerance (MW) | 总发=总用+损耗 | 硬约束 |
| reserve_requirement | percent | 最小备用比例 | 软约束 |
| phase_angle_diff | bus_a, bus_b, deg | 两节点相角差上限 | 硬约束 |
| short_circuit_capacity | bus_id, MVA | 母线短路容量上限 | 硬约束 |
| contingency | base, contingency, post | N-1 故障后满足约束 | 计划约束 |
| stability_margin | percent | 暂态稳定裕度 | 软约束 |
约束 DSL
约束采用类型化 DSL,避免字符串解析的运行时风险,且支持编译期检查。
use eneros_constraint::{Constraint, Contingency};
// 电压约束
let v = Constraint::voltage()
.bus(1)
.min(0.95)
.max(1.05)
.build();
// 热稳定约束
let thermal = Constraint::thermal()
.element(branch_id)
.limit_mva(50.0)
.emergency_limit_mva(60.0)
.duration_secs(15 * 60)
.build();
// N-1 约束
let n_minus_1 = Constraint::contingency()
.base_case(v.clone())
.contingency(Contingency::branch_out(5))
.post_contingency(v.clone())
.build();
// 频率约束
let freq = Constraint::frequency()
.min(49.5)
.max(50.5)
.rocof_max(0.5) // Hz/s 频率变化率
.build();
// 备用约束(软约束)
let reserve = Constraint::reserve()
.load_following(50.0) // MW
.spinning(100.0) // MW
.non_spinning(200.0) // MW
.severity(Severity::Soft)
.build();
Constraint 字段说明
| 字段 | 类型 | 含义 |
|---|---|---|
| id | ConstraintId | 约束唯一标识 |
| kind | ConstraintKind | 电压/热稳定/频率/备用/N-1 等 |
| scope | Scope | 作用范围:母线、支路、系统 |
| severity | Severity | Hard(硬)/ Soft(软)/ Planning(计划) |
| penalty | f64 | 软约束违反时的罚因子 |
| enabled | bool | 是否启用 |
| tags | Vec | 用户标签,便于分组查询 |
决策校验
Agent 产生的命令在执行前必须经过 validate:
use eneros_constraint::{ValidationResult, DispatchDecision};
let decision = DispatchDecision::new()
.set_generator(gen_id, 80.0) // MW
.open_branch(branch_id)
.adjust_tap(xfmr_id, 5)
.set_load(bus_id, 50.0); // MW
match engine.validate(&decision, &network) {
ValidationResult::Ok => {
ctx.execute(decision).await?;
}
ValidationResult::Violated(violations) => {
log::warn!("决策违反约束: {:?}", violations);
// 投影到最近可行解
let projected = engine.project(&decision, &network)?;
ctx.execute(projected).await?;
}
ValidationResult::Fatal(fatal) => {
log::error!("致命违反: {:?}", fatal);
ctx.emergency_block().await?;
}
}
ValidationResult 变体
| 变体 | 含义 | 处理方式 |
|---|---|---|
| Ok | 所有约束满足 | 直接执行 |
| Violated | 软/硬约束违反,可投影 | 调用 project() 投影 |
| Fatal | 致命违反(如频率失稳) | 紧急闭锁,停止 Agent |
Violation 结构
pub struct Violation {
pub constraint_id: ConstraintId,
pub kind: ConstraintKind,
pub element: ElementId,
pub actual: f64,
pub limit: f64,
pub deviation: f64,
pub severity: Severity,
pub message: String,
}
可行性投影
当原始决策违反约束时,投影层会基于 QP/LP 求解器将其投影到约束可行域的最近边界,避免简单拒绝导致的 Agent 陷入死循环。
use eneros_constraint::ProjectionStrategy;
// 投影策略:加权最小二乘
let strategy = ProjectionStrategy::WeightedLeastSquares {
weights: weights_map,
max_iter: 100,
tolerance: 1e-6,
};
let projected = engine.project_with(&decision, &network, strategy)?;
// projected 满足所有硬约束,且与原始决策距离最小
ProjectionStrategy 变体
| 策略 | 适用场景 | 求解器 | 典型耗时 |
|---|---|---|---|
| WeightedLeastSquares | 一般调度决策 | QP(OSQP) | < 20ms |
| LeastSquares | 等权重投影 | QP(OSQP) | < 15ms |
| LinearProgram | 纯线性约束 | LP(HiGHS) | < 5ms |
| MinShift | 最小调整量 | MILP | < 50ms |
| PriorityBased | 按优先级调整 | 启发式 | < 1ms |
自定义投影示例
use eneros_constraint::{Projector, Decision};
struct EconomicProjector {
gen_costs: Vec<f64>,
}
impl Projector for EconomicProjector {
fn project(
&self,
decision: &Decision,
constraints: &[Constraint],
network: &NetworkGraph,
) -> ProjectResult {
// 按发电成本升序调整,优先降成本高的机组
let mut sorted_gens = decision.gen_changes.clone();
sorted_gens.sort_by(|a, b| {
self.gen_costs[a.gen_id as usize]
.partial_cmp(&self.gen_costs[b.gen_id as usize])
.unwrap()
});
let mut projected = decision.clone();
for c in constraints {
if c.is_violated(&projected, network) {
self.adjust_for(&mut projected, c, &sorted_gens)?;
}
}
Ok(projected)
}
}
let projected = engine.project_with_strategy(&decision, &network, EconomicProjector { gen_costs })?;
约束类型矩阵
| 类别 | 约束 | 严格度 | 检查时机 |
|---|---|---|---|
| 电压 | V_min ≤ V_i ≤ V_max | 硬约束 | 实时 + 决策前 |
| 热稳定 | I_ij ≤ I_max | 硬约束 | 实时 + 决策前 |
| 频率 | f_min ≤ f ≤ f_max | 硬约束 | 实时 |
| 频率变化率 | df/dt | ≤ RoCoF_max | |
| 功率平衡 | ΣP_gen = ΣP_load + Loss | 硬约束 | 决策前 |
| 备用 | Reserve ≥ 5% Load | 软约束 | 计划 |
| N-1 | 单一故障后满足上述 | 计划约束 | 计划 |
| 短路容量 | I_sc ≤ I_sc_max | 硬约束 | 规划 |
| 暂态稳定 | 稳定裕度 ≥ 阈值 | 软约束 | 在线 |
| 电压稳定 | 裕度 ≥ 阈值 | 软约束 | 在线 |
内核强制执行
约束引擎作为内核模块,挂在命令执行通道上,所有 ctx.execute(decision) 调用都会自动经过约束校验,Agent 无法绕过:
use eneros_constraint::KernelHook;
// 内核启动时挂载约束钩子
KernelHook::mount(&constraint_engine)?;
// 此后所有 ctx.execute() 自动调用:
// 1. engine.validate(&decision, &network)
// 2. 若 Violated → 自动调用 project()
// 3. 若 Fatal → 拦截并触发 emergency_block
N-1 校验
针对关键场景的 N-1 安全校核:
use eneros_constraint::{ContingencySet, Contingency};
let contingencies = ContingencySet::new()
.add(Contingency::branch_out(5))
.add(Contingency::branch_out(12))
.add(Contingency::generator_out(gen_id))
.add(Contingency::bus_split(bus_id));
let report = engine.n_minus_1_check(&network, &contingencies)?;
for case in &report.cases {
if !case.feasible {
println!("故障 {:?} 后违反: {:?}", case.contingency, case.violations);
}
}
println!("通过率: {}/{}", report.passed, report.total);
性能指标
| 操作 | 延迟 | 备注 |
|---|---|---|
| 单约束校验 | < 5μs | 缓存命中 |
| 全量约束校验(1000 节点) | < 200μs | 含潮流结果 |
| 全量约束校验(10万 节点) | < 5ms | 含潮流结果 |
| 可行性投影(LP, 1000 变量) | < 5ms | HiGHS |
| 可行性投影(QP, 1000 变量) | < 20ms | OSQP |
| N-1 校验(100 故障集) | < 50ms | 并行 |
| 约束注册 | < 10μs | - |
| 约束热更新 | < 100μs | 不中断校验 |
测试环境:4 核 / 8GB / Ubuntu 22.04,节点数 1000。
配置参数
eneros.toml 中约束相关配置:
[constraint]
# 约束校验严格度:strict / permissive / audit_only
mode = "strict"
# 是否自动投影违反决策
auto_project = true
# 默认投影策略:wls / ls / lp / min_shift / priority
default_projection = "wls"
# N-1 校验并行度
n_minus_1_parallelism = 4
# 软约束罚因子(默认)
soft_penalty = 1000.0
# 致命违反是否触发紧急闭锁
fatal_emergency_block = true
# 约束缓存大小
cache_size = 4096
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| mode | enum | strict | 校验模式 |
| auto_project | bool | true | 自动投影违反决策 |
| default_projection | enum | wls | 默认投影策略 |
| n_minus_1_parallelism | u32 | 4 | N-1 校验并行度 |
| soft_penalty | f64 | 1000.0 | 软约束默认罚因子 |
| fatal_emergency_block | bool | true | 致命违反触发闭锁 |
| cache_size | u32 | 4096 | 约束结果缓存条数 |
与其他能力的关系
| 关联能力 | 互动方式 |
|---|---|
| 电网拓扑一等公民 | 约束基于拓扑计算 |
| 安全守卫 | 约束引擎是安全网关的核心组件 |
| 数字孪生引擎 | What-If 推演调用约束校验 |
| 多智能体协作 | Agent 决策必经约束校验 |
| 实时双执行域 | 实时域约束校验在微秒级完成 |
| 设备模型库 | 热稳定极限来自设备参数 |
| 时序原生操作 | 约束违反事件入库 |