pub fn abs2(re: f64, im: f64) -> f64 {
re * re + im * im
}
pub fn abs2_slice(complex: &[(f64, f64)]) -> Vec<f64> {
complex.iter().map(|&(re, im)| abs2(re, im)).collect()
}
pub fn phasor(angles: &[f64]) -> Vec<(f64, f64)> {
angles.iter().map(|&a| (a.cos(), a.sin())).collect()
}
pub fn phasor_mag(angles: &[f64], mag: &[f64]) -> Vec<(f64, f64)> {
assert_eq!(
angles.len(),
mag.len(),
"phasor_mag: angles and mag must have the same length"
);
angles
.iter()
.zip(mag.iter())
.map(|(&a, &m)| (m * a.cos(), m * a.sin()))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use serde::Deserialize;
#[derive(Deserialize)]
struct ComplexTestCase {
name: String,
function: String,
#[serde(default)]
input: Option<Vec<(f64, f64)>>,
#[serde(default)]
angles: Option<Vec<f64>>,
#[serde(default)]
magnitudes: Option<Vec<f64>>,
expected: serde_json::Value,
}
fn load_complex_references() -> Vec<ComplexTestCase> {
let data = include_str!("tests/references/complex_helpers.json");
serde_json::from_str(data).expect("Failed to parse complex_helpers.json")
}
#[test]
fn test_abs2_matches_numpy() {
let refs = load_complex_references();
for case in refs.iter().filter(|c| c.function == "abs2") {
let input = case.input.as_ref().unwrap();
let expected: Vec<f64> = serde_json::from_value(case.expected.clone()).unwrap();
let result = abs2_slice(input);
assert_eq!(result.len(), expected.len(), "{}", case.name);
for (i, (a, e)) in result.iter().zip(expected.iter()).enumerate() {
let diff = (a - e).abs();
assert!(diff < 1e-12, "{}[{i}]: {a} != {e}", case.name);
}
}
}
#[test]
fn test_phasor_matches_numpy() {
let refs = load_complex_references();
for case in refs.iter().filter(|c| c.function == "phasor") {
let angles = case.angles.as_ref().unwrap();
let expected: Vec<(f64, f64)> = serde_json::from_value(case.expected.clone()).unwrap();
let result = phasor(angles);
assert_eq!(result.len(), expected.len(), "{}", case.name);
for (i, ((ar, ai), (er, ei))) in result.iter().zip(expected.iter()).enumerate() {
assert!(
(ar - er).abs() < 1e-10,
"{}[{i}] re: {ar} != {er}",
case.name
);
assert!(
(ai - ei).abs() < 1e-10,
"{}[{i}] im: {ai} != {ei}",
case.name
);
}
}
}
#[test]
fn test_phasor_mag_matches_numpy() {
let refs = load_complex_references();
for case in refs.iter().filter(|c| c.function == "phasor_mag") {
let angles = case.angles.as_ref().unwrap();
let mags = case.magnitudes.as_ref().unwrap();
let expected: Vec<(f64, f64)> = serde_json::from_value(case.expected.clone()).unwrap();
let result = phasor_mag(angles, mags);
assert_eq!(result.len(), expected.len(), "{}", case.name);
for (i, ((ar, ai), (er, ei))) in result.iter().zip(expected.iter()).enumerate() {
assert!(
(ar - er).abs() < 1e-10,
"{}[{i}] re: {ar} != {er}",
case.name
);
assert!(
(ai - ei).abs() < 1e-10,
"{}[{i}] im: {ai} != {ei}",
case.name
);
}
}
}
#[test]
fn test_abs2_basic() {
assert_eq!(abs2(3.0, 4.0), 25.0);
assert_eq!(abs2(0.0, 0.0), 0.0);
assert_eq!(abs2(1.0, 0.0), 1.0);
}
#[test]
fn test_phasor_at_zero() {
let result = phasor(&[0.0]);
assert!((result[0].0 - 1.0).abs() < 1e-15);
assert!(result[0].1.abs() < 1e-15);
}
}