use crate::network::ShuntAdm;
use crate::network::{YBusSparse, build_ybus, stamp_shunts};
use crate::solver::{IslandReport, IslandStatus, JacobianBackend, PersistentSolver};
use crate::types::{Bus, Line, TapChanger, Transformer};
pub mod area;
pub mod qlimits;
pub mod remote;
pub mod slack;
pub mod taps;
pub use area::{AreaDefinition, AreaInterchange, AreaInterchangeReport};
pub use qlimits::{QLimitReport, ReactiveLimits};
pub use remote::{RemoteControlReport, RemoteOutcome, RemoteVoltageControl};
pub use slack::{DistributedSlack, SlackDistribution, SlackDistributionReport};
pub use taps::{
ControllerOutcome, ControllerReport, PhaseControl, RegulationMode, TapControlReport,
TapRegulation, TransformerVoltageControl,
};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum OuterLoopStatus {
Stable,
Unstable,
Failed(String),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Invalidates {
Nothing,
Pattern,
Admittances,
}
pub struct OuterLoopContext<'a, 'n> {
pub net: &'a mut SolveContext<'n>,
pub islands: &'a [IslandReport],
pub iteration: usize,
}
pub struct SolveContext<'a> {
pub buses: &'a mut [Bus],
pub lines: &'a [Line],
pub transformers: &'a mut [Transformer],
pub shunts: &'a [ShuntAdm],
pub tap_changers: &'a mut [Option<TapChanger>],
pub regulation: &'a [TapRegulation],
pub ybus: &'a mut YBusSparse,
}
impl<'a> SolveContext<'a> {
pub fn new(buses: &'a mut [Bus], ybus: &'a mut YBusSparse) -> Self {
Self {
buses,
lines: &[],
transformers: &mut [],
shunts: &[],
tap_changers: &mut [],
regulation: &[],
ybus,
}
}
pub fn with_branches(
mut self,
lines: &'a [Line],
transformers: &'a mut [Transformer],
shunts: &'a [ShuntAdm],
) -> Self {
self.lines = lines;
self.transformers = transformers;
self.shunts = shunts;
self
}
pub fn with_taps(
mut self,
tap_changers: &'a mut [Option<TapChanger>],
regulation: &'a [TapRegulation],
) -> Self {
self.tap_changers = tap_changers;
self.regulation = regulation;
self
}
pub fn restamp_ybus(&mut self) {
if self.lines.is_empty() && self.transformers.is_empty() {
return;
}
let n = self.ybus.n();
let mut y = build_ybus(n, self.lines, self.transformers);
stamp_shunts(&mut y, self.shunts);
*self.ybus = y.finish();
}
}
pub trait OuterLoop {
fn name(&self) -> &'static str;
fn invalidates(&self) -> Invalidates {
Invalidates::Nothing
}
fn initialize(&mut self, _ctx: &mut OuterLoopContext<'_, '_>) {}
fn check(&mut self, ctx: &mut OuterLoopContext<'_, '_>) -> OuterLoopStatus;
}
#[derive(Clone, Debug)]
pub struct LoopReport {
pub name: &'static str,
pub iterations: usize,
pub status: OuterLoopStatus,
}
#[derive(Clone, Debug)]
pub struct OuterLoopReport {
pub loops: Vec<LoopReport>,
pub total_iterations: usize,
pub solves: usize,
pub converged: bool,
pub budget_exhausted: bool,
}
impl OuterLoopReport {
pub fn loop_named(&self, name: &str) -> Option<&LoopReport> {
self.loops.iter().find(|l| l.name == name)
}
}
pub fn ordered<'a>(
slack: Option<&'a mut DistributedSlack>,
qlimits: Option<&'a mut ReactiveLimits>,
phase: Option<&'a mut PhaseControl>,
voltage: Option<&'a mut TransformerVoltageControl>,
) -> Vec<&'a mut dyn OuterLoop> {
let mut loops: Vec<&mut dyn OuterLoop> = Vec::new();
if let Some(l) = slack {
loops.push(l);
}
if let Some(l) = qlimits {
loops.push(l);
}
if let Some(l) = phase {
loops.push(l);
}
if let Some(l) = voltage {
loops.push(l);
}
loops
}
fn settled(reports: &[IslandReport]) -> bool {
reports.iter().all(|r| {
!matches!(
r.status,
IslandStatus::Singular | IslandStatus::MaxIterationsReached
)
})
}
pub fn solve_with_loops(
ctx: &mut SolveContext<'_>,
tol: f64,
max_iter: usize,
backend: JacobianBackend,
loops: &mut [&mut dyn OuterLoop],
budget: usize,
) -> (Vec<IslandReport>, OuterLoopReport) {
let mut solver = PersistentSolver::new(backend);
let mut counts = vec![0usize; loops.len()];
let mut statuses = vec![OuterLoopStatus::Stable; loops.len()];
{
let mut lc = OuterLoopContext {
net: ctx,
islands: &[],
iteration: 0,
};
for l in loops.iter_mut() {
l.initialize(&mut lc);
}
}
let mut reports = solver.solve(ctx.buses, ctx.ybus, tol, max_iter);
let mut solves = 1usize;
let mut total = 0usize;
let mut budget_exhausted = false;
let finish = |reports: Vec<IslandReport>,
loops: &[&mut dyn OuterLoop],
counts: &[usize],
statuses: &[OuterLoopStatus],
total: usize,
solves: usize,
converged: bool,
budget_exhausted: bool| {
let report = OuterLoopReport {
loops: loops
.iter()
.enumerate()
.map(|(i, l)| LoopReport {
name: l.name(),
iterations: counts[i],
status: statuses[i].clone(),
})
.collect(),
total_iterations: total,
solves,
converged,
budget_exhausted,
};
(reports, report)
};
if loops.is_empty() || !settled(&reports) {
let ok = settled(&reports);
return finish(reports, loops, &counts, &statuses, total, solves, ok, false);
}
let mut last_unstable: Option<usize> = None;
let mut failed = false;
'outer: loop {
let before = total;
for i in 0..loops.len() {
if Some(i) == last_unstable {
break 'outer;
}
if total >= budget {
budget_exhausted = true;
break 'outer;
}
loop {
let status = {
let mut lc = OuterLoopContext {
net: ctx,
islands: &reports,
iteration: counts[i],
};
loops[i].check(&mut lc)
};
statuses[i] = status.clone();
match status {
OuterLoopStatus::Stable => break,
OuterLoopStatus::Failed(_) => {
failed = true;
break 'outer;
}
OuterLoopStatus::Unstable => {
match loops[i].invalidates() {
Invalidates::Nothing => {}
Invalidates::Pattern => solver.reset(),
Invalidates::Admittances => {
ctx.restamp_ybus();
solver.invalidate_admittances();
}
}
reports = solver.solve(ctx.buses, ctx.ybus, tol, max_iter);
solves += 1;
counts[i] += 1;
total += 1;
last_unstable = Some(i);
if !settled(&reports) {
break 'outer;
}
if total >= budget {
budget_exhausted = true;
break 'outer;
}
}
}
}
}
if total == before {
break;
}
}
let converged = !failed
&& !budget_exhausted
&& settled(&reports)
&& statuses.iter().all(|s| *s == OuterLoopStatus::Stable);
finish(
reports,
loops,
&counts,
&statuses,
total,
solves,
converged,
budget_exhausted,
)
}