oxiz_math/polynomial/
root_counting.rs1#[allow(unused_imports)]
18use crate::prelude::*;
19use num_bigint::BigInt;
20use num_rational::BigRational;
21use num_traits::{Signed, Zero};
22
23#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct Polynomial {
26 pub coeffs: Vec<BigRational>,
28}
29
30impl Polynomial {
31 pub fn new(coeffs: Vec<BigRational>) -> Self {
33 let mut poly = Self { coeffs };
34 poly.normalize();
35 poly
36 }
37
38 fn normalize(&mut self) {
40 while self.coeffs.len() > 1 && self.coeffs.last().is_some_and(|c| c.is_zero()) {
42 self.coeffs.pop();
43 }
44 if self.coeffs.is_empty() {
45 self.coeffs.push(BigRational::zero());
46 }
47 }
48
49 pub fn degree(&self) -> usize {
51 if self.coeffs.len() == 1 && self.coeffs[0].is_zero() {
52 0
53 } else {
54 self.coeffs.len() - 1
55 }
56 }
57
58 pub fn eval(&self, x: &BigRational) -> BigRational {
60 let mut result = BigRational::zero();
62 for coeff in self.coeffs.iter().rev() {
63 result = result * x + coeff;
64 }
65 result
66 }
67
68 pub fn derivative(&self) -> Polynomial {
70 if self.degree() == 0 {
71 return Polynomial::new(vec![BigRational::zero()]);
72 }
73
74 let coeffs: Vec<_> = self
75 .coeffs
76 .iter()
77 .enumerate()
78 .skip(1)
79 .map(|(i, c)| c * BigRational::from_integer(BigInt::from(i)))
80 .collect();
81
82 Polynomial::new(coeffs)
83 }
84
85 pub fn remainder(&self, other: &Polynomial) -> Polynomial {
87 let mut rem = self.clone();
88 let divisor_deg = other.degree();
89 let divisor_lead = &other.coeffs[divisor_deg];
90
91 loop {
92 if rem.coeffs.len() == 1 && rem.coeffs[0].is_zero() {
96 break;
97 }
98 if rem.degree() < divisor_deg || rem.coeffs.is_empty() {
99 break;
100 }
101
102 let rem_deg = rem.degree();
103 let rem_lead = &rem.coeffs[rem_deg];
104 let factor = rem_lead / divisor_lead;
105
106 for i in 0..=divisor_deg {
108 rem.coeffs[rem_deg - divisor_deg + i] =
109 &rem.coeffs[rem_deg - divisor_deg + i] - &factor * &other.coeffs[i];
110 }
111
112 rem.normalize();
113 }
114
115 rem
116 }
117}
118
119#[derive(Debug, Clone)]
121pub struct RootCountConfig {
122 pub use_sturm: bool,
124 pub use_descartes: bool,
126 pub use_budan: bool,
128}
129
130impl Default for RootCountConfig {
131 fn default() -> Self {
132 Self {
133 use_sturm: true,
134 use_descartes: true,
135 use_budan: false,
136 }
137 }
138}
139
140#[derive(Debug, Clone, Default)]
142pub struct RootCountStats {
143 pub roots_counted: u64,
145 pub sturm_sequences: u64,
147 pub descartes_variations: u64,
149}
150
151#[derive(Debug)]
153pub struct RootCounter {
154 config: RootCountConfig,
156 stats: RootCountStats,
158}
159
160impl RootCounter {
161 pub fn new(config: RootCountConfig) -> Self {
163 Self {
164 config,
165 stats: RootCountStats::default(),
166 }
167 }
168
169 pub fn default_config() -> Self {
171 Self::new(RootCountConfig::default())
172 }
173
174 pub fn count_roots_sturm(
176 &mut self,
177 poly: &Polynomial,
178 a: &BigRational,
179 b: &BigRational,
180 ) -> usize {
181 if !self.config.use_sturm {
182 return 0;
183 }
184
185 self.stats.sturm_sequences += 1;
186
187 let sturm_seq = self.sturm_sequence(poly);
188
189 let sign_changes_a = self.count_sign_changes(&sturm_seq, a);
190 let sign_changes_b = self.count_sign_changes(&sturm_seq, b);
191
192 (sign_changes_a as isize - sign_changes_b as isize).unsigned_abs()
193 }
194
195 fn sturm_sequence(&self, poly: &Polynomial) -> Vec<Polynomial> {
197 let mut seq = vec![poly.clone(), poly.derivative()];
198
199 loop {
200 let n = seq.len();
201 if seq[n - 1].degree() == 0 {
204 break;
205 }
206 let remainder = seq[n - 2].remainder(&seq[n - 1]);
207
208 let negated = Polynomial::new(remainder.coeffs.iter().map(|c| -c).collect());
210
211 if negated.degree() == 0 && negated.coeffs[0].is_zero() {
212 break;
213 }
214
215 seq.push(negated);
216
217 if seq.len() > 1000 {
219 break;
220 }
221 }
222
223 seq
224 }
225
226 fn count_sign_changes(&self, seq: &[Polynomial], x: &BigRational) -> usize {
228 let mut last_sign = 0i8;
229 let mut changes = 0;
230
231 for poly in seq {
232 let val = poly.eval(x);
233 let sign = if val.is_positive() {
234 1
235 } else if val.is_negative() {
236 -1
237 } else {
238 0
239 };
240
241 if sign != 0 && last_sign != 0 && sign != last_sign {
242 changes += 1;
243 }
244
245 if sign != 0 {
246 last_sign = sign;
247 }
248 }
249
250 changes
251 }
252
253 pub fn count_positive_roots_descartes(&mut self, poly: &Polynomial) -> usize {
255 if !self.config.use_descartes {
256 return 0;
257 }
258
259 self.stats.descartes_variations += 1;
260
261 let mut last_sign = 0i8;
262 let mut variations = 0;
263
264 for coeff in &poly.coeffs {
265 let sign = if coeff.is_positive() {
266 1
267 } else if coeff.is_negative() {
268 -1
269 } else {
270 0
271 };
272
273 if sign != 0 && last_sign != 0 && sign != last_sign {
274 variations += 1;
275 }
276
277 if sign != 0 {
278 last_sign = sign;
279 }
280 }
281
282 variations
283 }
284
285 pub fn stats(&self) -> &RootCountStats {
287 &self.stats
288 }
289
290 pub fn reset_stats(&mut self) {
292 self.stats = RootCountStats::default();
293 }
294}
295
296impl Default for RootCounter {
297 fn default() -> Self {
298 Self::default_config()
299 }
300}
301
302#[cfg(test)]
303mod tests {
304 use super::*;
305 use num_traits::{One, Zero};
306
307 #[test]
308 fn test_polynomial_creation() {
309 let poly = Polynomial::new(vec![
310 BigRational::from_integer(1.into()),
311 BigRational::from_integer(2.into()),
312 BigRational::from_integer(3.into()),
313 ]);
314 assert_eq!(poly.degree(), 2);
315 }
316
317 #[test]
318 fn test_polynomial_eval() {
319 let poly = Polynomial::new(vec![
321 BigRational::from_integer(1.into()),
322 BigRational::from_integer(2.into()),
323 BigRational::from_integer(3.into()),
324 ]);
325
326 let val = poly.eval(&BigRational::zero());
328 assert_eq!(val, BigRational::from_integer(1.into()));
329
330 let val = poly.eval(&BigRational::one());
332 assert_eq!(val, BigRational::from_integer(6.into()));
333 }
334
335 #[test]
336 fn test_polynomial_derivative() {
337 let poly = Polynomial::new(vec![
339 BigRational::from_integer(1.into()),
340 BigRational::from_integer(2.into()),
341 BigRational::from_integer(3.into()),
342 ]);
343
344 let deriv = poly.derivative();
346 assert_eq!(deriv.degree(), 1);
347 assert_eq!(deriv.coeffs[0], BigRational::from_integer(2.into()));
348 assert_eq!(deriv.coeffs[1], BigRational::from_integer(6.into()));
349 }
350
351 #[test]
352 fn test_descartes_rule() {
353 let mut counter = RootCounter::default_config();
354
355 let poly = Polynomial::new(vec![
357 BigRational::from_integer((-1).into()),
358 BigRational::zero(),
359 BigRational::from_integer(1.into()),
360 ]);
361
362 let count = counter.count_positive_roots_descartes(&poly);
363 assert!(count >= 1); }
365
366 #[test]
367 fn test_counter_creation() {
368 let counter = RootCounter::default_config();
369 assert_eq!(counter.stats().sturm_sequences, 0);
370 }
371
372 #[test]
373 fn test_stats() {
374 let mut counter = RootCounter::default_config();
375 counter.stats.sturm_sequences = 5;
376
377 assert_eq!(counter.stats().sturm_sequences, 5);
378
379 counter.reset_stats();
380 assert_eq!(counter.stats().sturm_sequences, 0);
381 }
382}