1use crate::polynomial::root_counting::Polynomial;
19#[allow(unused_imports)]
20use crate::prelude::*;
21use core::cmp::Ordering;
22use core::fmt;
23use num_bigint::BigInt;
24use num_rational::BigRational;
25use num_traits::{One, Signed, Zero};
26
27#[derive(Debug, Clone, PartialEq, Eq)]
29pub enum AlgebraicNumberError {
30 NonIsolatingInterval,
32 NoRootInInterval,
34 ZeroPolynomial,
36 InvalidOperation(String),
38}
39
40impl fmt::Display for AlgebraicNumberError {
41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42 match self {
43 Self::NonIsolatingInterval => write!(f, "Interval doesn't isolate a single root"),
44 Self::NoRootInInterval => write!(f, "No root in interval"),
45 Self::ZeroPolynomial => write!(f, "Polynomial is zero"),
46 Self::InvalidOperation(msg) => write!(f, "Invalid operation: {}", msg),
47 }
48 }
49}
50
51impl core::error::Error for AlgebraicNumberError {}
52
53#[derive(Debug, Clone)]
55pub struct AlgebraicNumber {
56 pub minimal_poly: Polynomial,
58 pub lower: BigRational,
60 pub upper: BigRational,
62 refinement_level: usize,
64}
65
66impl AlgebraicNumber {
67 pub fn new(
79 poly: Polynomial,
80 lower: BigRational,
81 upper: BigRational,
82 ) -> Result<Self, AlgebraicNumberError> {
83 if poly.degree() == 0 && poly.coeffs.first().is_none_or(Zero::is_zero) {
85 return Err(AlgebraicNumberError::ZeroPolynomial);
86 }
87
88 if lower > upper {
89 return Err(AlgebraicNumberError::NonIsolatingInterval);
90 }
91
92 if lower == upper {
94 if poly.eval(&lower).is_zero() {
95 return Ok(Self {
96 minimal_poly: poly,
97 lower,
98 upper,
99 refinement_level: 0,
100 });
101 }
102 return Err(AlgebraicNumberError::NoRootInInterval);
103 }
104
105 let root_count = sturm_root_count(&poly, &lower, &upper);
107 if root_count != 1 {
108 return Err(AlgebraicNumberError::NonIsolatingInterval);
109 }
110
111 Ok(Self {
112 minimal_poly: poly,
113 lower,
114 upper,
115 refinement_level: 0,
116 })
117 }
118
119 pub fn from_rational(r: BigRational) -> Self {
123 let poly = Polynomial::new(vec![-r.clone(), BigRational::from(BigInt::from(1))]);
125
126 Self {
127 minimal_poly: poly,
128 lower: r.clone(),
129 upper: r,
130 refinement_level: 0,
131 }
132 }
133
134 pub fn is_rational(&self) -> bool {
136 self.minimal_poly.degree() == 1 || self.lower == self.upper
137 }
138
139 pub fn to_rational(&self) -> Option<BigRational> {
141 if self.is_rational() {
142 Some(self.lower.clone())
143 } else {
144 None
145 }
146 }
147
148 pub fn refine(&mut self) {
150 let mid = (&self.lower + &self.upper) / BigRational::from(BigInt::from(2));
151 let mid_value = self.minimal_poly.eval(&mid);
152
153 if mid_value.is_zero() {
154 self.lower = mid.clone();
156 self.upper = mid;
157 } else {
158 let lower_value = self.minimal_poly.eval(&self.lower);
159 if lower_value.signum() != mid_value.signum() {
160 self.upper = mid;
162 } else {
163 self.lower = mid;
165 }
166 }
167
168 self.refinement_level += 1;
169 }
170
171 pub fn refine_to_precision(&mut self, precision: &BigRational) {
173 while &self.upper - &self.lower > *precision {
174 self.refine();
175 }
176 }
177
178 pub fn interval_width(&self) -> BigRational {
180 &self.upper - &self.lower
181 }
182
183 pub fn midpoint(&self) -> BigRational {
185 (&self.lower + &self.upper) / BigRational::from(BigInt::from(2))
186 }
187
188 pub fn compare(&mut self, other: &mut AlgebraicNumber) -> Ordering {
190 loop {
191 if self.upper < other.lower {
192 return Ordering::Less;
193 }
194 if self.lower > other.upper {
195 return Ordering::Greater;
196 }
197
198 if self.minimal_poly == other.minimal_poly
200 && self.lower == other.lower
201 && self.upper == other.upper
202 {
203 return Ordering::Equal;
204 }
205
206 self.refine();
208 other.refine();
209
210 if self.refinement_level > 1000 {
211 return Ordering::Equal;
213 }
214 }
215 }
216
217 pub fn add(&self, other: &AlgebraicNumber) -> Result<AlgebraicNumber, AlgebraicNumberError> {
238 if self.is_rational() && other.is_rational() {
240 return Ok(AlgebraicNumber::from_rational(&self.lower + &other.lower));
241 }
242
243 let deg_p = self.minimal_poly.degree();
247 let deg_q = other.minimal_poly.degree();
248 let result_deg = deg_p * deg_q;
249 let num_points = result_deg + 1;
250
251 let eval_points: Vec<BigRational> = (0..num_points as i64)
252 .map(|k| BigRational::from(BigInt::from(k)))
253 .collect();
254
255 let values: Vec<BigRational> = eval_points
256 .iter()
257 .map(|x_val| {
258 let q_shifted = poly_substitute_affine_t(&other.minimal_poly, x_val);
262 univariate_resultant(&self.minimal_poly, &q_shifted)
264 })
265 .collect();
266
267 let raw_poly = lagrange_interpolate(&eval_points, &values);
269
270 if raw_poly.degree() == 0 && raw_poly.coeffs.first().is_none_or(Zero::is_zero) {
271 return Err(AlgebraicNumberError::ZeroPolynomial);
272 }
273
274 let result_poly = square_free_part(raw_poly);
276
277 let sum_lower = &self.lower + &other.lower;
280 let sum_upper = &self.upper + &other.upper;
281
282 isolate_root_in_interval(result_poly, sum_lower, sum_upper)
283 }
284
285 pub fn mul(&self, other: &AlgebraicNumber) -> Result<AlgebraicNumber, AlgebraicNumberError> {
303 if self.is_rational() && other.is_rational() {
305 return Ok(AlgebraicNumber::from_rational(&self.lower * &other.lower));
306 }
307
308 let deg_p = self.minimal_poly.degree();
309 let deg_q = other.minimal_poly.degree();
310 let result_deg = deg_p * deg_q;
311 let num_points = result_deg + 1;
312
313 let eval_points: Vec<BigRational> = (1..=(num_points as i64))
316 .map(|k| BigRational::from(BigInt::from(k)))
317 .collect();
318
319 let values: Vec<BigRational> = eval_points
320 .iter()
321 .map(|x_val| {
322 let h = poly_mul_pow_quot(&other.minimal_poly, x_val, deg_q);
327 univariate_resultant(&self.minimal_poly, &h)
328 })
329 .collect();
330
331 let raw_poly = lagrange_interpolate(&eval_points, &values);
332
333 if raw_poly.degree() == 0 && raw_poly.coeffs.first().is_none_or(Zero::is_zero) {
334 return Err(AlgebraicNumberError::ZeroPolynomial);
335 }
336
337 let result_poly = square_free_part(raw_poly);
341
342 let corners = [
344 &self.lower * &other.lower,
345 &self.lower * &other.upper,
346 &self.upper * &other.lower,
347 &self.upper * &other.upper,
348 ];
349 let prod_lower = corners
350 .iter()
351 .min_by(|a, b| a.cmp(b))
352 .expect("4 elements")
353 .clone();
354 let prod_upper = corners
355 .iter()
356 .max_by(|a, b| a.cmp(b))
357 .expect("4 elements")
358 .clone();
359
360 isolate_root_in_interval(result_poly, prod_lower, prod_upper)
361 }
362
363 pub fn negate(&self) -> AlgebraicNumber {
367 let neg_poly = poly_substitute_neg_x(&self.minimal_poly);
368 AlgebraicNumber {
369 minimal_poly: neg_poly,
370 lower: -&self.upper,
371 upper: -&self.lower,
372 refinement_level: self.refinement_level,
373 }
374 }
375
376 pub fn signum(&self) -> i32 {
378 if self.upper < BigRational::zero() {
379 -1
380 } else if self.lower > BigRational::zero() {
381 1
382 } else if self.lower == self.upper && self.lower.is_zero() {
383 0
384 } else {
385 let mut copy = self.clone();
387 copy.refine();
388 copy.signum()
389 }
390 }
391}
392
393fn poly_substitute_neg_x(poly: &Polynomial) -> Polynomial {
399 let coeffs: Vec<BigRational> = poly
400 .coeffs
401 .iter()
402 .enumerate()
403 .map(|(i, c)| if i % 2 == 0 { c.clone() } else { -c })
404 .collect();
405 Polynomial::new(coeffs)
406}
407
408fn sturm_root_count(poly: &Polynomial, lower: &BigRational, upper: &BigRational) -> usize {
412 let seq = build_sturm_sequence(poly);
413 let v_lower = sign_variations(&seq, lower);
414 let v_upper = sign_variations(&seq, upper);
415 (v_lower as isize - v_upper as isize).unsigned_abs()
416}
417
418fn build_sturm_sequence(poly: &Polynomial) -> Vec<Polynomial> {
424 let mut seq = vec![poly.clone(), poly.derivative()];
425
426 loop {
427 let n = seq.len();
428 let last = &seq[n - 1];
429
430 if last.degree() == 0 {
433 break;
434 }
435
436 let remainder = seq[n - 2].remainder(last);
437 let negated = Polynomial::new(remainder.coeffs.iter().map(|c| -c).collect());
438
439 if negated.degree() == 0 && negated.coeffs.first().is_none_or(Zero::is_zero) {
440 break;
441 }
442
443 seq.push(negated);
444
445 if seq.len() > 1000 {
447 break;
448 }
449 }
450
451 seq
452}
453
454fn sign_variations(seq: &[Polynomial], point: &BigRational) -> usize {
458 let signs: Vec<i32> = seq
459 .iter()
460 .map(|p| {
461 let val = p.eval(point);
462 if val.is_positive() {
463 1
464 } else if val.is_negative() {
465 -1
466 } else {
467 0
468 }
469 })
470 .filter(|&s| s != 0)
471 .collect();
472
473 signs.windows(2).filter(|w| w[0] != w[1]).count()
474}
475
476fn square_free_part(p: Polynomial) -> Polynomial {
484 let deriv = p.derivative();
485
486 if deriv.degree() == 0 && deriv.coeffs.first().is_none_or(|c| c.is_zero()) {
488 return p;
489 }
490
491 let g = univariate_gcd(&p, &deriv);
493
494 if g.degree() == 0 {
496 return p;
497 }
498
499 exact_poly_div(&p, &g)
501}
502
503fn univariate_gcd(p: &Polynomial, q: &Polynomial) -> Polynomial {
505 let mut a = p.clone();
506 let mut b = q.clone();
507
508 loop {
509 let b_is_zero = b.coeffs.iter().all(|c| c.is_zero());
511 if b_is_zero {
512 return make_monic(a);
514 }
515 let r = a.remainder(&b);
516 a = b;
517 b = r;
518 }
519}
520
521fn exact_poly_div(a: &Polynomial, b: &Polynomial) -> Polynomial {
523 let deg_b = b.degree();
524 let lead_b = b.coeffs[deg_b].clone();
525
526 if a.degree() < deg_b {
527 return Polynomial::new(vec![BigRational::zero()]);
528 }
529
530 let result_deg = a.degree() - deg_b;
531 let mut quotient_coeffs = vec![BigRational::zero(); result_deg + 1];
532 let mut rem_coeffs = a.coeffs.clone();
534
535 let mut rem_deg = a.degree();
537 while rem_deg >= deg_b {
538 if rem_coeffs[rem_deg].is_zero() {
540 if rem_deg == 0 {
541 break;
542 }
543 rem_deg -= 1;
544 continue;
545 }
546 let factor = rem_coeffs[rem_deg].clone() / &lead_b;
547 let shift = rem_deg - deg_b;
548 quotient_coeffs[shift] = factor.clone();
549
550 for i in 0..=deg_b {
551 let upd = rem_coeffs[shift + i].clone() - &factor * &b.coeffs[i];
552 rem_coeffs[shift + i] = upd;
553 }
554 if rem_deg == 0 {
555 break;
556 }
557 rem_deg -= 1;
558 }
559
560 Polynomial::new(quotient_coeffs)
561}
562
563fn make_monic(p: Polynomial) -> Polynomial {
565 let deg = p.degree();
566 let lead = &p.coeffs[deg];
567 if lead.is_zero() {
568 return p;
569 }
570 let inv = lead.clone().recip();
571 let coeffs: Vec<BigRational> = p.coeffs.iter().map(|c| c * &inv).collect();
572 Polynomial::new(coeffs)
573}
574
575fn poly_substitute_affine_t(q: &Polynomial, k: &BigRational) -> Polynomial {
586 let n = q.coeffs.len();
587 if n == 0 {
588 return Polynomial::new(vec![BigRational::zero()]);
589 }
590
591 let mut result_coeffs = vec![BigRational::zero(); n];
593
594 for (i, q_i) in q.coeffs.iter().enumerate() {
595 if q_i.is_zero() {
596 continue;
597 }
598 let binom_row = binomial_row(i);
600 for (j, binom_ij) in binom_row.iter().enumerate() {
601 let k_pow = rational_pow(k, i - j);
603 let sign = if j % 2 == 0 {
605 BigRational::one()
606 } else {
607 -BigRational::one()
608 };
609 let contrib = q_i * binom_ij * k_pow * sign;
610 result_coeffs[j] = result_coeffs[j].clone() + contrib;
611 }
612 }
613
614 Polynomial::new(result_coeffs)
615}
616
617fn poly_mul_pow_quot(q: &Polynomial, x_val: &BigRational, m: usize) -> Polynomial {
625 let mut result_coeffs = vec![BigRational::zero(); m + 1];
626 for (i, q_i) in q.coeffs.iter().enumerate() {
627 if q_i.is_zero() {
628 continue;
629 }
630 let j = m.saturating_sub(i);
631 let x_pow = rational_pow(x_val, i);
632 result_coeffs[j] = result_coeffs[j].clone() + q_i * x_pow;
633 }
634 Polynomial::new(result_coeffs)
635}
636
637fn univariate_resultant(p: &Polynomial, q: &Polynomial) -> BigRational {
643 let m = p.degree();
644 let n = q.degree();
645
646 if m == 0 {
648 let c = p.coeffs.first().cloned().unwrap_or_else(BigRational::zero);
649 return rational_pow(&c, n);
650 }
651 if n == 0 {
652 let c = q.coeffs.first().cloned().unwrap_or_else(BigRational::zero);
653 return rational_pow(&c, m);
654 }
655
656 let p_rev: Vec<BigRational> = p.coeffs.iter().rev().cloned().collect();
660 let q_rev: Vec<BigRational> = q.coeffs.iter().rev().cloned().collect();
661
662 let size = m + n;
663 let mut mat: Vec<Vec<BigRational>> = vec![vec![BigRational::zero(); size]; size];
664
665 for r in 0..n {
667 mat[r][r..(m + r + 1)].clone_from_slice(&p_rev[..(m + 1)]);
668 }
669 for r in 0..m {
671 mat[n + r][r..(n + r + 1)].clone_from_slice(&q_rev[..(n + 1)]);
672 }
673
674 bareiss_rat_det(mat)
676}
677
678fn bareiss_rat_det(mut mat: Vec<Vec<BigRational>>) -> BigRational {
683 let n = mat.len();
684 if n == 0 {
685 return BigRational::one();
686 }
687 if n == 1 {
688 return mat.remove(0).remove(0);
689 }
690
691 let mut sign = BigRational::one();
692 let neg_one = -BigRational::one();
693
694 for col in 0..n {
695 let pivot_row = (col..n).find(|&r| !mat[r][col].is_zero());
697 let pivot_row = match pivot_row {
698 Some(r) => r,
699 None => return BigRational::zero(),
700 };
701
702 if pivot_row != col {
703 mat.swap(col, pivot_row);
704 sign *= &neg_one;
705 }
706
707 let pivot = mat[col][col].clone();
708
709 for row in (col + 1)..n {
710 for j in (col + 1)..n {
711 let prod1 = &pivot * &mat[row][j];
712 let prod2 = &mat[row][col] * &mat[col][j];
713 let diff = prod1 - prod2;
714 if col == 0 {
715 mat[row][j] = diff;
716 } else {
717 let prev_pivot = mat[col - 1][col - 1].clone();
719 if prev_pivot.is_zero() {
720 mat[row][j] = BigRational::zero();
721 } else {
722 mat[row][j] = diff / prev_pivot;
723 }
724 }
725 }
726 mat[row][col] = BigRational::zero();
727 }
728 }
729
730 sign * mat[n - 1][n - 1].clone()
731}
732
733fn lagrange_interpolate(points: &[BigRational], values: &[BigRational]) -> Polynomial {
739 let n = points.len();
740 assert_eq!(n, values.len(), "points and values must have equal length");
741
742 if n == 0 {
743 return Polynomial::new(vec![BigRational::zero()]);
744 }
745 if n == 1 {
746 return Polynomial::new(vec![values[0].clone()]);
747 }
748
749 let mut result_coeffs = vec![BigRational::zero(); n];
751
752 for i in 0..n {
753 let mut basis_coeffs = vec![BigRational::one()]; let mut denom = BigRational::one();
757
758 for j in 0..n {
759 if j == i {
760 continue;
761 }
762 let mut new_coeffs = vec![BigRational::zero(); basis_coeffs.len() + 1];
764 for (k, c) in basis_coeffs.iter().enumerate() {
765 new_coeffs[k + 1] = new_coeffs[k + 1].clone() + c;
766 new_coeffs[k] = new_coeffs[k].clone() - c * &points[j];
767 }
768 basis_coeffs = new_coeffs;
769
770 denom *= &points[i] - &points[j];
772 }
773
774 let scale = &values[i] / &denom;
776 for (k, c) in basis_coeffs.iter().enumerate() {
777 result_coeffs[k] = result_coeffs[k].clone() + c * &scale;
778 }
779 }
780
781 Polynomial::new(result_coeffs)
782}
783
784fn binomial_row(k: usize) -> Vec<BigRational> {
786 let mut row = vec![BigRational::one(); k + 1];
787 for i in 1..k {
788 for j in (1..i + 1).rev() {
790 let prev = row[j - 1].clone();
791 row[j] = row[j].clone() + prev;
792 }
793 }
794 row
795}
796
797fn rational_pow(base: &BigRational, exp: usize) -> BigRational {
799 if exp == 0 {
800 return BigRational::one();
801 }
802 let mut result = BigRational::one();
803 let mut base = base.clone();
804 let mut exp = exp;
805 while exp > 0 {
806 if exp % 2 == 1 {
807 result *= &base;
808 }
809 let b = base.clone();
810 base *= &b;
811 exp /= 2;
812 }
813 result
814}
815
816fn isolate_root_in_interval(
822 r: Polynomial,
823 lo: BigRational,
824 hi: BigRational,
825) -> Result<AlgebraicNumber, AlgebraicNumberError> {
826 let mut lo = lo;
827 let mut hi = hi;
828
829 if lo == hi {
831 if r.eval(&lo).is_zero() {
832 return AlgebraicNumber::new(r, lo.clone(), hi);
833 }
834 return Err(AlgebraicNumberError::NoRootInInterval);
835 }
836
837 let eps = BigRational::new(BigInt::from(1), BigInt::from(1_000_000_000_i64));
840 lo -= &eps;
841 hi += &eps;
842
843 for _ in 0..200 {
845 let count = sturm_root_count(&r, &lo, &hi);
846 if count == 1 {
847 return AlgebraicNumber::new(r, lo, hi);
848 }
849 if count == 0 {
850 return Err(AlgebraicNumberError::NoRootInInterval);
851 }
852 let mid = (&lo + &hi) / BigRational::from(BigInt::from(2));
854 let count_left = sturm_root_count(&r, &lo, &mid);
855 if count_left >= 1 {
856 hi = mid;
857 } else {
858 lo = mid;
859 }
860 }
861
862 Err(AlgebraicNumberError::NonIsolatingInterval)
863}
864
865impl PartialEq for AlgebraicNumber {
868 fn eq(&self, other: &Self) -> bool {
869 let mut a = self.clone();
870 let mut b = other.clone();
871 matches!(a.compare(&mut b), Ordering::Equal)
872 }
873}
874
875impl Eq for AlgebraicNumber {}
876
877impl PartialOrd for AlgebraicNumber {
878 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
879 Some(self.cmp(other))
880 }
881}
882
883impl Ord for AlgebraicNumber {
884 fn cmp(&self, other: &Self) -> Ordering {
885 let mut a = self.clone();
886 let mut b = other.clone();
887 a.compare(&mut b)
888 }
889}
890
891#[cfg(test)]
894mod tests {
895 use super::*;
896
897 fn rat(n: i64) -> BigRational {
898 BigRational::from(BigInt::from(n))
899 }
900
901 #[test]
902 fn test_from_rational() {
903 let alg = AlgebraicNumber::from_rational(rat(42));
904 assert!(alg.is_rational());
905 assert_eq!(alg.to_rational(), Some(rat(42)));
906 }
907
908 #[test]
909 fn test_rational_arithmetic() {
910 let a = AlgebraicNumber::from_rational(rat(3));
911 let b = AlgebraicNumber::from_rational(rat(5));
912
913 let sum = a.add(&b).expect("test operation should succeed");
914 assert!(sum.is_rational());
915 assert_eq!(sum.to_rational(), Some(rat(8)));
916
917 let prod = a.mul(&b).expect("test operation should succeed");
918 assert!(prod.is_rational());
919 assert_eq!(prod.to_rational(), Some(rat(15)));
920 }
921
922 #[test]
923 fn test_negate() {
924 let a = AlgebraicNumber::from_rational(rat(5));
925 let neg_a = a.negate();
926
927 assert!(neg_a.is_rational());
928 assert_eq!(neg_a.to_rational(), Some(rat(-5)));
929 }
930
931 #[test]
932 fn test_compare() {
933 let a = AlgebraicNumber::from_rational(rat(3));
934 let b = AlgebraicNumber::from_rational(rat(5));
935
936 assert!(a < b);
937 assert!(b > a);
938 assert_eq!(a, a.clone());
939 }
940
941 #[test]
942 fn test_signum() {
943 let pos = AlgebraicNumber::from_rational(rat(5));
944 let neg = AlgebraicNumber::from_rational(rat(-3));
945 let zero = AlgebraicNumber::from_rational(rat(0));
946
947 assert_eq!(pos.signum(), 1);
948 assert_eq!(neg.signum(), -1);
949 assert_eq!(zero.signum(), 0);
950 }
951
952 #[test]
953 fn test_refine() {
954 let poly = Polynomial::new(vec![rat(-2), rat(0), rat(1)]);
956 let mut alg =
957 AlgebraicNumber::new(poly, rat(1), rat(2)).expect("test operation should succeed");
958
959 let initial_width = alg.interval_width();
960 alg.refine();
961 let refined_width = alg.interval_width();
962
963 assert!(refined_width < initial_width);
964 }
965
966 #[test]
967 fn test_interval_width() {
968 let alg = AlgebraicNumber::from_rational(rat(5));
969 assert_eq!(alg.interval_width(), rat(0));
970 }
971
972 #[test]
973 fn test_new_rejects_non_isolating_interval() {
974 let poly = Polynomial::new(vec![rat(-1), rat(0), rat(1)]);
976 let result = AlgebraicNumber::new(poly, rat(-2), rat(2));
977 assert!(result.is_err());
978 }
979
980 #[test]
981 fn test_new_accepts_isolating_interval() {
982 let poly = Polynomial::new(vec![rat(-2), rat(0), rat(1)]);
984 let result = AlgebraicNumber::new(poly, rat(1), rat(2));
985 assert!(result.is_ok());
986 }
987
988 #[test]
989 fn test_sturm_root_count() {
990 let poly = Polynomial::new(vec![rat(-2), rat(0), rat(1)]);
992 let count = sturm_root_count(&poly, &rat(1), &rat(2));
993 assert_eq!(count, 1);
994 }
995
996 #[test]
997 fn test_poly_substitute_neg_x() {
998 let poly = Polynomial::new(vec![rat(-2), rat(0), rat(1)]);
1000 let neg = poly_substitute_neg_x(&poly);
1001 assert_eq!(neg.eval(&rat(2)), poly.eval(&rat(2)));
1002
1003 let poly2 = Polynomial::new(vec![rat(0), rat(0), rat(0), rat(1)]);
1005 let neg2 = poly_substitute_neg_x(&poly2);
1006 assert_eq!(neg2.eval(&rat(2)), -poly2.eval(&rat(2)));
1007 }
1008
1009 fn sqrt2() -> AlgebraicNumber {
1013 let poly = Polynomial::new(vec![rat(-2), rat(0), rat(1)]);
1014 AlgebraicNumber::new(poly, rat(1), rat(2)).expect("√2 construction")
1015 }
1016
1017 fn sqrt3() -> AlgebraicNumber {
1019 let poly = Polynomial::new(vec![rat(-3), rat(0), rat(1)]);
1020 AlgebraicNumber::new(poly, rat(1), rat(2)).expect("√3 construction")
1021 }
1022
1023 #[test]
1029 fn test_add_irrational_sum_polynomial_root() {
1030 let a = sqrt2();
1031 let b = sqrt3();
1032 let mut sum = a.add(&b).expect("√2 + √3 should succeed");
1033
1034 let p = |x: &num_rational::BigRational| {
1038 x.clone() * x.clone() * x.clone() * x.clone() - rat(10) * x.clone() * x.clone() + rat(1)
1039 };
1040
1041 let tight = num_rational::BigRational::new(BigInt::from(1), BigInt::from(1 << 20));
1043 sum.refine_to_precision(&tight);
1044
1045 let val_lo = p(&sum.lower);
1046 let val_hi = p(&sum.upper);
1047
1048 assert!(
1050 val_lo.is_negative() != val_hi.is_negative() || val_lo.is_zero() || val_hi.is_zero(),
1051 "expected sign change: p(lo)={:?}, p(hi)={:?}",
1052 val_lo,
1053 val_hi
1054 );
1055
1056 assert!(
1058 sum.lower < rat(4) && sum.upper > rat(3),
1059 "interval [{:?}, {:?}] should contain 3.146",
1060 sum.lower,
1061 sum.upper
1062 );
1063 }
1064
1065 #[test]
1068 fn test_mul_irrational_product_polynomial_root() {
1069 let a = sqrt2();
1070 let b = sqrt3();
1071 let mut prod = a.mul(&b).expect("√2 * √3 should succeed");
1072
1073 assert!(
1075 prod.lower < num_rational::BigRational::new(BigInt::from(3), BigInt::from(1)),
1076 "lower bound should be < 3"
1077 );
1078 assert!(
1079 prod.upper > num_rational::BigRational::new(BigInt::from(2), BigInt::from(1)),
1080 "upper bound should be > 2"
1081 );
1082
1083 let p = |x: &num_rational::BigRational| x.clone() * x.clone() - rat(6);
1085
1086 let tight = num_rational::BigRational::new(BigInt::from(1), BigInt::from(1 << 20));
1087 prod.refine_to_precision(&tight);
1088
1089 let val_lo = p(&prod.lower);
1090 let val_hi = p(&prod.upper);
1091 assert!(
1092 val_lo.is_negative() != val_hi.is_negative() || val_lo.is_zero() || val_hi.is_zero(),
1093 "expected sign change for √6: p(lo)={:?}, p(hi)={:?}",
1094 val_lo,
1095 val_hi
1096 );
1097 }
1098
1099 #[test]
1101 fn test_add_rational_plus_irrational() {
1102 let one = AlgebraicNumber::from_rational(rat(1));
1103 let s2 = sqrt2();
1104 let result = one.add(&s2).expect("1 + √2 should succeed");
1105 assert!(
1107 result.lower < rat(3) && result.upper > rat(2),
1108 "1 + √2 expected in (2,3), got [{:?}, {:?}]",
1109 result.lower,
1110 result.upper
1111 );
1112 }
1113
1114 #[test]
1116 fn test_poly_substitute_affine_t() {
1117 let q = Polynomial::new(vec![rat(-1), rat(1)]);
1120 let shifted = poly_substitute_affine_t(&q, &rat(3));
1121 assert_eq!(shifted.eval(&rat(0)), rat(2), "q(3-0) = 2");
1122 assert_eq!(shifted.eval(&rat(1)), rat(1), "q(3-1) = 2 - 1 = 1");
1123 }
1124
1125 #[test]
1126 fn test_univariate_resultant_linear() {
1127 let p = Polynomial::new(vec![rat(-3), rat(1)]); let q = Polynomial::new(vec![rat(-5), rat(1)]); let res = univariate_resultant(&p, &q);
1131 assert!(
1133 !res.is_zero(),
1134 "resultant of coprime linear polys should be nonzero"
1135 );
1136 }
1137
1138 #[test]
1139 fn test_lagrange_interpolate_linear() {
1140 use num_bigint::BigInt;
1141 let points = vec![
1142 num_rational::BigRational::from(BigInt::from(0)),
1143 num_rational::BigRational::from(BigInt::from(1)),
1144 ];
1145 let values = vec![
1146 num_rational::BigRational::from(BigInt::from(3)),
1147 num_rational::BigRational::from(BigInt::from(5)),
1148 ];
1149 let poly = lagrange_interpolate(&points, &values);
1151 assert_eq!(poly.eval(&points[0]), values[0]);
1152 assert_eq!(poly.eval(&points[1]), values[1]);
1153 }
1154}