powerio_matrix/matrix/
incidence.rs1use sprs::CsMat;
10
11pub use powerio::DcConvention;
12
13use crate::Result;
14use crate::indexed::IndexedNetwork;
15use crate::matrix::triplet::CooBuilder;
16
17use super::{BuildOptions, ZeroImpedanceSkips};
18
19#[derive(Debug, Clone)]
21#[non_exhaustive]
22pub struct IncidenceParts {
23 pub a: CsMat<f64>,
25 pub b: Vec<f64>,
27 pub p_shift: Vec<f64>,
30 pub branch_of_col: Vec<usize>,
32 pub skipped_zero_impedance: ZeroImpedanceSkips,
34}
35
36impl IncidenceParts {
37 #[inline]
38 pub fn n(&self) -> usize {
39 self.a.rows()
40 }
41
42 #[inline]
43 pub fn m(&self) -> usize {
44 self.a.cols()
45 }
46}
47
48pub fn build_incidence(
56 case: &IndexedNetwork,
57 conv: DcConvention,
58 opts: &BuildOptions,
59) -> Result<IncidenceParts> {
60 let n = case.n();
61
62 let mut cols: Vec<Column> = Vec::new();
64 let mut skipped_zero_impedance = Vec::new();
65 for (idx, br) in case.in_service_branches() {
66 let i = case.bus_index(br.from).ok_or(powerio::Error::UnknownBus {
67 bus_id: br.from,
68 element_index: idx,
69 })?;
70 let j = case.bus_index(br.to).ok_or(powerio::Error::UnknownBus {
71 bus_id: br.to,
72 element_index: idx,
73 })?;
74 let degenerate_x = br.x.abs() < crate::matrix::MIN_DIVISIBLE_MAGNITUDE;
78 if i == j || degenerate_x {
79 if i != j && degenerate_x {
80 if !opts.skip_zero_impedance {
81 return Err(powerio::Error::ZeroImpedance { row: idx }.into());
82 }
83 skipped_zero_impedance.push(idx);
84 }
85 continue;
86 }
87 let b_e = conv.branch_susceptance(br.r, br.x, br.divisible_tap(idx)?);
90 if !b_e.is_finite() {
93 return Err(powerio::Error::NonFiniteSusceptance { row: idx }.into());
94 }
95 let shift_rad = if conv.includes_phase_shifts() {
98 case.angle_radians(br.shift)
99 } else {
100 0.0
101 };
102 cols.push(Column {
103 i,
104 j,
105 b_e,
106 shift_rad,
107 branch: idx,
108 });
109 }
110
111 let m = cols.len();
113 let mut a = CooBuilder::with_capacity_rect(n, m, 2 * m);
114 let mut b = Vec::with_capacity(m);
115 let mut p_shift = vec![0.0; n];
116 let mut branch_of_col = Vec::with_capacity(m);
117 for (k, col) in cols.iter().enumerate() {
118 a.add(col.i, k, 1.0);
119 a.add(col.j, k, -1.0);
120 b.push(col.b_e);
121 branch_of_col.push(col.branch);
122 if col.shift_rad != 0.0 {
123 p_shift[col.i] -= col.b_e * col.shift_rad;
126 p_shift[col.j] += col.b_e * col.shift_rad;
127 }
128 }
129
130 Ok(IncidenceParts {
131 a: a.finish_csr(),
132 b,
133 p_shift,
134 branch_of_col,
135 skipped_zero_impedance: ZeroImpedanceSkips::new(skipped_zero_impedance),
136 })
137}
138
139struct Column {
140 i: usize,
141 j: usize,
142 b_e: f64,
143 shift_rad: f64,
144 branch: usize,
145}
146
147pub fn diagonal(values: &[f64]) -> CsMat<f64> {
149 let n = values.len();
150 let mut d = CooBuilder::with_capacity(n, n);
151 for (k, &v) in values.iter().enumerate() {
152 d.add(k, k, v);
153 }
154 d.finish_csr()
155}
156
157pub fn susceptance_diag(b: &[f64]) -> CsMat<f64> {
159 diagonal(b)
160}
161
162pub fn build_flow_map(a: &CsMat<f64>, b: &[f64]) -> CsMat<f64> {
164 let d = susceptance_diag(b);
165 let at = a.transpose_view().to_csr();
166 &d * &at
167}