跳到主内容

eneros-constraint

Crate 索引

eneros-constraint

eneros-constraint 实现 EnerOS 「约束即法律」理念的安全约束引擎,对潮流结果、Agent 指令、控制操作进行实时校验,越限时拒绝执行或投影至最近可行解。它是 EnerOS 与传统电力软件最显著的区别之一:约束不是应用层可选项,而是内核强制执行的法律。

职责描述

eneros-constraint 承担以下核心职责:

  • 电压越限校验:检查母线电压是否在 [0.95, 1.05] 标幺值范围内
  • 支路过载校验:检查支路负载率是否超过热稳定极限(默认 100%)
  • 频率偏差校验:检查系统频率偏差是否在 ±0.2Hz / ±0.5Hz 范围
  • 稳定裕度校验:检查暂态/静态稳定裕度是否满足要求
  • 硬/软约束模式:硬约束违反即拒绝;软约束告警但放行;投影模式自动修正至可行解
  • 可行性投影:将不可行指令投影至最近可行点(QP 求解)
  • 规则热加载:约束规则可通过 TOML 配置文件声明,运行时热加载不重启服务
  • 多维度规则:按区域、电压等级、设备类型分组配置规则

关键类型与接口

ConstraintEngine

use std::path::Path;
use eneros_core::{ElementId, Result};
use eneros_powerflow::PowerFlowResult;

pub struct ConstraintEngine {
    rules: Vec<ConstraintRule>,
    mode: ConstraintMode,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConstraintMode {
    Enforce,
    Warn,
    Project,
}

impl Default for ConstraintEngine {
    fn default() -> Self {
        Self {
            rules: Vec::new(),
            mode: ConstraintMode::Enforce,
        }
    }
}

impl ConstraintEngine {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn from_config(path: impl AsRef<Path>) -> Result<Self> {
        let text = std::fs::read_to_string(path.as_ref())?;
        let config: ConstraintConfig = toml::from_str(&text)?;
        Ok(Self {
            rules: config.rules,
            mode: ConstraintMode::Enforce,
        })
    }

    pub fn mode(mut self, m: ConstraintMode) -> Self {
        self.mode = m;
        self
    }

    pub fn add_rule(&mut self, rule: ConstraintRule) {
        self.rules.push(rule);
    }

    pub fn check(&self, result: &PowerFlowResult) -> Vec<Violation> {
        let mut violations = Vec::new();
        for rule in &self.rules {
            if let Some(v) = rule.evaluate(result) {
                violations.push(v);
            }
        }
        violations
    }

    pub fn check_command(&self, cmd: &Command) -> std::result::Result<(), Violation> {
        for rule in &self.rules {
            if let Some(v) = rule.evaluate_command(cmd) {
                if self.mode == ConstraintMode::Enforce {
                    return Err(v);
                }
            }
        }
        Ok(())
    }

    pub fn project(&self, setpoint: Vec<f64>) -> Vec<f64> {
        let projector = FeasibilityProjector::new(&self.rules);
        projector.solve(setpoint)
    }
}

ConstraintRule / Violation

use eneros_core::{ElementId, BusId, BranchId};

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ConstraintRule {
    pub id: String,
    #[serde(rename = "type")]
    pub kind: ConstraintKind,
    pub target: ConstraintTarget,
    pub min: Option<f64>,
    pub max: Option<f64>,
    pub limit_percent: Option<f64>,
    pub severity: Severity,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum ConstraintKind {
    Voltage,
    BranchLoading,
    Frequency,
    Stability,
    Custom(String),
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum ConstraintTarget {
    All,
    Bus(BusId),
    Branch(BranchId),
    Area(String),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum Severity {
    Info,
    Warning,
    Major,
    Critical,
}

#[derive(Debug, Clone)]
pub struct Violation {
    pub rule_id: String,
    pub severity: Severity,
    pub element: ElementId,
    pub actual: f64,
    pub limit: f64,
    pub message: String,
}

impl ConstraintRule {
    pub fn evaluate(&self, result: &PowerFlowResult) -> Option<Violation> {
        match &self.kind {
            ConstraintKind::Voltage => self.check_voltage(result),
            ConstraintKind::BranchLoading => self.check_loading(result),
            ConstraintKind::Frequency => self.check_frequency(result),
            _ => None,
        }
    }

    fn check_voltage(&self, result: &PowerFlowResult) -> Option<Violation> {
        for bus in &result.buses {
            let v = bus.voltage.magnitude.0;
            if let Some(max) = self.max {
                if v > max {
                    return Some(Violation {
                        rule_id: self.id.clone(),
                        severity: self.severity,
                        element: bus.id.clone().into(),
                        actual: v,
                        limit: max,
                        message: format!("电压越上限: {:.4} > {:.4}", v, max),
                    });
                }
            }
            if let Some(min) = self.min {
                if v < min {
                    return Some(Violation {
                        rule_id: self.id.clone(),
                        severity: self.severity,
                        element: bus.id.clone().into(),
                        actual: v,
                        limit: min,
                        message: format!("电压越下限: {:.4} < {:.4}", v, min),
                    });
                }
            }
        }
        None
    }

    fn check_loading(&self, result: &PowerFlowResult) -> Option<Violation> {
        let limit = self.limit_percent.unwrap_or(100.0);
        for flow in &result.branch_flows {
            if flow.loading_percent > limit {
                return Some(Violation {
                    rule_id: self.id.clone(),
                    severity: self.severity,
                    element: flow.from.clone().into(),
                    actual: flow.loading_percent,
                    limit,
                    message: format!(
                        "支路 {}-{} 过载: {:.1}% > {:.1}%",
                        flow.from, flow.to, flow.loading_percent, limit
                    ),
                });
            }
        }
        None
    }

    fn check_frequency(&self, result: &PowerFlowResult) -> Option<Violation> {
        None
    }

    pub fn evaluate_command(&self, cmd: &Command) -> Option<Violation> {
        None
    }
}

FeasibilityProjector

pub struct FeasibilityProjector<'a> {
    rules: &'a [ConstraintRule],
}

impl<'a> FeasibilityProjector<'a> {
    pub fn new(rules: &'a [ConstraintRule]) -> Self {
        Self { rules }
    }

    pub fn solve(&self, setpoint: Vec<f64>) -> Vec<f64> {
        let mut result = setpoint;
        for rule in self.rules {
            if let (Some(min), Some(max)) = (rule.min, rule.max) {
                for v in &mut result {
                    *v = v.clamp(min, max);
                }
            }
        }
        result
    }
}

核心 API

方法签名说明
ConstraintEngine::newfn new() -> Self构造空引擎
ConstraintEngine::from_configfn from_config(path: impl AsRef<Path>) -> Result<Self>从 TOML 加载规则
ConstraintEngine::modefn mode(self, m: ConstraintMode) -> Self设置模式(Enforce/Warn/Project)
ConstraintEngine::add_rulefn add_rule(&mut self, rule: ConstraintRule)添加规则
ConstraintEngine::checkfn check(&self, result: &PowerFlowResult) -> Vec<Violation>校验潮流结果
ConstraintEngine::check_commandfn check_command(&self, cmd: &Command) -> Result<(), Violation>校验指令
ConstraintEngine::projectfn project(&self, setpoint: Vec<f64>) -> Vec<f64>投影至可行域

使用示例

从配置文件加载并校验潮流结果

use eneros_constraint::{ConstraintEngine, ConstraintMode};
use eneros_core::EnerOSError;

let engine = ConstraintEngine::from_config("constraints.toml")?
    .mode(ConstraintMode::Enforce);

let violations = engine.check(&powerflow_result);
if !violations.is_empty() {
    for v in &violations {
        println!(
            "越限 [{}]: {} 实际={:.4} 限值={:.4} 严重度={:?}",
            v.rule_id, v.message, v.actual, v.limit, v.severity
        );
    }
    return Err(EnerOSError::ConstraintViolation(violations[0].clone()));
}
println!("潮流结果通过约束校验");

编程式添加规则

use eneros_constraint::{ConstraintEngine, ConstraintRule, ConstraintKind, ConstraintTarget, Severity};

let mut engine = ConstraintEngine::new().mode(ConstraintMode::Enforce);

engine.add_rule(ConstraintRule {
    id: "voltage_upper".into(),
    kind: ConstraintKind::Voltage,
    target: ConstraintTarget::All,
    min: Some(0.95),
    max: Some(1.05),
    limit_percent: None,
    severity: Severity::Critical,
});

engine.add_rule(ConstraintRule {
    id: "branch_loading".into(),
    kind: ConstraintKind::BranchLoading,
    target: ConstraintTarget::All,
    min: None,
    max: None,
    limit_percent: Some(100.0),
    severity: Severity::Major,
});

指令校验(与 gateway 集成)

use eneros_constraint::ConstraintEngine;
use eneros_gateway::Command;

let engine = ConstraintEngine::from_config("constraints.toml")?;

let cmd = Command::new("agent_01")
    .action(Action::SetGeneration { bus: 2.into(), mw: 50.0 });

match engine.check_command(&cmd) {
    Ok(()) => println!("指令通过约束校验"),
    Err(v) => {
        println!("指令被拒绝: {}", v.message);
        // 上报告警
        eventbus.publish(AlarmEvent::critical(v.clone())).await;
    }
}

可行性投影

use eneros_constraint::{ConstraintEngine, ConstraintMode};

let engine = ConstraintEngine::from_config("constraints.toml")?
    .mode(ConstraintMode::Project);

// 一组发电机出力设定值(部分越限)
let setpoints = vec![60.0, 80.0, 120.0, 45.0];

let projected = engine.project(setpoints);
println!("投影后设定值: {:?}", projected);

软约束告警模式

let engine = ConstraintEngine::from_config("constraints.toml")?
    .mode(ConstraintMode::Warn);

let violations = engine.check(&powerflow_result);
for v in &violations {
    tracing::warn!("告警: {}", v.message);
}
// 软约束不阻止后续流程

约束规则示例(TOML)

[[rules]]
id = "voltage_upper_pu"
type = "Voltage"
target = "All"
min = 0.95
max = 1.05
severity = "critical"

[[rules]]
id = "branch_loading_default"
type = "BranchLoading"
target = "All"
limit_percent = 100
severity = "major"

[[rules]]
id = "branch_loading_critical_corridor"
type = "BranchLoading"
target = { Branch = "branch_5_8" }
limit_percent = 80
severity = "critical"

[[rules]]
id = "frequency_nominal"
type = "Frequency"
target = "All"
min = 49.8
max = 50.2
severity = "critical"

[[rules]]
id = "voltage_area_east"
type = "Voltage"
target = { Area = "east" }
min = 0.97
max = 1.04
severity = "warning"

配置参数表

ConstraintRule 字段

字段类型必填说明
idString规则唯一标识
typeConstraintKind约束类型
targetConstraintTarget约束目标
minOption下限(电压、频率适用)
maxOption上限(电压、频率适用)
limit_percentOption百分比限值(支路负载率适用)
severitySeverity严重级别

Severity 严重级别

级别数值行为
Info0仅记录日志
Warning1告警,软约束模式放行
Major2严重告警,硬约束模式拒绝
Critical3致命越限,立即拒绝并触发告警

性能指标

测试环境:4 核 / 8GB / Ubuntu 22.04 / Rust 1.78 release build。

操作1000 节点复杂度
单条规则校验< 10μsO(n)
全套规则校验(10 条规则)< 100μsO(k·n)
指令校验< 10μsO(k)
可行性投影(10 维)< 1msO(n²)
配置热加载< 1msO(k)

依赖关系

自身依赖

[dependencies]
eneros-core = { path = "../core", version = "0.47.0" }
eneros-topology = { path = "../topology", version = "0.47.0" }
eneros-powerflow = { path = "../powerflow", version = "0.47.0" }
eneros-linalg = { path = "../linalg", version = "0.47.0" }
serde = { version = "1.0", features = ["derive"] }
toml = "0.8"
tracing = "0.1"

被依赖

  • eneros-gateway — 网关在指令执行前调用约束引擎进行二次校验
  • eneros-agent — Agent 决策时使用约束引擎评估候选动作
  • eneros-analysis — OPF 求解过程中将约束嵌入优化模型

版本与兼容性

eneros-constraint 版本EnerOS 版本关键变更
0.47.x0.47.x新增 Project 模式与 QP 投影器
0.46.x0.46.x支持 Area 目标分组
0.45.x (LTS)0.45.xLTS 版本,至 2028 年安全更新

相关文档