use sprs::CsMat;
use crate::indexed::IndexedNetwork;
use crate::matrix::incidence::{IncidenceParts, diagonal};
use crate::matrix::triplet::CooBuilder;
use crate::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
#[non_exhaustive]
pub enum Units {
#[default]
PerUnit,
Native,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct BusCosts {
pub q: Vec<f64>,
pub c: Vec<f64>,
pub pmax: Vec<f64>,
pub pmin: Vec<f64>,
pub p_d: Vec<f64>,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct GenCosts {
pub q: Vec<f64>,
pub c: Vec<f64>,
pub pmax: Vec<f64>,
pub pmin: Vec<f64>,
pub gen_of_col: Vec<usize>,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct OpfInstance {
pub n: usize,
pub m: usize,
pub bus: BusCosts,
pub gen_costs: GenCosts,
pub f_max: Vec<f64>,
pub c_g: CsMat<f64>,
}
impl OpfInstance {
#[must_use]
pub fn n_gen(&self) -> usize {
self.gen_costs.q.len()
}
}
pub fn build_opf_instance(
case: &IndexedNetwork,
incidence: &IncidenceParts,
units: Units,
) -> Result<OpfInstance> {
let n = case.n();
let m = incidence.m();
let base = case.per_unit_base();
let p_scale = match units {
Units::PerUnit => 1.0 / base,
Units::Native => 1.0,
};
let (q_scale, c_scale) = match units {
Units::PerUnit => (base * base, base),
Units::Native => (1.0, 1.0),
};
let in_service: Vec<(usize, &crate::network::Generator)> = case.in_service_gens().collect();
let n_gen = in_service.len();
if n_gen == 0 {
return Err(Error::NoGenerators);
}
let mut q_gen = Vec::with_capacity(n_gen);
let mut c_gen = Vec::with_capacity(n_gen);
let mut pmax_gen = Vec::with_capacity(n_gen);
let mut pmin_gen = Vec::with_capacity(n_gen);
let mut gen_of_col = Vec::with_capacity(n_gen);
let mut cg = CooBuilder::with_capacity_rect(n, n_gen, n_gen);
for (col, &(gidx, generator)) in in_service.iter().enumerate() {
let bus = case.bus_index(generator.bus).ok_or(Error::UnknownBus {
bus_id: generator.bus,
element_index: gidx,
})?;
let cost = generator
.cost
.as_ref()
.ok_or(Error::MissingGenCost { gen_index: gidx })?;
let (q_raw, c_raw) = cost.quadratic().ok_or(Error::UnsupportedCostModel {
gen_index: gidx,
model: cost.model,
ncost: cost.ncost,
})?;
q_gen.push(q_raw * q_scale);
c_gen.push(c_raw * c_scale);
pmax_gen.push(generator.pmax * p_scale);
pmin_gen.push(generator.pmin * p_scale);
gen_of_col.push(gidx);
cg.add(bus, col, 1.0);
}
let c_g = cg.finish_csr();
let q_bus = project_gen_to_bus(&c_g, &q_gen);
let c_bus = project_gen_to_bus(&c_g, &c_gen);
let pmax_bus = project_gen_to_bus(&c_g, &pmax_gen);
let pmin_bus = project_gen_to_bus(&c_g, &pmin_gen);
let p_d: Vec<f64> = case.pd().iter().map(|&p| p * p_scale).collect();
let f_max: Vec<f64> = incidence
.branch_of_col
.iter()
.map(|&k| case.branches()[k].rate_a * p_scale)
.collect();
Ok(OpfInstance {
n,
m,
bus: BusCosts {
q: q_bus,
c: c_bus,
pmax: pmax_bus,
pmin: pmin_bus,
p_d,
},
gen_costs: GenCosts {
q: q_gen,
c: c_gen,
pmax: pmax_gen,
pmin: pmin_gen,
gen_of_col,
},
f_max,
c_g,
})
}
pub fn project_gen_to_bus(c_g: &CsMat<f64>, v: &[f64]) -> Vec<f64> {
let mut out = vec![0.0; c_g.rows()];
for (&val, (bus, g)) in c_g {
out[bus] += val * v[g];
}
out
}
pub fn cost_quadratic_diag(q: &[f64]) -> CsMat<f64> {
diagonal(q)
}