use serde::{Deserialize, Serialize};
use crate::network::{BranchRef, WeightedBranchRef};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Interface {
pub name: String,
pub members: Vec<WeightedBranchRef>,
pub limit_forward_mw: f64,
pub limit_reverse_mw: f64,
pub in_service: bool,
#[serde(default)]
pub limit_forward_mw_schedule: Vec<f64>,
#[serde(default)]
pub limit_reverse_mw_schedule: Vec<f64>,
}
impl Interface {
pub fn effective_limit_forward_mw(&self, t: usize) -> f64 {
self.limit_forward_mw_schedule
.get(t)
.copied()
.unwrap_or(self.limit_forward_mw)
}
pub fn effective_limit_reverse_mw(&self, t: usize) -> f64 {
self.limit_reverse_mw_schedule
.get(t)
.copied()
.unwrap_or(self.limit_reverse_mw)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Flowgate {
pub name: String,
pub monitored: Vec<WeightedBranchRef>,
pub contingency_branch: Option<BranchRef>,
pub limit_mw: f64,
#[serde(default)]
pub limit_reverse_mw: f64,
pub in_service: bool,
#[serde(default)]
pub limit_mw_schedule: Vec<f64>,
#[serde(default)]
pub limit_reverse_mw_schedule: Vec<f64>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub hvdc_coefficients: Vec<(usize, f64)>,
}
impl Flowgate {
pub fn effective_limit_mw(&self, t: usize) -> f64 {
self.limit_mw_schedule
.get(t)
.copied()
.unwrap_or(self.limit_mw)
}
pub fn effective_limit_reverse_mw(&self, t: usize) -> f64 {
self.limit_reverse_mw_schedule
.get(t)
.copied()
.unwrap_or(self.limit_reverse_mw)
}
pub fn effective_reverse_or_forward(&self, t: usize) -> f64 {
let rev = self.effective_limit_reverse_mw(t);
if rev > 0.0 {
rev
} else {
self.effective_limit_mw(t)
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OperatingNomogram {
pub name: String,
pub index_flowgate: String,
pub constrained_flowgate: String,
pub points: Vec<(f64, f64)>,
pub in_service: bool,
}
impl OperatingNomogram {
pub fn evaluate(&self, index_flow_mw: f64) -> f64 {
if self.points.is_empty() {
return f64::INFINITY;
}
if index_flow_mw <= self.points[0].0 {
return self.points[0].1;
}
let last = self.points[self.points.len() - 1];
if index_flow_mw >= last.0 {
return last.1;
}
for w in self.points.windows(2) {
let (x0, y0) = w[0];
let (x1, y1) = w[1];
if index_flow_mw < x1 {
let t = (index_flow_mw - x0) / (x1 - x0);
return y0 + t * (y1 - y0);
}
}
last.1
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_nomogram_evaluate() {
let nom = OperatingNomogram {
name: "N1".into(),
index_flowgate: "FG_A".into(),
constrained_flowgate: "FG_B".into(),
points: vec![(-500.0, 1000.0), (0.0, 800.0), (500.0, 500.0)],
in_service: true,
};
assert!((nom.evaluate(-600.0) - 1000.0).abs() < 1e-9);
assert!((nom.evaluate(0.0) - 800.0).abs() < 1e-9);
assert!((nom.evaluate(250.0) - 650.0).abs() < 1e-9);
assert!((nom.evaluate(600.0) - 500.0).abs() < 1e-9);
}
#[test]
fn test_effective_limit_mw_schedule() {
let fg = Flowgate {
name: "FG".into(),
monitored: vec![],
contingency_branch: None,
limit_mw: 100.0,
limit_reverse_mw: 0.0,
in_service: true,
limit_mw_schedule: vec![90.0, 80.0, 70.0],
limit_reverse_mw_schedule: vec![],
hvdc_coefficients: vec![],
};
assert_eq!(fg.effective_limit_mw(0), 90.0);
assert_eq!(fg.effective_limit_mw(2), 70.0);
assert_eq!(fg.effective_limit_mw(5), 100.0);
assert_eq!(fg.effective_reverse_or_forward(0), 90.0);
}
}