surge_network/network/bus.rs
1// SPDX-License-Identifier: LicenseRef-PolyForm-Noncommercial-1.0.0
2//! Bus (node) representation in the power system network.
3
4use serde::{Deserialize, Serialize};
5
6use crate::market::AmbientConditions;
7
8/// Bus type classification for power flow analysis.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10pub enum BusType {
11 /// PQ bus — real and reactive power specified (load bus).
12 PQ = 1,
13 /// PV bus — real power and voltage magnitude specified (generator bus).
14 PV = 2,
15 /// Slack (reference) bus — voltage magnitude and angle specified.
16 Slack = 3,
17 /// Isolated bus — disconnected from the network.
18 Isolated = 4,
19}
20
21/// A bus (node) in the power system network.
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct Bus {
24 /// Bus number (unique identifier).
25 pub number: u32,
26 /// Bus name.
27 pub name: String,
28 /// Bus type (PQ, PV, Slack, Isolated).
29 pub bus_type: BusType,
30 /// Shunt conductance (MW demanded at V = 1.0 p.u.).
31 pub shunt_conductance_mw: f64,
32 /// Shunt susceptance (MVAr injected at V = 1.0 p.u.).
33 pub shunt_susceptance_mvar: f64,
34 /// Area number.
35 pub area: u32,
36 /// Voltage magnitude in per-unit.
37 pub voltage_magnitude_pu: f64,
38 /// Voltage angle in radians.
39 pub voltage_angle_rad: f64,
40 /// Base voltage in kV.
41 pub base_kv: f64,
42 /// Zone number.
43 pub zone: u32,
44 /// Maximum voltage magnitude in per-unit.
45 pub voltage_max_pu: f64,
46 /// Minimum voltage magnitude in per-unit.
47 pub voltage_min_pu: f64,
48 /// Connected-component island ID (0 = largest island).
49 /// Populated by CGMES importer; other importers default to 0.
50 /// The NR/DC solvers perform their own island detection at solve time
51 /// using in-service branches (which may differ from import topology).
52 pub island_id: u32,
53 /// Latitude in decimal degrees (WGS84). None if unknown.
54 #[serde(default, skip_serializing_if = "Option::is_none")]
55 pub latitude: Option<f64>,
56 /// Longitude in decimal degrees (WGS84). None if unknown.
57 #[serde(default, skip_serializing_if = "Option::is_none")]
58 pub longitude: Option<f64>,
59 /// Bus frequency (Hz). None = nominal (Network.freq_hz).
60 #[serde(default, skip_serializing_if = "Option::is_none")]
61 pub freq_hz: Option<f64>,
62 /// Ambient conditions at this location. None = use Network.market_data.ambient.
63 #[serde(default, skip_serializing_if = "Option::is_none")]
64 pub ambient: Option<AmbientConditions>,
65 /// Reserve zone name. References a ReserveZone on Network.
66 #[serde(default, skip_serializing_if = "Option::is_none")]
67 pub reserve_zone: Option<String>,
68 /// Ownership entries (PSS/E OWNER field). Single-owner for buses.
69 #[serde(default, skip_serializing_if = "Vec::is_empty")]
70 pub owners: Vec<super::owner::OwnershipEntry>,
71}
72
73impl Default for Bus {
74 fn default() -> Self {
75 Self {
76 number: 0,
77 name: String::new(),
78 bus_type: BusType::PQ,
79 shunt_conductance_mw: 0.0,
80 shunt_susceptance_mvar: 0.0,
81 base_kv: 0.0,
82 voltage_magnitude_pu: 1.0,
83 voltage_angle_rad: 0.0,
84 area: 1,
85 zone: 1,
86 voltage_max_pu: 1.1,
87 voltage_min_pu: 0.9,
88 island_id: 0,
89 latitude: None,
90 longitude: None,
91 freq_hz: None,
92 ambient: None,
93 reserve_zone: None,
94 owners: Vec::new(),
95 }
96 }
97}
98
99impl Bus {
100 pub fn new(number: u32, bus_type: BusType, base_kv: f64) -> Self {
101 Self {
102 number,
103 bus_type,
104 base_kv,
105 ..Default::default()
106 }
107 }
108
109 /// True if this bus is the slack (reference) bus.
110 pub fn is_slack(&self) -> bool {
111 self.bus_type == BusType::Slack
112 }
113
114 /// True if this bus is a PV (generator) bus.
115 pub fn is_pv(&self) -> bool {
116 self.bus_type == BusType::PV
117 }
118
119 /// True if this bus is a PQ (load) bus.
120 pub fn is_pq(&self) -> bool {
121 self.bus_type == BusType::PQ
122 }
123}