1use crate::polynomial::root_counting::Polynomial;
18#[allow(unused_imports)]
19use crate::prelude::*;
20use num_bigint::BigInt;
21use num_rational::BigRational;
22use num_traits::{Signed, Zero};
23
24#[derive(Debug, Clone)]
26pub struct IsolatingInterval {
27 pub lower: BigRational,
29 pub upper: BigRational,
31 pub sign_lower: i32,
33 pub sign_upper: i32,
35}
36
37impl IsolatingInterval {
38 pub fn new(lower: BigRational, upper: BigRational, sign_lower: i32, sign_upper: i32) -> Self {
40 Self {
41 lower,
42 upper,
43 sign_lower,
44 sign_upper,
45 }
46 }
47
48 pub fn width(&self) -> BigRational {
50 &self.upper - &self.lower
51 }
52
53 pub fn midpoint(&self) -> BigRational {
55 (&self.lower + &self.upper) / BigRational::from(BigInt::from(2))
56 }
57}
58
59#[derive(Debug, Clone)]
61pub struct IsolationConfig {
62 pub use_sturm: bool,
64 pub use_descartes: bool,
66 pub max_iterations: usize,
68 pub precision: BigRational,
70}
71
72impl Default for IsolationConfig {
73 fn default() -> Self {
74 Self {
75 use_sturm: true,
76 use_descartes: true,
77 max_iterations: 1000,
78 precision: BigRational::new(BigInt::from(1), BigInt::from(1_000_000)),
79 }
80 }
81}
82
83#[derive(Debug, Clone, Default)]
85pub struct IsolationStats {
86 pub sturm_computations: u64,
88 pub bisection_steps: u64,
90 pub sign_evaluations: u64,
92 pub roots_isolated: u64,
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98pub enum IntervalRefinement {
99 Bisection,
101 Newton,
103 Hybrid,
105}
106
107pub struct RootIsolator {
109 poly: Polynomial,
111 config: IsolationConfig,
113 stats: IsolationStats,
115 sturm_sequence: Option<Vec<Polynomial>>,
117}
118
119impl RootIsolator {
120 pub fn new(poly: Polynomial, config: IsolationConfig) -> Self {
122 Self {
123 poly,
124 config,
125 stats: IsolationStats::default(),
126 sturm_sequence: None,
127 }
128 }
129
130 pub fn default_config(poly: Polynomial) -> Self {
132 Self::new(poly, IsolationConfig::default())
133 }
134
135 pub fn isolate_roots(&mut self) -> Vec<IsolatingInterval> {
137 let is_zero = self.poly.degree() == 0 && self.poly.coeffs.first().is_none_or(Zero::is_zero);
139 if is_zero || self.poly.degree() == 0 {
140 return Vec::new();
141 }
142
143 if self.config.use_sturm {
145 self.compute_sturm_sequence();
146 }
147
148 let (lower_bound, upper_bound) = self.root_bounds();
150
151 self.isolate_in_interval(lower_bound, upper_bound)
153 }
154
155 fn compute_sturm_sequence(&mut self) {
157 let mut seq = vec![self.poly.clone(), self.poly.derivative()];
158
159 loop {
160 let len = seq.len();
161 if len < 2 {
162 break;
163 }
164
165 let last = &seq[len - 1];
166 if last.degree() == 0 {
169 break;
170 }
171
172 let remainder = seq[len - 2].remainder(&seq[len - 1]);
173
174 let negated = Polynomial::new(remainder.coeffs.iter().map(|c| -c).collect());
176
177 if negated.degree() == 0 && negated.coeffs.first().is_none_or(Zero::is_zero) {
178 break;
179 }
180
181 seq.push(negated);
182
183 if seq.len() > 1000 {
185 break;
186 }
187 }
188
189 self.sturm_sequence = Some(seq);
190 self.stats.sturm_computations += 1;
191 }
192
193 fn count_roots(&mut self, lower: &BigRational, upper: &BigRational) -> usize {
195 if self.sturm_sequence.is_none() {
196 self.compute_sturm_sequence();
197 }
198
199 let seq: Vec<Polynomial> = self.sturm_sequence.as_ref().cloned().unwrap_or_default();
201
202 let sign_changes_lower = self.count_sign_changes(&seq, lower);
203 let sign_changes_upper = self.count_sign_changes(&seq, upper);
204
205 (sign_changes_lower as isize - sign_changes_upper as isize).unsigned_abs()
206 }
207
208 fn count_sign_changes(&mut self, seq: &[Polynomial], point: &BigRational) -> usize {
210 self.stats.sign_evaluations += seq.len() as u64;
211
212 let signs: Vec<i32> = seq
213 .iter()
214 .map(|p| {
215 let val = p.eval(point);
216 if val.is_positive() {
217 1
218 } else if val.is_negative() {
219 -1
220 } else {
221 0
222 }
223 })
224 .filter(|&s| s != 0) .collect();
226
227 let mut changes = 0;
229 for i in 0..signs.len().saturating_sub(1) {
230 if signs[i] != signs[i + 1] {
231 changes += 1;
232 }
233 }
234
235 changes
236 }
237
238 pub(crate) fn root_bounds(&self) -> (BigRational, BigRational) {
242 let coeffs = &self.poly.coeffs;
243 if coeffs.is_empty() {
244 return (BigRational::zero(), BigRational::zero());
245 }
246
247 let leading = match coeffs.last() {
248 Some(c) => c.abs(),
249 None => return (BigRational::zero(), BigRational::zero()),
250 };
251
252 if leading.is_zero() {
253 return (BigRational::zero(), BigRational::zero());
254 }
255
256 let mut max_ratio = BigRational::zero();
257 for coeff in coeffs.iter().take(coeffs.len() - 1) {
258 let ratio = coeff.abs() / &leading;
259 if ratio > max_ratio {
260 max_ratio = ratio;
261 }
262 }
263
264 let bound = BigRational::from(BigInt::from(1)) + max_ratio;
265
266 (-bound.clone(), bound)
267 }
268
269 fn isolate_in_interval(
271 &mut self,
272 lower: BigRational,
273 upper: BigRational,
274 ) -> Vec<IsolatingInterval> {
275 let num_roots = self.count_roots(&lower, &upper);
276
277 if num_roots == 0 {
278 return Vec::new();
279 }
280
281 if num_roots == 1 {
282 let sign_lower = self.eval_sign(&lower);
284 let sign_upper = self.eval_sign(&upper);
285
286 self.stats.roots_isolated += 1;
287
288 return vec![IsolatingInterval::new(lower, upper, sign_lower, sign_upper)];
289 }
290
291 self.stats.bisection_steps += 1;
293
294 let mid = (&lower + &upper) / BigRational::from(BigInt::from(2));
295
296 let mut left = self.isolate_in_interval(lower, mid.clone());
297 let mut right = self.isolate_in_interval(mid, upper);
298
299 left.append(&mut right);
300 left
301 }
302
303 fn eval_sign(&mut self, point: &BigRational) -> i32 {
305 self.stats.sign_evaluations += 1;
306
307 let val = self.poly.eval(point);
308
309 if val.is_positive() {
310 1
311 } else if val.is_negative() {
312 -1
313 } else {
314 0
315 }
316 }
317
318 pub fn refine_interval(
320 &mut self,
321 interval: &IsolatingInterval,
322 method: IntervalRefinement,
323 ) -> IsolatingInterval {
324 match method {
325 IntervalRefinement::Bisection => self.refine_bisection(interval),
326 IntervalRefinement::Newton => self.refine_newton(interval),
327 IntervalRefinement::Hybrid => {
328 let newton_result = self.refine_newton(interval);
330 if newton_result.width()
331 < interval.width() * BigRational::new(BigInt::from(9), BigInt::from(10))
332 {
333 newton_result
334 } else {
335 self.refine_bisection(interval)
336 }
337 }
338 }
339 }
340
341 fn refine_bisection(&mut self, interval: &IsolatingInterval) -> IsolatingInterval {
343 self.stats.bisection_steps += 1;
344
345 let mid = interval.midpoint();
346 let sign_mid = self.eval_sign(&mid);
347
348 if sign_mid == 0 {
349 return IsolatingInterval::new(mid.clone(), mid, 0, 0);
351 }
352
353 if sign_mid != interval.sign_lower {
354 IsolatingInterval::new(interval.lower.clone(), mid, interval.sign_lower, sign_mid)
356 } else {
357 IsolatingInterval::new(mid, interval.upper.clone(), sign_mid, interval.sign_upper)
359 }
360 }
361
362 fn refine_newton(&mut self, interval: &IsolatingInterval) -> IsolatingInterval {
364 let mid = interval.midpoint();
365 let f_mid = self.poly.eval(&mid);
366 let df_mid = self.poly.derivative().eval(&mid);
367
368 if df_mid.is_zero() {
369 return self.refine_bisection(interval);
371 }
372
373 let x_new = &mid - (&f_mid / &df_mid);
375
376 if x_new > interval.lower && x_new < interval.upper {
378 let sign_new = self.eval_sign(&x_new);
379
380 if sign_new == 0 {
381 return IsolatingInterval::new(x_new.clone(), x_new, 0, 0);
382 }
383
384 if sign_new != interval.sign_lower {
385 IsolatingInterval::new(interval.lower.clone(), x_new, interval.sign_lower, sign_new)
386 } else {
387 IsolatingInterval::new(x_new, interval.upper.clone(), sign_new, interval.sign_upper)
388 }
389 } else {
390 self.refine_bisection(interval)
392 }
393 }
394
395 pub fn stats(&self) -> &IsolationStats {
397 &self.stats
398 }
399
400 pub fn reset_stats(&mut self) {
402 self.stats = IsolationStats::default();
403 }
404}
405
406#[cfg(test)]
407mod tests {
408 use super::*;
409
410 fn rat(n: i64) -> BigRational {
411 BigRational::from(BigInt::from(n))
412 }
413
414 #[test]
415 fn test_isolator_creation() {
416 let poly = Polynomial::new(vec![rat(-2), rat(0), rat(1)]);
417
418 let isolator = RootIsolator::default_config(poly);
419 assert_eq!(isolator.stats().roots_isolated, 0);
420 }
421
422 #[test]
423 fn test_root_bounds() {
424 let poly = Polynomial::new(vec![rat(-6), rat(5), rat(1)]); let isolator = RootIsolator::default_config(poly);
427 let (lower, upper) = isolator.root_bounds();
428
429 assert!(lower < rat(-6));
431 assert!(upper > rat(1));
432 }
433
434 #[test]
435 fn test_isolate_linear() {
436 let poly = Polynomial::new(vec![rat(-5), rat(1)]); let mut isolator = RootIsolator::default_config(poly);
439 let intervals = isolator.isolate_roots();
440
441 assert_eq!(intervals.len(), 1);
442 assert!(intervals[0].lower <= rat(5));
443 assert!(intervals[0].upper >= rat(5));
444 }
445
446 #[test]
447 fn test_refine_bisection() {
448 let poly = Polynomial::new(vec![rat(-2), rat(0), rat(1)]); let mut isolator = RootIsolator::default_config(poly);
451
452 let interval = IsolatingInterval::new(rat(1), rat(2), -1, 1);
453
454 let refined = isolator.refine_interval(&interval, IntervalRefinement::Bisection);
455
456 assert!(refined.width() < interval.width());
457 }
458
459 #[test]
460 fn test_stats() {
461 let poly = Polynomial::new(vec![rat(-5), rat(1)]);
462
463 let mut isolator = RootIsolator::default_config(poly);
464 isolator.isolate_roots();
465
466 assert!(isolator.stats().roots_isolated > 0);
467 assert!(isolator.stats().sign_evaluations > 0);
468 }
469}