use crate::batch::Scenario;
use crate::jacobian::JacobianPattern;
use crate::network::{YBusSparse, effective_injection, linear_initial_guess, power_injections};
use crate::solver::{LinearSolver, SolveStats, SolveStatus};
use crate::types::{Bus, BusType};
pub struct BlockDiagonal {
block: JacobianPattern,
n_scenarios: usize,
block_size: usize,
rows: Vec<u32>,
cols: Vec<u32>,
}
impl BlockDiagonal {
pub fn analyze(buses: &[Bus], ybus: &YBusSparse, n_scenarios: usize) -> Self {
let block = JacobianPattern::analyze(buses, ybus);
let block_size = block.n_unknowns;
let nnz = block.len();
let mut rows = Vec::with_capacity(nnz * n_scenarios);
let mut cols = Vec::with_capacity(nnz * n_scenarios);
for s in 0..n_scenarios {
let off = (s * block_size) as u32;
rows.extend(block.rows().iter().map(|&r| r + off));
cols.extend(block.cols().iter().map(|&c| c + off));
}
Self {
block,
n_scenarios,
block_size,
rows,
cols,
}
}
pub fn n_unknowns(&self) -> usize {
self.n_scenarios * self.block_size
}
pub fn block_size(&self) -> usize {
self.block_size
}
pub fn len(&self) -> usize {
self.rows.len()
}
pub fn is_empty(&self) -> bool {
self.rows.is_empty()
}
pub fn to_triplets(&self, values: &[f64]) -> Vec<(usize, usize, f64)> {
self.rows
.iter()
.zip(&self.cols)
.zip(values)
.map(|((&r, &c), &v)| (r as usize, c as usize, v))
.collect()
}
pub fn fill(
&self,
states: &[Vec<Bus>],
p_calc: &[Vec<f64>],
q_calc: &[Vec<f64>],
active: &[bool],
values: &mut Vec<f64>,
) {
values.clear();
for s in 0..self.n_scenarios {
if active[s] {
self.block
.fill_into(&states[s], &p_calc[s], &q_calc[s], values);
} else {
self.block.fill_identity_into(values);
}
}
}
}
pub struct BdeScenarioResult {
pub buses: Vec<Bus>,
pub stats: SolveStats,
}
pub fn solve_batch_block_diagonal<S: LinearSolver>(
buses_template: &[Bus],
ybus: &YBusSparse,
scenarios: &[Scenario],
tol: f64,
max_iter: usize,
) -> Vec<BdeScenarioResult> {
let nb = scenarios.len();
if nb == 0 {
return Vec::new();
}
let non_slack_idx: Vec<usize> = buses_template
.iter()
.filter(|b| !matches!(b.bus_type, BusType::Slack))
.map(|b| b.idx)
.collect();
let pq_idx: Vec<usize> = buses_template
.iter()
.filter(|b| matches!(b.bus_type, BusType::PQ))
.map(|b| b.idx)
.collect();
let n_angle = non_slack_idx.len();
let blk = n_angle + pq_idx.len();
let mut states: Vec<Vec<Bus>> = scenarios
.iter()
.map(|sc| {
let mut buses = buses_template.to_vec();
for ov in &sc.bus_overrides {
let b = &mut buses[ov.bus];
if let Some(p) = ov.p_spec {
b.p_spec = p;
}
if let Some(q) = ov.q_spec {
b.q_spec = q;
}
if let Some(vm) = ov.voltage_mag {
b.voltage_mag = vm;
}
}
linear_initial_guess(&mut buses, ybus);
buses
})
.collect();
let pattern = BlockDiagonal::analyze(buses_template, ybus, nb);
let mut cache: Option<S> = None;
let mut active = vec![true; nb];
let mut history: Vec<Vec<f64>> = vec![Vec::new(); nb];
let mut status = vec![SolveStatus::MaxIterationsReached; nb];
let mut values: Vec<f64> = Vec::with_capacity(pattern.len());
let mut rhs: Vec<f64> = vec![0.0; pattern.n_unknowns()];
let mut p_all: Vec<Vec<f64>> = vec![Vec::new(); nb];
let mut q_all: Vec<Vec<f64>> = vec![Vec::new(); nb];
for _ in 0..max_iter {
rhs.iter_mut().for_each(|v| *v = 0.0);
for s in 0..nb {
let (p_calc, q_calc) = power_injections(&states[s], ybus);
if !active[s] {
p_all[s] = p_calc;
q_all[s] = q_calc;
continue;
}
let base = s * blk;
let mut max_mis = 0.0f64;
for (r, &i) in non_slack_idx.iter().enumerate() {
let (p_eff, _) = effective_injection(&states[s][i]);
let v = p_eff - p_calc[i];
rhs[base + r] = v;
max_mis = max_mis.max(v.abs());
}
for (r, &i) in pq_idx.iter().enumerate() {
let (_, q_eff) = effective_injection(&states[s][i]);
let v = q_eff - q_calc[i];
rhs[base + n_angle + r] = v;
max_mis = max_mis.max(v.abs());
}
history[s].push(max_mis);
if max_mis < tol {
status[s] = SolveStatus::Converged;
active[s] = false;
rhs[base..base + blk].iter_mut().for_each(|v| *v = 0.0);
}
p_all[s] = p_calc;
q_all[s] = q_calc;
}
if active.iter().all(|&a| !a) {
break;
}
pattern.fill(&states, &p_all, &q_all, &active, &mut values);
if cache.is_none() {
cache = S::new(pattern.n_unknowns(), &pattern.to_triplets(&values));
}
let Some(system) = cache.as_mut() else {
for s in 0..nb {
if active[s] {
status[s] = SolveStatus::Singular;
}
}
break;
};
let Some(dx) = system.factor_and_solve_values(&values, &rhs) else {
for s in 0..nb {
if active[s] {
status[s] = SolveStatus::Singular;
}
}
break;
};
for s in 0..nb {
if !active[s] {
continue;
}
let base = s * blk;
for (r, &i) in non_slack_idx.iter().enumerate() {
states[s][i].voltage_ang += dx[base + r];
}
for (r, &i) in pq_idx.iter().enumerate() {
states[s][i].voltage_mag += dx[base + n_angle + r];
}
}
}
states
.into_iter()
.zip(history)
.zip(status)
.map(|((buses, mismatch_history), status)| BdeScenarioResult {
buses,
stats: SolveStats {
status,
mismatch_history,
},
})
.collect()
}