use num_complex::Complex;
use num_traits::Float;
pub fn cauchy_matvec<T: Float>(nodes: &[Complex<T>], poles: &[Complex<T>], weights: &[Complex<T>]) -> Vec<Complex<T>> {
assert_eq!(poles.len(), weights.len(), "poles and weights must be the same length");
nodes
.iter()
.map(|&z| {
poles
.iter()
.zip(weights.iter())
.fold(Complex::new(T::zero(), T::zero()), |acc, (&lambda, &w)| {
acc + w / (z - lambda)
})
})
.collect()
}
pub fn vandermonde_matvec<T: Float>(poles: &[Complex<T>], weights: &[Complex<T>], length: usize) -> Vec<Complex<T>> {
assert_eq!(poles.len(), weights.len(), "poles and weights must be the same length");
let mut running_powers: Vec<Complex<T>> = vec![Complex::new(T::one(), T::zero()); poles.len()];
let mut out = Vec::with_capacity(length);
for _ in 0..length {
let kernel_t = running_powers
.iter()
.zip(weights.iter())
.fold(Complex::new(T::zero(), T::zero()), |acc, (&p, &w)| acc + w * p);
out.push(kernel_t);
for (p, &lambda) in running_powers.iter_mut().zip(poles.iter()) {
*p = *p * lambda;
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cauchy_matches_dense_reference() {
fn dense_reference(nodes: &[Complex<f64>], poles: &[Complex<f64>], weights: &[Complex<f64>]) -> Vec<Complex<f64>> {
let mut out = vec![Complex::new(0.0, 0.0); nodes.len()];
for i in 0..poles.len() {
for k in 0..nodes.len() {
out[k] += weights[i] / (nodes[k] - poles[i]);
}
}
out
}
let l = 8;
let nodes: Vec<Complex<f64>> = (0..l)
.map(|k| Complex::from_polar(1.0, 2.0 * std::f64::consts::PI * k as f64 / l as f64))
.collect();
let poles: Vec<Complex<f64>> = vec![
Complex::new(-0.5, 0.3),
Complex::new(-0.5, -0.3),
Complex::new(-0.2, 0.7),
Complex::new(-0.2, -0.7),
];
let weights: Vec<Complex<f64>> = vec![
Complex::new(1.0, 0.2),
Complex::new(1.0, -0.2),
Complex::new(0.5, 0.1),
Complex::new(0.5, -0.1),
];
let expected = dense_reference(&nodes, &poles, &weights);
let actual = cauchy_matvec(&nodes, &poles, &weights);
for (e, got) in expected.iter().zip(actual.iter()) {
assert!((e - got).norm() < 1e-9, "expected {e}, got {got}");
}
}
#[test]
fn vandermonde_matches_direct_powu() {
let poles: Vec<Complex<f64>> = vec![
Complex::new(0.9, 0.1),
Complex::new(0.9, -0.1),
Complex::new(0.7, 0.4),
];
let weights: Vec<Complex<f64>> = vec![
Complex::new(1.0, 0.0),
Complex::new(1.0, 0.0),
Complex::new(0.5, -0.2),
];
let length = 12;
let expected: Vec<Complex<f64>> = (0..length as u32)
.map(|t| {
poles
.iter()
.zip(weights.iter())
.fold(Complex::new(0.0, 0.0), |acc, (&p, &w)| acc + w * p.powu(t))
})
.collect();
let actual = vandermonde_matvec(&poles, &weights, length);
for (e, got) in expected.iter().zip(actual.iter()) {
assert!((e - got).norm() < 1e-9, "expected {e}, got {got}");
}
}
#[test]
fn vandermonde_at_t_zero_is_sum_of_weights() {
let poles: Vec<Complex<f64>> = vec![Complex::new(0.5, 0.5), Complex::new(-0.3, 0.2)];
let weights: Vec<Complex<f64>> = vec![Complex::new(2.0, 0.0), Complex::new(1.0, 1.0)];
let result = vandermonde_matvec(&poles, &weights, 3);
let expected_t0 = weights[0] + weights[1];
assert!((result[0] - expected_t0).norm() < 1e-12);
}
}