Skip to main content

surge_network/network/
load.rs

1// SPDX-License-Identifier: LicenseRef-PolyForm-Noncommercial-1.0.0
2//! Load representation.
3
4use serde::{Deserialize, Serialize};
5
6/// Load class for planning and demand categorization.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
8pub enum LoadClass {
9    /// Residential customer load.
10    Residential,
11    /// Commercial customer load.
12    Commercial,
13    /// Industrial customer load.
14    Industrial,
15    /// Agricultural customer load (irrigation, processing).
16    Agricultural,
17    /// Data center load (high power factor, constant demand).
18    DataCenter,
19    /// Electric vehicle charging load.
20    EvCharging,
21    /// Uncategorized load.
22    Other,
23}
24
25/// Winding connection type for fault and unbalanced analysis.
26///
27/// Determines how the load's zero-sequence impedance participates in
28/// short-circuit calculations.
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
30pub enum LoadConnection {
31    /// Wye-connected with grounded neutral (zero-sequence current path exists).
32    #[default]
33    WyeGrounded,
34    /// Wye-connected with ungrounded (floating) neutral.
35    WyeUngrounded,
36    /// Delta-connected (no zero-sequence current path).
37    Delta,
38}
39
40/// A load connected to a bus in the transmission network.
41///
42/// Supports ZIP (constant impedance / current / power) voltage dependence,
43/// frequency sensitivity, and composite load modeling (CMPLDW motor fractions).
44/// All power quantities are in MW/MVAr.
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct Load {
47    // --- identity ---
48    /// Bus number where the load is connected.
49    pub bus: u32,
50    /// Optional load identifier.
51    #[serde(default)]
52    pub id: String,
53    /// Load status (true = in service).
54    pub in_service: bool,
55    /// Whether this load conforms to system-wide scaling forecasts.
56    #[serde(default = "default_true")]
57    pub conforming: bool,
58
59    // --- steady-state injection ---
60    /// Real power demand in MW.
61    pub active_power_demand_mw: f64,
62    /// Reactive power demand in MVAr.
63    pub reactive_power_demand_mvar: f64,
64
65    // --- voltage dependence (ZIP) ---
66    /// Constant-impedance P fraction \[0,1\]. Default 0.
67    #[serde(default)]
68    pub zip_p_impedance_frac: f64,
69    /// Constant-current P fraction \[0,1\]. Default 0.
70    #[serde(default)]
71    pub zip_p_current_frac: f64,
72    /// Constant-power P fraction \[0,1\]. Default 1.
73    #[serde(default = "default_one")]
74    pub zip_p_power_frac: f64,
75    /// Constant-impedance Q fraction \[0,1\]. Default 0.
76    #[serde(default)]
77    pub zip_q_impedance_frac: f64,
78    /// Constant-current Q fraction \[0,1\]. Default 0.
79    #[serde(default)]
80    pub zip_q_current_frac: f64,
81    /// Constant-power Q fraction \[0,1\]. Default 1.
82    #[serde(default = "default_one")]
83    pub zip_q_power_frac: f64,
84
85    // --- frequency dependence ---
86    /// Active power frequency sensitivity (%P per Hz). Default 0.
87    #[serde(default)]
88    pub freq_sensitivity_p_pct_per_hz: f64,
89    /// Reactive power frequency sensitivity (%Q per Hz). Default 0.
90    #[serde(default)]
91    pub freq_sensitivity_q_pct_per_hz: f64,
92
93    // --- composition (CMPLDW bridge) ---
94    /// 3-phase large industrial motor fraction \[0,1\]. Default 0.
95    #[serde(default)]
96    pub frac_motor_a: f64,
97    /// 3-phase commercial motor fraction \[0,1\]. Default 0.
98    #[serde(default)]
99    pub frac_motor_b: f64,
100    /// 1-phase A/C compressor motor fraction \[0,1\]. Default 0.
101    #[serde(default)]
102    pub frac_motor_c: f64,
103    /// 1-phase other motor fraction \[0,1\]. Default 0.
104    #[serde(default)]
105    pub frac_motor_d: f64,
106    /// Power electronic load fraction \[0,1\]. Default 0.
107    #[serde(default)]
108    pub frac_electronic: f64,
109    /// Static (ZIP) load fraction \[0,1\]. Default 1.
110    #[serde(default = "default_one")]
111    pub frac_static: f64,
112
113    // --- classification ---
114    /// Load class for planning.
115    #[serde(default, skip_serializing_if = "Option::is_none")]
116    pub load_class: Option<LoadClass>,
117    /// Winding connection for fault/unbalanced analysis. Default WyeGrounded.
118    #[serde(default)]
119    pub connection: LoadConnection,
120    /// UFLS/UVLS shedding tier (1 = first shed, higher = later).
121    #[serde(default, skip_serializing_if = "Option::is_none")]
122    pub shedding_priority: Option<u32>,
123    /// Ownership entries (PSS/E OWNER field). Single-owner for loads.
124    #[serde(default, skip_serializing_if = "Vec::is_empty")]
125    pub owners: Vec<super::owner::OwnershipEntry>,
126}
127
128use crate::network::serde_defaults::{default_one, default_true};
129
130impl Default for Load {
131    fn default() -> Self {
132        Self {
133            bus: 0,
134            active_power_demand_mw: 0.0,
135            reactive_power_demand_mvar: 0.0,
136            in_service: true,
137            conforming: true,
138            id: String::new(),
139            zip_p_impedance_frac: 0.0,
140            zip_p_current_frac: 0.0,
141            zip_p_power_frac: 1.0,
142            zip_q_impedance_frac: 0.0,
143            zip_q_current_frac: 0.0,
144            zip_q_power_frac: 1.0,
145            freq_sensitivity_p_pct_per_hz: 0.0,
146            freq_sensitivity_q_pct_per_hz: 0.0,
147            frac_motor_a: 0.0,
148            frac_motor_b: 0.0,
149            frac_motor_c: 0.0,
150            frac_motor_d: 0.0,
151            frac_electronic: 0.0,
152            frac_static: 1.0,
153            load_class: None,
154            connection: LoadConnection::WyeGrounded,
155            shedding_priority: None,
156            owners: Vec::new(),
157        }
158    }
159}
160
161impl Load {
162    /// Create a load with the given bus, active power (MW), and reactive power (MVAr).
163    pub fn new(bus: u32, active_power_demand_mw: f64, reactive_power_demand_mvar: f64) -> Self {
164        Self {
165            bus,
166            active_power_demand_mw,
167            reactive_power_demand_mvar,
168            ..Default::default()
169        }
170    }
171}