#![allow(clippy::needless_range_loop, clippy::explicit_iter_loop)]
use sprs::CsMat;
use crate::indexed::IndexedNetwork;
use crate::matrix::incidence::{DcConvention, IncidenceParts, build_flow_map, build_incidence};
use crate::matrix::laplacian::{Grounding, build_weighted_laplacian, ground_with};
use crate::matrix::triplet::CooBuilder;
use crate::{Error, Result};
const PRUNE: f64 = 1e-12;
pub fn build_ptdf(case: &IndexedNetwork, conv: DcConvention) -> Result<CsMat<f64>> {
case.check_reference_coverage()?;
let refs = case.reference_bus_indices();
let inc = build_incidence(case, conv)?;
let (dense, m, n) = ptdf_dense(&inc, &refs)?;
Ok(dense_to_csr(&dense, m, n))
}
pub fn build_lodf(case: &IndexedNetwork, conv: DcConvention) -> Result<CsMat<f64>> {
case.check_reference_coverage()?;
let refs = case.reference_bus_indices();
let inc = build_incidence(case, conv)?;
let (ptdf, m, n) = ptdf_dense(&inc, &refs)?;
Ok(lodf_from_dense(&ptdf, &inc.a, m, n))
}
pub fn build_ptdf_lodf(
case: &IndexedNetwork,
conv: DcConvention,
) -> Result<(CsMat<f64>, CsMat<f64>)> {
case.check_reference_coverage()?;
let refs = case.reference_bus_indices();
let inc = build_incidence(case, conv)?;
let (dense, m, n) = ptdf_dense(&inc, &refs)?;
let ptdf = dense_to_csr(&dense, m, n);
let lodf = lodf_from_dense(&dense, &inc.a, m, n);
Ok((ptdf, lodf))
}
fn lodf_from_dense(ptdf: &[f64], a: &CsMat<f64>, m: usize, n: usize) -> CsMat<f64> {
let (from, to) = endpoints(a, m);
let delta = |l: usize, k: usize| ptdf[l * n + from[k]] - ptdf[l * n + to[k]];
let mut lodf = CooBuilder::new(m); for k in 0..m {
let denom = 1.0 - delta(k, k);
let islands = denom.abs() < 1e-9;
for l in 0..m {
let v = if l == k {
-1.0
} else if islands {
0.0
} else {
delta(l, k) / denom
};
if v.abs() > PRUNE {
lodf.add(l, k, v);
}
}
}
lodf.finish_csr()
}
fn ptdf_dense(inc: &IncidenceParts, refs: &[usize]) -> Result<(Vec<f64>, usize, usize)> {
let n = inc.n();
let m = inc.m();
let g = Grounding::new(refs);
let nr = n - g.len();
let lr = ground_with(&build_weighted_laplacian(&inc.a, &inc.b), &g);
let chol = DenseCholesky::factor(&densify(&lr, nr), nr).ok_or(Error::SingularNetwork)?;
let rinv = chol.inverse();
let flow = build_flow_map(&inc.a, &inc.b); let mut ptdf = vec![0.0; m * n];
for (&w, (l, c)) in flow.iter() {
let Some(rc) = g.reduced(c) else { continue }; for k in 0..n {
if let Some(rk) = g.reduced(k) {
ptdf[l * n + k] += w * rinv[rc * nr + rk];
}
}
}
Ok((ptdf, m, n))
}
fn endpoints(a: &CsMat<f64>, m: usize) -> (Vec<usize>, Vec<usize>) {
let mut from = vec![0usize; m];
let mut to = vec![0usize; m];
for (&v, (bus, branch)) in a.iter() {
if v > 0.0 {
from[branch] = bus;
} else {
to[branch] = bus;
}
}
(from, to)
}
fn densify(a: &CsMat<f64>, n: usize) -> Vec<f64> {
let mut d = vec![0.0; n * n];
for (&v, (i, j)) in a.iter() {
d[i * n + j] = v;
}
d
}
fn dense_to_csr(dense: &[f64], rows: usize, cols: usize) -> CsMat<f64> {
let mut coo = CooBuilder::with_capacity_rect(rows, cols, dense.len() / 2);
for i in 0..rows {
for j in 0..cols {
let v = dense[i * cols + j];
if v.abs() > PRUNE {
coo.add(i, j, v);
}
}
}
coo.finish_csr()
}
struct DenseCholesky {
n: usize,
l: Vec<f64>, }
impl DenseCholesky {
fn factor(a: &[f64], n: usize) -> Option<Self> {
let mut l = vec![0.0; n * n];
for i in 0..n {
for j in 0..=i {
let mut s = a[i * n + j];
for k in 0..j {
s -= l[i * n + k] * l[j * n + k];
}
if i == j {
#[allow(clippy::neg_cmp_op_on_partial_ord)]
if !(s > 0.0) {
return None;
}
l[i * n + i] = s.sqrt();
} else {
l[i * n + j] = s / l[j * n + j];
}
}
}
Some(Self { n, l })
}
fn solve(&self, b: &mut [f64]) {
let n = self.n;
for i in 0..n {
let mut s = b[i];
for k in 0..i {
s -= self.l[i * n + k] * b[k];
}
b[i] = s / self.l[i * n + i];
}
for i in (0..n).rev() {
let mut s = b[i];
for k in (i + 1)..n {
s -= self.l[k * n + i] * b[k];
}
b[i] = s / self.l[i * n + i];
}
}
fn inverse(&self) -> Vec<f64> {
let n = self.n;
let mut inv = vec![0.0; n * n];
let mut e = vec![0.0; n];
for j in 0..n {
e.fill(0.0);
e[j] = 1.0;
self.solve(&mut e);
for (i, &x) in e.iter().enumerate() {
inv[i * n + j] = x;
}
}
inv
}
}