#[derive(Clone, Copy, Debug, PartialEq)]
pub struct TemporaryLimit {
pub acceptable_duration_s: Option<f64>,
pub value_a: f64,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct BranchLimits {
pub patl_a: Option<f64>,
pub tatl: Vec<TemporaryLimit>,
}
impl BranchLimits {
pub fn permanent(value_a: f64) -> Self {
Self {
patl_a: Some(value_a),
tatl: Vec::new(),
}
}
pub fn is_unlimited(&self) -> bool {
self.patl_a.is_none() && self.tatl.is_empty()
}
pub fn limit_for(&self, duration_s: f64) -> Option<f64> {
let temporary = self
.tatl
.iter()
.filter(|t| match t.acceptable_duration_s {
Some(d) => d >= duration_s,
None => true,
})
.map(|t| t.value_a)
.fold(f64::INFINITY, f64::min);
match (temporary.is_finite(), self.patl_a) {
(true, Some(patl)) => Some(temporary.max(patl)),
(true, None) => Some(temporary),
(false, patl) => patl,
}
}
pub fn tightest(&self) -> Option<f64> {
let temporary = self
.tatl
.iter()
.map(|t| t.value_a)
.fold(f64::INFINITY, f64::min);
match (temporary.is_finite(), self.patl_a) {
(true, Some(patl)) => Some(temporary.min(patl)),
(true, None) => Some(temporary),
(false, patl) => patl,
}
}
}
pub fn current_to_power_pu(current_a: f64, u_rated_v: f64, s_base_va: f64) -> f64 {
3f64.sqrt() * u_rated_v * current_a / s_base_va
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_empty_limit_set_is_unlimited_rather_than_zero() {
let limits = BranchLimits::default();
assert!(limits.is_unlimited());
assert_eq!(limits.tightest(), None);
assert_eq!(limits.limit_for(0.0), None);
}
#[test]
fn a_temporary_limit_applies_only_while_its_duration_covers_the_request() {
let limits = BranchLimits {
patl_a: Some(1000.0),
tatl: vec![TemporaryLimit {
acceptable_duration_s: Some(1200.0),
value_a: 1200.0,
}],
};
assert_eq!(limits.limit_for(1200.0), Some(1200.0));
assert_eq!(limits.limit_for(3600.0), Some(1000.0));
assert_eq!(limits.tightest(), Some(1000.0));
}
#[test]
fn several_covering_temporary_limits_resolve_to_the_tightest() {
let limits = BranchLimits {
patl_a: Some(1000.0),
tatl: vec![
TemporaryLimit {
acceptable_duration_s: Some(600.0),
value_a: 1500.0,
},
TemporaryLimit {
acceptable_duration_s: Some(900.0),
value_a: 1300.0,
},
],
};
assert_eq!(limits.limit_for(600.0), Some(1300.0));
}
#[test]
fn a_temporary_limit_never_reports_below_the_permanent_one() {
let limits = BranchLimits {
patl_a: Some(1000.0),
tatl: vec![TemporaryLimit {
acceptable_duration_s: Some(60.0),
value_a: 800.0,
}],
};
assert_eq!(limits.limit_for(60.0), Some(1000.0));
}
#[test]
fn current_converts_to_apparent_power_through_the_root_three() {
let s = current_to_power_pu(1000.0, 380_000.0, 100e6);
assert!((s - 6.5818).abs() < 1e-3, "got {s}");
}
}