use super::horner::horner_eval_f;
use super::{Matrix2, Vector2};
type Vec2 = Vector2<f64>;
type Mat2 = Matrix2<f64>;
const PI: f64 = std::f64::consts::PI;
#[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(vA: &Vec2, vr: &Vec2, vp: &Vec2) -> Vec2 {
let mp = make_adjoint(vr, vp); mp.mdot(vA) / mp.det() }
#[inline]
pub fn delta1(vA: &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(vA) / mp.det() }
#[inline]
pub fn suppress_old(vA: &mut Vec2, vA1: &mut Vec2, vri: &Vec2, vrj: &Vec2) {
let (A, B) = (vA.x_, vA.y_);
let (A1, B1) = (vA1.x_, vA1.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 a = ((A * s) - (B * p)) / e;
let b = ((B * f) - (A * qp)) / e;
let c = A1 - a;
let d = (B1 - b) - (a * p);
vA.x_ = a;
vA.y_ = b;
vA1.x_ = ((c * s) - (d * p)) / e;
vA1.y_ = ((d * f) - (c * qp)) / e;
}
#[inline]
pub fn suppress(vA: &Vec2, vA1: &Vec2, vri: &Vec2, vrj: &Vec2) -> (Vec2, Vec2) {
let vp = vri - vrj;
let m_inverse = make_inverse(vri, &vp);
let va = m_inverse.mdot(vA);
let mut vc = vA1 - 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 k = PI / (degree as f64);
let m = center * center + radius * radius;
(1..degree)
.step_by(2)
.map(|i| {
let temp = radius * (k * i as f64).cos();
let r0 = 2.0 * (center + temp);
let t0 = m + 2.0 * center * temp;
Vector2::<f64>::new(r0, -t0)
})
.collect()
}
pub fn pbairstow_even(coeffs: &[f64], vrs: &mut [Vec2], options: &Options) -> (usize, bool) {
let m_rs = vrs.len();
let mut converged = vec![false; m_rs];
for niter in 1..options.max_iters {
let mut tolerance = 0.0;
for i in 0..m_rs {
if converged[i] {
continue;
}
let mut vri = vrs[i];
if let Some(tol_i) = pbairstow_even_job(coeffs, i, &mut vri, &mut converged[i], vrs) {
if tolerance < tol_i {
tolerance = tol_i;
}
}
vrs[i] = vri;
}
if tolerance < options.tolerance {
return (niter, true);
}
}
(options.max_iters, false)
}
pub fn pbairstow_even_mt(coeffs: &[f64], vrs: &mut Vec<Vec2>, options: &Options) -> (usize, bool) {
use rayon::prelude::*;
let m_rs = vrs.len();
let mut vrsc = vec![Vec2::default(); m_rs];
let mut converged = vec![false; m_rs];
for niter in 1..options.max_iters {
let mut tolerance = 0.0;
vrsc.copy_from_slice(vrs);
let tol_i = vrs
.par_iter_mut()
.zip(converged.par_iter_mut())
.enumerate()
.filter(|(_, (_, converged))| !**converged)
.filter_map(|(i, (vri, converged))| {
pbairstow_even_job(coeffs, i, vri, converged, &vrsc)
})
.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)
}
fn pbairstow_even_job(
coeffs: &[f64],
i: usize,
vri: &mut Vec2,
converged: &mut bool,
vrsc: &[Vec2],
) -> Option<f64> {
let mut coeffs1 = coeffs.to_owned();
let degree = coeffs1.len() - 1; let mut vA = horner(&mut coeffs1, degree, vri);
let tol_i = vA.norm_inf();
if tol_i < 1e-15 {
*converged = true;
return None;
}
let mut vA1 = horner(&mut coeffs1, degree - 2, vri);
for (_, vrj) in vrsc.iter().enumerate().filter(|t| t.0 != i) {
suppress_old(&mut vA, &mut vA1, vri, vrj);
}
let dt = delta(&vA, vri, &vA1); *vri -= dt;
Some(tol_i)
}
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 k = PI / (degree as f64);
let m = radius * radius;
(1..degree)
.step_by(2)
.map(|i| Vector2::<f64>::new(2.0 * radius * (k * i as f64).cos(), -m))
.collect()
}
pub fn pbairstow_autocorr(coeffs: &[f64], vrs: &mut [Vec2], options: &Options) -> (usize, bool) {
let m_rs = vrs.len();
let mut converged = vec![false; m_rs];
for niter in 0..options.max_iters {
let mut tolerance = 0.0;
for i in 0..m_rs {
if converged[i] {
continue;
}
let mut vri = vrs[i];
let tol_i = pbairstow_autocorr_mt_job(coeffs, i, &mut vri, &mut converged[i], vrs);
if let Some(tol_i) = tol_i {
if tolerance < tol_i {
tolerance = tol_i;
}
}
vrs[i] = vri;
}
if tolerance < options.tolerance {
return (niter, true);
}
}
(options.max_iters, false)
}
pub fn pbairstow_autocorr_mt(
coeffs: &[f64],
vrs: &mut Vec<Vec2>,
options: &Options,
) -> (usize, bool) {
use rayon::prelude::*;
let m_rs = vrs.len();
let mut vrsc = vec![Vec2::default(); m_rs];
let mut converged = vec![false; m_rs];
for niter in 1..options.max_iters {
let mut tolerance = 0.0;
vrsc.copy_from_slice(vrs);
let tol_i = vrs
.par_iter_mut()
.zip(converged.par_iter_mut())
.enumerate()
.filter(|(_, (_, converged))| !**converged)
.filter_map(|(i, (vri, converged))| {
pbairstow_autocorr_mt_job(coeffs, i, vri, converged, &vrsc)
})
.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)
}
fn pbairstow_autocorr_mt_job(
coeffs: &[f64],
i: usize,
vri: &mut Vec2,
converged: &mut bool,
vrsc: &[Vec2],
) -> Option<f64> {
let mut coeffs1 = coeffs.to_owned();
let degree = coeffs1.len() - 1; let mut vA = horner(&mut coeffs1, degree, vri);
let tol_i = vA.norm_inf();
if tol_i < 1e-15 {
*converged = true;
return None;
}
let mut vA1 = horner(&mut coeffs1, degree - 2, vri);
for (_j, vrj) in vrsc.iter().enumerate().filter(|t| t.0 != i) {
suppress_old(&mut vA, &mut vA1, vri, vrj);
let vrjn = Vector2::<f64>::new(-vrj.x_, 1.0) / vrj.y_;
suppress_old(&mut vA, &mut vA1, vri, &vrjn);
}
let vrin = Vector2::<f64>::new(-vri.x_, 1.0) / vri.y_;
suppress_old(&mut vA, &mut vA1, vri, &vrin);
let dt = delta(&vA, vri, &vA1); *vri -= dt;
Some(tol_i)
}
#[allow(dead_code)]
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 Vector2::<f64>::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 Vector2::<f64>::new(a1 + a2, -a1 * a2);
}
if a2.abs() > 1.0 {
a2 = 1.0 / a2;
return Vector2::<f64>::new(a1 + a2, -a1 * a2);
}
vr
}
#[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 vA = 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(&vA, &vr, &vp);
assert_approx_eq!(delta.x_, 0.2);
assert_approx_eq!(delta.y_, 0.4);
}
#[test]
fn test_suppress_old() {
let mut vA = Vector2::new(3.0, 3.0);
let mut vA1 = 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 vA, &mut vA1, &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_suppress() {
let vA = Vector2::new(3.0, 3.0);
let vA1 = 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(&vA, &vA1, &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_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_extract_autocorr() {
let vr = Vector2::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_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_delta1() {
let vA = 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(&vA, &vr, &vp);
assert_approx_eq!(delta.x_, 0.2);
assert_approx_eq!(delta.y_, 0.4);
}
}