跳到主内容

eneros-topology

Crate 索引

eneros-topology

eneros-topology 将电网拓扑作为操作系统内核的一等公民数据结构,基于 petgraph 提供有向图建模与一次遍历的拓扑分析算法。所有上层组件(Agent、潮流、约束、孪生)共享同一份拓扑视图,避免传统电力软件中拓扑在多个应用间重复建模、状态不一致的问题。

职责描述

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

  • 图结构建模:母线(Bus)、支路(Branch)、电气岛(Island)的图结构表达,支持线路、变压器、开关等多种支路类型
  • 一次遍历分析:单次 DFS/BFS 完成电气岛检测、连通性判断、最短路径搜索,避免多次扫描
  • 拓扑变更广播:开关变位、支路投退等拓扑变更通过事件总线广播,自动失效潮流缓存与约束重算
  • 增量更新:开关变位 O(log n) 复杂度,支持高频拓扑变更场景
  • 持久化:支持 JSON / CIM / CDF 等多种格式导入导出
  • 索引加速:基于 HashMap<BusId, NodeIndex> 的双向索引,O(1) 查找节点

关键类型与接口

NetworkGraph

use petgraph::stable_graph::StableDiGraph;
use petgraph::graph::NodeIndex;
use std::collections::HashMap;
use eneros_core::{BusId, BranchId, ElementId, Voltage, BusType};

pub struct NetworkGraph {
    graph: StableDiGraph<Bus, Branch>,
    bus_index: HashMap<BusId, NodeIndex>,
    metadata: NetworkMetadata,
}

#[derive(Debug, Clone)]
pub struct NetworkMetadata {
    pub id: String,
    pub name: String,
    pub base_mva: f64,
    pub created_at: chrono::DateTime<chrono::Utc>,
}

impl NetworkGraph {
    pub fn new() -> Self {
        Self {
            graph: StableDiGraph::new(),
            bus_index: HashMap::new(),
            metadata: NetworkMetadata {
                id: uuid::Uuid::new_v4().to_string(),
                name: "unnamed".into(),
                base_mva: 100.0,
                created_at: chrono::Utc::now(),
            },
        }
    }

    pub fn add_bus(&mut self, bus: Bus) -> bool {
        if self.bus_index.contains_key(&bus.id) {
            return false;
        }
        let idx = self.graph.add_node(bus.clone());
        self.bus_index.insert(bus.id.clone(), idx);
        true
    }

    pub fn add_branch(&mut self, branch: Branch) -> bool {
        let from = match self.bus_index.get(&branch.from) {
            Some(&i) => i,
            None => return false,
        };
        let to = match self.bus_index.get(&branch.to) {
            Some(&i) => i,
            None => return false,
        };
        self.graph.add_edge(from, to, branch);
        true
    }

    pub fn remove_bus(&mut self, id: &BusId) -> Option<Bus> {
        let idx = self.bus_index.remove(id)?;
        self.graph.remove_node(idx)
    }

    pub fn bus_count(&self) -> usize {
        self.graph.node_count()
    }

    pub fn branch_count(&self) -> usize {
        self.graph.edge_count()
    }

    pub fn topology(&self) -> TopologyEngine<'_> {
        TopologyEngine { graph: self }
    }
}

Bus / Branch

#[derive(Debug, Clone)]
pub struct Bus {
    pub id: BusId,
    pub bus_type: BusType,
    pub voltage: Voltage,
    pub nominal_kv: f64,
    pub area: Option<String>,
    pub zone: Option<String>,
}

impl Bus {
    pub fn new(id: impl Into<String>, bus_type: BusType, voltage: Voltage) -> Self {
        Self {
            id: ElementId::new(id),
            bus_type,
            voltage,
            nominal_kv: 110.0,
            area: None,
            zone: None,
        }
    }
}

#[derive(Debug, Clone)]
pub struct Branch {
    pub from: BusId,
    pub to: BusId,
    pub params: BranchParams,
    pub status: BranchStatus,
    pub kind: BranchKind,
}

impl Branch {
    pub fn new(
        from: impl Into<String>,
        to: impl Into<String>,
        params: BranchParams,
    ) -> Self {
        Self {
            from: ElementId::new(from),
            to: ElementId::new(to),
            params,
            status: BranchStatus::Closed,
            kind: BranchKind::Line,
        }
    }
}

#[derive(Debug, Clone)]
pub struct BranchParams {
    pub r_pu: f64,
    pub x_pu: f64,
    pub b_pu: f64,
    pub rate_mva: f64,
}

impl BranchParams {
    pub fn line(r_pu: f64, x_pu: f64, b_pu: f64) -> Self {
        Self {
            r_pu,
            x_pu,
            b_pu,
            rate_mva: 100.0,
        }
    }

    pub fn transformer(r_pu: f64, x_pu: f64, ratio: f64) -> Self {
        Self {
            r_pu,
            x_pu,
            b_pu: 0.0,
            rate_mva: 100.0,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BranchStatus {
    Closed,
    Open,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BranchKind {
    Line,
    Transformer,
    Switch,
    Breaker,
}

TopologyEngine

use std::collections::HashSet;

pub struct TopologyEngine<'a> {
    graph: &'a NetworkGraph,
}

#[derive(Debug, Clone)]
pub struct Island {
    pub buses: Vec<BusId>,
    pub has_slack: bool,
}

impl<'a> TopologyEngine<'a> {
    pub fn find_islands(&self) -> Vec<Island> {
        let mut visited: HashSet<NodeIndex> = HashSet::new();
        let mut islands = Vec::new();

        for node in self.graph.graph.node_indices() {
            if visited.contains(&node) {
                continue;
            }
            let mut component: Vec<BusId> = Vec::new();
            let mut stack = vec![node];
            let mut has_slack = false;

            while let Some(n) = stack.pop() {
                if !visited.insert(n) {
                    continue;
                }
                let bus = &self.graph.graph[n];
                component.push(bus.id.clone());
                if bus.bus_type.is_slack() {
                    has_slack = true;
                }
                for edge in self.graph.graph.edges(n) {
                    let target = edge.target();
                    if !visited.contains(&target) {
                        stack.push(target);
                    }
                }
            }

            islands.push(Island { buses: component, has_slack });
        }
        islands
    }

    pub fn is_connected(&self, a: &BusId, b: &BusId) -> bool {
        let start = match self.graph.bus_index.get(a) {
            Some(&i) => i,
            None => return false,
        };
        let target = match self.graph.bus_index.get(b) {
            Some(&i) => i,
            None => return false,
        };
        let mut visited: HashSet<NodeIndex> = HashSet::new();
        let mut stack = vec![start];
        while let Some(n) = stack.pop() {
            if n == target {
                return true;
            }
            if !visited.insert(n) {
                continue;
            }
            for edge in self.graph.graph.edges(n) {
                if !visited.contains(&edge.target()) {
                    stack.push(edge.target());
                }
            }
        }
        false
    }

    pub fn shortest_path(&self, a: &BusId, b: &BusId) -> Option<Vec<BusId>> {
        let start = self.graph.bus_index.get(a)?;
        let target = self.graph.bus_index.get(b)?;
        let mut parent: HashMap<NodeIndex, NodeIndex> = HashMap::new();
        let mut queue = std::collections::VecDeque::new();
        queue.push_back(*start);
        let mut visited: HashSet<NodeIndex> = HashSet::new();
        visited.insert(*start);

        while let Some(node) = queue.pop_front() {
            if node == *target {
                let mut path = Vec::new();
                let mut cur = *target;
                path.push(self.graph.graph[cur].id.clone());
                while let Some(&p) = parent.get(&cur) {
                    path.push(self.graph.graph[p].id.clone());
                    cur = p;
                }
                path.reverse();
                return Some(path);
            }
            for edge in self.graph.graph.edges(node) {
                let t = edge.target();
                if visited.insert(t) {
                    parent.insert(t, node);
                    queue.push_back(t);
                }
            }
        }
        None
    }

    pub fn neighbors(&self, bus: &BusId) -> Vec<BusId> {
        let idx = match self.graph.bus_index.get(bus) {
            Some(&i) => i,
            None => return Vec::new(),
        };
        self.graph
            .graph
            .edges(idx)
            .map(|e| self.graph.graph[e.target()].id.clone())
            .collect()
    }
}

核心 API

方法签名说明
NetworkGraph::newfn new() -> Self构造空图
NetworkGraph::add_busfn add_bus(&mut self, bus: Bus) -> bool添加母线,重复返回 false
NetworkGraph::add_branchfn add_branch(&mut self, branch: Branch) -> bool添加支路
NetworkGraph::remove_busfn remove_bus(&mut self, id: &BusId) -> Option<Bus>删除母线
NetworkGraph::bus_countfn bus_count(&self) -> usize母线数量
NetworkGraph::branch_countfn branch_count(&self) -> usize支路数量
NetworkGraph::topologyfn topology(&self) -> TopologyEngine<'_>借用式拓扑分析器
TopologyEngine::find_islandsfn find_islands(&self) -> Vec<Island>电气岛检测
TopologyEngine::is_connectedfn is_connected(&self, a: &BusId, b: &BusId) -> bool连通性判断
TopologyEngine::shortest_pathfn shortest_path(&self, a: &BusId, b: &BusId) -> Option<Vec<BusId>>最短路径(BFS)
TopologyEngine::neighborsfn neighbors(&self, bus: &BusId) -> Vec<BusId>邻接母线列表

使用示例

构建简单网络

use eneros_topology::{NetworkGraph, Bus, Branch, BranchParams, BusType};
use eneros_core::Voltage;

let mut net = NetworkGraph::new();
net.add_bus(Bus::new("1", BusType::Slack, Voltage::new(1.05, 0.0)));
net.add_bus(Bus::new("2", BusType::PQ,   Voltage::new(1.00, 0.0)));
net.add_bus(Bus::new("3", BusType::PQ,   Voltage::new(1.00, 0.0)));

net.add_branch(Branch::new("1", "2", BranchParams::line(0.1, 0.2, 0.02)));
net.add_branch(Branch::new("2", "3", BranchParams::line(0.05, 0.1, 0.01)));
net.add_branch(Branch::new("1", "3", BranchParams::line(0.08, 0.15, 0.015)));

println!("母线数: {} 支路数: {}", net.bus_count(), net.branch_count());

电气岛检测

let topo = net.topology();
let islands = topo.find_islands();
println!("电气岛数量: {}", islands.len());
for (i, island) in islands.iter().enumerate() {
    println!(
        "岛 {}: 母线数={} 含Slack={}",
        i,
        island.buses.len(),
        island.has_slack
    );
}

连通性与最短路径

let topo = net.topology();
assert!(topo.is_connected(&"1".into(), &"3".into()));

if let Some(path) = topo.shortest_path(&"1".into(), &"3".into()) {
    println!("最短路径: {:?}", path);
}

let neighbors = topo.neighbors(&"2".into());
println!("母线 2 的邻接母线: {:?}", neighbors);

拓扑变更:开关变位

use eneros_topology::{BranchStatus, BranchKind};

let mut net = NetworkGraph::new();
net.add_bus(Bus::new("A", BusType::Slack, Voltage::new(1.0, 0.0)));
net.add_bus(Bus::new("B", BusType::PQ,   Voltage::new(1.0, 0.0)));

let mut sw = Branch::new("A", "B", BranchParams::line(0.0, 0.0, 0.0));
sw.kind = BranchKind::Switch;
sw.status = BranchStatus::Closed;
net.add_branch(sw);

assert!(net.topology().is_connected(&"A".into(), &"B".into()));

// 开关断开:电气岛分裂
let topo = net.topology();
let islands_before = topo.find_islands().len();
println!("断开前电气岛数: {}", islands_before);

持久化(JSON)

use eneros_topology::NetworkGraph;

let mut net = NetworkGraph::new();
net.add_bus(Bus::new("1", BusType::Slack, Voltage::new(1.05, 0.0)));
net.add_bus(Bus::new("2", BusType::PQ,   Voltage::new(1.00, 0.0)));
net.add_branch(Branch::new("1", "2", BranchParams::line(0.1, 0.2, 0.02)));

let json = serde_json::to_string_pretty(&net)?;
std::fs::write("network.json", json)?;

let restored: NetworkGraph = serde_json::from_str(&std::fs::read_to_string("network.json")?)?;
assert_eq!(restored.bus_count(), 2);

性能指标

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

操作1000 节点10000 节点复杂度
添加母线< 1μs< 1μsO(1)
添加支路< 1μs< 1μsO(1)
电气岛检测(BFS)< 5ms< 60msO(V+E)
最短路径(BFS)< 100μs< 1msO(V+E)
邻接查询< 50ns< 50nsO(1)
拓扑变更广播< 10μs< 15μsO(log n)
JSON 序列化< 5ms< 50msO(V+E)

依赖关系

自身依赖

[dependencies]
eneros-core = { path = "../core", version = "0.47.0" }
eneros-linalg = { path = "../linalg", version = "0.47.0" }
petgraph = "0.6"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = { version = "0.4", features = ["serde"] }
uuid = { version = "1", features = ["v4", "serde"] }
tracing = "0.1"

被依赖

eneros-topology 是电力内核的核心,被以下 crate 直接依赖:

  • eneros-powerflow — 基于拓扑构建 YBus 矩阵
  • eneros-constraint — 基于拓扑校验电压与支路约束
  • eneros-analysis — 状态估计与 OPF
  • eneros-twin — 数字孪生共享拓扑视图
  • eneros-agent — Agent 通过拓扑感知电网位置
  • eneros-network — 拓扑-潮流统一管线

版本与兼容性

eneros-topology 版本EnerOS 版本关键变更
0.47.x0.47.x新增 BranchKind 区分线路/变压器/开关
0.46.x0.46.x增量更新复杂度从 O(n) 降至 O(log n)
0.45.x (LTS)0.45.xLTS 版本,至 2028 年安全更新

相关文档