1#![allow(clippy::many_single_char_names, clippy::too_many_arguments)]
19
20use sprs::CsMat;
21
22use crate::matrix::incidence::{build_flow_map, diagonal};
23use crate::matrix::laplacian::{GroundedIndexMap, build_weighted_laplacian, ground_at};
24use crate::matrix::triplet::CooBuilder;
25use crate::{Error, Result};
26
27#[derive(Debug, Clone)]
30pub struct KktOperators {
31 pub l1: CsMat<f64>,
33 pub l2: CsMat<f64>,
35 pub d: Vec<f64>,
37 pub l1_grounded: CsMat<f64>,
39 pub l2_grounded: CsMat<f64>,
41 pub l_eff_grounded: Option<CsMat<f64>>,
44 pub map: GroundedIndexMap,
45}
46
47pub fn assemble_kkt(
54 a: &CsMat<f64>,
55 b: &[f64],
56 l: &CsMat<f64>,
57 q_bus: &[f64],
58 theta_f_inv: &[f64],
59 theta_g_inv: &[f64],
60 r: usize,
61 want_l_eff: bool,
62) -> Result<KktOperators> {
63 let n = l.rows();
64 let m = b.len();
65 check_len("theta_f_inv", theta_f_inv, m)?;
66 check_len("theta_g_inv", theta_g_inv, n)?;
67 check_len("q_bus", q_bus, n)?;
68
69 let w: Vec<f64> = (0..m).map(|k| b[k] * b[k] * theta_f_inv[k]).collect();
71 let l1 = build_weighted_laplacian(a, &w);
72 let l2 = l.clone();
73
74 let d: Vec<f64> = (0..n).map(|i| 1.0 / (q_bus[i] + theta_g_inv[i])).collect();
76
77 let l1_grounded = ground_at(&l1, r);
78 let l2_grounded = ground_at(&l2, r);
79
80 let l_eff_grounded = if want_l_eff {
81 let dmat = diagonal(&d);
83 let ld = l * &dmat; let ldl = &ld * l; let l_eff = add_csr(&l1, &ldl);
86 Some(ground_at(&l_eff, r))
87 } else {
88 None
89 };
90
91 Ok(KktOperators {
92 l1,
93 l2,
94 d,
95 l1_grounded,
96 l2_grounded,
97 l_eff_grounded,
98 map: GroundedIndexMap::new(n, r),
99 })
100}
101
102pub fn assemble_reduced_kkt(
106 a: &CsMat<f64>,
107 b: &[f64],
108 l: &CsMat<f64>,
109 q_bus: &[f64],
110 theta_f_inv: &[f64],
111 theta_g_inv: &[f64],
112 r: usize,
113) -> Result<CsMat<f64>> {
114 let n = l.rows();
115 let m = b.len();
116 check_len("theta_f_inv", theta_f_inv, m)?;
117 check_len("theta_g_inv", theta_g_inv, n)?;
118 check_len("q_bus", q_bus, n)?;
119
120 let (o_pg, o_th, o_f, o_nu, o_eta, o_rho) = (0, n, 2 * n, 2 * n + m, 3 * n + m, 3 * n + 2 * m);
122 let dim = 3 * n + 2 * m + 1;
123
124 let ab = a * &diagonal(b); let flow = build_flow_map(a, b); let mut k =
128 CooBuilder::with_capacity_rect(dim, dim, 4 * l.nnz() + 4 * ab.nnz() + 4 * n + 4 * m);
129
130 for i in 0..n {
132 k.add(o_pg + i, o_pg + i, q_bus[i] + theta_g_inv[i]);
133 k.add(o_pg + i, o_nu + i, -1.0);
134 k.add(o_nu + i, o_pg + i, -1.0); }
136 for (&v, (i, j)) in l {
138 k.add(o_th + i, o_nu + j, v);
139 k.add(o_nu + i, o_th + j, v);
140 }
141 for (&v, (i, j)) in &ab {
143 k.add(o_th + i, o_eta + j, -v);
144 }
145 for (&v, (i, j)) in &flow {
146 k.add(o_eta + i, o_th + j, -v);
147 }
148 k.add(o_th + r, o_rho, 1.0);
150 k.add(o_rho, o_th + r, 1.0);
151 for (kk, &tf) in theta_f_inv.iter().enumerate() {
153 k.add(o_f + kk, o_f + kk, tf);
154 k.add(o_f + kk, o_eta + kk, 1.0);
155 k.add(o_eta + kk, o_f + kk, 1.0);
156 }
157
158 Ok(k.finish_csr())
159}
160
161fn check_len(what: &'static str, v: &[f64], expected: usize) -> Result<()> {
162 if v.len() == expected {
163 Ok(())
164 } else {
165 Err(Error::ShapeMismatch {
166 what,
167 expected,
168 got: v.len(),
169 })
170 }
171}
172
173fn add_csr(a: &CsMat<f64>, b: &CsMat<f64>) -> CsMat<f64> {
175 let mut out = CooBuilder::with_capacity_rect(a.rows(), a.cols(), a.nnz() + b.nnz());
176 for (&v, (i, j)) in a {
177 out.add(i, j, v);
178 }
179 for (&v, (i, j)) in b {
180 out.add(i, j, v);
181 }
182 out.finish_csr()
183}