跳到主内容

v0.3.0 版本说明

EnerOS v0.3.0

发布日期:2024年3月10日 版本代号:Topology(拓扑) Git Tag:v0.3.0 支持状态:内部预览(Internal Preview) Crate 总数:2 测试用例数:312

版本概述

EnerOS v0.3.0「Topology」是 EnerOS 首次引入电力领域专有数据模型的版本。本版本发布 eneros-topology crate,定义了电网拓扑的核心数据结构:Bus(母线)、Branch(支路)、Generator(发电机)三大节点类型,并基于 petgraph 构建了电网拓扑图数据结构。这是 EnerOS 从「通用操作系统」走向「电力原生操作系统」的分水岭——从此 EnerOS 的内核能够直接理解电网的电气连通性,而非把电网视为一堆无差别的「资源」。

v0.3.0 的拓扑模型设计参考了 IEC 61970 CIM(Common Information Model)标准,但并未完全照搬 CIM 的复杂类层次。CIM 是一个面向对象的数据模型,包含数百个类,适合作为企业级数据交换格式,但不适合作为操作系统内核的内存表示。EnerOS 采用了「CIM 兼容但更精简」的策略:对外通过 From/Into trait 与 CIM XML/RDF 格式互转,对内使用扁平化的 Rust struct,兼顾标准兼容性与运行时性能。例如,CIM 中 BusbarSectionConnectivityNodeTopologicalNode 三个类在 EnerOS 内核中统一表示为 Bus,避免了不必要的间接层。

本版本还首次集成了 IEEE 14-bus 标准测试系统,这是电力系统分析领域最常用的 benchmark。IEEE 14-bus 系统由 IEEE 电力系统委员会于 1962 年发布,包含 14 条母线、20 条支路、5 台发电机,模拟了一个典型的输电网络。EnerOS 将该测试系统内置为 eneros-topology::test_cases::ieee14 模块,方便开发者快速验证拓扑算法的正确性。此外,v0.3.0 引入了基本拓扑验证器,能够检查孤立节点、重复支路、电压等级不匹配等常见建模错误。

关键数据

指标数值说明
节点类型3Bus / Branch / Generator
拓扑图实现petgraph稀疏邻接矩阵
IEEE 测试系统114-bus
拓扑验证规则12覆盖常见建模错误
14-bus 加载耗时280 μs含解析与建图
拓扑遍历吞吐180 万节点/秒BFS 算法

新特性

1. Bus 母线节点

Bus 是电网拓扑的基本节点,表示电气上等电位的连接点。一条母线可能对应物理上的一个母线段,也可能对应多个通过开关闭合而连通的母线段(即「拓扑母线」)。

// crates/eneros-topology/src/bus.rs
use serde::{Deserialize, Serialize};
use std::fmt;

/// 母线唯一标识
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct BusId(pub u32);

impl fmt::Display for BusId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "bus:{}", self.0)
    }
}

/// 电压等级(单位:kV)
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum VoltageLevel {
    Lv0_4,      // 0.4 kV 低压
    Mv10,       // 10 kV 中压
    Mv35,       // 35 kV 中压
    Hv110,      // 110 kV 高压
    Hv220,      // 220 kV 高压
    Ehv500,     // 500 kV 超高压
    Uhv1000,    // 1000 kV 特高压
    Custom(f64),
}

impl VoltageLevel {
    pub fn kv(&self) -> f64 {
        match self {
            VoltageLevel::Lv0_4 => 0.4,
            VoltageLevel::Mv10 => 10.0,
            VoltageLevel::Mv35 => 35.0,
            VoltageLevel::Hv110 => 110.0,
            VoltageLevel::Hv220 => 220.0,
            VoltageLevel::Ehv500 => 500.0,
            VoltageLevel::Uhv1000 => 1000.0,
            VoltageLevel::Custom(v) => *v,
        }
    }
}

/// 母线类型(潮流计算用)
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum BusType {
    /// PQ 节点:负荷母线,有功无功给定
    PQ,
    /// PV 节点:发电机母线,有功电压给定
    PV,
    /// 平衡节点:电压幅值与相角给定
    Slack,
    /// 孤立节点:未连通
    Isolated,
}

/// 母线状态
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Bus {
    pub id: BusId,
    pub name: String,
    pub voltage_level: VoltageLevel,
    pub bus_type: BusType,
    /// 基准电压(单位:kV,用于标幺值换算)
    pub base_kv: f64,
    /// 电压幅值标幺值
    pub voltage_pu: f64,
    /// 电压相角(单位:弧度)
    pub angle_rad: f64,
    /// 区域编号
    pub area: u32,
    /// 区域控制误差分区
    pub zone: u32,
}

impl Bus {
    pub fn new(id: BusId, name: impl Into<String>, vl: VoltageLevel) -> Self {
        Self {
            id,
            name: name.into(),
            voltage_level: vl,
            bus_type: BusType::PQ,
            base_kv: vl.kv(),
            voltage_pu: 1.0,
            angle_rad: 0.0,
            area: 1,
            zone: 1,
        }
    }
}

2. Branch 支路

Branch 表示母线之间的电气连接,包括输电线路、变压器等。每条支路采用 PI 型等值电路模型。

// crates/eneros-topology/src/branch.rs
use crate::bus::BusId;
use serde::{Deserialize, Serialize};

/// 支路唯一标识
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct BranchId(pub u32);

/// 支路类型
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum BranchKind {
    Line,           // 输电线路
    Transformer,    // 变压器
    PhaseShifter,   // 移相变压器
}

/// PI 型等值电路参数(标幺值)
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PiParameters {
    /// 串联电阻 R (pu)
    pub r_pu: f64,
    /// 串联电抗 X (pu)
    pub x_pu: f64,
    /// 线路充电电容 B/2 (pu),变压器通常为 0
    pub b_half_pu: f64,
    /// 非标准变比(变压器用,线路为 1.0)
    pub tap_ratio: f64,
    /// 移相角(弧度,移相变压器用)
    pub phase_shift: f64,
}

impl PiParameters {
    /// 线路参数(无变比)
    pub fn line(r: f64, x: f64, b_half: f64) -> Self {
        Self { r_pu: r, x_pu: x, b_half_pu: b_half, tap_ratio: 1.0, phase_shift: 0.0 }
    }

    /// 变压器参数
    pub fn transformer(r: f64, x: f64, tap: f64) -> Self {
        Self { r_pu: r, x_pu: x, b_half_pu: 0.0, tap_ratio: tap, phase_shift: 0.0 }
    }
}

/// 支路
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Branch {
    pub id: BranchId,
    pub from_bus: BusId,
    pub to_bus: BusId,
    pub kind: BranchKind,
    pub params: PiParameters,
    /// 额定容量 MVA
    pub rate_mva: f64,
    /// 是否在线
    pub in_service: bool,
}

impl Branch {
    pub fn line(id: BranchId, from: BusId, to: BusId, params: PiParameters) -> Self {
        Self {
            id, from_bus: from, to_bus: to,
            kind: BranchKind::Line,
            params,
            rate_mva: 100.0,
            in_service: true,
        }
    }
}

3. Generator 发电机节点

// crates/eneros-topology/src/generator.rs
use crate::bus::BusId;
use serde::{Deserialize, Serialize};

/// 发电机标识
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct GeneratorId(pub u32);

/// 发电机类型
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum GeneratorKind {
    Thermal,    // 火电
    Hydro,      // 水电
    Nuclear,    // 核电
    Wind,       // 风电
    Solar,      // 光伏
    GasTurbine, // 燃气轮机
}

/// 发电机模型
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Generator {
    pub id: GeneratorId,
    pub bus: BusId,
    pub kind: GeneratorKind,
    /// 额定有功出力 MW
    pub p_nominal_mw: f64,
    /// 额定无功容量 MVar
    pub q_nominal_mvar: f64,
    /// 当前有功出力 MW
    pub p_mw: f64,
    /// 当前无功出力 MVar
    pub q_mvar: f64,
    /// 电压设定值 pu(PV 节点用)
    pub v_setpoint_pu: f64,
    /// 有功调节上下限
    pub p_min_mw: f64,
    pub p_max_mw: f64,
    /// 无功调节上下限
    pub q_min_mvar: f64,
    pub q_max_mvar: f64,
    pub in_service: bool,
}

impl Generator {
    pub fn new(id: GeneratorId, bus: BusId, kind: GeneratorKind, p_nom: f64) -> Self {
        Self {
            id, bus, kind,
            p_nominal_mw: p_nom,
            q_nominal_mvar: p_nom * 0.6,
            p_mw: 0.0,
            q_mvar: 0.0,
            v_setpoint_pu: 1.0,
            p_min_mw: 0.0,
            p_max_mw: p_nom,
            q_min_mvar: -p_nom * 0.3,
            q_max_mvar: p_nom * 0.6,
            in_service: true,
        }
    }
}

4. 拓扑图数据结构

基于 petgraph 的有向图,节点为 Bus,边为 Branch

// crates/eneros-topology/src/network.rs
use petgraph::graph::{DiGraph, NodeIndex, EdgeIndex};
use std::collections::HashMap;
use crate::{bus::{Bus, BusId}, branch::{Branch, BranchId}, generator::{Generator, GeneratorId}};

/// 电网拓扑网络
pub struct Network {
    graph: DiGraph<Bus, Branch>,
    bus_index: HashMap<BusId, NodeIndex>,
    branch_index: HashMap<BranchId, EdgeIndex>,
    generators: HashMap<GeneratorId, Generator>,
}

impl Network {
    pub fn new() -> Self {
        Self {
            graph: DiGraph::new(),
            bus_index: HashMap::new(),
            branch_index: HashMap::new(),
            generators: HashMap::new(),
        }
    }

    /// 添加母线
    pub fn add_bus(&mut self, bus: Bus) -> BusId {
        let id = bus.id;
        let idx = self.graph.add_node(bus);
        self.bus_index.insert(id, idx);
        id
    }

    /// 添加支路
    pub fn add_branch(&mut self, branch: Branch) -> Result<BranchId, TopologyError> {
        let from = self.bus_index.get(&branch.from_bus)
            .ok_or(TopologyError::BusNotFound(branch.from_bus))?;
        let to = self.bus_index.get(&branch.to_bus)
            .ok_or(TopologyError::BusNotFound(branch.to_bus))?;
        let id = branch.id;
        let edge = self.graph.add_edge(*from, *to, branch);
        self.branch_index.insert(id, edge);
        Ok(id)
    }

    /// 添加发电机
    pub fn add_generator(&mut self, gen: Generator) -> Result<GeneratorId, TopologyError> {
        if !self.bus_index.contains_key(&gen.bus) {
            return Err(TopologyError::BusNotFound(gen.bus));
        }
        let id = gen.id;
        self.generators.insert(id, gen);
        Ok(id)
    }

    /// 获取母线
    pub fn bus(&self, id: BusId) -> Option<&Bus> {
        self.bus_index.get(&id).and_then(|i| self.graph.node_weight(*i))
    }

    /// 获取邻接母线
    pub fn neighbors(&self, id: BusId) -> Vec<BusId> {
        self.bus_index.get(&id)
            .map(|i| self.graph.neighbors(*i).filter_map(|n| self.graph.node_weight(n).map(|b| b.id)).collect())
            .unwrap_or_default()
    }

    /// 电气岛划分(基于连通分量)
    pub fn electrical_islands(&self) -> Vec<Vec<BusId>> {
        use petgraph::algo::connected_components;
        let components = connected_components(&self.graph);
        let mut islands: Vec<Vec<BusId>> = vec![Vec::new(); components];
        for (bus_id, idx) in &self.bus_index {
            let comp = components[*idx];
            islands[comp].push(*bus_id);
        }
        islands
    }
}

5. IEEE 14-bus 测试系统

内置标准测试数据,开箱即用:

// crates/eneros-topology/src/test_cases/ieee14.rs
use crate::network::Network;
use crate::bus::{Bus, BusId, VoltageLevel, BusType};
use crate::branch::{Branch, BranchId, PiParameters};
use crate::generator::{Generator, GeneratorId, GeneratorKind};

/// 加载 IEEE 14-bus 测试系统
pub fn load() -> Network {
    let mut net = Network::new();

    // 14 条母线
    let buses = [
        (1, "Slack Bus",     138.0, BusType::Slack),
        (2, "Gen Bus 2",     138.0, BusType::PV),
        (3, "Gen Bus 3",     138.0, BusType::PV),
        (4, "Bus 4",         138.0, BusType::PQ),
        // ... 其余 10 条母线
    ];

    for (id, name, kv, bt) in buses {
        let mut bus = Bus::new(BusId(id), name, VoltageLevel::Custom(kv));
        bus.bus_type = bt;
        net.add_bus(bus);
    }

    // 20 条支路(线路 + 变压器)
    let branches = [
        // (id, from, to, r, x, b/2)
        (1, 1, 2, 0.01938, 0.05917, 0.0264),
        (2, 1, 5, 0.05403, 0.22304, 0.0246),
        // ...
    ];

    for (id, f, t, r, x, b) in branches {
        let br = Branch::line(BranchId(id), BusId(f), BusId(t), PiParameters::line(r, x, b));
        net.add_branch(br).unwrap();
    }

    // 5 台发电机
    let gens = [
        (1, 1, GeneratorKind::Thermal, 232.0),
        (2, 2, GeneratorKind::Thermal, 40.0),
        (3, 3, GeneratorKind::Thermal, 60.0),
        // ...
    ];

    for (id, bus, kind, p) in gens {
        net.add_generator(Generator::new(GeneratorId(id), BusId(bus), kind, p)).unwrap();
    }

    net
}

使用示例:

use eneros_topology::test_cases::ieee14;

let network = ieee14::load();
println!("母线数: {}", network.bus_count());   // 14
println!("支路数: {}", network.branch_count()); // 20

// 查询母线 1 的邻居
let neighbors = network.neighbors(BusId(1));
println!("Bus 1 邻接母线: {:?}", neighbors);

// 电气岛划分
let islands = network.electrical_islands();
println!("电气岛数量: {}", islands.len());      // 1

IEEE 14-bus 系统参数概览:

项目数值
母线数14
支路数20(含 3 台变压器)
发电机数5
负荷数9
系统总有功259 MW
系统总无功73.5 MVar
最高电压等级138 kV

6. 拓扑验证

提供 12 条验证规则,覆盖常见建模错误:

// crates/eneros-topology/src/validation.rs
use crate::network::Network;

/// 验证结果
#[derive(Debug, Clone)]
pub struct ValidationError {
    pub rule: ValidationRule,
    pub message: String,
    pub bus_id: Option<crate::bus::BusId>,
}

#[derive(Debug, Clone, Copy)]
pub enum ValidationRule {
    IsolatedBus,        // 孤立母线
    DuplicateBranch,    // 重复支路
    VoltageMismatch,    // 电压等级不匹配
    NoSlackBus,         // 缺少平衡节点
    MultipleSlackBus,   // 多个平衡节点
    ZeroImpedance,      // 零阻抗支路
    // ...
}

/// 验证网络拓扑
pub fn validate(network: &Network) -> Vec<ValidationError> {
    let mut errors = Vec::new();
    // 检查孤立母线、重复支路、电压等级匹配等
    errors
}

改进

  • eneros-core:新增 ResourceId 类型,用于唯一标识内核资源
  • 错误码:新增 E3xxx 拓扑错误类别,共 10 个错误码
  • CI:新增 cargo doc 部署步骤,API 文档自动发布到 GitHub Pages
  • 基准测试:新增 topology_bench,覆盖建图、遍历、电气岛划分

Bug 修复

  • 修复 Network::add_branchfrom_bus == to_bus 时未拒绝自环的问题(#45)
  • 修复 electrical_islands 返回空岛的问题(#49)
  • 修复 PiParameters::linetap_ratio 默认值不为 1.0 的问题(#52)
  • 修复 IEEE 14-bus 数据中第 8 条支路阻抗数据错误的问题(#55)

破坏性变更

  • eneros_core::Handle:新增 resource_type 字段,区分布线、支路等资源类型
  • Syscall::ReadBus:返回类型从 Bus 改为 BusState,仅包含运行时状态字段

性能提升

操作v0.2.0v0.3.0说明
14-bus 加载N/A280 μs含解析与建图
BFS 遍历 14 节点N/A12 μs-
电气岛划分N/A28 μs-
bus() 查询N/A18 nsHashMap 查找
neighbors()N/A240 ns平均 2.5 邻接

贡献者

贡献者角色提交数
@eneros-foundation架构师38
@grid-rustaceanRust 工程师45
@cim-expertCIM 标准专家22
@powerdomain-reviewer电力领域专家14
@test-eng测试工程师11

升级指南

新增依赖

# crates/your-app/Cargo.toml
[dependencies]
eneros-core = { version = "0.3", path = "../eneros-core" }
eneros-topology = { version = "0.3", path = "../eneros-topology" }
petgraph = "0.6"

从 v0.2.0 迁移

// v0.2.0(旧):用 Handle 表示所有资源
let handle = Handle(1);

// v0.3.0(新):用具体 ID 类型
use eneros_topology::bus::BusId;
let bus_id = BusId(1);

使用 IEEE 14-bus 测试

use eneros_topology::test_cases::ieee14;

let network = ieee14::load();
let errors = eneros_topology::validation::validate(&network);
assert!(errors.is_empty(), "IEEE 14-bus 应通过验证");