powerio_matrix/matrix/
ybus.rs1use num_complex::Complex64;
16use sprs::CsMat;
17
18use crate::indexed::IndexedNetwork;
19use crate::{Error, Result};
20
21use super::triplet::CooBuilder;
22
23#[derive(Debug, Clone)]
25#[non_exhaustive]
26pub struct YbusParts {
27 pub g: CsMat<f64>,
28 pub b: CsMat<f64>,
29}
30
31#[allow(clippy::struct_excessive_bools)]
35#[derive(Debug, Clone, Copy)]
36pub(crate) struct YbusFlags {
37 pub zero_resistance: bool,
38 pub zero_charging: bool,
39 pub unity_taps: bool,
40 pub zero_shifts: bool,
41 pub skip_bus_shunts: bool,
42 pub skip_zero_impedance: bool,
43}
44
45impl Default for YbusFlags {
46 fn default() -> Self {
47 Self {
48 zero_resistance: false,
49 zero_charging: false,
50 unity_taps: false,
51 zero_shifts: false,
52 skip_bus_shunts: false,
53 skip_zero_impedance: true,
54 }
55 }
56}
57
58pub fn build_ybus(case: &IndexedNetwork, opts: &super::BuildOptions) -> Result<YbusParts> {
59 let flags = YbusFlags {
60 zero_resistance: false,
61 zero_charging: false,
62 unity_taps: !opts.include_taps,
63 zero_shifts: !opts.include_shifts,
64 skip_bus_shunts: false,
65 skip_zero_impedance: opts.skip_zero_impedance,
66 };
67 build_ybus_with_flags(case, flags)
68}
69
70#[allow(clippy::many_single_char_names)]
73pub(crate) fn build_ybus_with_flags(case: &IndexedNetwork, flags: YbusFlags) -> Result<YbusParts> {
74 let n = case.n();
75 let mut g_coo = CooBuilder::with_capacity(n, 4 * case.branches().len() + n);
76 let mut b_coo = CooBuilder::with_capacity(n, 4 * case.branches().len() + n);
77
78 for (row_idx, br) in case.in_service_branches() {
79 let i = case.bus_index(br.from).ok_or(Error::UnknownBus {
80 bus_id: br.from,
81 element_index: row_idx,
82 })?;
83 let j = case.bus_index(br.to).ok_or(Error::UnknownBus {
84 bus_id: br.to,
85 element_index: row_idx,
86 })?;
87
88 let shift_rad = if flags.zero_shifts {
89 0.0
90 } else {
91 case.angle_radians(br.shift)
92 };
93 let Some([y_ii, y_ij, y_ji, y_jj]) = branch_admittance(br, flags, shift_rad, row_idx)?
94 else {
95 continue;
97 };
98
99 if i == j {
100 let combined = y_ii + y_jj + y_ij + y_ji;
102 g_coo.add(i, i, combined.re);
103 b_coo.add(i, i, combined.im);
104 continue;
105 }
106
107 g_coo.add(i, i, y_ii.re);
108 b_coo.add(i, i, y_ii.im);
109 g_coo.add(j, j, y_jj.re);
110 b_coo.add(j, j, y_jj.im);
111 g_coo.add(i, j, y_ij.re);
112 b_coo.add(i, j, y_ij.im);
113 g_coo.add(j, i, y_ji.re);
114 b_coo.add(j, i, y_ji.im);
115 }
116
117 if !flags.skip_bus_shunts {
118 let base = case.per_unit_base();
121 for idx in 0..n {
122 g_coo.add(idx, idx, case.gs()[idx] / base);
123 b_coo.add(idx, idx, case.bs()[idx] / base);
124 }
125 }
126
127 Ok(YbusParts {
128 g: g_coo.finish_csr(),
129 b: b_coo.finish_csr(),
130 })
131}
132
133#[allow(clippy::many_single_char_names)]
147pub(crate) fn branch_admittance(
148 br: &crate::network::Branch,
149 flags: YbusFlags,
150 shift_rad: f64,
151 row: usize,
152) -> Result<Option<[Complex64; 4]>> {
153 let r = if flags.zero_resistance { 0.0 } else { br.r };
154 let x = br.x;
155 let denom = r * r + x * x;
156 if denom == 0.0 {
157 if flags.skip_zero_impedance {
158 return Ok(None);
159 }
160 return Err(Error::ZeroImpedance { row });
161 }
162 if !denom.is_finite() {
166 return Err(Error::NonFiniteSusceptance { row });
167 }
168 let y_series = Complex64::new(r / denom, -x / denom);
169
170 let charging = if flags.zero_charging {
171 crate::network::BranchCharging::new(0.0, 0.0, 0.0, 0.0)
172 } else {
173 br.terminal_charging()
174 };
175 let y_fr = Complex64::new(charging.g_fr, charging.b_fr);
176 let y_to = Complex64::new(charging.g_to, charging.b_to);
177
178 let tap_mag = if flags.unity_taps {
179 1.0
180 } else {
181 br.effective_tap()
182 };
183 let a = Complex64::from_polar(tap_mag, shift_rad);
187 let a_norm_sqr = tap_mag * tap_mag;
188
189 let y_ff = (y_series + y_fr) / a_norm_sqr;
190 let y_tt = y_series + y_to;
191 let y_ft = -y_series / a.conj();
192 let y_tf = -y_series / a;
193 Ok(Some([y_ff, y_ft, y_tf, y_tt]))
194}
195
196#[cfg(feature = "gridfm")]
208pub(crate) fn branch_flows(
209 y: &[Complex64; 4],
210 vi: Complex64,
211 vj: Complex64,
212) -> (Complex64, Complex64) {
213 let i_from = y[0] * vi + y[1] * vj;
214 let i_to = y[2] * vi + y[3] * vj;
215 (vi * i_from.conj(), vj * i_to.conj())
216}