Skip to main content

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}