跳到主内容

约束即法律

核心概念

约束即法律

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)ProjectCritical
电流约束支路limit_a (A)ProjectHigh
热极限约束支路limit_mva, emergency_limit_mvaProjectHigh
频率约束系统min_hz, max_hz, rocofRejectCritical
有功约束发电机min_mw, max_mw, ramp_rateProjectMedium
无功约束发电机/SVCmin_mvar, max_mvarProjectMedium
相角差约束支路两端max_angle_diff (度)ProjectHigh
短路容量约束母线max_scc_mvaRejectCritical
稳定约束系统/区域阻尼比, 裕度RejectCritical
备用约束系统min_reserve_mwRejectHigh
N-1 约束任意支路任一元件退出后满足上述约束WarnHigh

严重等级与处理动作

严重等级默认动作说明
CriticalReject直接拒绝,记录告警
HighProjectAndWarn投影到可行解,记录告警
MediumProject投影到可行解
LowWarn仅记录告警,允许执行

约束引擎工作原理

引擎架构

┌──────────────────────────────────────────┐
│            约束引擎 (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μs1000万次/秒
多约束校验(10 条)< 5μs20万次/秒
约束投影(QP)< 5ms200次/秒
约束投影(启发式)< 100μs1万次/秒
约束配置加载< 50ms-
审计日志写入< 10μs10万次/秒

与传统方案对比

维度传统约束校验EnerOS 约束即法律
校验位置应用层内核态
强制性可选强制
校验遗漏易发生不可能
约束投影通常不支持内置 QP 求解器
一致性多应用可能冲突内核统一
审计分散日志集中审计
性能1-5ms< 10μs
配置代码硬编码TOML 配置 + API
动态更新需重启热加载

限制与权衡

权衡点说明缓解策略
内核开销每条命令都需校验约束索引加速,< 10μs
投影可能不最优QP 求解局部最优支持多种投影方法切换
约束冲突多约束可能互相矛盾启动时冲突检测
配置复杂大量约束配置提供标准模板与默认值
实时域限制投影求解较慢实时域使用启发式投影

下一步