use super::Resolution;
use super::castor::{PerimeterPlan, Plan};
use super::crac::{Crac, State};
use super::evaluate::{self, AcOptions, Network};
#[derive(Clone, Copy, Debug)]
pub struct ValidationOptions<'a> {
pub min_margin_mw: f64,
pub max_regression_mw: f64,
pub ac: AcOptions<'a>,
}
impl Default for ValidationOptions<'_> {
fn default() -> Self {
Self {
min_margin_mw: 0.0,
max_regression_mw: f64::INFINITY,
ac: AcOptions::default(),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Verdict {
Accepted,
Diverged,
Insecure {
margin_mw: f64,
},
Regressed {
by_mw: f64,
},
}
impl Verdict {
pub fn is_accepted(&self) -> bool {
matches!(self, Verdict::Accepted)
}
}
#[derive(Clone, Debug)]
pub struct PerimeterValidation {
pub states: Vec<State>,
pub dc_margin_mw: f64,
pub ac_margin_mw: f64,
pub worst_cnec: Option<usize>,
pub verdict: Verdict,
}
impl PerimeterValidation {
pub fn regression_mw(&self) -> f64 {
self.dc_margin_mw - self.ac_margin_mw
}
}
#[derive(Clone, Debug)]
pub struct Validation {
pub perimeters: Vec<PerimeterValidation>,
pub ac_margin_mw: f64,
}
impl Validation {
pub fn is_accepted(&self) -> bool {
self.perimeters.iter().all(|p| p.verdict.is_accepted())
}
pub fn rejected(&self) -> impl Iterator<Item = &PerimeterValidation> {
self.perimeters.iter().filter(|p| !p.verdict.is_accepted())
}
}
pub fn validate(
crac: &Crac,
network: &Network<'_>,
resolution: &Resolution,
plan: &Plan,
options: &ValidationOptions<'_>,
) -> Validation {
let perimeters: Vec<PerimeterValidation> = plan
.perimeters()
.map(|p| validate_perimeter(crac, network, resolution, p, options))
.collect();
let ac_margin_mw = perimeters
.iter()
.map(|p| p.ac_margin_mw)
.fold(f64::INFINITY, f64::min);
Validation {
perimeters,
ac_margin_mw,
}
}
fn validate_perimeter(
crac: &Crac,
network: &Network<'_>,
resolution: &Resolution,
plan: &PerimeterPlan,
options: &ValidationOptions<'_>,
) -> PerimeterValidation {
let post = Network {
generation: network.generation,
buses: &plan.buses,
lines: network.lines,
transformers: &plan.transformers,
branch_ids: network.branch_ids,
bus_ids: network.bus_ids,
initially_open: &[],
bus_countries: network.bus_countries,
shunts: network.shunts,
tap_changers: network.tap_changers,
base_mva: network.base_mva,
};
let result = evaluate::evaluate_ac(crac, &post, resolution, &plan.open_branches, &options.ac);
let mut ac_margin_mw = f64::INFINITY;
let mut worst_cnec = None;
let mut diverged = false;
for perimeter in result
.perimeters
.iter()
.filter(|p| plan.states.contains(&p.state))
{
if perimeter.severed {
diverged = true;
}
for cnec in &perimeter.cnecs {
if cnec.margin_mw < ac_margin_mw {
ac_margin_mw = cnec.margin_mw;
worst_cnec = Some(cnec.cnec);
}
}
}
let dc_margin_mw = plan.final_margin_mw;
let verdict = if diverged {
Verdict::Diverged
} else if ac_margin_mw < options.min_margin_mw {
Verdict::Insecure {
margin_mw: ac_margin_mw,
}
} else if dc_margin_mw - ac_margin_mw > options.max_regression_mw {
Verdict::Regressed {
by_mw: dc_margin_mw - ac_margin_mw,
}
} else {
Verdict::Accepted
};
PerimeterValidation {
states: plan.states.clone(),
dc_margin_mw,
ac_margin_mw,
worst_cnec,
verdict,
}
}