#![allow(clippy::many_single_char_names, clippy::too_many_arguments)]
use sprs::CsMat;
use crate::matrix::incidence::{build_flow_map, diagonal};
use crate::matrix::laplacian::{GroundedIndexMap, build_weighted_laplacian, ground_at};
use crate::matrix::triplet::CooBuilder;
use crate::{Error, Result};
#[derive(Debug, Clone)]
pub struct KktOperators {
pub l1: CsMat<f64>,
pub l2: CsMat<f64>,
pub d: Vec<f64>,
pub l1_grounded: CsMat<f64>,
pub l2_grounded: CsMat<f64>,
pub l_eff_grounded: Option<CsMat<f64>>,
pub map: GroundedIndexMap,
}
pub fn assemble_kkt(
a: &CsMat<f64>,
b: &[f64],
l: &CsMat<f64>,
q_bus: &[f64],
theta_f_inv: &[f64],
theta_g_inv: &[f64],
r: usize,
want_l_eff: bool,
) -> Result<KktOperators> {
let n = l.rows();
let m = b.len();
check_len("theta_f_inv", theta_f_inv, m)?;
check_len("theta_g_inv", theta_g_inv, n)?;
check_len("q_bus", q_bus, n)?;
let w: Vec<f64> = (0..m).map(|k| b[k] * b[k] * theta_f_inv[k]).collect();
let l1 = build_weighted_laplacian(a, &w);
let l2 = l.clone();
let d: Vec<f64> = (0..n).map(|i| 1.0 / (q_bus[i] + theta_g_inv[i])).collect();
let l1_grounded = ground_at(&l1, r);
let l2_grounded = ground_at(&l2, r);
let l_eff_grounded = if want_l_eff {
let dmat = diagonal(&d);
let ld = l * &dmat; let ldl = &ld * l; let l_eff = add_csr(&l1, &ldl);
Some(ground_at(&l_eff, r))
} else {
None
};
Ok(KktOperators {
l1,
l2,
d,
l1_grounded,
l2_grounded,
l_eff_grounded,
map: GroundedIndexMap::new(n, r),
})
}
pub fn assemble_reduced_kkt(
a: &CsMat<f64>,
b: &[f64],
l: &CsMat<f64>,
q_bus: &[f64],
theta_f_inv: &[f64],
theta_g_inv: &[f64],
r: usize,
) -> Result<CsMat<f64>> {
let n = l.rows();
let m = b.len();
check_len("theta_f_inv", theta_f_inv, m)?;
check_len("theta_g_inv", theta_g_inv, n)?;
check_len("q_bus", q_bus, n)?;
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);
let dim = 3 * n + 2 * m + 1;
let ab = a * &diagonal(b); let flow = build_flow_map(a, b);
let mut k =
CooBuilder::with_capacity_rect(dim, dim, 4 * l.nnz() + 4 * ab.nnz() + 4 * n + 4 * m);
for i in 0..n {
k.add(o_pg + i, o_pg + i, q_bus[i] + theta_g_inv[i]);
k.add(o_pg + i, o_nu + i, -1.0);
k.add(o_nu + i, o_pg + i, -1.0); }
for (&v, (i, j)) in l {
k.add(o_th + i, o_nu + j, v);
k.add(o_nu + i, o_th + j, v);
}
for (&v, (i, j)) in &ab {
k.add(o_th + i, o_eta + j, -v);
}
for (&v, (i, j)) in &flow {
k.add(o_eta + i, o_th + j, -v);
}
k.add(o_th + r, o_rho, 1.0);
k.add(o_rho, o_th + r, 1.0);
for (kk, &tf) in theta_f_inv.iter().enumerate() {
k.add(o_f + kk, o_f + kk, tf);
k.add(o_f + kk, o_eta + kk, 1.0);
k.add(o_eta + kk, o_f + kk, 1.0);
}
Ok(k.finish_csr())
}
fn check_len(what: &'static str, v: &[f64], expected: usize) -> Result<()> {
if v.len() == expected {
Ok(())
} else {
Err(Error::ShapeMismatch {
what,
expected,
got: v.len(),
})
}
}
fn add_csr(a: &CsMat<f64>, b: &CsMat<f64>) -> CsMat<f64> {
let mut out = CooBuilder::with_capacity_rect(a.rows(), a.cols(), a.nnz() + b.nnz());
for (&v, (i, j)) in a {
out.add(i, j, v);
}
for (&v, (i, j)) in b {
out.add(i, j, v);
}
out.finish_csr()
}