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, Default)]
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}
43
44pub fn build_ybus(case: &IndexedNetwork, opts: &super::BuildOptions) -> Result<YbusParts> {
45 let flags = YbusFlags {
46 zero_resistance: false,
47 zero_charging: false,
48 unity_taps: !opts.include_taps,
49 zero_shifts: !opts.include_shifts,
50 skip_bus_shunts: false,
51 };
52 build_ybus_with_flags(case, flags)
53}
54
55#[allow(clippy::many_single_char_names)]
58pub(crate) fn build_ybus_with_flags(case: &IndexedNetwork, flags: YbusFlags) -> Result<YbusParts> {
59 let n = case.n();
60 let mut g_coo = CooBuilder::with_capacity(n, 4 * case.branches().len() + n);
61 let mut b_coo = CooBuilder::with_capacity(n, 4 * case.branches().len() + n);
62
63 for (row_idx, br) in case.in_service_branches() {
64 let i = case.bus_index(br.from).ok_or(Error::UnknownBus {
65 bus_id: br.from,
66 element_index: row_idx,
67 })?;
68 let j = case.bus_index(br.to).ok_or(Error::UnknownBus {
69 bus_id: br.to,
70 element_index: row_idx,
71 })?;
72
73 let shift_rad = if flags.zero_shifts {
74 0.0
75 } else {
76 case.angle_radians(br.shift)
77 };
78 let Some([y_ii, y_ij, y_ji, y_jj]) = branch_admittance(br, flags, shift_rad, row_idx)?
79 else {
80 continue;
82 };
83
84 if i == j {
85 let combined = y_ii + y_jj + y_ij + y_ji;
87 g_coo.add(i, i, combined.re);
88 b_coo.add(i, i, combined.im);
89 continue;
90 }
91
92 g_coo.add(i, i, y_ii.re);
93 b_coo.add(i, i, y_ii.im);
94 g_coo.add(j, j, y_jj.re);
95 b_coo.add(j, j, y_jj.im);
96 g_coo.add(i, j, y_ij.re);
97 b_coo.add(i, j, y_ij.im);
98 g_coo.add(j, i, y_ji.re);
99 b_coo.add(j, i, y_ji.im);
100 }
101
102 if !flags.skip_bus_shunts {
103 let base = case.per_unit_base();
106 for idx in 0..n {
107 g_coo.add(idx, idx, case.gs()[idx] / base);
108 b_coo.add(idx, idx, case.bs()[idx] / base);
109 }
110 }
111
112 Ok(YbusParts {
113 g: g_coo.finish_csr(),
114 b: b_coo.finish_csr(),
115 })
116}
117
118#[allow(clippy::many_single_char_names)]
132pub(crate) fn branch_admittance(
133 br: &crate::network::Branch,
134 flags: YbusFlags,
135 shift_rad: f64,
136 row: usize,
137) -> Result<Option<[Complex64; 4]>> {
138 let r = if flags.zero_resistance { 0.0 } else { br.r };
139 let x = br.x;
140 let denom = r * r + x * x;
141 if denom == 0.0 {
142 return Ok(None);
143 }
144 if !denom.is_finite() {
148 return Err(Error::NonFiniteSusceptance { row });
149 }
150 let y_series = Complex64::new(r / denom, -x / denom);
151
152 let b_charging = if flags.zero_charging { 0.0 } else { br.b };
153 let y_shunt_half = Complex64::new(0.0, b_charging / 2.0);
154
155 let tap_mag = if flags.unity_taps {
156 1.0
157 } else {
158 br.effective_tap()
159 };
160 let a = Complex64::from_polar(tap_mag, shift_rad);
164 let a_norm_sqr = tap_mag * tap_mag;
165
166 let y_ff = (y_series + y_shunt_half) / a_norm_sqr;
167 let y_tt = y_series + y_shunt_half;
168 let y_ft = -y_series / a.conj();
169 let y_tf = -y_series / a;
170 Ok(Some([y_ff, y_ft, y_tf, y_tt]))
171}
172
173#[cfg(feature = "gridfm")]
185pub(crate) fn branch_flows(
186 y: &[Complex64; 4],
187 vi: Complex64,
188 vj: Complex64,
189) -> (Complex64, Complex64) {
190 let i_from = y[0] * vi + y[1] * vj;
191 let i_to = y[2] * vi + y[3] * vj;
192 (vi * i_from.conj(), vj * i_to.conj())
193}