use crate::execution_policy::{atomic_decoupled_run, jacobi_mt_run, sequential_run, Step};
use crate::rootfinding::{delta, horner, roots_from_quadratic, suppress_old, Options, Vec2};
use crate::scaling::scaled_coeffs;
use crate::seqlock::AtomicVec2;
use num_complex::Complex;
pub struct AutocorrBairstowStep<'a> {
pub coeffs: &'a [f64],
pub degree: usize,
pub options: &'a Options,
}
impl Step<Vec2> for AutocorrBairstowStep<'_> {
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 vrjn = Vec2::new(-vrj.x_, 1.0) / vrj.y_;
suppress_old(&mut v_big_a, &mut v_big_a1, &vri, &vrjn);
}
let vrin = Vec2::new(-vri.x_, 1.0) / vri.y_;
suppress_old(&mut v_big_a, &mut v_big_a1, &vri, &vrin);
let dt = delta(&v_big_a, &vri, &v_big_a1); (tol_i, Some(vri - dt))
}
}
pub fn initial_autocorr(coeffs: &[f64]) -> Vec<Vec2> {
let degree = coeffs.len() - 1;
let radius = coeffs[degree].abs().powf(1.0 / (degree as f64));
let degree = degree / 2;
let m = radius * radius;
let num_points = degree / 2;
(0..num_points)
.map(|i| Vec2::new(2.0 * radius * crate::tables::cos_pi_vdc2(i), -m))
.collect()
}
pub fn pbairstow_autocorr(coeffs: &[f64], vrs: &mut [Vec2], options: &Options) -> (usize, bool) {
let scaled = scaled_coeffs(coeffs);
let step = AutocorrBairstowStep {
coeffs: &scaled,
degree: scaled.len() - 1,
options,
};
sequential_run(vrs, options, &step)
}
pub fn pbairstow_autocorr_mt(coeffs: &[f64], vrs: &mut [Vec2], options: &Options) -> (usize, bool) {
let scaled = scaled_coeffs(coeffs);
let step = AutocorrBairstowStep {
coeffs: &scaled,
degree: scaled.len() - 1,
options,
};
jacobi_mt_run(vrs, options, &step)
}
pub fn pbairstow_autocorr_atomic(
coeffs: &[f64],
vrs: &mut [Vec2],
options: &Options,
) -> (usize, bool) {
let scaled = scaled_coeffs(coeffs);
let step = AutocorrBairstowStep {
coeffs: &scaled,
degree: scaled.len() - 1,
options,
};
atomic_decoupled_run(vrs, options, &step)
}
pub fn extract_autocorr(vr: Vec2) -> Vec2 {
let Vec2 { x_: r, y_: q } = vr;
let hr = r / 2.0;
let d = hr * hr + q;
if d < 0.0 {
if q < -1.0 {
return Vec2::new(-r, 1.0) / q;
}
}
let mut a1 = hr + (if hr >= 0.0 { d.sqrt() } else { -d.sqrt() });
let mut a2 = -q / a1;
if a1.abs() > 1.0 {
if a2.abs() > 1.0 {
a2 = 1.0 / a2;
}
a1 = 1.0 / a1;
return Vec2::new(a1 + a2, -a1 * a2);
}
if a2.abs() > 1.0 {
a2 = 1.0 / a2;
return Vec2::new(a1 + a2, -a1 * a2);
}
vr
}
pub fn poly_from_autocorr_factors(vrs: &[Vec2]) -> Vec<f64> {
if vrs.is_empty() {
return vec![1.0];
}
let mut all_roots: Vec<Complex<f64>> = Vec::with_capacity(4 * vrs.len());
for vr in vrs {
let (r1, r2) = roots_from_quadratic(vr);
all_roots.push(r1);
all_roots.push(r2);
all_roots.push(1.0 / r1);
all_roots.push(1.0 / r2);
}
crate::aberth::poly_from_roots(&all_roots)
}
#[cfg(test)]
mod tests {
use super::*;
use approx_eq::assert_approx_eq;
#[test]
fn test_initial_autocorr() {
let coeffs = vec![10.0, 34.0, 75.0, 94.0, 150.0, 94.0, 75.0, 34.0, 10.0];
let guesses = initial_autocorr(&coeffs);
assert_eq!(guesses.len(), 2);
assert!(guesses[0].x_.abs() > 0.0);
assert!(guesses[0].y_.abs() > 0.0);
}
#[test]
fn test_pbairstow_autocorr() {
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_autocorr(&coeffs);
let options = Options::default();
let (niter, found) = pbairstow_autocorr(&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_autocorr_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_autocorr(&coeffs);
let (niter, found) = pbairstow_autocorr_atomic(&coeffs, &mut vrs, &Options::default());
assert!(niter > 0);
assert!(found);
}
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_autocorr_atomic_fir() {
let options = Options {
max_iters: 20000,
tolerance: 1e-2,
..Options::default()
};
let mut vrs = initial_autocorr(&FIR_COEFFS);
let (_, found) = pbairstow_autocorr_atomic(&FIR_COEFFS, &mut vrs, &options);
assert!(found);
}
#[test]
fn test_pbairstow_autocorr_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_autocorr(&coeffs);
let (_, found) = pbairstow_autocorr_atomic(&coeffs, &mut vrs, &Options::default());
assert!(found);
let monic = poly_from_autocorr_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_extract_autocorr() {
let vr = Vec2::new(1.0, -4.0);
let result = extract_autocorr(vr);
assert_approx_eq!(result.x_, 0.25);
assert_approx_eq!(result.y_, -0.25);
}
#[test]
fn test_poly_from_autocorr_factors() {
let vrs = vec![Vec2::new(3.0, -2.0)];
let coeffs = poly_from_autocorr_factors(&vrs);
assert_eq!(coeffs.len(), 5);
assert!((coeffs[0] - 1.0).abs() < 1e-12);
}
}