#![allow(non_snake_case)]
use super::horner::{horner_eval_c, horner_eval_f};
use super::Options;
use lds_rs::lds::Circle;
use num_complex::Complex;
const TWO_PI: f64 = std::f64::consts::TAU;
pub fn initial_aberth(coeffs: &[f64]) -> Vec<Complex<f64>> {
let degree = coeffs.len() - 1;
let center = -coeffs[1] / (coeffs[0] * degree as f64);
let poly_c = horner_eval_f(coeffs, center);
let radius = Complex::<f64>::new(-poly_c, 0.0).powf(1.0 / degree as f64);
let mut c_gen = Circle::new(2);
(0..degree)
.map(|_idx| {
let [ycoord, xcoord] = c_gen.pop(); center + radius * Complex::<f64>::new(xcoord, ycoord)
})
.collect()
}
pub fn initial_aberth_orig(coeffs: &[f64]) -> Vec<Complex<f64>> {
let degree = coeffs.len() - 1;
let center = -coeffs[1] / (coeffs[0] * degree as f64);
let poly_c = horner_eval_f(coeffs, center);
let radius = Complex::<f64>::new(-poly_c, 0.0).powf(1.0 / degree as f64);
let k = TWO_PI / (degree as f64);
(0..degree)
.map(|idx| {
let theta = k * (0.25 + idx as f64);
center + radius * Complex::<f64>::new(theta.cos(), theta.sin())
})
.collect()
}
fn aberth_job(
coeffs: &[f64],
i: usize,
zi: &mut Complex<f64>,
zsc: &[Complex<f64>],
coeffs1: &[f64],
) -> f64 {
let p_eval = horner_eval_c(coeffs, zi);
let tol_i = p_eval.l1_norm(); let mut p1_eval = horner_eval_c(coeffs1, zi);
for (_, zj) in zsc.iter().enumerate().filter(|t| t.0 != i) {
p1_eval -= p_eval / (*zi - zj);
}
*zi -= p_eval / p1_eval; tol_i
}
pub fn aberth(coeffs: &[f64], zs: &mut [Complex<f64>], options: &Options) -> (usize, bool) {
let m_zs = zs.len();
let degree = coeffs.len() - 1; let coeffs1: Vec<_> = coeffs[0..degree]
.iter()
.enumerate()
.map(|(i, ci)| ci * (degree - i) as f64)
.collect();
for niter in 0..options.max_iters {
let mut tolerance = 0.0;
for i in 0..m_zs {
let mut zi = zs[i];
let tol_i = aberth_job(coeffs, i, &mut zi, zs, &coeffs1);
if tolerance < tol_i {
tolerance = tol_i;
}
zs[i] = zi;
}
if tolerance < options.tolerance {
return (niter, true);
}
}
(options.max_iters, false)
}
pub fn aberth_mt(coeffs: &[f64], zs: &mut Vec<Complex<f64>>, options: &Options) -> (usize, bool) {
fn aberth_job2(
coeffs: &[f64],
i: usize,
zi: &mut Complex<f64>,
zsc: &[Complex<f64>],
coeffs1: &[f64],
) -> f64 {
let p_eval = horner_eval_c(coeffs, zi);
let tol_i = p_eval.l1_norm(); let mut p1_eval = horner_eval_c(coeffs1, zi);
for (_, zj) in zsc.iter().enumerate().filter(|t| t.0 != i) {
p1_eval -= p_eval / (*zi - zj);
}
*zi -= p_eval / p1_eval; tol_i
}
use rayon::prelude::*;
let m_zs = zs.len();
let degree = coeffs.len() - 1; let coeffs1: Vec<_> = (0..degree)
.map(|i| coeffs[i] * (degree - i) as f64)
.collect();
let mut zsc = vec![Complex::default(); m_zs];
for niter in 0..options.max_iters {
let mut tolerance = 0.0;
zsc.copy_from_slice(zs);
let tol_i = zs
.par_iter_mut()
.enumerate()
.map(|(i, zi)| aberth_job2(coeffs, i, zi, &zsc, &coeffs1))
.reduce(|| tolerance, |x, y| x.max(y));
if tolerance < tol_i {
tolerance = tol_i;
}
if tolerance < options.tolerance {
return (niter, true);
}
}
(options.max_iters, false)
}
pub fn initial_aberth_autocorr(coeffs: &[f64]) -> Vec<Complex<f64>> {
let degree = coeffs.len() - 1; let center = -coeffs[1] / (coeffs[0] * degree as f64);
let poly_c = horner_eval_f(coeffs, center);
let mut radius = poly_c.abs().powf(1.0 / degree as f64);
if radius > 1.0 {
radius = 1.0 / radius;
}
let mut c_gen = Circle::new(2);
(0..degree / 2)
.map(|_idx| {
let [y, x] = c_gen.pop();
center + radius * Complex::<f64>::new(x, y)
})
.collect()
}
fn aberth_autocorr_job(
coeffs: &[f64],
i: usize,
zi: &mut Complex<f64>,
zsc: &[Complex<f64>],
coeffs1: &[f64],
) -> f64 {
let p_eval = horner_eval_c(coeffs, zi);
let tol_i = p_eval.l1_norm(); let mut p1_eval = horner_eval_c(coeffs1, zi);
for (_, zj) in zsc.iter().enumerate().filter(|t| t.0 != i) {
p1_eval -= p_eval / (*zi - zj);
p1_eval -= p_eval / (*zi - 1.0 / zj);
}
*zi -= p_eval / p1_eval; tol_i
}
pub fn aberth_autocorr(
coeffs: &[f64],
zs: &mut [Complex<f64>],
options: &Options,
) -> (usize, bool) {
let m_zs = zs.len();
let degree = coeffs.len() - 1; let coeffs1: Vec<_> = coeffs[0..degree]
.iter()
.enumerate()
.map(|(i, ci)| ci * (degree - i) as f64)
.collect();
for niter in 0..options.max_iters {
let mut tolerance = 0.0;
for i in 0..m_zs {
let mut zi = zs[i];
let tol_i = aberth_autocorr_job(coeffs, i, &mut zi, zs, &coeffs1);
if tolerance < tol_i {
tolerance = tol_i;
}
zs[i] = zi;
}
if tolerance < options.tolerance {
return (niter, true);
}
}
(options.max_iters, false)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_horner_eval() {
let coeffs = vec![10.0, 34.0, 75.0, 94.0, 150.0, 94.0, 75.0, 34.0, 10.0];
let z = Complex::new(0.0, 0.0);
let p_eval = horner_eval_c(&coeffs, &z);
assert_eq!(p_eval.re, 10.0);
assert_eq!(p_eval.im, 0.0);
let z = Complex::new(1.0, 0.0);
let p_eval = horner_eval_c(&coeffs, &z);
assert_eq!(p_eval.re, 576.0);
assert_eq!(p_eval.im, 0.0);
}
#[test]
fn test_aberth() {
let coeffs = vec![10.0, 34.0, 75.0, 94.0, 150.0, 94.0, 75.0, 34.0, 10.0];
let mut zrs = initial_aberth(&coeffs);
let (niter, found) = aberth(&coeffs, &mut zrs, &Options::default());
assert_eq!(niter, 5);
assert!(found);
}
#[test]
fn test_aberth_mt() {
let coeffs = vec![10.0, 34.0, 75.0, 94.0, 150.0, 94.0, 75.0, 34.0, 10.0];
let mut zrs = initial_aberth(&coeffs);
let (niter, found) = aberth_mt(&coeffs, &mut zrs, &Options::default());
assert_eq!(niter, 6);
assert!(found);
}
#[test]
fn test_aberth_autocorr() {
let coeffs = vec![10.0, 34.0, 75.0, 94.0, 150.0, 94.0, 75.0, 34.0, 10.0];
let mut zrs = initial_aberth_autocorr(&coeffs);
let (niter, found) = aberth_autocorr(&coeffs, &mut zrs, &Options::default());
assert!(niter <= 7);
assert!(found);
}
}