powerio_matrix/matrix/
incidence.rs1use sprs::CsMat;
10
11use crate::indexed::IndexedNetwork;
12use crate::matrix::triplet::CooBuilder;
13use crate::{Error, Result};
14
15use super::{BuildOptions, ZeroImpedanceSkips};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
19#[non_exhaustive]
20pub enum DcConvention {
21 #[default]
24 PaperPure,
25 Matpower,
28}
29
30#[derive(Debug, Clone)]
32#[non_exhaustive]
33pub struct IncidenceParts {
34 pub a: CsMat<f64>,
36 pub b: Vec<f64>,
38 pub p_shift: Vec<f64>,
41 pub branch_of_col: Vec<usize>,
43 pub skipped_zero_impedance: ZeroImpedanceSkips,
45}
46
47impl IncidenceParts {
48 #[inline]
49 pub fn n(&self) -> usize {
50 self.a.rows()
51 }
52
53 #[inline]
54 pub fn m(&self) -> usize {
55 self.a.cols()
56 }
57}
58
59pub fn build_incidence(
65 case: &IndexedNetwork,
66 conv: DcConvention,
67 opts: &BuildOptions,
68) -> Result<IncidenceParts> {
69 let n = case.n();
70
71 let mut cols: Vec<Column> = Vec::new();
73 let mut skipped_zero_impedance = Vec::new();
74 for (idx, br) in case.in_service_branches() {
75 let i = case.bus_index(br.from).ok_or(Error::UnknownBus {
76 bus_id: br.from,
77 element_index: idx,
78 })?;
79 let j = case.bus_index(br.to).ok_or(Error::UnknownBus {
80 bus_id: br.to,
81 element_index: idx,
82 })?;
83 if i == j || br.x == 0.0 {
84 if i != j && br.x == 0.0 {
85 if !opts.skip_zero_impedance {
86 return Err(Error::ZeroImpedance { row: idx });
87 }
88 skipped_zero_impedance.push(idx);
89 }
90 continue;
91 }
92 let b_e = match conv {
93 DcConvention::PaperPure => 1.0 / br.x,
94 DcConvention::Matpower => 1.0 / (br.x * br.effective_tap()),
95 };
96 if !b_e.is_finite() {
99 return Err(Error::NonFiniteSusceptance { row: idx });
100 }
101 let shift_rad = match conv {
102 DcConvention::PaperPure => 0.0,
103 DcConvention::Matpower => case.angle_radians(br.shift),
106 };
107 cols.push(Column {
108 i,
109 j,
110 b_e,
111 shift_rad,
112 branch: idx,
113 });
114 }
115
116 let m = cols.len();
118 let mut a = CooBuilder::with_capacity_rect(n, m, 2 * m);
119 let mut b = Vec::with_capacity(m);
120 let mut p_shift = vec![0.0; n];
121 let mut branch_of_col = Vec::with_capacity(m);
122 for (k, col) in cols.iter().enumerate() {
123 a.add(col.i, k, 1.0);
124 a.add(col.j, k, -1.0);
125 b.push(col.b_e);
126 branch_of_col.push(col.branch);
127 if col.shift_rad != 0.0 {
128 p_shift[col.i] -= col.b_e * col.shift_rad;
131 p_shift[col.j] += col.b_e * col.shift_rad;
132 }
133 }
134
135 Ok(IncidenceParts {
136 a: a.finish_csr(),
137 b,
138 p_shift,
139 branch_of_col,
140 skipped_zero_impedance: ZeroImpedanceSkips::new(skipped_zero_impedance),
141 })
142}
143
144struct Column {
145 i: usize,
146 j: usize,
147 b_e: f64,
148 shift_rad: f64,
149 branch: usize,
150}
151
152pub fn diagonal(values: &[f64]) -> CsMat<f64> {
154 let n = values.len();
155 let mut d = CooBuilder::with_capacity(n, n);
156 for (k, &v) in values.iter().enumerate() {
157 d.add(k, k, v);
158 }
159 d.finish_csr()
160}
161
162pub fn susceptance_diag(b: &[f64]) -> CsMat<f64> {
164 diagonal(b)
165}
166
167pub fn build_flow_map(a: &CsMat<f64>, b: &[f64]) -> CsMat<f64> {
169 let d = susceptance_diag(b);
170 let at = a.transpose_view().to_csr();
171 &d * &at
172}