oxiz_math/polynomial/
root_isolation.rs1#[allow(unused_imports)]
10use crate::prelude::*;
11use num_bigint::BigInt;
12use num_rational::BigRational;
13use num_traits::Zero;
14
15pub struct RootIsolator {
17 precision: BigRational,
19 max_iterations: usize,
21 stats: IsolationStats,
23}
24
25#[derive(Debug, Clone)]
27pub struct RootInterval {
28 pub left: BigRational,
30 pub right: BigRational,
32 pub left_closed: bool,
34 pub right_closed: bool,
36 pub multiplicity: usize,
38}
39
40#[derive(Debug, Clone, Default)]
42pub struct IsolationStats {
43 pub sturm_evaluations: usize,
45 pub descartes_tests: usize,
47 pub bisection_steps: usize,
49 pub intervals_generated: usize,
51}
52
53impl RootIsolator {
54 pub fn new(precision: BigRational) -> Self {
56 Self {
57 precision,
58 max_iterations: 1000,
59 stats: IsolationStats::default(),
60 }
61 }
62
63 pub fn isolate_roots(
65 &mut self,
66 poly: &[BigRational],
67 interval: (BigRational, BigRational),
68 ) -> Vec<RootInterval> {
69 let poly = Self::normalize_polynomial(poly);
71
72 if poly.len() <= 1 {
73 return vec![];
74 }
75
76 let sturm_seq = self.build_sturm_sequence(&poly);
78
79 let (left, right) = interval;
81 let left_variations = self.count_sign_variations(&sturm_seq, &left);
82 let right_variations = self.count_sign_variations(&sturm_seq, &right);
83 self.stats.sturm_evaluations += 2;
84
85 let num_roots = left_variations - right_variations;
86
87 if num_roots == 0 {
88 return vec![];
89 } else if num_roots == 1 {
90 let refined = self.refine_root_interval(&poly, left, right);
92 return vec![refined];
93 }
94
95 self.bisect_and_isolate(&poly, &sturm_seq, left, right)
97 }
98
99 fn build_sturm_sequence(&self, poly: &[BigRational]) -> Vec<Vec<BigRational>> {
101 let mut sequence = Vec::new();
102
103 sequence.push(poly.to_vec());
105
106 let derivative = Self::derivative(poly);
108 if derivative.is_empty() {
109 return sequence;
110 }
111 sequence.push(derivative);
112
113 loop {
115 let len = sequence.len();
116 let f_prev = &sequence[len - 2];
117 let f_curr = &sequence[len - 1];
118
119 let remainder = Self::polynomial_remainder(f_prev, f_curr);
120
121 if remainder.is_empty() || Self::is_zero_poly(&remainder) {
122 break;
123 }
124
125 let neg_remainder: Vec<BigRational> = remainder.iter().map(|c| -c.clone()).collect();
127
128 sequence.push(neg_remainder);
129 }
130
131 sequence
132 }
133
134 fn count_sign_variations(&self, sturm_seq: &[Vec<BigRational>], x: &BigRational) -> usize {
136 let mut signs = Vec::new();
137
138 for poly in sturm_seq {
139 let value = Self::evaluate(poly, x);
140 if !value.is_zero() {
141 signs.push(value > BigRational::zero());
142 }
143 }
144
145 let mut variations = 0;
147 for i in 0..signs.len().saturating_sub(1) {
148 if signs[i] != signs[i + 1] {
149 variations += 1;
150 }
151 }
152
153 variations
154 }
155
156 fn bisect_and_isolate(
158 &mut self,
159 poly: &[BigRational],
160 sturm_seq: &[Vec<BigRational>],
161 left: BigRational,
162 right: BigRational,
163 ) -> Vec<RootInterval> {
164 self.stats.bisection_steps += 1;
165
166 let mid = (&left + &right) / BigRational::from_integer(BigInt::from(2));
167
168 let left_vars = self.count_sign_variations(sturm_seq, &left);
169 let mid_vars = self.count_sign_variations(sturm_seq, &mid);
170 let right_vars = self.count_sign_variations(sturm_seq, &right);
171 self.stats.sturm_evaluations += 3;
172
173 let mut intervals = Vec::new();
174
175 let left_roots = left_vars - mid_vars;
177 if left_roots > 0 {
178 intervals.extend(self.isolate_roots(poly, (left.clone(), mid.clone())));
179 }
180
181 let right_roots = mid_vars - right_vars;
183 if right_roots > 0 {
184 intervals.extend(self.isolate_roots(poly, (mid, right)));
185 }
186
187 intervals
188 }
189
190 fn refine_root_interval(
192 &mut self,
193 poly: &[BigRational],
194 mut left: BigRational,
195 mut right: BigRational,
196 ) -> RootInterval {
197 let mut iterations = 0;
198
199 while &right - &left > self.precision && iterations < self.max_iterations {
200 let mid = (&left + &right) / BigRational::from_integer(BigInt::from(2));
201 let mid_val = Self::evaluate(poly, &mid);
202
203 if mid_val.is_zero() {
204 return RootInterval {
205 left: mid.clone(),
206 right: mid,
207 left_closed: true,
208 right_closed: true,
209 multiplicity: 1,
210 };
211 }
212
213 let left_val = Self::evaluate(poly, &left);
214
215 if (left_val > BigRational::zero()) == (mid_val > BigRational::zero()) {
216 left = mid;
217 } else {
218 right = mid;
219 }
220
221 iterations += 1;
222 }
223
224 self.stats.intervals_generated += 1;
225
226 RootInterval {
227 left,
228 right,
229 left_closed: false,
230 right_closed: false,
231 multiplicity: 1,
232 }
233 }
234
235 fn evaluate(poly: &[BigRational], x: &BigRational) -> BigRational {
237 if poly.is_empty() {
238 return BigRational::zero();
239 }
240
241 let mut result = poly[0].clone();
242 for coeff in &poly[1..] {
243 result = result * x + coeff;
244 }
245 result
246 }
247
248 fn derivative(poly: &[BigRational]) -> Vec<BigRational> {
250 if poly.len() <= 1 {
251 return vec![];
252 }
253
254 let mut deriv = Vec::with_capacity(poly.len() - 1);
255 for (i, coeff) in poly.iter().enumerate().take(poly.len() - 1) {
256 let degree = (poly.len() - 1 - i) as i64;
257 deriv.push(coeff * BigRational::from_integer(BigInt::from(degree)));
258 }
259 deriv
260 }
261
262 fn polynomial_remainder(dividend: &[BigRational], divisor: &[BigRational]) -> Vec<BigRational> {
264 if divisor.is_empty() || Self::is_zero_poly(divisor) {
265 return vec![];
266 }
267
268 let mut remainder = dividend.to_vec();
269
270 while remainder.len() >= divisor.len() && !Self::is_zero_poly(&remainder) {
271 let lead_div = &divisor[0];
272 let lead_rem = &remainder[0];
273
274 if lead_div.is_zero() {
275 break;
276 }
277
278 let quotient_coeff = lead_rem / lead_div;
279
280 for i in 0..divisor.len() {
281 remainder[i] = &remainder[i] - "ient_coeff * &divisor[i];
282 }
283
284 remainder.remove(0);
285 }
286
287 remainder
288 }
289
290 fn normalize_polynomial(poly: &[BigRational]) -> Vec<BigRational> {
292 let mut result = poly.to_vec();
293 while !result.is_empty() && result[0].is_zero() {
294 result.remove(0);
295 }
296 result
297 }
298
299 fn is_zero_poly(poly: &[BigRational]) -> bool {
301 poly.iter().all(|c| c.is_zero())
302 }
303
304 pub fn stats(&self) -> &IsolationStats {
306 &self.stats
307 }
308}
309
310#[cfg(test)]
311mod tests {
312 use super::*;
313 use num_traits::{One, Zero};
314
315 #[test]
316 fn test_root_isolator() {
317 let precision = BigRational::new(BigInt::from(1), BigInt::from(1000));
318 let isolator = RootIsolator::new(precision);
319
320 assert_eq!(isolator.stats.sturm_evaluations, 0);
321 }
322
323 #[test]
324 fn test_sturm_sequence() {
325 let precision = BigRational::new(BigInt::from(1), BigInt::from(100));
326 let isolator = RootIsolator::new(precision);
327
328 let poly = vec![
330 BigRational::one(),
331 BigRational::zero(),
332 BigRational::from_integer(BigInt::from(-2)),
333 ];
334
335 let sturm = isolator.build_sturm_sequence(&poly);
336 assert!(!sturm.is_empty());
337 }
338}