use num_complex::Complex;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct SequenceValue {
pub zero: Complex<f64>,
pub positive: Complex<f64>,
pub negative: Complex<f64>,
}
impl SequenceValue {
pub fn from_phase(abc: &[Complex<f64>; 3]) -> Self {
let a = Complex::from_polar(1.0, 2.0 * std::f64::consts::PI / 3.0);
let a2 = a * a;
let third = 1.0 / 3.0;
Self {
zero: (abc[0] + abc[1] + abc[2]) * third,
positive: (abc[0] + a * abc[1] + a2 * abc[2]) * third,
negative: (abc[0] + a2 * abc[1] + a * abc[2]) * third,
}
}
pub fn to_phase(self) -> [Complex<f64>; 3] {
let a = Complex::from_polar(1.0, 2.0 * std::f64::consts::PI / 3.0);
let a2 = a * a;
[
self.zero + self.positive + self.negative,
self.zero + a2 * self.positive + a * self.negative,
self.zero + a * self.positive + a2 * self.negative,
]
}
}
pub fn node_sequences(u_bus: &[[Complex<f64>; 3]]) -> Vec<SequenceValue> {
u_bus.iter().map(SequenceValue::from_phase).collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn balanced(magnitude: f64) -> [Complex<f64>; 3] {
[0, 1, 2].map(|p| Complex::from_polar(magnitude, super::super::PHASE_ANG[p]))
}
#[test]
fn a_balanced_set_is_pure_positive_sequence() {
let s = SequenceValue::from_phase(&balanced(1.0));
assert!(s.positive.norm() > 0.999, "positive = {}", s.positive);
assert!(s.zero.norm() < 1e-12, "zero = {}", s.zero);
assert!(s.negative.norm() < 1e-12, "negative = {}", s.negative);
}
#[test]
fn an_equal_set_is_pure_zero_sequence() {
let x = Complex::new(0.4, -0.2);
let s = SequenceValue::from_phase(&[x, x, x]);
assert!((s.zero - x).norm() < 1e-15);
assert!(s.positive.norm() < 1e-15);
assert!(s.negative.norm() < 1e-15);
}
#[test]
fn the_projection_round_trips() {
let abc = [
Complex::new(0.9, 0.1),
Complex::new(-0.3, -0.8),
Complex::new(-0.5, 0.6),
];
let back = SequenceValue::from_phase(&abc).to_phase();
for p in 0..3 {
assert!(
(abc[p] - back[p]).norm() < 1e-14,
"phase {p}: {} vs {}",
abc[p],
back[p]
);
}
}
}