#![allow(non_snake_case)]
use super::horner::{horner_eval_c, horner_eval_f};
use super::Options;
use crate::execution_policy::{atomic_decoupled_run, jacobi_mt_run, sequential_run, Step};
use crate::leja_order::leja_order;
use crate::scaling::{derivative_coeffs, scaled_coeffs};
use crate::seqlock::AtomicComplex;
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);
(0..degree)
.map(|i| {
let xcoord = crate::tables::circle2_table_y(i);
let ycoord = crate::tables::circle2_table_x(i);
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()
}
pub struct AberthStep<'a> {
pub coeffs: &'a [f64],
pub coeffs1: &'a [f64],
}
impl Step<Complex<f64>> for AberthStep<'_> {
type Cell = AtomicComplex;
fn run<G, N>(&self, idx: usize, get: G, neighbors: N) -> (f64, Option<Complex<f64>>)
where
G: Fn(usize) -> Complex<f64>,
N: Iterator<Item = usize>,
{
let zi = get(idx);
let p_eval = horner_eval_c(self.coeffs, &zi);
let tol_i = p_eval.l1_norm(); let mut p1_eval = horner_eval_c(self.coeffs1, &zi);
for j in neighbors {
let zj = get(j);
p1_eval -= p_eval / (zi - zj);
}
(tol_i, Some(zi - p_eval / p1_eval)) }
}
#[cfg_attr(feature = "doc-images", doc = svgbobdoc::transform!(
/// ```svgbob
/// .───────────. .───────────. .──────────────.
/// │ Polynomial│───►│ Initial │───►│ Aberth │
/// │ coeffs │ │ Guess │ │ Iteration │
/// '───────────' '───────────' '──────┬───────'
/// │
/// .────▼───────.
/// │ Converged? │
/// '────┬───────'
/// No │ Yes
/// .─────────┘ │
/// â–¼ â–¼
/// .───────────. .───────────.
/// │ Iterate │ │ Roots │
/// '───────────' '───────────'
/// ```
))]
pub fn aberth(coeffs: &[f64], zs: &mut [Complex<f64>], options: &Options) -> (usize, bool) {
let scaled = scaled_coeffs(coeffs);
let coeffs1 = derivative_coeffs(&scaled);
let step = AberthStep {
coeffs: &scaled,
coeffs1: &coeffs1,
};
sequential_run(zs, options, &step)
}
pub fn aberth_mt(coeffs: &[f64], zs: &mut [Complex<f64>], options: &Options) -> (usize, bool) {
let scaled = scaled_coeffs(coeffs);
let coeffs1 = derivative_coeffs(&scaled);
let step = AberthStep {
coeffs: &scaled,
coeffs1: &coeffs1,
};
jacobi_mt_run(zs, options, &step)
}
pub fn aberth_atomic(coeffs: &[f64], zs: &mut [Complex<f64>], options: &Options) -> (usize, bool) {
let scaled = scaled_coeffs(coeffs);
let coeffs1 = derivative_coeffs(&scaled);
let step = AberthStep {
coeffs: &scaled,
coeffs1: &coeffs1,
};
atomic_decoupled_run(zs, options, &step)
}
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;
}
(0..degree / 2)
.map(|i| {
let xcoord = crate::tables::circle2_table_y(i);
let ycoord = crate::tables::circle2_table_x(i);
center + radius * Complex::<f64>::new(xcoord, ycoord)
})
.collect()
}
pub struct AberthAutocorrStep<'a> {
pub coeffs: &'a [f64],
pub coeffs1: &'a [f64],
}
impl Step<Complex<f64>> for AberthAutocorrStep<'_> {
type Cell = AtomicComplex;
fn run<G, N>(&self, idx: usize, get: G, neighbors: N) -> (f64, Option<Complex<f64>>)
where
G: Fn(usize) -> Complex<f64>,
N: Iterator<Item = usize>,
{
let zi = get(idx);
let p_eval = horner_eval_c(self.coeffs, &zi);
let tol_i = p_eval.l1_norm(); let mut p1_eval = horner_eval_c(self.coeffs1, &zi);
for j in neighbors {
let zj = get(j);
p1_eval -= p_eval / (zi - zj);
p1_eval -= p_eval / (zi - 1.0 / zj);
}
(tol_i, Some(zi - p_eval / p1_eval)) }
}
pub fn aberth_autocorr(
coeffs: &[f64],
zs: &mut [Complex<f64>],
options: &Options,
) -> (usize, bool) {
let scaled = scaled_coeffs(coeffs);
let coeffs1 = derivative_coeffs(&scaled);
let step = AberthAutocorrStep {
coeffs: &scaled,
coeffs1: &coeffs1,
};
sequential_run(zs, options, &step)
}
pub fn poly_from_roots(zs: &[Complex<f64>]) -> Vec<f64> {
if zs.is_empty() {
return vec![1.0];
}
let ordered = leja_order(zs.to_vec());
let mut coeffs = vec![Complex::new(1.0, 0.0)];
for z in &ordered {
let mut prev = coeffs[0];
for item in coeffs.iter_mut().skip(1) {
let old = *item;
*item -= z * prev;
prev = old;
}
coeffs.push(-z * prev);
}
coeffs.iter().map(|c| c.re).collect()
}
pub fn poly_from_autocorr_roots(zs: &[Complex<f64>]) -> Vec<f64> {
if zs.is_empty() {
return vec![1.0];
}
let mut all_roots: Vec<Complex<f64>> = Vec::with_capacity(2 * zs.len());
for z in zs {
all_roots.push(*z);
all_roots.push(1.0 / z);
}
poly_from_roots(&all_roots)
}
#[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_atomic() {
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_atomic(&coeffs, &mut zrs, &Options::default());
assert!(found);
assert!(niter <= 14);
}
const FIR_COEFFS: [f64; 49] = [
-0.00196191,
-0.00094597,
-0.00023823,
0.00134667,
0.00380494,
0.00681596,
0.0097864,
0.01186197,
0.0121238,
0.00985211,
0.00474894,
-0.00281751,
-0.01173923,
-0.0201885,
-0.02590168,
-0.02658216,
-0.02035729,
-0.00628271,
0.01534627,
0.04279982,
0.0732094,
0.10275561,
0.12753013,
0.14399228,
0.15265722,
0.14399228,
0.12753013,
0.10275561,
0.0732094,
0.04279982,
0.01534627,
-0.00628271,
-0.02035729,
-0.02658216,
-0.02590168,
-0.0201885,
-0.01173923,
-0.00281751,
0.00474894,
0.00985211,
0.0121238,
0.01186197,
0.0097864,
0.00681596,
0.00380494,
0.00134667,
-0.00023823,
-0.00094597,
-0.00196191,
];
#[test]
fn test_aberth_atomic_fir() {
let options = Options {
tolerance: 1e-8,
..Options::default()
};
let mut zrs = initial_aberth(&FIR_COEFFS);
let (niter, found) = aberth_atomic(&FIR_COEFFS, &mut zrs, &options);
assert!(found);
assert!(niter <= 100, "niter={niter}");
}
#[test]
fn test_aberth_atomic_reconstruction() {
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 (_, found) = aberth_atomic(&coeffs, &mut zrs, &Options::default());
assert!(found);
let monic = poly_from_roots(&zrs);
let scale = coeffs[0];
for (i, c) in coeffs.iter().enumerate() {
assert!(
(monic[i] * scale - c).abs() < 1e-8,
"coefficient {i} mismatch: {} vs {}",
monic[i] * scale,
c
);
}
}
#[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);
}
#[test]
fn test_aberth_scaled_polynomial_converges() {
let base = vec![10.0, 34.0, 75.0, 94.0, 150.0, 94.0, 75.0, 34.0, 10.0];
let scaled: Vec<f64> = base.iter().map(|c| c * 1e6).collect();
let mut zs = initial_aberth(&scaled);
let (niter, found) = aberth(&scaled, &mut zs, &Options::default());
assert!(found);
assert!(niter <= 12, "niter={niter}");
}
#[test]
fn test_scaling_does_not_change_roots() {
let base = vec![10.0, 34.0, 75.0, 94.0, 150.0, 94.0, 75.0, 34.0, 10.0];
let scaled: Vec<f64> = base.iter().map(|c| c * 1e6).collect();
let z0 = initial_aberth(&base);
let mut za = z0.clone();
let mut zb = z0.clone();
let (na, oka) = aberth(&base, &mut za, &Options::default());
let (nb, okb) = aberth(&scaled, &mut zb, &Options::default());
assert!(oka && okb);
assert_eq!(na, nb);
for (a, b) in za.iter().zip(zb.iter()) {
assert!((a - b).norm() < 1e-9, "{a} vs {b}");
}
}
#[test]
fn test_poly_from_roots() {
let roots = vec![Complex::new(1.0, 0.0), Complex::new(2.0, 0.0)];
let coeffs = poly_from_roots(&roots);
assert_eq!(coeffs.len(), 3);
assert!((coeffs[0] - 1.0).abs() < 1e-12);
assert!((coeffs[1] + 3.0).abs() < 1e-12);
assert!((coeffs[2] - 2.0).abs() < 1e-12);
}
#[test]
fn test_poly_from_autocorr_roots() {
let roots = vec![Complex::new(0.5, 0.5), Complex::new(0.5, -0.5)];
let coeffs = poly_from_autocorr_roots(&roots);
assert_eq!(coeffs.len(), 5);
assert!((coeffs[0] - 1.0).abs() < 1e-12);
}
#[test]
fn test_poly_from_roots_empty() {
let coeffs = poly_from_roots(&[]);
assert_eq!(coeffs, vec![1.0]);
}
}