use std::sync::Mutex;
use crate::scenario::{BatchError, Scenario};
use crate::types::{Bus, Line, Transformer};
use super::DcOptions;
use super::btheta::{DcIslandReport, DcSolution, dc_branches, dc_power_flow};
use super::sensitivity::DcSensitivity;
#[derive(Clone, Debug, PartialEq)]
pub struct DcBatchResult {
pub voltage_ang: Vec<f64>,
pub branch_p: Vec<f64>,
pub slack_pickup: Vec<f64>,
}
pub struct DcBatchSolver {
pool: Option<rayon::ThreadPool>,
}
impl Default for DcBatchSolver {
fn default() -> Self {
Self::new()
}
}
impl DcBatchSolver {
pub fn new() -> Self {
Self { pool: None }
}
pub fn with_threads(threads: usize) -> Result<Self, BatchError> {
let pool = rayon::ThreadPoolBuilder::new()
.num_threads(threads)
.build()
.map_err(|e| BatchError::ThreadPool(e.to_string()))?;
Ok(Self { 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],
lines: &[Line],
transformers: &[Transformer],
opts: DcOptions,
scenarios: &[Scenario],
) -> Result<Vec<DcBatchResult>, 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 prepared = Prepared::new(buses_template, lines, transformers, opts)
.ok_or(BatchError::DcSingular)?;
let collected: Mutex<Vec<(usize, DcBatchResult)>> =
Mutex::new(Vec::with_capacity(scenarios.len()));
let run = || {
use rayon::prelude::*;
scenarios.par_iter().enumerate().for_each(|(i, sc)| {
let result = prepared.solve_one(sc);
collected
.lock()
.expect("batch collector poisoned")
.push((i, result));
});
};
match &self.pool {
Some(pool) => pool.install(run),
None => run(),
}
let mut collected = collected.into_inner().expect("batch collector poisoned");
collected.sort_by_key(|(i, _)| *i);
Ok(collected.into_iter().map(|(_, r)| r).collect())
}
pub fn islands(
buses_template: &[Bus],
lines: &[Line],
transformers: &[Transformer],
opts: DcOptions,
) -> Vec<DcIslandReport> {
let mut scratch = buses_template.to_vec();
dc_power_flow(&mut scratch, lines, transformers, opts).islands
}
}
struct Prepared {
base_ang: Vec<f64>,
base: DcSolution,
sensitivity: DcSensitivity,
base_injection: Vec<f64>,
zip_sum: Vec<f64>,
island_members: Vec<Vec<usize>>,
}
impl Prepared {
fn new(
buses_template: &[Bus],
lines: &[Line],
transformers: &[Transformer],
opts: DcOptions,
) -> Option<Self> {
let mut scratch = buses_template.to_vec();
let base = dc_power_flow(&mut scratch, lines, transformers, opts);
let base_ang: Vec<f64> = scratch.iter().map(|b| b.voltage_ang).collect();
let branches = dc_branches(lines, transformers, opts);
let n_branches = lines.len() + transformers.len();
let sensitivity = DcSensitivity::new(&scratch, &branches, n_branches)?;
let zip_sum: Vec<f64> = buses_template.iter().map(zip_total).collect();
let base_injection: Vec<f64> = buses_template
.iter()
.zip(&zip_sum)
.map(|(b, z)| b.p_spec + z)
.collect();
let island_members = base
.islands
.iter()
.map(|island| {
island
.bus_indices
.iter()
.copied()
.filter(|&i| !island.slack_indices.contains(&i))
.collect()
})
.collect();
Some(Self {
base_ang,
base,
sensitivity,
base_injection,
zip_sum,
island_members,
})
}
fn solve_one(&self, scenario: &Scenario) -> DcBatchResult {
let mut delta = vec![0.0; self.base_injection.len()];
let mut scenario_injection = self.base_injection.clone();
for ov in &scenario.bus_overrides {
if let Some(p) = ov.p_spec {
let new = p + self.zip_sum[ov.bus];
delta[ov.bus] = new - self.base_injection[ov.bus];
scenario_injection[ov.bus] = new;
}
}
let (d_ang, d_flow) = self
.sensitivity
.response(&delta)
.expect("delta is one entry per bus and the factorization is non-singular");
let voltage_ang: Vec<f64> = self
.base_ang
.iter()
.zip(&d_ang)
.map(|(a, d)| a + d)
.collect();
let branch_p: Vec<f64> = self
.base
.branch_p
.iter()
.zip(&d_flow)
.map(|(p, d)| p + d)
.collect();
let slack_pickup = self
.island_members
.iter()
.map(|members| -members.iter().map(|&i| scenario_injection[i]).sum::<f64>())
.collect();
DcBatchResult {
voltage_ang,
branch_p,
slack_pickup,
}
}
}
fn zip_total(bus: &Bus) -> f64 {
bus.zip_terms.iter().map(|z| z.s_const.re).sum()
}