rust_poly/poly/roots/single_root/
halley.rs1use super::{
2 line_search_accelerate, line_search_decelerate, multiplicity_lagouanelle, LazyDerivatives,
3};
4use crate::{
5 num::{Complex, One, Zero},
6 poly::roots,
7 roots::initial_guess::initial_guess_smallest,
8 util::{
9 self,
10 complex::c_from_f64,
11 doc_macros::{errors_no_converge, panic_t_from_f64, panic_t_from_int},
12 },
13 Poly, RealScalar,
14};
15
16#[doc = errors_no_converge!()]
24#[doc = panic_t_from_f64!()]
27#[doc = panic_t_from_int!(r"usize")]
28#[allow(clippy::items_after_statements)]
29#[allow(clippy::type_complexity)]
31#[allow(clippy::similar_names)]
32pub fn halley<T: RealScalar>(
33 poly: &Poly<T>,
34 epsilon: T,
35 max_iter: Option<usize>,
36 initial_guess: Option<Complex<T>>,
37) -> std::result::Result<(Vec<Complex<T>>, u128), roots::Error<Vec<Complex<T>>>> {
38 let mut eval_counter = 0;
39 let mut guess = initial_guess.unwrap_or_else(|| initial_guess_smallest(poly));
40 let mut guess_old = guess.clone();
41 let mut guess_old_old = guess.clone();
42 let mut guess_delta_old = Complex::one();
43
44 const CYCLE_COUNT_THRESHOLD: usize = 17;
47 let mut cycle_counter = 0;
48 let mut best_guess = guess.clone();
49 let mut best_px_norm = guess.clone().norm_sqr();
50
51 let mut diffs = LazyDerivatives::new(poly);
52
53 for i in util::iterator::saturating_counter() {
55 let px = poly.eval(guess.clone());
56 log::trace!("{{current_guess: {guess}, error: {}}}", px.norm_sqr());
57
58 if px.norm_sqr() <= epsilon {
60 return Ok((vec![guess], eval_counter));
61 }
62
63 if i > 3
65 && super::stopping_criterion_garwick(guess.clone(), guess_old.clone(), guess_old_old)
66 {
67 return Ok((vec![guess], eval_counter));
68 }
69
70 if max_iter.is_some_and(|max| i >= max) {
72 return Err(roots::Error::NoConverge(vec![guess]));
73 }
74
75 if px.norm_sqr() >= best_px_norm {
77 cycle_counter += 1;
78 if cycle_counter > CYCLE_COUNT_THRESHOLD {
79 const ROTATION_RADIANS: f64 = 0.925_024_5;
81 const SCALE: f64 = 5.0;
82
83 cycle_counter = 0;
84 log::trace!("cycle detected, backing off {{current_guess: {guess}, best_guess: {best_guess}}}");
85
86 let backoff = c_from_f64(Complex::from_polar(SCALE, ROTATION_RADIANS));
89 guess = best_guess.clone() - guess_delta_old.clone() * backoff;
91 }
92 } else {
93 cycle_counter = 0;
94 best_guess = guess.clone();
95 best_px_norm = px.norm_sqr();
96 }
97
98 let pdx = diffs.get_nth_derivative(1).eval(guess.clone());
99 let pddx = diffs.get_nth_derivative(2).eval(guess.clone());
100 eval_counter += 2;
101 let denom = (pdx.clone() * pdx.clone()).scale(T::from_u8(2).expect("overflow"))
102 - px.clone() * pddx.clone();
103
104 let guess_delta = if denom.is_zero() || pdx.is_zero() {
105 const ROTATION_RADIANS: f64 = 0.925_024_5;
107 const SCALE: f64 = 5.0;
108
109 log::trace!("local minimum, backing off");
110
111 let backoff = c_from_f64(Complex::from_polar(SCALE, ROTATION_RADIANS));
114 <Complex<T> as std::ops::Mul>::mul(guess_delta_old.clone(), backoff)
115 } else {
116 let m = multiplicity_lagouanelle(px.clone(), pdx.clone(), pddx);
117 (m + Complex::one()) * (px.clone() * pdx) / denom
118 };
119
120 const EXPLODE_THRESHOLD: f64 = 5.0;
121 let guess_delta = if guess_delta.norm_sqr()
122 > guess_delta_old.norm_sqr() * T::from_f64(EXPLODE_THRESHOLD).expect("overflow")
123 {
124 const ROTATION_RADIANS: f64 = 0.925_024_5;
126 const SCALE: f64 = 5.0;
127
128 log::trace!("exploding gradient, backing off");
129
130 let backoff = c_from_f64(Complex::from_polar(SCALE, ROTATION_RADIANS))
133 .scale(guess_delta_old.norm_sqr() / guess_delta.norm_sqr());
134 guess_delta * backoff
135 } else {
136 guess_delta
137 };
138
139 eval_counter += 1;
140 let guess_new =
141 if poly.eval(guess.clone() - guess_delta.clone()).norm_sqr() >= px.norm_sqr() {
142 log::trace!("overshooting, shortening step");
143 let res = line_search_decelerate(poly, guess.clone(), guess_delta.clone());
144 eval_counter += res.1;
145 res.0
146 } else {
147 log::trace!("undershooting, lengthening step");
148 let res = line_search_accelerate(poly, guess.clone(), guess_delta.clone());
149 eval_counter += res.1;
150 res.0
151 };
152
153 guess_delta_old = guess_delta;
154 guess_old_old = guess_old;
155 guess_old = guess;
156 guess = guess_new;
157 }
158 unreachable!()
159}
160
161#[cfg(test)]
162mod test {
163 use crate::{num::One, roots::halley_deflate, util::__testing::check_roots, Poly64};
164
165 #[test]
166 pub fn degree_0() {
167 let mut p = Poly64::one();
168 let roots = halley_deflate(&mut p, Some(1E-14), Some(100), &[]).unwrap();
169 assert!(roots.is_empty());
170 assert!(p.is_one());
171 }
172
173 #[test]
174 fn degree_1() {
175 let roots_expected = vec![complex!(1.0)];
176 let mut p = crate::Poly::from_roots(&roots_expected);
177 let roots = halley_deflate(&mut p, Some(1E-14), Some(100), &[]).unwrap();
178 assert!(check_roots(roots, roots_expected, 1E-12));
179 }
180
181 #[test]
182 fn degree_2() {
183 let roots_expected = vec![complex!(1.0), complex!(2.0)];
184 let mut p = crate::Poly::from_roots(&roots_expected);
185 let roots = halley_deflate(&mut p, Some(1E-14), Some(100), &[]).unwrap();
186 assert!(check_roots(roots, roots_expected, 1E-12));
187 }
188
189 #[test]
190 fn degree_3() {
191 let roots_expected = vec![complex!(1.0), complex!(2.0), complex!(3.0)];
192 let mut p = crate::Poly::from_roots(&roots_expected);
193 let roots = halley_deflate(&mut p, Some(1E-14), Some(100), &[]).unwrap();
194 assert!(check_roots(roots, roots_expected, 1E-7));
195 }
196
197 #[test]
198 fn degree_3_complex() {
199 let roots_expected = vec![complex!(1.0), complex!(0.0, 1.0), complex!(0.0, -1.0)];
200 let mut p = crate::Poly::from_roots(&roots_expected);
201 let roots = halley_deflate(&mut p, Some(1E-14), Some(100), &[]).unwrap();
202 assert!(check_roots(roots, roots_expected, 1E-7));
203 }
204
205 #[test]
206 fn degree_5_multiplicity_3() {
207 let roots_expected = vec![
208 complex!(1.0),
209 complex!(2.0),
210 complex!(2.0),
211 complex!(2.0),
212 complex!(3.0),
213 ];
214 let mut p = crate::Poly::from_roots(&roots_expected);
215 let roots = halley_deflate(&mut p, Some(1E-14), Some(100), &[]).unwrap();
216 assert!(
217 check_roots(roots.clone(), roots_expected, 1E-3),
218 "{roots:?}"
219 );
220 }
221
222 #[test]
223 fn degree_5_2_zeros() {
224 let roots_expected = vec![
225 complex!(0.0),
226 complex!(0.0),
227 complex!(1.0),
228 complex!(2.0),
229 complex!(3.0),
230 ];
231 let mut p = crate::Poly::from_roots(&roots_expected);
232 let roots = halley_deflate(&mut p, Some(1E-14), Some(100), &[]).unwrap();
233 assert!(check_roots(roots, roots_expected, 1E-7));
234 }
235}