use super::complex::Complex64;
use crate::{Error, Result};
#[derive(Debug, Clone, PartialEq)]
pub struct PortResult {
freqs: Vec<f64>,
v: Vec<Complex64>,
i: Vec<Complex64>,
z_ref: Vec<Complex64>,
}
impl PortResult {
pub fn new(
freqs: Vec<f64>,
v: Vec<Complex64>,
i: Vec<Complex64>,
z_ref: Complex64,
) -> Result<Self> {
let z_ref = vec![z_ref; freqs.len().max(1)];
Self::checked(freqs, v, i, z_ref)
}
pub fn new_per_frequency(
freqs: Vec<f64>,
v: Vec<Complex64>,
i: Vec<Complex64>,
z_ref: Vec<Complex64>,
) -> Result<Self> {
if z_ref.len() != freqs.len() {
return Err(Error::Invalid(format!(
"port result: {} frequencies but {} reference impedances",
freqs.len(),
z_ref.len()
)));
}
Self::checked(freqs, v, i, z_ref)
}
fn checked(
freqs: Vec<f64>,
v: Vec<Complex64>,
i: Vec<Complex64>,
mut z_ref: Vec<Complex64>,
) -> Result<Self> {
if v.len() != freqs.len() || i.len() != freqs.len() {
return Err(Error::Invalid(format!(
"port result: {} frequencies but {} voltages, {} currents and {} \
reference impedances",
freqs.len(),
v.len(),
i.len(),
z_ref.len()
)));
}
if let Some(z) = z_ref.iter().find(|z| !(z.is_finite() && z.re > 0.0)) {
return Err(Error::Invalid(format!(
"reference impedance {z} must have a positive real part"
)));
}
z_ref.truncate(freqs.len());
Ok(Self { freqs, v, i, z_ref })
}
pub fn with_z_ref(&self, z_ref: Complex64) -> Result<Self> {
Self::new(self.freqs.clone(), self.v.clone(), self.i.clone(), z_ref)
}
pub fn with_z_ref_per_frequency(&self, z_ref: Vec<Complex64>) -> Result<Self> {
Self::new_per_frequency(self.freqs.clone(), self.v.clone(), self.i.clone(), z_ref)
}
pub fn freqs(&self) -> &[f64] {
&self.freqs
}
pub fn voltage(&self) -> &[Complex64] {
&self.v
}
pub fn current(&self) -> &[Complex64] {
&self.i
}
pub fn z_ref(&self) -> &[Complex64] {
&self.z_ref
}
fn viz(&self) -> impl Iterator<Item = (Complex64, Complex64, Complex64)> + '_ {
self.v
.iter()
.zip(&self.i)
.zip(&self.z_ref)
.map(|((&v, &i), &z)| (v, i, z))
}
pub fn a(&self) -> Vec<Complex64> {
self.viz()
.map(|(v, i, z)| (v + z * i) * (0.5 / z.re.sqrt()))
.collect()
}
pub fn b(&self) -> Vec<Complex64> {
self.viz()
.map(|(v, i, z)| (v - z.conj() * i) * (0.5 / z.re.sqrt()))
.collect()
}
pub fn v_inc(&self) -> Vec<Complex64> {
self.viz().map(|(v, i, z)| (v + z * i) * 0.5).collect()
}
pub fn v_ref(&self) -> Vec<Complex64> {
self.viz().map(|(v, i, z)| (v - z * i) * 0.5).collect()
}
pub fn i_inc(&self) -> Vec<Complex64> {
self.viz().map(|(v, i, z)| (v / z + i) * 0.5).collect()
}
pub fn i_ref(&self) -> Vec<Complex64> {
self.viz().map(|(v, i, z)| (i - v / z) * 0.5).collect()
}
pub fn z_in(&self) -> Vec<Complex64> {
self.v.iter().zip(&self.i).map(|(&v, &i)| v / i).collect()
}
pub fn s11(&self) -> Vec<Complex64> {
s_param(self, self)
}
pub fn incident_power(&self) -> Vec<f64> {
self.a().iter().map(|a| 0.5 * a.norm_sqr()).collect()
}
pub fn reflected_power(&self) -> Vec<f64> {
self.b().iter().map(|b| 0.5 * b.norm_sqr()).collect()
}
pub fn accepted_power(&self) -> Vec<f64> {
self.v
.iter()
.zip(&self.i)
.map(|(&v, &i)| 0.5 * (v.re * i.re + v.im * i.im))
.collect()
}
}
pub fn s_param(out: &PortResult, inc: &PortResult) -> Vec<Complex64> {
assert_eq!(
out.freqs, inc.freqs,
"port results at different frequencies"
);
out.b().iter().zip(inc.a()).map(|(&b, a)| b / a).collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn c(re: f64, im: f64) -> Complex64 {
Complex64::new(re, im)
}
#[test]
fn load_reflection_and_power_balance() {
let zl = c(100.0, 30.0);
let i = c(2.0, 0.0) / (zl + 50.0);
let v = zl * i;
let p = PortResult::new(vec![1e9], vec![v], vec![i], c(50.0, 0.0)).unwrap();
let s = p.s11()[0];
let want = (zl - 50.0) / (zl + 50.0);
assert!((s - want).norm() < 1e-14);
assert!((p.z_in()[0] - zl).norm() < 1e-12);
assert!((p.a()[0] - c(1.0 / 50f64.sqrt(), 0.0)).norm() < 1e-14);
let (pi, pr, pa) = (
p.incident_power()[0],
p.reflected_power()[0],
p.accepted_power()[0],
);
assert!((pi - pr - pa).abs() < 1e-14 * pi);
let q = p.with_z_ref(c(100.0, -30.0)).unwrap();
assert!(q.s11()[0].norm() < 1e-14);
assert!((q.incident_power()[0] - q.accepted_power()[0]).abs() < 1e-14);
let q = p.with_z_ref(c(20.0, 70.0)).unwrap();
let d = q.incident_power()[0] - q.reflected_power()[0] - q.accepted_power()[0];
assert!(d.abs() < 1e-13 * q.incident_power()[0]);
}
#[test]
fn frequency_dependent_reference() {
let z = vec![c(50.0, 0.0), c(80.0, 0.0)];
let i = vec![c(1.0, 0.0), c(0.5, 0.2)];
let v: Vec<Complex64> = i.iter().zip(&z).map(|(&i, &z)| z * i).collect();
let p = PortResult::new_per_frequency(vec![1e9, 2e9], v, i, z.clone()).unwrap();
assert!(p.s11().iter().all(|s| s.norm() < 1e-15));
assert_eq!(p.z_ref(), &z[..]);
let bad = vec![c(50.0, 0.0), c(0.0, 1.0)];
assert!(PortResult::new_per_frequency(vec![1e9, 2e9], z.clone(), z.clone(), bad).is_err());
assert!(PortResult::new_per_frequency(vec![1e9], z.clone(), z.clone(), z).is_err());
}
#[test]
fn waves_and_per_frequency_reference() {
let zl = c(100.0, 30.0);
let i = vec![c(0.01, 0.002), c(-0.003, 0.02)];
let v: Vec<_> = i.iter().map(|&i| zl * i).collect();
let z = vec![c(50.0, 0.0), c(60.0, -5.0)];
let p =
PortResult::new_per_frequency(vec![1e9, 2e9], v.clone(), i.clone(), z.clone()).unwrap();
assert_eq!(p.z_ref(), &z[..]);
let (vi, vr, ii, ir) = (p.v_inc(), p.v_ref(), p.i_inc(), p.i_ref());
for k in 0..2 {
assert!((vi[k] + vr[k] - v[k]).norm() < 1e-14);
assert!((ii[k] + ir[k] - i[k]).norm() < 1e-16);
assert!((vi[k] / ii[k] - z[k]).norm() < 1e-12);
assert!((vr[k] / ir[k] + z[k]).norm() < 1e-12);
let want = (zl - z[k]) / (zl + z[k]);
if k == 0 {
assert!((vr[k] / vi[k] - want).norm() < 1e-14);
assert!((p.s11()[k] - want).norm() < 1e-14);
}
}
let one = p.with_z_ref(c(50.0, 0.0)).unwrap();
assert_eq!(one.z_ref(), &[c(50.0, 0.0); 2]);
assert!(p.with_z_ref_per_frequency(vec![c(50.0, 0.0)]).is_err());
assert!(
p.with_z_ref_per_frequency(vec![c(50.0, 0.0), c(-1.0, 0.0)])
.is_err()
);
}
#[test]
fn validation() {
let z = vec![Complex64::ZERO];
assert!(PortResult::new(vec![1.0], z.clone(), z.clone(), c(0.0, 1.0)).is_err());
assert!(PortResult::new(vec![1.0, 2.0], z.clone(), z.clone(), c(50.0, 0.0)).is_err());
assert!(PortResult::new(vec![1.0], z.clone(), z, c(f64::NAN, 0.0)).is_err());
}
}