use std::fmt;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Mutex;
use crate::network::{linear_initial_guess, YBusSparse};
use crate::solver::{JacobianBackend, PersistentSolver};
use crate::types::Bus;
use crate::PowerFlowReport;
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct BusOverride {
pub bus: usize,
pub p_spec: Option<f64>,
pub q_spec: Option<f64>,
pub voltage_mag: Option<f64>,
}
impl BusOverride {
pub fn new(bus: usize) -> Self {
Self { bus, ..Default::default() }
}
pub fn p(mut self, p_spec: f64) -> Self {
self.p_spec = Some(p_spec);
self
}
pub fn q(mut self, q_spec: f64) -> Self {
self.q_spec = Some(q_spec);
self
}
pub fn vm(mut self, voltage_mag: f64) -> Self {
self.voltage_mag = Some(voltage_mag);
self
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Scenario {
pub bus_overrides: Vec<BusOverride>,
pub branch_outages: Vec<usize>,
}
impl Scenario {
pub fn new(bus_overrides: Vec<BusOverride>) -> Self {
Self { bus_overrides, branch_outages: Vec::new() }
}
}
pub fn uniform_load_scaling(buses_template: &[Bus], factor: f64) -> Scenario {
let overrides = buses_template
.iter()
.filter(|b| b.p_spec != 0.0 || b.q_spec != 0.0)
.map(|b| BusOverride::new(b.idx).p(b.p_spec * factor).q(b.q_spec * factor))
.collect();
Scenario::new(overrides)
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum BatchError {
OutagesUnsupported { scenario: usize },
BusOutOfRange { scenario: usize, bus: usize, n_buses: usize },
ThreadPool(String),
}
impl fmt::Display for BatchError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BatchError::OutagesUnsupported { scenario } => write!(
f,
"scenario {scenario} requests branch outages, which are not implemented yet \
(see batch::Scenario::branch_outages)"
),
BatchError::BusOutOfRange { scenario, bus, n_buses } => write!(
f,
"scenario {scenario} overrides bus {bus}, but the template has only {n_buses} bus(es)"
),
BatchError::ThreadPool(e) => write!(f, "building the rayon thread pool failed: {e}"),
}
}
}
impl std::error::Error for BatchError {}
pub struct BatchSolver {
backend: JacobianBackend,
pool: Option<rayon::ThreadPool>,
}
impl BatchSolver {
pub fn new(backend: JacobianBackend) -> Self {
Self { backend, pool: None }
}
pub fn with_threads(backend: JacobianBackend, threads: usize) -> Result<Self, BatchError> {
let pool = rayon::ThreadPoolBuilder::new()
.num_threads(threads)
.build()
.map_err(|e| BatchError::ThreadPool(e.to_string()))?;
Ok(Self { backend, pool: Some(pool) })
}
pub fn threads(&self) -> usize {
match &self.pool {
Some(p) => p.current_num_threads(),
None => rayon::current_num_threads(),
}
}
pub fn solve(
&self,
buses_template: &[Bus],
ybus: &YBusSparse,
scenarios: &[Scenario],
tol: f64,
max_iter: usize,
) -> Result<Vec<PowerFlowReport>, BatchError> {
for (i, sc) in scenarios.iter().enumerate() {
if !sc.branch_outages.is_empty() {
return Err(BatchError::OutagesUnsupported { scenario: i });
}
for ov in &sc.bus_overrides {
if ov.bus >= buses_template.len() {
return Err(BatchError::BusOutOfRange {
scenario: i,
bus: ov.bus,
n_buses: buses_template.len(),
});
}
}
}
if scenarios.is_empty() {
return Ok(Vec::new());
}
let next = AtomicUsize::new(0);
let collected: Mutex<Vec<(usize, PowerFlowReport)>> =
Mutex::new(Vec::with_capacity(scenarios.len()));
let n_workers = self.threads().max(1).min(scenarios.len());
let backend = self.backend;
let run = || {
rayon::scope(|s| {
for _ in 0..n_workers {
s.spawn(|_| {
let mut solver = PersistentSolver::new(backend);
loop {
let i = next.fetch_add(1, Ordering::Relaxed);
if i >= scenarios.len() {
break;
}
let mut buses = buses_template.to_vec();
for ov in &scenarios[i].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);
let (islands, stats) =
solver.solve_with_stats(&mut buses, ybus, tol, max_iter);
collected
.lock()
.expect("batch result mutex poisoned by a panicking worker")
.push((i, PowerFlowReport { buses, islands, stats }));
}
});
}
});
};
match &self.pool {
Some(pool) => pool.install(run),
None => run(),
}
let mut out = collected.into_inner().expect("batch result mutex poisoned");
out.sort_by_key(|(i, _)| *i);
Ok(out.into_iter().map(|(_, r)| r).collect())
}
}