Skip to main content

ginger/
rootfinding.rs

1use super::horner::horner_eval_f;
2use super::{Matrix2, Vector2};
3use num_complex::Complex;
4
5type Vec2 = Vector2<f64>;
6type Mat2 = Matrix2<f64>;
7
8/// The below code defines a struct named Options with three fields: max_iters, tolerance, and tol_ind.
9///
10/// Properties:
11///
12/// * `max_iters`: The `max_iters` property represents the maximum number of iterations allowed for a
13///   certain algorithm or process. It is of type `usize`, which means it can only hold non-negative
14///   integer values.
15/// * `tolerance`: The `tolerance` property is a floating-point number that represents the tolerance for convergence
16///   in an algorithm. It is used to determine when the algorithm has reached a satisfactory solution.
17/// * `tol_ind`: The `tol_ind` property in the `Options` struct represents the tolerance for individual
18///   values. It is a floating-point number (`f64`) that determines the acceptable difference between the
19///   expected value and the actual value for each element in a calculation or comparison.
20#[derive(Debug)]
21pub struct Options {
22    pub max_iters: usize,
23    pub tolerance: f64,
24    pub tol_ind: f64,
25}
26
27/// The below code is implementing the `Default` trait for the `Options` struct in Rust. The `Default`
28/// trait provides a default value for a type, which can be used when creating an instance of the type
29/// without specifying any values. In this case, the `default` function is defined to return an instance
30/// of the `Options` struct with default values for the `max_iters`, `tolerance`, and `tol_ind` fields.
31impl Default for Options {
32    fn default() -> Self {
33        Options {
34            max_iters: 2000,
35            tolerance: 1e-12,
36            tol_ind: 1e-15,
37        }
38    }
39}
40
41/// The function `make_adjoint` calculates the adjoint matrix between two vectors.
42///
43/// Arguments:
44///
45/// * `vr`: A vector representing the direction of the reference frame's x-axis.
46/// * `vp`: The parameter `vp` represents a vector `vp = (p, s)`, where `p` and `s` are the components
47///   of the vector.
48///
49/// Returns:
50///
51/// The function `make_adjoint` returns a `Mat2` object.
52/// Calculates the adjoint matrix for a 2x2 matrix from two vectors.
53///
54/// The adjoint is computed as:
55/// |  s  -p |
56/// | -p*q  p*r+s |
57///
58/// Arguments:
59///
60/// * `vr`: A vector representing the row of a 2x2 matrix
61/// * `vp`: Another vector representing the row of a 2x2 matrix
62///
63/// Returns:
64///
65/// A 2x2 matrix representing the adjoint
66#[inline]
67pub fn make_adjoint(vr: &Vec2, vp: &Vec2) -> Mat2 {
68    let (r, q) = (vr.x_, vr.y_);
69    let (p, s) = (vp.x_, vp.y_);
70    Mat2::new(
71        Vector2::<f64>::new(s, -p),
72        Vector2::<f64>::new(-p * q, p * r + s),
73    )
74}
75
76/// The function `make_inverse` calculates the inverse of a 2x2 matrix.
77///
78/// Arguments:
79///
80/// * `vr`: A vector representing the row of a 2x2 matrix. The components of the vector are vr.x_ and vr.y_.
81/// * `vp`: The parameter `vp` represents a 2D vector with components `x` and `y`.
82///
83/// Returns:
84///
85/// The function `make_inverse` returns a `Mat2` object.
86/// Calculates the inverse of a 2x2 matrix from two vectors.
87///
88/// Arguments:
89///
90/// * `vr`: A vector representing the row of a 2x2 matrix
91/// * `vp`: Another vector representing the row of a 2x2 matrix
92///
93/// Returns:
94///
95/// A 2x2 matrix representing the inverse
96#[inline]
97pub fn make_inverse(vr: &Vec2, vp: &Vec2) -> Mat2 {
98    let (r, q) = (vr.x_, vr.y_);
99    let (p, s) = (vp.x_, vp.y_);
100    let m_adjoint = Mat2::new(
101        Vector2::<f64>::new(s, -p),
102        Vector2::<f64>::new(-p * q, p * r + s),
103    );
104    m_adjoint / m_adjoint.det()
105}
106
107/// The `delta` function calculates the delta value for the Bairstow's method
108///
109/// Arguments:
110///
111/// * `vA`: A vector representing the coefficients of a polynomial equation.
112/// * `vr`: The parameter `vr` represents the vector `[-2.0, 0.0]`.
113/// * `vp`: The parameter `vp` represents the vector vr - vrj
114///
115/// Returns:
116///
117/// The function `delta` returns a `Vec2` object.
118///
119/// r * p - m   -p
120/// q * p       -m
121///
122/// # Examples:
123///
124/// ```
125/// use ginger::rootfinding::delta;
126/// use ginger::vector2::Vector2;
127///
128/// let mut vA1 = Vector2::new(1.0, 2.0);
129/// let vri = Vector2::new(-2.0, 0.0);
130/// let vrj = Vector2::new(4.0, 5.0);
131/// let vd = delta(&vA1, &vri, &vrj);
132/// assert_eq!(vd, Vector2::new(0.2, 0.4));
133/// ```
134#[inline]
135pub fn delta(vA: &Vec2, vr: &Vec2, vp: &Vec2) -> Vec2 {
136    let mp = make_adjoint(vr, vp); // 2 mul's
137    mp.mdot(vA) / mp.det() // 6 mul's + 2 div's
138}
139
140/// delta 1 for ri - rj
141///
142/// # Examples:
143///
144/// ```
145/// use ginger::rootfinding::delta1;
146/// use ginger::vector2::Vector2;
147///
148/// let mut vA1 = Vector2::new(1.0, 2.0);
149/// let vri = Vector2::new(-2.0, -0.0);
150/// let vrj = Vector2::new(4.0, -5.0);
151/// let vd = delta1(&vA1, &vri, &vrj);
152/// assert_eq!(vd, Vector2::new(0.2, 0.4));
153/// ```
154#[inline]
155pub fn delta1(vA: &Vec2, vr: &Vec2, vp: &Vec2) -> Vec2 {
156    let (r, q) = (vr.x_, vr.y_);
157    let (p, s) = (vp.x_, vp.y_);
158    let mp = Matrix2::new(Vec2::new(-s, -p), Vec2::new(p * q, p * r - s));
159    mp.mdot(vA) / mp.det() // 6 mul's + 2 div's
160}
161
162/// The `suppress_old` function performs zero suppression on a set of vectors.
163///
164/// Arguments:
165///
166/// * `vA`: A mutable reference to a Vector2 object representing the coefficients of a polynomial. The
167///   coefficients are stored in the x_ and y_ fields of the Vector2 object.
168/// * `vA1`: vA1 is a mutable reference to a Vector2 object.
169/// * `vri`: The parameter `vri` represents a vector with components `r` and `i`. It is used in the
170///   `suppress_old` function to perform calculations.
171/// * `vrj`: The parameter `vrj` represents a vector with components `x` and `y`.
172///
173/// # Examples:
174///
175/// ```
176/// use ginger::rootfinding::delta;
177/// use ginger::rootfinding::suppress_old;
178/// use ginger::vector2::Vector2;
179/// use approx_eq::assert_approx_eq;
180///
181/// let mut vA = Vector2::new(3.0, 3.0);
182/// let mut vA1 = Vector2::new(1.0, 2.0);
183/// let vri = Vector2::new(-2.0, 0.0);
184/// let vrj = Vector2::new(4.0, 5.0);
185///
186/// suppress_old(&mut vA, &mut vA1, &vri, &vrj);
187/// let dr = delta(&vA, &vri, &vA1);
188/// assert_approx_eq!(dr.x_, -16.780821917808325);
189/// assert_approx_eq!(dr.y_, 1.4383561643835612);
190#[inline]
191pub fn suppress_old(vA: &mut Vec2, vA1: &mut Vec2, vri: &Vec2, vrj: &Vec2) {
192    let (A, B) = (vA.x_, vA.y_);
193    let (A1, B1) = (vA1.x_, vA1.y_);
194    let vp = vri - vrj;
195    let (r, q) = (vri.x_, vri.y_);
196    let (p, s) = (vp.x_, vp.y_);
197    let f = (r * p) + s;
198    let qp = q * p;
199    let e = (f * s) - (qp * p);
200    let a = ((A * s) - (B * p)) / e;
201    let b = ((B * f) - (A * qp)) / e;
202    let c = A1 - a;
203    let d = (B1 - b) - (a * p);
204    vA.x_ = a;
205    vA.y_ = b;
206    vA1.x_ = ((c * s) - (d * p)) / e;
207    vA1.y_ = ((d * f) - (c * qp)) / e;
208}
209
210/// The `suppress` function in Rust performs zero suppression on a set of vectors.
211///
212/// Arguments:
213///
214/// * `vA`: A vector representing the coefficients of a polynomial function.
215/// * `vA1`: The parameter `vA1` is a `Vector2` object representing a vector with two components. It is
216///   used as an input parameter in the `suppress` function.
217/// * `vri`: The parameter `vri` represents the vector `ri`, and `vrj` represents the vector `rj`. These
218///   vectors are used in the calculation of the suppression step in the Bairstow's method for root
219///   finding.
220/// * `vrj`: The parameter `vrj` represents a vector with coordinates (4.0, 5.0).
221///   Zero suppression
222///
223/// # Examples:
224///
225/// ```
226/// use ginger::rootfinding::delta;
227/// use ginger::rootfinding::suppress;
228/// use ginger::vector2::Vector2;
229/// use approx_eq::assert_approx_eq;
230///
231/// let mut vA = Vector2::new(3.0, 3.0);
232/// let mut vA1 = Vector2::new(1.0, 2.0);
233/// let vri = Vector2::new(-2.0, 0.0);
234/// let vrj = Vector2::new(4.0, 5.0);
235///
236/// (vA, vA1) = suppress(&mut vA, &mut vA1, &vri, &vrj);
237/// let dr = delta(&vA, &vri, &vA1);
238/// assert_approx_eq!(dr.x_, -16.780821917808325);
239/// assert_approx_eq!(dr.y_, 1.4383561643835612);
240#[inline]
241pub fn suppress(vA: &Vec2, vA1: &Vec2, vri: &Vec2, vrj: &Vec2) -> (Vec2, Vec2) {
242    let vp = vri - vrj;
243    let m_inverse = make_inverse(vri, &vp);
244    let va = m_inverse.mdot(vA);
245    let mut vc = vA1 - va;
246    vc.y_ -= va.x_ * vp.x_;
247    let va1 = m_inverse.mdot(&vc);
248    (va, va1)
249}
250
251/// The `horner` function implements Horner's evaluation for Bairstow's method in Rust.
252///
253/// Arguments:
254///
255/// * `coeffs`: A mutable slice of f64 values representing the coefficients of the polynomial. The
256///   coefficients are in descending order of degree.
257/// * `degree`: The `degree` parameter represents the degree of the polynomial. It is used to determine
258///   the number of coefficients in the `coeffs` array.
259/// * `vr`: The parameter `vr` is a `Vec2` struct that contains two values, `x_` and `y_`. In the
260///   example, `vr` is initialized with the values `-1.0` and `-2.0`.
261///
262/// Returns:
263///
264/// The function `horner` returns a `Vec2` struct, which contains two `f64` values representing the
265/// results of the Horner evaluation.
266///
267/// # Examples:
268///
269/// ```
270/// use ginger::rootfinding::horner;
271/// use ginger::vector2::Vector2;
272/// use approx_eq::assert_approx_eq;
273///
274/// let mut coeffs = vec![10.0, 34.0, 75.0, 94.0, 150.0, 94.0, 75.0, 34.0, 10.0];
275/// let px = horner(&mut coeffs, 8, &Vector2::new(-1.0, -2.0));
276///
277/// assert_approx_eq!(px.x_, 114.0);
278/// assert_approx_eq!(px.y_, 134.0);
279/// assert_approx_eq!(coeffs[3], 15.0);
280/// ```
281pub fn horner(coeffs: &mut [f64], degree: usize, vr: &Vec2) -> Vec2 {
282    let Vec2 { x_: r, y_: q } = vr;
283    for idx in 0..(degree - 1) {
284        coeffs[idx + 1] += coeffs[idx] * r;
285        coeffs[idx + 2] += coeffs[idx] * q;
286    }
287    Vector2::<f64>::new(coeffs[degree - 1], coeffs[degree])
288}
289
290/// The `initial_guess` function in Rust calculates the initial guesses for the roots of a polynomial
291/// using Bairstow's method.
292///
293/// Arguments:
294///
295/// * `coeffs`: A vector of coefficients representing a polynomial.
296///
297/// Returns:
298///
299/// The function `initial_guess` returns a vector of `Vector2` structs, which represent the initial
300/// guesses for the roots of a polynomial equation.
301///
302/// # Examples:
303///
304/// ```
305/// use ginger::rootfinding::initial_guess;
306/// use ginger::vector2::Vector2;
307///
308/// let coeffs = vec![10.0, 34.0, 75.0, 94.0, 150.0, 94.0, 75.0, 34.0, 10.0];
309/// let vr0s = initial_guess(&coeffs);
310/// ```
311pub fn initial_guess(coeffs: &[f64]) -> Vec<Vec2> {
312    let mut degree = coeffs.len() - 1;
313    let center = -coeffs[1] / (coeffs[0] * degree as f64);
314    let centroid = horner_eval_f(coeffs, center); // ???
315    let radius = centroid.abs().powf(1.0 / (degree as f64));
316    degree /= 2;
317    degree *= 2; // make even
318    let m = center * center + radius * radius;
319    let num_points = degree / 2;
320    (0..num_points)
321        .map(|i| {
322            let temp = radius * crate::tables::cos_pi_vdc2(i);
323            let r0 = 2.0 * (center + temp);
324            let t0 = m + 2.0 * center * temp;
325            Vector2::<f64>::new(r0, -t0)
326        })
327        .collect()
328}
329
330/// Parallel Bairstow's method (even degree only)
331///
332/// The `pbairstow_even` function implements the parallel Bairstow's method for finding roots of
333/// even-degree polynomials.
334///
335/// Arguments:
336///
337/// * `coeffs`: The `coeffs` parameter is a slice of `f64` values representing the coefficients of a polynomial.
338///   It is assumed that the polynomial has an even degree.
339/// * `vrs`: A vector of initial guesses for the roots of the polynomial. Each element of the vector is
340///   a complex number representing a root guess.
341/// * `options`: The `options` parameter is an instance of the `Options` struct, which contains the
342///   following fields:
343///
344/// # Examples:
345///
346/// ```
347/// use ginger::rootfinding::{initial_guess, pbairstow_even, Options};
348///
349/// let coeffs = vec![10.0, 34.0, 75.0, 94.0, 150.0, 94.0, 75.0, 34.0, 10.0];
350/// let mut vrs = initial_guess(&coeffs);
351/// let (niter, found) = pbairstow_even(&coeffs, &mut vrs, &Options::default());
352///
353/// assert!(niter > 0);
354/// assert!(found);
355/// ```
356pub fn pbairstow_even(coeffs: &[f64], vrs: &mut [Vec2], options: &Options) -> (usize, bool) {
357    let m_rs = vrs.len();
358    let mut converged = vec![false; m_rs];
359
360    for niter in 1..options.max_iters {
361        let mut tolerance = 0.0;
362        for i in 0..m_rs {
363            if converged[i] {
364                continue;
365            }
366            let mut vri = vrs[i];
367            if let Some(tol_i) = pbairstow_even_job(coeffs, i, &mut vri, &mut converged[i], vrs) {
368                if tolerance < tol_i {
369                    tolerance = tol_i;
370                }
371            }
372            vrs[i] = vri;
373        }
374        if tolerance < options.tolerance {
375            return (niter, true);
376        }
377    }
378    (options.max_iters, false)
379}
380
381/// Multi-threading Bairstow's method (even degree only)
382///
383/// The `pbairstow_even_mt` function implements the multi-threading parallel Bairstow's
384/// method for finding roots of even-degree polynomials.
385///
386/// Arguments:
387///
388/// * `coeffs`: The `coeffs` parameter is a slice of `f64` values representing the coefficients of a polynomial.
389///   It is assumed that the polynomial has an even degree.
390/// * `vrs`: A vector of initial guesses for the roots of the polynomial. Each element of the vector is
391///   a complex number representing a root guess.
392/// * `options`: The `options` parameter is an instance of the `Options` struct, which contains the
393///   following fields:
394///
395/// # Examples:
396///
397/// ```
398/// use ginger::rootfinding::{initial_guess, pbairstow_even_mt, Options};
399///
400/// let coeffs = vec![10.0, 34.0, 75.0, 94.0, 150.0, 94.0, 75.0, 34.0, 10.0];
401/// let mut vrs = initial_guess(&coeffs);
402/// let (niter, found) = pbairstow_even_mt(&coeffs, &mut vrs, &Options::default());
403///
404/// assert!(niter > 0);
405/// assert!(found);
406/// ```
407pub fn pbairstow_even_mt(coeffs: &[f64], vrs: &mut Vec<Vec2>, options: &Options) -> (usize, bool) {
408    use rayon::prelude::*;
409
410    let m_rs = vrs.len();
411    let mut vrsc = vec![Vec2::default(); m_rs];
412    let mut converged = vec![false; m_rs];
413
414    for niter in 1..options.max_iters {
415        let mut tolerance = 0.0;
416        vrsc.copy_from_slice(vrs);
417
418        let tol_i = vrs
419            .par_iter_mut()
420            .zip(converged.par_iter_mut())
421            .enumerate()
422            .filter(|(_, (_, converged))| !**converged)
423            .filter_map(|(i, (vri, converged))| {
424                pbairstow_even_job(coeffs, i, vri, converged, &vrsc)
425            })
426            .reduce(|| tolerance, |x, y| x.max(y));
427        if tolerance < tol_i {
428            tolerance = tol_i;
429        }
430        if tolerance < options.tolerance {
431            return (niter, true);
432        }
433    }
434    (options.max_iters, false)
435}
436
437/// Internal job function for parallel Bairstow's method (even degree)
438///
439/// Performs a single iteration of Bairstow's method for one root approximation,
440/// suppressing the effect of other roots (Gauss-Seidel style).
441///
442/// Arguments:
443///
444/// * `coeffs`: Polynomial coefficients
445/// * `i`: Current root index
446/// * `vri`: Current root approximation (mutable)
447/// * `converged`: Convergence flag for this root
448/// * `vrsc`: Current approximations of all roots
449///
450/// Returns:
451///
452/// Option containing tolerance value if not yet converged
453fn pbairstow_even_job(
454    coeffs: &[f64],
455    i: usize,
456    vri: &mut Vec2,
457    converged: &mut bool,
458    vrsc: &[Vec2],
459) -> Option<f64> {
460    let mut coeffs1 = coeffs.to_owned();
461    let degree = coeffs1.len() - 1; // degree, assume even
462    let mut vA = horner(&mut coeffs1, degree, vri);
463    let tol_i = vA.norm_inf();
464    if tol_i < 1e-15 {
465        *converged = true;
466        return None;
467    }
468    let mut vA1 = horner(&mut coeffs1, degree - 2, vri);
469    for (_, vrj) in vrsc.iter().enumerate().filter(|t| t.0 != i) {
470        suppress_old(&mut vA, &mut vA1, vri, vrj);
471    }
472    let dt = delta(&vA, vri, &vA1); // Gauss-Seidel fashion
473    *vri -= dt;
474    Some(tol_i)
475}
476
477/// The `initial_autocorr` function calculates the initial guesses for Bairstow's method for finding
478/// roots of a polynomial, specifically for the auto-correlation function.
479///
480/// Arguments:
481///
482/// * `coeffs`: The `coeffs` parameter is a slice of `f64` values representing the coefficients of a
483///   polynomial. The coefficients are ordered from highest degree to lowest degree.
484///
485/// Returns:
486///
487/// The function `initial_autocorr` returns a vector of `Vec2` structs.
488///
489/// # Examples:
490///
491/// ```
492/// use ginger::rootfinding::initial_autocorr;
493/// use ginger::vector2::Vector2;
494///
495/// let coeffs = vec![10.0, 34.0, 75.0, 94.0, 150.0, 94.0, 75.0, 34.0, 10.0];
496/// let vr0s = initial_autocorr(&coeffs);
497/// ```
498pub fn initial_autocorr(coeffs: &[f64]) -> Vec<Vec2> {
499    let degree = coeffs.len() - 1;
500    let radius = coeffs[degree].abs().powf(1.0 / (degree as f64));
501    let degree = degree / 2;
502    let m = radius * radius;
503    let num_points = degree / 2;
504    (0..num_points)
505        .map(|i| Vector2::<f64>::new(2.0 * radius * crate::tables::cos_pi_vdc2(i), -m))
506        .collect()
507}
508
509/// The `pbairstow_autocorr` function implements the simultaneous Bairstow's method for finding roots of
510/// a polynomial, specifically for the auto-correlation function.
511///
512/// Arguments:
513///
514/// * `coeffs`: The `coeffs` parameter is a slice of `f64` values representing the coefficients of a
515///   polynomial. These coefficients are used to calculate the auto-correlation function.
516/// * `vrs`: `vrs` is a vector of complex numbers representing the initial guesses for the roots of the
517///   polynomial. Each element of `vrs` is a `Vec2` struct, which contains two fields: `x_` and `y_`.
518///   These fields represent the real and imaginary parts of the
519/// * `options`: The `Options` struct is used to specify the parameters for the Bairstow's method
520///   algorithm. It has the following fields:
521///
522/// # Examples:
523///
524/// ```
525/// use ginger::rootfinding::{initial_autocorr, pbairstow_autocorr, Options};
526///
527/// let coeffs = vec![10.0, 34.0, 75.0, 94.0, 150.0, 94.0, 75.0, 34.0, 10.0];
528/// let mut vrs = initial_autocorr(&coeffs);
529/// let (niter, found) = pbairstow_autocorr(&coeffs, &mut vrs, &Options::default());
530///
531/// assert!(niter > 0);
532/// assert!(found);
533/// ```
534pub fn pbairstow_autocorr(coeffs: &[f64], vrs: &mut [Vec2], options: &Options) -> (usize, bool) {
535    let m_rs = vrs.len();
536    let mut converged = vec![false; m_rs];
537
538    for niter in 0..options.max_iters {
539        let mut tolerance = 0.0;
540
541        for i in 0..m_rs {
542            if converged[i] {
543                continue;
544            }
545            let mut vri = vrs[i];
546            let tol_i = pbairstow_autocorr_mt_job(coeffs, i, &mut vri, &mut converged[i], vrs);
547            if let Some(tol_i) = tol_i {
548                if tolerance < tol_i {
549                    tolerance = tol_i;
550                }
551            }
552            vrs[i] = vri;
553        }
554        if tolerance < options.tolerance {
555            return (niter, true);
556        }
557    }
558    (options.max_iters, false)
559}
560
561/// The `pbairstow_autocorr_mt` function is a multi-threaded implementation of Bairstow's method for
562/// finding roots of a polynomial, specifically for auto-correlation functions.
563///
564/// Arguments:
565///
566/// * `coeffs`: The `coeffs` parameter is a slice of `f64` values representing the coefficients of a
567///   polynomial. These coefficients are used as input for the Bairstow's method algorithm.
568/// * `vrs`: `vrs` is a vector of complex numbers representing the initial guesses for the roots of the
569///   polynomial. Each element of `vrs` is a `Vec2` struct, which contains the real and imaginary parts of
570///   the complex number.
571/// * `options`: The `options` parameter is an instance of the `Options` struct, which contains the
572///   following fields:
573///
574/// # Examples:
575///
576/// ```
577/// use ginger::rootfinding::{initial_autocorr, pbairstow_autocorr_mt, Options};
578///
579/// let coeffs = vec![10.0, 34.0, 75.0, 94.0, 150.0, 94.0, 75.0, 34.0, 10.0];
580/// let mut vrs = initial_autocorr(&coeffs);
581/// let (niter, found) = pbairstow_autocorr_mt(&coeffs, &mut vrs, &Options::default());
582///
583/// assert!(niter > 0);
584/// assert!(found);
585/// ```
586pub fn pbairstow_autocorr_mt(
587    coeffs: &[f64],
588    vrs: &mut Vec<Vec2>,
589    options: &Options,
590) -> (usize, bool) {
591    use rayon::prelude::*;
592
593    let m_rs = vrs.len();
594    let mut vrsc = vec![Vec2::default(); m_rs];
595    let mut converged = vec![false; m_rs];
596
597    for niter in 1..options.max_iters {
598        let mut tolerance = 0.0;
599        vrsc.copy_from_slice(vrs);
600
601        let tol_i = vrs
602            .par_iter_mut()
603            .zip(converged.par_iter_mut())
604            .enumerate()
605            .filter(|(_, (_, converged))| !**converged)
606            .filter_map(|(i, (vri, converged))| {
607                pbairstow_autocorr_mt_job(coeffs, i, vri, converged, &vrsc)
608            })
609            .reduce(|| tolerance, |x, y| x.max(y));
610        if tolerance < tol_i {
611            tolerance = tol_i;
612        }
613        if tolerance < options.tolerance {
614            return (niter, true);
615        }
616    }
617    (options.max_iters, false)
618}
619
620/// Internal job function for parallel Bairstow's method (auto-correlation)
621///
622/// Performs a single iteration of Bairstow's method for auto-correlation polynomials,
623/// considering both roots and their reciprocals.
624///
625/// Arguments:
626///
627/// * `coeffs`: Polynomial coefficients
628/// * `i`: Current root index
629/// * `vri`: Current root approximation (mutable)
630/// * `converged`: Convergence flag for this root
631/// * `vrsc`: Current approximations of all roots
632///
633/// Returns:
634///
635/// Option containing tolerance value if not yet converged
636fn pbairstow_autocorr_mt_job(
637    coeffs: &[f64],
638    i: usize,
639    vri: &mut Vec2,
640    converged: &mut bool,
641    vrsc: &[Vec2],
642) -> Option<f64> {
643    let mut coeffs1 = coeffs.to_owned();
644    let degree = coeffs1.len() - 1; // assumed divided by 4
645    let mut vA = horner(&mut coeffs1, degree, vri);
646    let tol_i = vA.norm_inf();
647    if tol_i < 1e-15 {
648        *converged = true;
649        return None;
650    }
651    let mut vA1 = horner(&mut coeffs1, degree - 2, vri);
652    for (_j, vrj) in vrsc.iter().enumerate().filter(|t| t.0 != i) {
653        suppress_old(&mut vA, &mut vA1, vri, vrj);
654        let vrjn = Vector2::<f64>::new(-vrj.x_, 1.0) / vrj.y_;
655        suppress_old(&mut vA, &mut vA1, vri, &vrjn);
656    }
657    let vrin = Vector2::<f64>::new(-vri.x_, 1.0) / vri.y_;
658    suppress_old(&mut vA, &mut vA1, vri, &vrin);
659    let dt = delta(&vA, vri, &vA1); // Gauss-Seidel fashion
660    *vri -= dt;
661    Some(tol_i)
662}
663
664/// The `extract_autocorr` function extracts the quadratic function where its roots are within a unit
665/// circle.
666///
667/// x^2 - r*x - t or x^2 + (r/t) * x + (-1/t)
668/// (x - a1)(x - a2) = x^2 - (a1 + a2) x + a1 * a2
669///
670/// Arguments:
671///
672/// * `vr`: A vector containing two values, representing the coefficients of a quadratic function. The
673///   first value represents the coefficient of x^2, and the second value represents the coefficient of x.
674///
675/// Returns:
676///
677/// The function `extract_autocorr` returns a `Vec2` struct, which contains two elements `x_` and `y_`.
678///
679/// # Examples:
680///
681/// ```
682/// use ginger::rootfinding::extract_autocorr;
683/// use ginger::vector2::Vector2;
684/// use approx_eq::assert_approx_eq;
685///
686/// let vr = extract_autocorr(Vector2::new(1.0, -4.0));
687///
688/// assert_approx_eq!(vr.x_, 0.25);
689/// assert_approx_eq!(vr.y_, -0.25);
690/// ```
691pub fn extract_autocorr(vr: Vec2) -> Vec2 {
692    let Vec2 { x_: r, y_: q } = vr;
693    let hr = r / 2.0;
694    let d = hr * hr + q;
695    if d < 0.0 {
696        // complex conjugate root
697        if q < -1.0 {
698            return Vector2::<f64>::new(-r, 1.0) / q;
699        }
700    }
701    // two real roots
702    let mut a1 = hr + (if hr >= 0.0 { d.sqrt() } else { -d.sqrt() });
703    let mut a2 = -q / a1;
704
705    if a1.abs() > 1.0 {
706        if a2.abs() > 1.0 {
707            a2 = 1.0 / a2;
708        }
709        a1 = 1.0 / a1;
710        return Vector2::<f64>::new(a1 + a2, -a1 * a2);
711    }
712    if a2.abs() > 1.0 {
713        a2 = 1.0 / a2;
714        return Vector2::<f64>::new(a1 + a2, -a1 * a2);
715    }
716    // else no need to change
717    vr
718}
719
720/// Extract the two roots from a quadratic factor x^2 - r*x - q
721///
722/// Given a quadratic factor represented as Vec2 where x() = r and y() = -q
723/// (i.e., x^2 - r*x - q), return the two roots as complex numbers.
724fn roots_from_quadratic(vr: &Vec2) -> (Complex<f64>, Complex<f64>) {
725    let r = vr.x_;
726    let q = vr.y_;
727    let disc = r * r + 4.0 * q;
728    if disc >= 0.0 {
729        let sqrt_disc = disc.sqrt();
730        (
731            Complex::new((r + sqrt_disc) / 2.0, 0.0),
732            Complex::new((r - sqrt_disc) / 2.0, 0.0),
733        )
734    } else {
735        let sqrt_disc = (-disc).sqrt();
736        (
737            Complex::new(r / 2.0, sqrt_disc / 2.0),
738            Complex::new(r / 2.0, -sqrt_disc / 2.0),
739        )
740    }
741}
742
743/// Reconstruct a monic polynomial from its quadratic factors
744///
745/// Given the quadratic factors found by Bairstow's method (each representing
746/// x^2 - r*x - q), multiply them together to recover the monic polynomial
747/// coefficients. To get the original polynomial, multiply the result by the
748/// original leading coefficient.
749///
750/// Arguments:
751///
752/// * `vrs` - Quadratic factors from pbairstow_even, each as a Vec2 with x() = r, y() = q
753///
754/// Returns:
755///
756/// Monic polynomial coefficients (highest degree first)
757pub fn poly_from_quadratic_factors(vrs: &[Vec2]) -> Vec<f64> {
758    if vrs.is_empty() {
759        return vec![1.0];
760    }
761    // Extract all roots from quadratic factors and reconstruct with Leja ordering
762    let mut all_roots: Vec<Complex<f64>> = Vec::with_capacity(2 * vrs.len());
763    for vr in vrs {
764        let (r1, r2) = roots_from_quadratic(vr);
765        all_roots.push(r1);
766        all_roots.push(r2);
767    }
768    crate::aberth::poly_from_roots(&all_roots)
769}
770
771/// Reconstruct a monic polynomial from its autocorrelation quadratic factors
772///
773/// Auto-correlation (palindromic) polynomials have roots in reciprocal pairs.
774/// Each quadratic factor x^2 - r*x - q found by pbairstow_autocorr carries 2 roots.
775/// This function adds the reciprocal of each root, then reconstructs the full
776/// monic polynomial with Leja ordering for numerical accuracy.
777///
778/// Arguments:
779///
780/// * `vrs` - Quadratic factors from pbairstow_autocorr
781///
782/// Returns:
783///
784/// Monic polynomial coefficients (highest degree first)
785pub fn poly_from_autocorr_factors(vrs: &[Vec2]) -> Vec<f64> {
786    if vrs.is_empty() {
787        return vec![1.0];
788    }
789    // Each factor x^2 - r*x - q contributes 2 roots. For palindromic/autocorrelation
790    // polynomials, the reciprocal of each root is also a root. Collect all roots
791    // and their reciprocals, then reconstruct with Leja ordering.
792    let mut all_roots: Vec<Complex<f64>> = Vec::with_capacity(4 * vrs.len());
793    for vr in vrs {
794        let (r1, r2) = roots_from_quadratic(vr);
795        all_roots.push(r1);
796        all_roots.push(r2);
797        all_roots.push(1.0 / r1);
798        all_roots.push(1.0 / r2);
799    }
800    crate::aberth::poly_from_roots(&all_roots)
801}
802
803#[cfg(test)]
804mod tests {
805    use super::*;
806    use approx_eq::assert_approx_eq;
807
808    #[test]
809    fn test_options_default() {
810        let options = Options::default();
811        assert_eq!(options.max_iters, 2000);
812        assert_eq!(options.tolerance, 1e-12);
813        assert_eq!(options.tol_ind, 1e-15);
814    }
815
816    // #[test]
817    // fn test_make_adjoint() {
818    //     let vr = Vector2::new(1.0, 2.0);
819    //     let vp = Vector2::new(3.0, 4.0);
820    //     let adjoint = make_adjoint(&vr, &vp);
821
822    //     assert_eq!(adjoint.x_.x_, 4.0);
823    //     assert_eq!(adjoint.x_.y_, -3.0);
824    //     assert_eq!(adjoint.y_.x_, -6.0);
825    //     assert_eq!(adjoint.y_.y_, 11.0);
826    // }
827
828    // #[test]
829    // fn test_make_inverse() {
830    //     let vr = Vector2::new(1.0, 2.0);
831    //     let vp = Vector2::new(3.0, 4.0);
832    //     let inverse = make_inverse(&vr, &vp);
833
834    //     // Verify inverse by multiplying with original matrix
835    //     let original = Matrix2::new(vr, Vector2::new(vp.x_, 0.0));
836    //     let product = original * inverse;
837    //     assert_approx_eq!(product.x_.x_, 1.0);
838    //     assert_approx_eq!(product.x_.y_, 0.0);
839    //     assert_approx_eq!(product.y_.x_, 0.0);
840    //     assert_approx_eq!(product.y_.y_, 1.0);
841    // }
842
843    #[test]
844    fn test_delta() {
845        let vA = Vector2::new(1.0, 2.0);
846        let vr = Vector2::new(-2.0, 0.0);
847        let vp = Vector2::new(4.0, 5.0);
848        let delta = delta(&vA, &vr, &vp);
849
850        assert_approx_eq!(delta.x_, 0.2);
851        assert_approx_eq!(delta.y_, 0.4);
852    }
853
854    #[test]
855    fn test_suppress_old() {
856        let mut vA = Vector2::new(3.0, 3.0);
857        let mut vA1 = Vector2::new(1.0, 2.0);
858        let vri = Vector2::new(-2.0, 0.0);
859        let vrj = Vector2::new(4.0, 5.0);
860
861        suppress_old(&mut vA, &mut vA1, &vri, &vrj);
862        let dr = delta(&vA, &vri, &vA1);
863        assert_approx_eq!(dr.x_, -16.780821917808325);
864        assert_approx_eq!(dr.y_, 1.4383561643835612);
865    }
866
867    #[test]
868    fn test_suppress() {
869        let vA = Vector2::new(3.0, 3.0);
870        let vA1 = Vector2::new(1.0, 2.0);
871        let vri = Vector2::new(-2.0, 0.0);
872        let vrj = Vector2::new(4.0, 5.0);
873
874        let (va, va1) = suppress(&vA, &vA1, &vri, &vrj);
875        let dr = delta(&va, &vri, &va1);
876        assert_approx_eq!(dr.x_, -16.780821917808325);
877        assert_approx_eq!(dr.y_, 1.4383561643835612);
878    }
879
880    #[test]
881    fn test_horner_eval() {
882        let coeffs = vec![10.0, 34.0, 75.0, 94.0, 150.0, 94.0, 75.0, 34.0, 10.0];
883        let result = horner_eval_f(&coeffs, 2.0);
884
885        assert_eq!(result, 18250.0);
886    }
887
888    #[test]
889    fn test_horner() {
890        let mut coeffs = vec![10.0, 34.0, 75.0, 94.0, 150.0, 94.0, 75.0, 34.0, 10.0];
891        let vr = Vector2::new(-1.0, -2.0);
892        let result = horner(&mut coeffs, 8, &vr);
893
894        assert_approx_eq!(result.x_, 114.0);
895        assert_approx_eq!(result.y_, 134.0);
896        assert_eq!(coeffs[3], 15.0);
897    }
898
899    #[test]
900    fn test_initial_guess() {
901        let coeffs = vec![10.0, 34.0, 75.0, 94.0, 150.0, 94.0, 75.0, 34.0, 10.0];
902        let guesses = initial_guess(&coeffs);
903
904        assert_eq!(guesses.len(), 4);
905        // Verify the first guess is reasonable
906        assert!(guesses[0].x_.abs() > 0.0);
907        assert!(guesses[0].y_.abs() > 0.0);
908    }
909
910    #[test]
911    fn test_pbairstow_even() {
912        let coeffs = vec![10.0, 34.0, 75.0, 94.0, 150.0, 94.0, 75.0, 34.0, 10.0];
913        let mut vrs = initial_guess(&coeffs);
914        let options = Options::default();
915
916        let (niter, found) = pbairstow_even(&coeffs, &mut vrs, &options);
917
918        assert!(niter > 0);
919        assert!(found);
920        // Verify at least one root is close to actual root
921        let mut has_root = false;
922        for vr in vrs {
923            let val = horner(&mut coeffs.clone(), coeffs.len() - 1, &vr);
924            if val.norm_inf() < options.tolerance {
925                has_root = true;
926                break;
927            }
928        }
929        assert!(has_root);
930    }
931
932    #[test]
933    fn test_initial_autocorr() {
934        let coeffs = vec![10.0, 34.0, 75.0, 94.0, 150.0, 94.0, 75.0, 34.0, 10.0];
935        let guesses = initial_autocorr(&coeffs);
936
937        assert_eq!(guesses.len(), 2);
938        // Verify the first guess is reasonable
939        assert!(guesses[0].x_.abs() > 0.0);
940        assert!(guesses[0].y_.abs() > 0.0);
941    }
942
943    #[test]
944    fn test_extract_autocorr() {
945        let vr = Vector2::new(1.0, -4.0);
946        let result = extract_autocorr(vr);
947
948        assert_approx_eq!(result.x_, 0.25);
949        assert_approx_eq!(result.y_, -0.25);
950    }
951
952    #[test]
953    fn test_pbairstow_autocorr() {
954        let coeffs = vec![10.0, 34.0, 75.0, 94.0, 150.0, 94.0, 75.0, 34.0, 10.0];
955        let mut vrs = initial_autocorr(&coeffs);
956        let options = Options::default();
957
958        let (niter, found) = pbairstow_autocorr(&coeffs, &mut vrs, &options);
959
960        assert!(niter > 0);
961        assert!(found);
962        // Verify at least one root is close to actual root
963        let mut has_root = false;
964        for vr in vrs {
965            let val = horner(&mut coeffs.clone(), coeffs.len() - 1, &vr);
966            if val.norm_inf() < options.tolerance {
967                has_root = true;
968                break;
969            }
970        }
971        assert!(has_root);
972    }
973
974    #[test]
975    fn test_delta1() {
976        let vA = Vector2::new(1.0, 2.0);
977        let vr = Vector2::new(-2.0, -0.0);
978        let vp = Vector2::new(4.0, -5.0);
979        let delta = delta1(&vA, &vr, &vp);
980
981        assert_approx_eq!(delta.x_, 0.2);
982        assert_approx_eq!(delta.y_, 0.4);
983    }
984
985    #[test]
986    fn test_roots_from_quadratic_real() {
987        // x^2 - 3x + 2 = (x-1)(x-2) -> r=3, q=-2
988        let vr = Vec2::new(3.0, -2.0);
989        let (r1, r2) = roots_from_quadratic(&vr);
990        assert!((r1.re - 2.0).abs() < 1e-12);
991        assert!((r2.re - 1.0).abs() < 1e-12);
992        assert!(r1.im.abs() < 1e-12);
993        assert!(r2.im.abs() < 1e-12);
994    }
995
996    #[test]
997    fn test_roots_from_quadratic_complex() {
998        // x^2 + 1 = 0 -> r=0, q=-1 (since x^2 - r*x - q = 0) -> roots: i, -i
999        let vr = Vec2::new(0.0, -1.0);
1000        let (r1, r2) = roots_from_quadratic(&vr);
1001        assert!((r1.re).abs() < 1e-12);
1002        assert!((r1.im - 1.0).abs() < 1e-12);
1003        assert!((r2.re).abs() < 1e-12);
1004        assert!((r2.im + 1.0).abs() < 1e-12);
1005    }
1006
1007    #[test]
1008    fn test_poly_from_quadratic_factors() {
1009        // (x-1)(x-2) = x^2 - 3x + 2 -> r=3, q=-2
1010        let vrs = vec![Vec2::new(3.0, -2.0)];
1011        let coeffs = poly_from_quadratic_factors(&vrs);
1012        assert_eq!(coeffs.len(), 3);
1013        assert!((coeffs[0] - 1.0).abs() < 1e-12);
1014        assert!((coeffs[1] + 3.0).abs() < 1e-12);
1015        assert!((coeffs[2] - 2.0).abs() < 1e-12);
1016    }
1017
1018    #[test]
1019    fn test_poly_from_autocorr_factors() {
1020        let vrs = vec![Vec2::new(3.0, -2.0)];
1021        let coeffs = poly_from_autocorr_factors(&vrs);
1022        // With reciprocals: roots are 2, 1, 0.5, 1.0
1023        // polynomial = (x-2)(x-1)(x-0.5)(x-1) = ...
1024        assert_eq!(coeffs.len(), 5);
1025        assert!((coeffs[0] - 1.0).abs() < 1e-12);
1026    }
1027}