use crate::network::{effective_injection, power_injections};
use crate::types::{Bus, BusType};
use super::{Invalidates, OuterLoop, OuterLoopContext, OuterLoopStatus};
#[derive(Clone, Debug)]
pub struct SlackDistribution {
pub factors: Vec<f64>,
pub tolerance: f64,
pub max_outer_iter: usize,
}
impl SlackDistribution {
pub fn uniform(buses: &[Bus]) -> Self {
let factors = buses
.iter()
.map(|b| match b.bus_type {
BusType::Slack | BusType::PV => 1.0,
BusType::PQ => 0.0,
})
.collect();
Self {
factors,
tolerance: 1e-8,
max_outer_iter: 20,
}
}
pub fn from_weights(factors: Vec<f64>) -> Self {
Self {
factors,
tolerance: 1e-8,
max_outer_iter: 20,
}
}
}
#[derive(Clone, Debug, Default)]
pub struct SlackDistributionReport {
pub shift: Vec<f64>,
pub residual: Vec<f64>,
pub outer_iterations: usize,
pub converged: bool,
pub undistributed: Vec<(usize, &'static str)>,
}
#[derive(Clone, Debug)]
pub struct DistributedSlack {
distribution: SlackDistribution,
report: SlackDistributionReport,
exhausted: bool,
}
impl DistributedSlack {
pub fn new(distribution: SlackDistribution) -> Self {
Self {
distribution,
report: SlackDistributionReport::default(),
exhausted: false,
}
}
pub fn report(&self) -> &SlackDistributionReport {
&self.report
}
pub fn into_report(self) -> SlackDistributionReport {
self.report
}
}
impl OuterLoop for DistributedSlack {
fn name(&self) -> &'static str {
"DistributedSlack"
}
fn invalidates(&self) -> Invalidates {
Invalidates::Nothing
}
fn initialize(&mut self, ctx: &mut OuterLoopContext<'_, '_>) {
let n = ctx.net.buses.len();
assert_eq!(
self.distribution.factors.len(),
n,
"participation factors must carry one weight per bus"
);
self.report.shift = vec![0.0; n];
}
fn check(&mut self, ctx: &mut OuterLoopContext<'_, '_>) -> OuterLoopStatus {
if self.exhausted {
return OuterLoopStatus::Stable;
}
self.report.outer_iterations += 1;
let (p_calc, _) = power_injections(ctx.net.buses, ctx.net.ybus);
let mut residual = Vec::with_capacity(ctx.islands.len());
let mut undistributed = Vec::new();
let mut worst = 0.0f64;
let mut updates: Vec<(usize, f64)> = Vec::new();
for (island, report) in ctx.islands.iter().enumerate() {
let slack = match report.slack_indices.as_slice() {
[only] => *only,
[] => {
residual.push(0.0);
undistributed.push((island, "no reference bus"));
continue;
}
_ => {
residual.push(0.0);
undistributed.push((island, "ambiguous reference bus"));
continue;
}
};
let scheduled = effective_injection(&ctx.net.buses[slack]).0;
let delta = p_calc[slack] - scheduled;
let total: f64 = report
.bus_indices
.iter()
.map(|&i| self.distribution.factors[i].max(0.0))
.sum();
if total <= 0.0 {
residual.push(delta);
undistributed.push((island, "no participating generator in this island"));
continue;
}
residual.push(delta);
worst = worst.max(delta.abs());
for &i in &report.bus_indices {
let weight = self.distribution.factors[i].max(0.0);
if weight > 0.0 {
updates.push((i, weight / total * delta));
}
}
}
self.report.residual = residual;
self.report.undistributed = undistributed;
if worst <= self.distribution.tolerance {
self.report.converged = true;
return OuterLoopStatus::Stable;
}
for (i, amount) in updates {
ctx.net.buses[i].p_spec += amount;
self.report.shift[i] += amount;
}
if self.report.outer_iterations >= self.distribution.max_outer_iter.max(1) {
self.exhausted = true;
}
OuterLoopStatus::Unstable
}
}