1mod adjacency;
10mod bdoubleprime;
11mod bprime;
12pub mod incidence;
13#[cfg(feature = "kkt")]
17pub mod kkt;
18mod lacpf;
19pub mod laplacian;
20pub mod opf;
21pub mod sensitivity;
22pub mod triplet;
23mod ybus;
24
25#[cfg(test)]
26mod tests;
27
28pub use adjacency::build_adjacency;
29pub use bdoubleprime::build_bdoubleprime;
30pub use bprime::build_bprime;
31pub use incidence::{
32 DcConvention, IncidenceParts, build_flow_map, build_incidence, susceptance_diag,
33};
34pub use lacpf::build_lacpf;
35pub use laplacian::{
36 GroundedIndexMap, build_weighted_laplacian, ground_at, ground_at_each, reference_indicator,
37 unit_vector,
38};
39pub use opf::{BusCosts, GenCosts, OpfInstance, Units, build_opf_instance};
40pub use sensitivity::{build_lodf, build_ptdf, build_ptdf_lodf};
41pub use ybus::{YbusParts, build_ybus};
42#[cfg(feature = "gridfm")]
45pub(crate) use ybus::{YbusFlags, branch_admittance, branch_flows};
46
47use sprs::CsMat;
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
54#[non_exhaustive]
55pub enum Scheme {
56 #[default]
57 Bx,
58 Xb,
59}
60
61#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
62pub struct BuildOptions {
63 pub scheme: Scheme,
64 pub include_taps: bool,
66 pub include_shifts: bool,
68 pub skip_zero_impedance: bool,
70}
71
72impl Default for BuildOptions {
73 fn default() -> Self {
74 Self {
75 scheme: Scheme::Bx,
76 include_taps: true,
77 include_shifts: true,
78 skip_zero_impedance: true,
79 }
80 }
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
86#[non_exhaustive]
87pub enum ZeroImpedanceRule {
88 Series,
91 Reactance,
94}
95
96#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
99#[non_exhaustive]
100pub struct ZeroImpedanceSkips {
101 pub count: usize,
102 pub branch_indices: Vec<usize>,
103}
104
105impl ZeroImpedanceSkips {
106 pub fn new(branch_indices: Vec<usize>) -> Self {
107 Self {
108 count: branch_indices.len(),
109 branch_indices,
110 }
111 }
112
113 pub fn is_empty(&self) -> bool {
114 self.count == 0
115 }
116}
117
118pub fn skipped_zero_impedance(
121 case: &crate::indexed::IndexedNetwork,
122 rule: ZeroImpedanceRule,
123) -> ZeroImpedanceSkips {
124 let branch_indices = case
125 .in_service_branches()
126 .filter_map(|(row, br)| {
127 let zero = match rule {
128 ZeroImpedanceRule::Series => br.r * br.r + br.x * br.x == 0.0,
129 ZeroImpedanceRule::Reactance => br.x == 0.0,
130 };
131 zero.then_some(row)
132 })
133 .collect();
134 ZeroImpedanceSkips::new(branch_indices)
135}
136
137#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
139#[non_exhaustive]
140pub struct MatrixStats {
141 pub n: usize,
142 pub nnz: usize,
143 pub min_diag: f64,
144 pub max_diag: f64,
145 pub min_dd_margin: f64,
148 pub m_matrix_sign: bool,
150 pub frobenius_norm: f64,
151 #[serde(default)]
153 pub skipped_zero_impedance: usize,
154 #[serde(default)]
156 pub skipped_zero_impedance_branches: Vec<usize>,
157}
158
159impl MatrixStats {
160 pub fn from_csr(a: &CsMat<f64>) -> Self {
161 let n = a.rows();
162 let mut min_diag = f64::INFINITY;
163 let mut max_diag = f64::NEG_INFINITY;
164 let mut min_dd = f64::INFINITY;
165 let mut m_sign = true;
166 let mut fro_sq = 0.0_f64;
167
168 for (row_idx, row) in a.outer_iterator().enumerate() {
169 let mut diag = 0.0_f64;
170 let mut off_abs = 0.0_f64;
171 for (col, &v) in row.iter() {
172 fro_sq += v * v;
173 if col == row_idx {
174 diag = v;
175 } else {
176 off_abs += v.abs();
177 if v > 0.0 {
178 m_sign = false;
179 }
180 }
181 }
182 min_diag = min_diag.min(diag);
183 max_diag = max_diag.max(diag);
184 min_dd = min_dd.min(diag - off_abs);
185 }
186
187 Self {
188 n,
189 nnz: a.nnz(),
190 min_diag,
191 max_diag,
192 min_dd_margin: min_dd,
193 m_matrix_sign: m_sign,
194 frobenius_norm: fro_sq.sqrt(),
195 skipped_zero_impedance: 0,
196 skipped_zero_impedance_branches: Vec::new(),
197 }
198 }
199
200 #[must_use]
201 pub fn with_zero_impedance_skips(mut self, skips: ZeroImpedanceSkips) -> Self {
202 self.skipped_zero_impedance = skips.count;
203 self.skipped_zero_impedance_branches = skips.branch_indices;
204 self
205 }
206}
207
208pub(crate) fn negate_into(mut a: CsMat<f64>) -> CsMat<f64> {
212 a.data_mut().iter_mut().for_each(|v| *v = -*v);
213 a
214}
215
216pub fn sddm_check(a: &CsMat<f64>) -> bool {
219 let stats = MatrixStats::from_csr(a);
220 stats.m_matrix_sign && stats.min_dd_margin >= -1e-12 && stats.min_diag > 0.0
221}