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;
86 let (left, right) = if left <= right {
87 (left, right)
88 } else {
89 (right, left)
90 };
91 let left_variations = self.count_sign_variations(&sturm_seq, &left);
92 let right_variations = self.count_sign_variations(&sturm_seq, &right);
93 self.stats.sturm_evaluations += 2;
94
95 let num_roots = left_variations.saturating_sub(right_variations);
99
100 if num_roots == 0 {
101 return vec![];
102 } else if num_roots == 1 {
103 let refined = self.refine_root_interval(&poly, left, right);
105 return vec![refined];
106 }
107
108 self.bisect_and_isolate(&poly, &sturm_seq, left, right)
110 }
111
112 fn build_sturm_sequence(&self, poly: &[BigRational]) -> Vec<Vec<BigRational>> {
114 let mut sequence = Vec::new();
115
116 sequence.push(poly.to_vec());
118
119 let derivative = Self::derivative(poly);
121 if derivative.is_empty() {
122 return sequence;
123 }
124 sequence.push(derivative);
125
126 loop {
128 let len = sequence.len();
129 let f_prev = &sequence[len - 2];
130 let f_curr = &sequence[len - 1];
131
132 let remainder = Self::polynomial_remainder(f_prev, f_curr);
133
134 if remainder.is_empty() || Self::is_zero_poly(&remainder) {
135 break;
136 }
137
138 let neg_remainder: Vec<BigRational> = remainder.iter().map(|c| -c.clone()).collect();
140
141 sequence.push(neg_remainder);
142 }
143
144 sequence
145 }
146
147 fn count_sign_variations(&self, sturm_seq: &[Vec<BigRational>], x: &BigRational) -> usize {
149 let mut signs = Vec::new();
150
151 for poly in sturm_seq {
152 let value = Self::evaluate(poly, x);
153 if !value.is_zero() {
154 signs.push(value > BigRational::zero());
155 }
156 }
157
158 let mut variations = 0;
160 for i in 0..signs.len().saturating_sub(1) {
161 if signs[i] != signs[i + 1] {
162 variations += 1;
163 }
164 }
165
166 variations
167 }
168
169 fn bisect_and_isolate(
171 &mut self,
172 poly: &[BigRational],
173 sturm_seq: &[Vec<BigRational>],
174 left: BigRational,
175 right: BigRational,
176 ) -> Vec<RootInterval> {
177 self.stats.bisection_steps += 1;
178
179 let mid = (&left + &right) / BigRational::from_integer(BigInt::from(2));
180
181 let left_vars = self.count_sign_variations(sturm_seq, &left);
182 let mid_vars = self.count_sign_variations(sturm_seq, &mid);
183 let right_vars = self.count_sign_variations(sturm_seq, &right);
184 self.stats.sturm_evaluations += 3;
185
186 let mut intervals = Vec::new();
187
188 let left_roots = left_vars.saturating_sub(mid_vars);
190 if left_roots > 0 {
191 intervals.extend(self.isolate_roots(poly, (left.clone(), mid.clone())));
192 }
193
194 let right_roots = mid_vars.saturating_sub(right_vars);
196 if right_roots > 0 {
197 intervals.extend(self.isolate_roots(poly, (mid, right)));
198 }
199
200 intervals
201 }
202
203 fn refine_root_interval(
205 &mut self,
206 poly: &[BigRational],
207 mut left: BigRational,
208 mut right: BigRational,
209 ) -> RootInterval {
210 let mut iterations = 0;
211
212 while &right - &left > self.precision && iterations < self.max_iterations {
213 let mid = (&left + &right) / BigRational::from_integer(BigInt::from(2));
214 let mid_val = Self::evaluate(poly, &mid);
215
216 if mid_val.is_zero() {
217 return RootInterval {
218 left: mid.clone(),
219 right: mid,
220 left_closed: true,
221 right_closed: true,
222 multiplicity: 1,
223 };
224 }
225
226 let left_val = Self::evaluate(poly, &left);
227
228 if (left_val > BigRational::zero()) == (mid_val > BigRational::zero()) {
229 left = mid;
230 } else {
231 right = mid;
232 }
233
234 iterations += 1;
235 }
236
237 self.stats.intervals_generated += 1;
238
239 RootInterval {
240 left,
241 right,
242 left_closed: false,
243 right_closed: false,
244 multiplicity: 1,
245 }
246 }
247
248 fn evaluate(poly: &[BigRational], x: &BigRational) -> BigRational {
250 if poly.is_empty() {
251 return BigRational::zero();
252 }
253
254 let mut result = poly[0].clone();
255 for coeff in &poly[1..] {
256 result = result * x + coeff;
257 }
258 result
259 }
260
261 fn derivative(poly: &[BigRational]) -> Vec<BigRational> {
263 if poly.len() <= 1 {
264 return vec![];
265 }
266
267 let mut deriv = Vec::with_capacity(poly.len() - 1);
268 for (i, coeff) in poly.iter().enumerate().take(poly.len() - 1) {
269 let degree = (poly.len() - 1 - i) as i64;
270 deriv.push(coeff * BigRational::from_integer(BigInt::from(degree)));
271 }
272 deriv
273 }
274
275 fn polynomial_remainder(dividend: &[BigRational], divisor: &[BigRational]) -> Vec<BigRational> {
277 if divisor.is_empty() || Self::is_zero_poly(divisor) {
278 return vec![];
279 }
280
281 let mut remainder = dividend.to_vec();
282
283 while remainder.len() >= divisor.len() && !Self::is_zero_poly(&remainder) {
284 let lead_div = &divisor[0];
285 let lead_rem = &remainder[0];
286
287 if lead_div.is_zero() {
288 break;
289 }
290
291 let quotient_coeff = lead_rem / lead_div;
292
293 for i in 0..divisor.len() {
294 remainder[i] = &remainder[i] - "ient_coeff * &divisor[i];
295 }
296
297 remainder.remove(0);
298 }
299
300 remainder
301 }
302
303 fn normalize_polynomial(poly: &[BigRational]) -> Vec<BigRational> {
305 let mut result = poly.to_vec();
306 while !result.is_empty() && result[0].is_zero() {
307 result.remove(0);
308 }
309 result
310 }
311
312 fn is_zero_poly(poly: &[BigRational]) -> bool {
314 poly.iter().all(|c| c.is_zero())
315 }
316
317 pub fn stats(&self) -> &IsolationStats {
319 &self.stats
320 }
321}
322
323#[cfg(test)]
324mod tests {
325 use super::*;
326 use num_traits::{One, Zero};
327
328 #[test]
329 fn test_root_isolator() {
330 let precision = BigRational::new(BigInt::from(1), BigInt::from(1000));
331 let isolator = RootIsolator::new(precision);
332
333 assert_eq!(isolator.stats.sturm_evaluations, 0);
334 }
335
336 #[test]
337 fn test_sturm_sequence() {
338 let precision = BigRational::new(BigInt::from(1), BigInt::from(100));
339 let isolator = RootIsolator::new(precision);
340
341 let poly = vec![
343 BigRational::one(),
344 BigRational::zero(),
345 BigRational::from_integer(BigInt::from(-2)),
346 ];
347
348 let sturm = isolator.build_sturm_sequence(&poly);
349 assert!(!sturm.is_empty());
350 }
351}