ginger/
rootfinding.rs

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