use super::horner::horner_eval_f;
use super::{Matrix2, Vector2};
use crate::execution_policy::{atomic_decoupled_run, jacobi_mt_run, sequential_run, Step};
use crate::scaling::scaled_coeffs;
use crate::seqlock::AtomicVec2;
use num_complex::Complex;
pub type Vec2 = Vector2<f64>;
type Mat2 = Matrix2<f64>;
#[derive(Debug)]
pub struct Options {
pub max_iters: usize,
pub tolerance: f64,
pub tol_ind: f64,
}
impl Default for Options {
fn default() -> Self {
Options {
max_iters: 2000,
tolerance: 1e-12,
tol_ind: 1e-15,
}
}
}
#[inline]
pub fn make_adjoint(vr: &Vec2, vp: &Vec2) -> Mat2 {
let (r, q) = (vr.x_, vr.y_);
let (p, s) = (vp.x_, vp.y_);
Mat2::new(
Vector2::<f64>::new(s, -p),
Vector2::<f64>::new(-p * q, p * r + s),
)
}
#[inline]
pub fn make_inverse(vr: &Vec2, vp: &Vec2) -> Mat2 {
let (r, q) = (vr.x_, vr.y_);
let (p, s) = (vp.x_, vp.y_);
let m_adjoint = Mat2::new(
Vector2::<f64>::new(s, -p),
Vector2::<f64>::new(-p * q, p * r + s),
);
m_adjoint / m_adjoint.det()
}
#[inline]
pub fn delta(v_big_a: &Vec2, vr: &Vec2, vp: &Vec2) -> Vec2 {
let mp = make_adjoint(vr, vp); mp.mdot(v_big_a) / mp.det() }
#[inline]
pub fn delta1(v_big_a: &Vec2, vr: &Vec2, vp: &Vec2) -> Vec2 {
let (r, q) = (vr.x_, vr.y_);
let (p, s) = (vp.x_, vp.y_);
let mp = Matrix2::new(Vec2::new(-s, -p), Vec2::new(p * q, p * r - s));
mp.mdot(v_big_a) / mp.det() }
#[inline]
pub fn suppress_old(v_big_a: &mut Vec2, v_big_a1: &mut Vec2, vri: &Vec2, vrj: &Vec2) {
let (big_a, big_b) = (v_big_a.x_, v_big_a.y_);
let (big_a1, big_b1) = (v_big_a1.x_, v_big_a1.y_);
let vp = vri - vrj;
let (r, q) = (vri.x_, vri.y_);
let (p, s) = (vp.x_, vp.y_);
let f = (r * p) + s;
let qp = q * p;
let e = (f * s) - (qp * p);
let new_a = ((big_a * s) - (big_b * p)) / e;
let new_b = ((big_b * f) - (big_a * qp)) / e;
let c = big_a1 - new_a;
let d = (big_b1 - new_b) - (new_a * p);
v_big_a.x_ = new_a;
v_big_a.y_ = new_b;
v_big_a1.x_ = ((c * s) - (d * p)) / e;
v_big_a1.y_ = ((d * f) - (c * qp)) / e;
}
#[inline]
pub fn suppress(v_big_a: &Vec2, v_big_a1: &Vec2, vri: &Vec2, vrj: &Vec2) -> (Vec2, Vec2) {
let vp = vri - vrj;
let m_inverse = make_inverse(vri, &vp);
let va = m_inverse.mdot(v_big_a);
let mut vc = v_big_a1 - va;
vc.y_ -= va.x_ * vp.x_;
let va1 = m_inverse.mdot(&vc);
(va, va1)
}
pub fn horner(coeffs: &mut [f64], degree: usize, vr: &Vec2) -> Vec2 {
let Vec2 { x_: r, y_: q } = vr;
for idx in 0..(degree - 1) {
coeffs[idx + 1] += coeffs[idx] * r;
coeffs[idx + 2] += coeffs[idx] * q;
}
Vector2::<f64>::new(coeffs[degree - 1], coeffs[degree])
}
pub fn initial_guess(coeffs: &[f64]) -> Vec<Vec2> {
let mut degree = coeffs.len() - 1;
let center = -coeffs[1] / (coeffs[0] * degree as f64);
let centroid = horner_eval_f(coeffs, center); let radius = centroid.abs().powf(1.0 / (degree as f64));
degree /= 2;
degree *= 2; let m = center * center + radius * radius;
let num_points = degree / 2;
(0..num_points)
.map(|i| {
let temp = radius * crate::tables::cos_pi_vdc2(i);
let r0 = 2.0 * (center + temp);
let t0 = m + 2.0 * center * temp;
Vector2::<f64>::new(r0, -t0)
})
.collect()
}
pub struct EvenBairstowStep<'a> {
pub coeffs: &'a [f64],
pub degree: usize,
pub options: &'a Options,
}
impl Step<Vec2> for EvenBairstowStep<'_> {
type Cell = AtomicVec2;
fn run<G, N>(&self, idx: usize, get: G, neighbors: N) -> (f64, Option<Vec2>)
where
G: Fn(usize) -> Vec2,
N: Iterator<Item = usize>,
{
let vri = get(idx);
let mut coeffs1 = self.coeffs.to_owned(); let mut v_big_a = horner(&mut coeffs1, self.degree, &vri);
let tol_i = v_big_a.norm_inf();
if tol_i < self.options.tol_ind {
return (0.0, None);
}
let mut v_big_a1 = horner(&mut coeffs1, self.degree - 2, &vri);
for j in neighbors {
let vrj = get(j);
suppress_old(&mut v_big_a, &mut v_big_a1, &vri, &vrj);
}
let dt = delta(&v_big_a, &vri, &v_big_a1); (tol_i, Some(vri - dt))
}
}
pub fn pbairstow_even(coeffs: &[f64], vrs: &mut [Vec2], options: &Options) -> (usize, bool) {
let scaled = scaled_coeffs(coeffs);
let step = EvenBairstowStep {
coeffs: &scaled,
degree: scaled.len() - 1,
options,
};
sequential_run(vrs, options, &step)
}
pub fn pbairstow_even_mt(coeffs: &[f64], vrs: &mut [Vec2], options: &Options) -> (usize, bool) {
let scaled = scaled_coeffs(coeffs);
let step = EvenBairstowStep {
coeffs: &scaled,
degree: scaled.len() - 1,
options,
};
jacobi_mt_run(vrs, options, &step)
}
pub fn pbairstow_even_atomic(coeffs: &[f64], vrs: &mut [Vec2], options: &Options) -> (usize, bool) {
let scaled = scaled_coeffs(coeffs);
let step = EvenBairstowStep {
coeffs: &scaled,
degree: scaled.len() - 1,
options,
};
atomic_decoupled_run(vrs, options, &step)
}
pub(crate) fn roots_from_quadratic(vr: &Vec2) -> (Complex<f64>, Complex<f64>) {
let r = vr.x_;
let q = vr.y_;
let disc = r * r + 4.0 * q;
if disc >= 0.0 {
let sqrt_disc = disc.sqrt();
(
Complex::new((r + sqrt_disc) / 2.0, 0.0),
Complex::new((r - sqrt_disc) / 2.0, 0.0),
)
} else {
let sqrt_disc = (-disc).sqrt();
(
Complex::new(r / 2.0, sqrt_disc / 2.0),
Complex::new(r / 2.0, -sqrt_disc / 2.0),
)
}
}
pub fn poly_from_quadratic_factors(vrs: &[Vec2]) -> Vec<f64> {
if vrs.is_empty() {
return vec![1.0];
}
let mut all_roots: Vec<Complex<f64>> = Vec::with_capacity(2 * vrs.len());
for vr in vrs {
let (r1, r2) = roots_from_quadratic(vr);
all_roots.push(r1);
all_roots.push(r2);
}
crate::aberth::poly_from_roots(&all_roots)
}
#[cfg(test)]
mod tests {
use super::*;
use approx_eq::assert_approx_eq;
#[test]
fn test_options_default() {
let options = Options::default();
assert_eq!(options.max_iters, 2000);
assert_eq!(options.tolerance, 1e-12);
assert_eq!(options.tol_ind, 1e-15);
}
#[test]
fn test_delta() {
let v_big_a = Vector2::new(1.0, 2.0);
let vr = Vector2::new(-2.0, 0.0);
let vp = Vector2::new(4.0, 5.0);
let delta = delta(&v_big_a, &vr, &vp);
assert_approx_eq!(delta.x_, 0.2);
assert_approx_eq!(delta.y_, 0.4);
}
#[test]
fn test_suppress_old() {
let mut v_big_a = Vector2::new(3.0, 3.0);
let mut v_big_a1 = Vector2::new(1.0, 2.0);
let vri = Vector2::new(-2.0, 0.0);
let vrj = Vector2::new(4.0, 5.0);
suppress_old(&mut v_big_a, &mut v_big_a1, &vri, &vrj);
let dr = delta(&v_big_a, &vri, &v_big_a1);
assert_approx_eq!(dr.x_, -16.780821917808325);
assert_approx_eq!(dr.y_, 1.4383561643835612);
}
#[test]
fn test_suppress() {
let v_big_a = Vector2::new(3.0, 3.0);
let v_big_a1 = Vector2::new(1.0, 2.0);
let vri = Vector2::new(-2.0, 0.0);
let vrj = Vector2::new(4.0, 5.0);
let (va, va1) = suppress(&v_big_a, &v_big_a1, &vri, &vrj);
let dr = delta(&va, &vri, &va1);
assert_approx_eq!(dr.x_, -16.780821917808325);
assert_approx_eq!(dr.y_, 1.4383561643835612);
}
#[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 result = horner_eval_f(&coeffs, 2.0);
assert_eq!(result, 18250.0);
}
#[test]
fn test_horner() {
let mut coeffs = vec![10.0, 34.0, 75.0, 94.0, 150.0, 94.0, 75.0, 34.0, 10.0];
let vr = Vector2::new(-1.0, -2.0);
let result = horner(&mut coeffs, 8, &vr);
assert_approx_eq!(result.x_, 114.0);
assert_approx_eq!(result.y_, 134.0);
assert_eq!(coeffs[3], 15.0);
}
#[test]
fn test_initial_guess() {
let coeffs = vec![10.0, 34.0, 75.0, 94.0, 150.0, 94.0, 75.0, 34.0, 10.0];
let guesses = initial_guess(&coeffs);
assert_eq!(guesses.len(), 4);
assert!(guesses[0].x_.abs() > 0.0);
assert!(guesses[0].y_.abs() > 0.0);
}
#[test]
fn test_pbairstow_even() {
let coeffs = vec![10.0, 34.0, 75.0, 94.0, 150.0, 94.0, 75.0, 34.0, 10.0];
let mut vrs = initial_guess(&coeffs);
let options = Options::default();
let (niter, found) = pbairstow_even(&coeffs, &mut vrs, &options);
assert!(niter > 0);
assert!(found);
let mut has_root = false;
for vr in vrs {
let val = horner(&mut coeffs.clone(), coeffs.len() - 1, &vr);
if val.norm_inf() < options.tolerance {
has_root = true;
break;
}
}
assert!(has_root);
}
#[test]
fn test_pbairstow_even_atomic() {
let coeffs = vec![10.0, 34.0, 75.0, 94.0, 150.0, 94.0, 75.0, 34.0, 10.0];
let mut vrs = initial_guess(&coeffs);
let (niter, found) = pbairstow_even_atomic(&coeffs, &mut vrs, &Options::default());
assert!(niter > 0);
assert!(found);
}
#[test]
fn test_pbairstow_even_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 vrs = initial_guess(&scaled);
let (niter, found) = pbairstow_even(&scaled, &mut vrs, &Options::default());
assert!(found);
assert!(niter <= 12, "niter={niter}");
}
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_pbairstow_even_atomic_fir() {
let options = Options {
max_iters: 20000,
tolerance: 1e-2,
..Options::default()
};
let mut vrs = initial_guess(&FIR_COEFFS);
let (_, found) = pbairstow_even_atomic(&FIR_COEFFS, &mut vrs, &options);
assert!(found);
}
#[test]
fn test_pbairstow_even_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 vrs = initial_guess(&coeffs);
let (_, found) = pbairstow_even_atomic(&coeffs, &mut vrs, &Options::default());
assert!(found);
let monic = poly_from_quadratic_factors(&vrs);
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_delta1() {
let v_big_a = Vector2::new(1.0, 2.0);
let vr = Vector2::new(-2.0, -0.0);
let vp = Vector2::new(4.0, -5.0);
let delta = delta1(&v_big_a, &vr, &vp);
assert_approx_eq!(delta.x_, 0.2);
assert_approx_eq!(delta.y_, 0.4);
}
#[test]
fn test_roots_from_quadratic_real() {
let vr = Vec2::new(3.0, -2.0);
let (r1, r2) = roots_from_quadratic(&vr);
assert!((r1.re - 2.0).abs() < 1e-12);
assert!((r2.re - 1.0).abs() < 1e-12);
assert!(r1.im.abs() < 1e-12);
assert!(r2.im.abs() < 1e-12);
}
#[test]
fn test_roots_from_quadratic_complex() {
let vr = Vec2::new(0.0, -1.0);
let (r1, r2) = roots_from_quadratic(&vr);
assert!((r1.re).abs() < 1e-12);
assert!((r1.im - 1.0).abs() < 1e-12);
assert!((r2.re).abs() < 1e-12);
assert!((r2.im + 1.0).abs() < 1e-12);
}
#[test]
fn test_poly_from_quadratic_factors() {
let vrs = vec![Vec2::new(3.0, -2.0)];
let coeffs = poly_from_quadratic_factors(&vrs);
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);
}
fn sorted_roots(vrs: &[Vec2]) -> Vec<(f64, f64)> {
let mut roots = Vec::new();
for vr in vrs {
let (a, b) = roots_from_quadratic(vr);
roots.push((a.re, a.im));
roots.push((b.re, b.im));
}
roots.sort_by(|x, y| x.partial_cmp(y).unwrap());
roots
}
fn max_root_set_diff(a: &[(f64, f64)], b: &[(f64, f64)]) -> f64 {
a.iter()
.zip(b.iter())
.map(|(x, y)| (x.0 - y.0).abs().max((x.1 - y.1).abs()))
.fold(0.0_f64, f64::max)
}
#[test]
fn test_jacobi_mt_order_independent() {
let coeffs = vec![
1.0, 0.0, 2.0, 0.0, 3.0, 0.0, 4.0, 0.0, 3.0, 0.0, 2.0, 0.0, 1.0,
];
let opts = Options::default();
let base = initial_guess(&coeffs);
assert_eq!(base.len(), 6);
let perms = vec![
base.clone(),
base.iter().rev().cloned().collect(),
vec![base[1], base[4], base[0], base[5], base[2], base[3]],
vec![base[3], base[1], base[5], base[0], base[4], base[2]],
];
let mut niters = Vec::new();
let mut rootsets = Vec::new();
for perm in &perms {
let mut vrs = perm.clone();
let (niter, found) = pbairstow_even_mt(&coeffs, &mut vrs, &opts);
assert!(found, "Jacobi mt failed to converge");
niters.push(niter);
rootsets.push(sorted_roots(&vrs));
}
assert!(
niters.iter().all(|x| *x == niters[0]),
"Jacobi mt iteration count is order-dependent: {niters:?}"
);
for (k, rs) in rootsets.iter().enumerate() {
let diff = max_root_set_diff(&rootsets[0], rs);
assert!(
diff < 1e-9,
"Jacobi mt root set differs across perms: perm {k} diff {diff:.3e}"
);
}
}
#[test]
fn test_jacobi_processing_order_bit_identical() {
let coeffs = vec![10.0, 34.0, 75.0, 94.0, 150.0, 94.0, 75.0, 34.0, 10.0];
let vrs0 = initial_guess(&coeffs);
let options = Options::default();
let step = EvenBairstowStep {
coeffs: &coeffs,
degree: coeffs.len() - 1,
options: &options,
};
let orders = [
vec![0usize, 1, 2, 3],
vec![3usize, 2, 1, 0],
vec![1usize, 3, 0, 2],
vec![2usize, 0, 3, 1],
];
let mut next_states = Vec::new();
for order in &orders {
let vrsc = vrs0.clone(); let mut next = vrs0.clone();
for &i in order {
let (_, new_value) =
step.run(i, |j| vrsc[j], (0..vrs0.len()).filter(move |&j| j != i));
if let Some(value) = new_value {
next[i] = value;
}
}
next_states.push(next);
}
for (k, state) in next_states.iter().enumerate().skip(1) {
assert_eq!(
next_states[0], *state,
"Jacobi next state differs for processing order {:?}",
orders[k]
);
}
}
#[test]
fn test_suppression_order_machine_epsilon() {
let coeffs = vec![10.0, 34.0, 75.0, 94.0, 150.0, 94.0, 75.0, 34.0, 10.0];
let vrs = initial_guess(&coeffs);
let orders = [
vec![0usize, 1, 2, 3],
vec![3usize, 2, 1, 0],
vec![1usize, 3, 0, 2],
vec![2usize, 0, 3, 1],
];
let mut worst = 0.0_f64;
for i in 0..vrs.len() {
let mut ref_vri = None;
for order in &orders {
let vri = vrs[i];
let mut coeffs1 = coeffs.clone();
let degree = coeffs1.len() - 1;
let mut v_big_a = horner(&mut coeffs1, degree, &vri);
if v_big_a.norm_inf() < 1e-15 {
continue;
}
let mut v_big_a1 = horner(&mut coeffs1, degree - 2, &vri);
for &j in order {
if j == i {
continue;
}
suppress_old(&mut v_big_a, &mut v_big_a1, &vri, &vrs[j]);
}
let dt = delta(&v_big_a, &vri, &v_big_a1);
let new_vri = vri - dt;
match ref_vri {
None => ref_vri = Some(new_vri),
Some(r) => {
let d = (new_vri - r).norm_inf();
worst = worst.max(d);
}
}
}
}
assert!(
worst < 1e-12,
"suppression-order drift too large: {worst:.3e}"
);
}
#[test]
fn test_gs_order_dependent_iterations() {
let coeffs = vec![10.0, 34.0, 75.0, 94.0, 150.0, 94.0, 75.0, 34.0, 10.0];
let opts = Options::default();
let base = initial_guess(&coeffs);
let perms = vec![
base.clone(),
base.iter().rev().cloned().collect(),
vec![base[1], base[3], base[0], base[2]],
vec![base[2], base[0], base[3], base[1]],
];
let mut niters = Vec::new();
for perm in &perms {
let mut vrs = perm.clone();
let (niter, found) = pbairstow_even(&coeffs, &mut vrs, &opts);
assert!(found, "Gauss-Seidel failed to converge");
niters.push(niter);
}
assert!(
!niters.iter().all(|x| *x == niters[0]),
"expected Gauss-Seidel iteration count to be order-dependent, got {niters:?}"
);
}
}