1use ocas_domain::{Domain, EuclideanDomain};
8
9const KARATSUBA_THRESHOLD: usize = 32;
11
12#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct DenseUnivariatePolynomial<D: Domain> {
39 coeffs: Vec<D::Element>,
42 domain: D,
45}
46
47impl<D: Domain> DenseUnivariatePolynomial<D> {
48 pub fn new(domain: D) -> Self {
50 Self {
51 coeffs: Vec::new(),
52 domain,
53 }
54 }
55
56 pub fn from_coeffs(domain: D, coeffs: Vec<D::Element>) -> Self {
75 let mut poly = Self { coeffs, domain };
76 poly.trim_trailing_zeros();
77 poly
78 }
79
80 pub fn domain(&self) -> &D {
82 &self.domain
83 }
84
85 pub fn coeffs(&self) -> &[D::Element] {
87 &self.coeffs
88 }
89
90 pub fn is_zero(&self) -> bool {
92 self.coeffs.is_empty()
93 }
94
95 pub fn degree(&self) -> Option<usize> {
97 self.coeffs.len().checked_sub(1)
98 }
99
100 pub fn coeff(&self, n: usize) -> Option<&D::Element> {
102 self.coeffs.get(n)
103 }
104
105 pub fn leading_coeff(&self) -> Option<&D::Element> {
107 self.coeffs.last()
108 }
109
110 pub fn lcoeff(&self) -> D::Element {
113 self.leading_coeff()
114 .cloned()
115 .unwrap_or_else(|| self.domain.zero())
116 }
117
118 pub fn constant(&self) -> D::Element {
121 self.coeff(0).cloned().unwrap_or_else(|| self.domain.zero())
122 }
123
124 pub fn zero(&self) -> Self {
126 Self::new(self.domain.clone())
127 }
128
129 pub fn one(&self) -> Self {
131 Self::from_coeffs(self.domain.clone(), vec![self.domain.one()])
132 }
133
134 pub fn is_one(&self) -> bool {
136 self.coeffs.len() == 1 && self.domain.is_one(&self.coeffs[0])
137 }
138
139 pub fn neg(&self) -> Self {
141 let coeffs = self.coeffs.iter().map(|c| self.domain.neg(c)).collect();
142 Self::from_coeffs(self.domain.clone(), coeffs)
143 }
144
145 pub fn add(&self, other: &Self) -> Self {
147 let len = self.coeffs.len().max(other.coeffs.len());
148 let mut coeffs = Vec::with_capacity(len);
149 let zero = self.domain.zero();
150 for i in 0..len {
151 let a = self.coeffs.get(i).unwrap_or(&zero);
152 let b = other.coeffs.get(i).unwrap_or(&zero);
153 coeffs.push(self.domain.add(a, b));
154 }
155 Self::from_coeffs(self.domain.clone(), coeffs)
156 }
157
158 pub fn sub(&self, other: &Self) -> Self {
160 let len = self.coeffs.len().max(other.coeffs.len());
161 let mut coeffs = Vec::with_capacity(len);
162 let zero = self.domain.zero();
163 for i in 0..len {
164 let a = self.coeffs.get(i).unwrap_or(&zero);
165 let b = other.coeffs.get(i).unwrap_or(&zero);
166 coeffs.push(self.domain.sub(a, b));
167 }
168 Self::from_coeffs(self.domain.clone(), coeffs)
169 }
170
171 pub fn mul_scalar(&self, scalar: &D::Element) -> Self {
173 if self.domain.is_zero(scalar) {
174 return self.zero();
175 }
176 let coeffs = self
177 .coeffs
178 .iter()
179 .map(|c| self.domain.mul(c, scalar))
180 .collect();
181 Self::from_coeffs(self.domain.clone(), coeffs)
182 }
183
184 pub fn mul(&self, other: &Self) -> Self {
205 if self.is_zero() || other.is_zero() {
206 return self.zero();
207 }
208 let mut buf = Vec::new();
209 self.mul_into(other, &mut buf);
210 Self::from_coeffs(self.domain.clone(), buf)
211 }
212
213 pub fn mul_into(&self, other: &Self, buf: &mut Vec<D::Element>) {
221 if self.is_zero() || other.is_zero() {
222 buf.clear();
223 return;
224 }
225 if self.coeffs.len().min(other.coeffs.len()) >= KARATSUBA_THRESHOLD {
226 Self::karatsuba_mul_into(&self.coeffs, &other.coeffs, &self.domain, buf);
227 } else {
228 Self::schoolbook_mul_into(&self.coeffs, &other.coeffs, &self.domain, buf);
229 }
230 }
231
232 fn schoolbook_mul_into(
234 a: &[D::Element],
235 b: &[D::Element],
236 domain: &D,
237 buf: &mut Vec<D::Element>,
238 ) {
239 let result_len = a.len() + b.len() - 1;
240 buf.clear();
241 buf.resize(result_len, domain.zero());
242 for (i, ai) in a.iter().enumerate() {
243 for (j, bj) in b.iter().enumerate() {
244 let prod = domain.mul(ai, bj);
245 buf[i + j] = domain.add(&buf[i + j], &prod);
246 }
247 }
248 }
249
250 fn karatsuba_mul_into(
262 a: &[D::Element],
263 b: &[D::Element],
264 domain: &D,
265 buf: &mut Vec<D::Element>,
266 ) {
267 let n = a.len().max(b.len());
268 if n < KARATSUBA_THRESHOLD {
269 Self::schoolbook_mul_into(a, b, domain, buf);
270 return;
271 }
272
273 let m = n / 2;
274 let zero = domain.zero();
275
276 let (a0, a1) = if a.len() <= m {
278 (a, &[][..])
279 } else {
280 (&a[..m], &a[m..])
281 };
282 let (b0, b1) = if b.len() <= m {
283 (b, &[][..])
284 } else {
285 (&b[..m], &b[m..])
286 };
287
288 let mut z0 = Vec::new();
290 Self::karatsuba_mul_into(a0, b0, domain, &mut z0);
291
292 let mut z2 = Vec::new();
294 Self::karatsuba_mul_into(a1, b1, domain, &mut z2);
295
296 let a01_len = a0.len().max(a1.len());
298 let b01_len = b0.len().max(b1.len());
299 let mut a01 = vec![zero.clone(); a01_len];
300 let mut b01 = vec![zero.clone(); b01_len];
301 for (i, a01_val) in a01.iter_mut().enumerate() {
302 let ai = a0.get(i).unwrap_or(&zero);
303 let aj = a1.get(i).unwrap_or(&zero);
304 *a01_val = domain.add(ai, aj);
305 }
306 for (i, b01_val) in b01.iter_mut().enumerate() {
307 let bi = b0.get(i).unwrap_or(&zero);
308 let bj = b1.get(i).unwrap_or(&zero);
309 *b01_val = domain.add(bi, bj);
310 }
311
312 let mut z1 = Vec::new();
314 Self::karatsuba_mul_into(&a01, &b01, domain, &mut z1);
315 for (i, z1_val) in z1.iter_mut().enumerate() {
316 let z0i = z0.get(i).unwrap_or(&zero);
317 let z2i = z2.get(i).unwrap_or(&zero);
318 *z1_val = domain.sub(z1_val, z0i);
319 *z1_val = domain.sub(z1_val, z2i);
320 }
321
322 let result_len = a.len() + b.len() - 1;
324 buf.clear();
325 buf.resize(result_len, zero);
326
327 for (i, c) in z0.iter().enumerate() {
328 buf[i] = domain.add(&buf[i], c);
329 }
330 for (i, c) in z1.iter().enumerate() {
331 let idx = i + m;
332 if idx < buf.len() {
333 buf[idx] = domain.add(&buf[idx], c);
334 }
335 }
336 for (i, c) in z2.iter().enumerate() {
337 let idx = i + 2 * m;
338 if idx < buf.len() {
339 buf[idx] = domain.add(&buf[idx], c);
340 }
341 }
342 }
343
344 pub fn eval(&self, x: &D::Element) -> D::Element {
363 let mut result = self.domain.zero();
364 for coeff in self.coeffs.iter().rev() {
365 result = self.domain.mul(&result, x);
366 result = self.domain.add(&result, coeff);
367 }
368 result
369 }
370
371 pub fn derivative(&self) -> Self {
376 if self.degree().is_none() {
377 return self.zero();
378 }
379 let mut coeffs = Vec::with_capacity(self.coeffs.len().saturating_sub(1));
380 for (i, c) in self.coeffs.iter().enumerate().skip(1) {
381 let scalar = self.domain.cast_u64(i as u64);
382 coeffs.push(self.domain.mul(c, &scalar));
383 }
384 Self::from_coeffs(self.domain.clone(), coeffs)
385 }
386
387 pub fn integral(&self) -> Self {
392 if self.is_zero() {
393 return self.zero();
394 }
395 let mut coeffs = Vec::with_capacity(self.coeffs.len() + 1);
396 coeffs.push(self.domain.zero());
397 for (i, c) in self.coeffs.iter().enumerate() {
398 let denom = self.domain.cast_u64((i + 1) as u64);
399 let inv = self
400 .domain
401 .inv(&denom)
402 .unwrap_or_else(|| self.domain.zero());
403 coeffs.push(self.domain.mul(c, &inv));
404 }
405 Self::from_coeffs(self.domain.clone(), coeffs)
406 }
407}
408
409impl<D: EuclideanDomain> DenseUnivariatePolynomial<D> {
410 pub fn mul_coeff(&self, c: &D::Element) -> Self {
415 self.mul_scalar(c)
416 }
417
418 pub fn div_coeff(&self, c: &D::Element) -> Self {
422 let inv = self.domain.inv(c).expect("div_coeff: cannot invert zero");
423 self.mul_scalar(&inv)
424 }
425
426 pub fn div_rem(&self, divisor: &Self) -> Option<(Self, Self)> {
450 if divisor.is_zero() {
451 return None;
452 }
453 if self.is_zero() {
454 return Some((self.zero(), self.zero()));
455 }
456 let mut remainder = self.clone();
457 let mut quotient_coeffs: Vec<D::Element> = Vec::new();
458 let domain = self.domain.clone();
459 let divisor_degree = divisor.degree().unwrap_or(0);
460 let divisor_lc = divisor.leading_coeff().unwrap().clone();
461
462 while let Some(deg) = remainder.degree() {
463 if deg < divisor_degree {
464 break;
465 }
466 let lc = remainder.leading_coeff().unwrap().clone();
467 let (q, _r) = domain.div_rem(&lc, &divisor_lc)?;
468 let term_degree = deg - divisor_degree;
469
470 if term_degree >= quotient_coeffs.len() {
472 quotient_coeffs.resize(term_degree + 1, domain.zero());
473 }
474 quotient_coeffs[term_degree] = domain.add("ient_coeffs[term_degree], &q);
475
476 let mut sub_coeffs = vec![domain.zero(); term_degree];
478 sub_coeffs.extend(divisor.coeffs.iter().map(|c| domain.mul(c, &q)));
479 let sub = Self::from_coeffs(domain.clone(), sub_coeffs);
480 remainder = remainder.sub(&sub);
481
482 if let Some(rem_deg) = remainder.degree() {
484 if rem_deg >= deg {
485 break;
486 }
487 } else {
488 break;
489 }
490 }
491
492 let quotient = Self::from_coeffs(domain, quotient_coeffs);
493 Some((quotient, remainder))
494 }
495
496 pub fn pow(&self, n: u32) -> Self {
502 if n == 0 {
503 return self.one();
504 }
505 let mut base = self.clone();
506 let mut exp = n;
507 let mut result = self.one();
508 while exp > 0 {
509 if exp & 1 == 1 {
510 result = result.mul(&base);
511 }
512 base = base.mul(&base);
513 exp >>= 1;
514 }
515 result
516 }
517
518 pub fn extended_gcd_poly(&self, other: &Self) -> (Self, Self, Self) {
523 let d = self.domain();
524 let mut old_r = self.clone();
525 let mut r = other.clone();
526 let mut old_s = self.one();
527 let mut s = self.zero();
528 let mut old_t = self.zero();
529 let mut t = self.one();
530
531 while !r.is_zero() {
532 let (q, rem) = old_r.div_rem(&r).unwrap_or((old_r.zero(), old_r.clone()));
533 old_r = r;
534 r = rem;
535
536 let new_s = old_s.sub(&q.mul(&s));
537 old_s = s;
538 s = new_s;
539
540 let new_t = old_t.sub(&q.mul(&t));
541 old_t = t;
542 t = new_t;
543 }
544
545 if let Some(lc) = old_r.leading_coeff()
547 && !d.is_one(lc)
548 && let Some(lc_inv) = d.inv(lc)
549 {
550 old_r = old_r.mul_scalar(&lc_inv);
551 old_s = old_s.mul_scalar(&lc_inv);
552 old_t = old_t.mul_scalar(&lc_inv);
553 }
554
555 (old_r, old_s, old_t)
556 }
557
558 pub fn diophantine(polys: &mut [Self], b: &Self) -> Vec<Self> {
572 let n = polys.len();
573 if n == 0 {
574 return Vec::new();
575 }
576 if n == 1 {
577 let (_, r) = b
578 .div_rem(&polys[0])
579 .unwrap_or_else(|| (b.zero(), b.clone()));
580 return vec![r];
581 }
582
583 let mut suffix: Vec<Self> = Vec::with_capacity(n);
585 let mut prod = polys[n - 1].one(); for i in (0..n - 1).rev() {
587 prod = prod.mul(&polys[i + 1]);
588 suffix.push(prod.clone());
589 }
590 suffix.reverse();
591 let mut cur = b.clone();
599 let mut result = Vec::with_capacity(n);
600
601 for i in 0..n {
602 let (g, s, t) = polys[i].extended_gcd_poly(&suffix[i]);
603
604 let ts = t.mul(&cur);
607 let ts_div_g = if g.is_one() {
608 ts
609 } else {
610 ts.div_rem(&g).map(|(q, _)| q).unwrap_or(ts)
611 };
612 let (_, ri) = ts_div_g
613 .div_rem(&polys[i])
614 .unwrap_or_else(|| (ts_div_g.zero(), ts_div_g));
615
616 if i < n - 1 {
618 let ss = s.mul(&cur);
619 let ss_div_g = if g.is_one() {
620 ss
621 } else {
622 ss.div_rem(&g).map(|(q, _)| q).unwrap_or(ss)
623 };
624 let (_, new_cur) = ss_div_g
625 .div_rem(&suffix[i])
626 .unwrap_or_else(|| (ss_div_g.zero(), ss_div_g));
627 cur = new_cur;
628 }
629
630 result.push(ri);
631 }
632
633 result
634 }
635
636 pub fn p_adic_expansion(&self, p: &Self) -> Vec<Self> {
647 let mut result = Vec::new();
648 let mut r = self.clone();
649 while !r.is_zero() {
650 let (q, rem) = match r.div_rem(p) {
651 Some(v) => v,
652 None => break,
653 };
654 result.push(rem);
655 r = q;
656 }
657 result
658 }
659}
660
661impl<D: Domain> DenseUnivariatePolynomial<D> {
662 fn trim_trailing_zeros(&mut self) {
663 while let Some(last) = self.coeffs.last() {
664 if self.domain.is_zero(last) {
665 self.coeffs.pop();
666 } else {
667 break;
668 }
669 }
670 }
671}
672
673#[cfg(feature = "ntt")]
680impl DenseUnivariatePolynomial<ocas_domain::FiniteField> {
681 pub fn mul_ntt(&self, other: &Self, buf: &mut Vec<ocas_domain::FiniteFieldElement>) {
688 if self.is_zero() || other.is_zero() {
689 buf.clear();
690 return;
691 }
692
693 let prime = self.domain.prime();
695 if let Some(result) = crate::ntt::try_ntt_mul_fp(
696 &self
697 .coeffs
698 .iter()
699 .map(|c| c.value().clone())
700 .collect::<Vec<_>>(),
701 &other
702 .coeffs
703 .iter()
704 .map(|c| c.value().clone())
705 .collect::<Vec<_>>(),
706 prime,
707 ) {
708 buf.clear();
709 buf.extend(result.into_iter().map(|v| self.domain.element(v)));
710 return;
711 }
712
713 if self.coeffs.len().min(other.coeffs.len()) >= KARATSUBA_THRESHOLD {
715 Self::karatsuba_mul_into(&self.coeffs, &other.coeffs, &self.domain, buf);
716 } else {
717 Self::schoolbook_mul_into(&self.coeffs, &other.coeffs, &self.domain, buf);
718 }
719 }
720
721 pub fn would_use_ntt(&self, other: &Self) -> bool {
723 let prime = self.domain.prime();
724 crate::ntt::try_ntt_mul_fp(
725 &self
726 .coeffs
727 .iter()
728 .map(|c| c.value().clone())
729 .collect::<Vec<_>>(),
730 &other
731 .coeffs
732 .iter()
733 .map(|c| c.value().clone())
734 .collect::<Vec<_>>(),
735 prime,
736 )
737 .is_some()
738 }
739}
740
741#[cfg(test)]
742mod tests {
743 use super::*;
744 use ocas_domain::{FiniteField, Integer, IntegerDomain, Rational, RationalDomain};
745
746 fn int(i: i64) -> Integer {
747 Integer::from(i)
748 }
749
750 #[test]
751 fn zero_polynomial_has_no_degree() {
752 let domain = IntegerDomain;
753 let p = DenseUnivariatePolynomial::new(domain);
754 assert!(p.is_zero());
755 assert_eq!(p.degree(), None);
756 }
757
758 #[test]
759 fn degree_and_coeffs() {
760 let domain = IntegerDomain;
761 let p = DenseUnivariatePolynomial::from_coeffs(
762 domain,
763 vec![3.into(), 0.into(), 2.into(), 0.into()],
764 );
765 assert_eq!(p.degree(), Some(2));
766 assert_eq!(p.coeff(0).cloned(), Some(3.into()));
767 assert_eq!(p.coeff(2).cloned(), Some(2.into()));
768 assert_eq!(p.coeff(3), None);
769 assert_eq!(p.leading_coeff().cloned(), Some(2.into()));
770 }
771
772 #[test]
773 fn trailing_zeros_are_trimmed() {
774 let domain = IntegerDomain;
775 let p = DenseUnivariatePolynomial::from_coeffs(
776 domain,
777 vec![1.into(), 2.into(), 0.into(), 0.into()],
778 );
779 assert_eq!(p.degree(), Some(1));
780 assert_eq!(p.coeffs().len(), 2);
781 }
782
783 #[test]
784 fn add_polynomials() {
785 let domain = IntegerDomain;
786 let a = DenseUnivariatePolynomial::from_coeffs(domain, vec![1.into(), 2.into()]);
787 let b = DenseUnivariatePolynomial::from_coeffs(domain, vec![3.into(), 0.into(), 4.into()]);
788 let sum = a.add(&b);
789 assert_eq!(sum.coeffs().to_vec(), vec![4.into(), 2.into(), 4.into()]);
790 }
791
792 #[test]
793 fn sub_polynomials() {
794 let domain = IntegerDomain;
795 let a = DenseUnivariatePolynomial::from_coeffs(domain, vec![1.into(), 2.into()]);
796 let b = DenseUnivariatePolynomial::from_coeffs(domain, vec![3.into(), 0.into(), 4.into()]);
797 let diff = a.sub(&b);
798 assert_eq!(
799 diff.coeffs().to_vec(),
800 vec![(-2).into(), 2.into(), (-4).into()]
801 );
802 }
803
804 #[test]
805 fn mul_polynomials() {
806 let domain = IntegerDomain;
807 let a = DenseUnivariatePolynomial::from_coeffs(domain, vec![1.into(), 2.into()]);
809 let b = DenseUnivariatePolynomial::from_coeffs(domain, vec![3.into(), 0.into(), 4.into()]);
810 let prod = a.mul(&b);
811 assert_eq!(
812 prod.coeffs().to_vec(),
813 vec![3.into(), 6.into(), 4.into(), 8.into()]
814 );
815 }
816
817 #[test]
818 fn mul_by_zero_yields_zero() {
819 let domain = IntegerDomain;
820 let a = DenseUnivariatePolynomial::from_coeffs(domain, vec![1.into(), 2.into()]);
821 let zero = DenseUnivariatePolynomial::new(domain);
822 let prod = a.mul(&zero);
823 assert!(prod.is_zero());
824 }
825
826 #[test]
827 fn rational_polynomial_multiplication() {
828 let domain = RationalDomain;
829 let a = DenseUnivariatePolynomial::from_coeffs(
830 domain,
831 vec![Rational::new(1, 2), Rational::new(1, 1)],
832 );
833 let b = DenseUnivariatePolynomial::from_coeffs(
834 domain,
835 vec![Rational::new(2, 1), Rational::new(1, 1)],
836 );
837 let prod = a.mul(&b);
838 assert_eq!(prod.coeff(0).cloned(), Some(Rational::new(1, 1)));
840 assert_eq!(prod.coeff(1).cloned(), Some(Rational::new(5, 2)));
841 assert_eq!(prod.coeff(2).cloned(), Some(Rational::new(1, 1)));
842 }
843
844 #[test]
845 fn finite_field_polynomial_arithmetic() {
846 let domain = FiniteField::new(num_bigint::BigInt::from(7));
847 let a = DenseUnivariatePolynomial::from_coeffs(
848 domain.clone(),
849 vec![domain.element(3), domain.element(1)],
850 );
851 let b = DenseUnivariatePolynomial::from_coeffs(
852 domain.clone(),
853 vec![domain.element(2), domain.element(0), domain.element(1)],
854 );
855 let prod = a.mul(&b);
856 assert_eq!(prod.coeff(0).cloned(), Some(domain.element(6)));
858 assert_eq!(prod.coeff(1).cloned(), Some(domain.element(2)));
859 assert_eq!(prod.coeff(2).cloned(), Some(domain.element(3)));
860 assert_eq!(prod.coeff(3).cloned(), Some(domain.element(1)));
861 }
862
863 #[test]
864 fn evaluate_polynomial() {
865 let domain = IntegerDomain;
866 let p = DenseUnivariatePolynomial::from_coeffs(domain, vec![int(1), int(2), int(3)]);
868 assert_eq!(p.eval(&int(0)), int(1));
869 assert_eq!(p.eval(&int(1)), int(6));
870 assert_eq!(p.eval(&int(2)), int(17));
871 }
872
873 #[test]
874 fn polynomial_derivative() {
875 let domain = IntegerDomain;
876 let p =
878 DenseUnivariatePolynomial::from_coeffs(domain, vec![int(1), int(2), int(3), int(4)]);
879 let dp = p.derivative();
880 assert_eq!(dp.coeffs().to_vec(), vec![int(2), int(6), int(12)]);
881 }
882
883 #[test]
884 fn polynomial_integral() {
885 let domain = RationalDomain;
886 let p = DenseUnivariatePolynomial::from_coeffs(
888 domain,
889 vec![Rational::new(1, 1), Rational::new(2, 1)],
890 );
891 let ip = p.integral();
892 assert_eq!(ip.coeff(0).cloned(), Some(Rational::new(0, 1)));
893 assert_eq!(ip.coeff(1).cloned(), Some(Rational::new(1, 1)));
894 assert_eq!(ip.coeff(2).cloned(), Some(Rational::new(1, 1)));
895 }
896
897 #[test]
898 fn polynomial_division_with_remainder_over_integers() {
899 let domain = IntegerDomain;
900 let dividend = DenseUnivariatePolynomial::from_coeffs(domain, vec![int(1), int(0), int(1)]);
902 let divisor = DenseUnivariatePolynomial::from_coeffs(domain, vec![int(-1), int(1)]);
903 let (q, r) = dividend.div_rem(&divisor).unwrap();
904 assert_eq!(q.coeffs().to_vec(), vec![int(1), int(1)]);
905 assert_eq!(r.coeffs().to_vec(), vec![int(2)]);
906 }
907
908 #[test]
909 fn polynomial_division_exact_over_rationals() {
910 let domain = RationalDomain;
911 let dividend = DenseUnivariatePolynomial::from_coeffs(
913 domain,
914 vec![
915 Rational::new(-1, 1),
916 Rational::new(0, 1),
917 Rational::new(1, 1),
918 ],
919 );
920 let divisor = DenseUnivariatePolynomial::from_coeffs(
921 domain,
922 vec![Rational::new(-1, 1), Rational::new(1, 1)],
923 );
924 let (q, r) = dividend.div_rem(&divisor).unwrap();
925 assert_eq!(q.degree(), Some(1));
926 assert_eq!(q.coeff(0).cloned(), Some(Rational::new(1, 1)));
927 assert_eq!(q.coeff(1).cloned(), Some(Rational::new(1, 1)));
928 assert!(r.is_zero());
929 }
930
931 #[test]
932 fn karatsuba_large_multiplication() {
933 let d = IntegerDomain;
934 let coeffs_a: Vec<Integer> = (0..=100).map(|i| Integer::from(i as i64)).collect();
936 let coeffs_b: Vec<Integer> = (0..=100).map(|i| Integer::from((i + 1) as i64)).collect();
937 let a = DenseUnivariatePolynomial::from_coeffs(d, coeffs_a);
938 let b = DenseUnivariatePolynomial::from_coeffs(d, coeffs_b);
939
940 let c = a.mul(&b);
941
942 assert_eq!(c.degree(), Some(200));
944 assert_eq!(c.leading_coeff(), Some(&Integer::from(10100)));
946 assert_eq!(c.constant(), Integer::from(0));
948 assert_eq!(c.coeff(1).cloned(), Some(Integer::from(1)));
950 }
951
952 #[test]
953 fn karatsuba_cross_check_with_schoolbook() {
954 use ocas_domain::RationalDomain;
955 let d = RationalDomain;
956 let coeffs_a: Vec<Rational> = (0..=50)
958 .map(|i| Rational::new(i as i64, (i + 1) as i64))
959 .collect();
960 let coeffs_b: Vec<Rational> = (0..=50)
961 .map(|i| Rational::new((i + 1) as i64, (i + 2) as i64))
962 .collect();
963 let a = DenseUnivariatePolynomial::from_coeffs(d, coeffs_a.clone());
964 let b = DenseUnivariatePolynomial::from_coeffs(d, coeffs_b.clone());
965
966 let c_karat = a.mul(&b);
967
968 let result_len = coeffs_a.len() + coeffs_b.len() - 1;
970 let mut expected = vec![Rational::new(0, 1); result_len];
971 for (i, ai) in coeffs_a.iter().enumerate() {
972 for (j, bj) in coeffs_b.iter().enumerate() {
973 let prod = d.mul(ai, bj);
974 expected[i + j] = d.add(&expected[i + j], &prod);
975 }
976 }
977
978 assert_eq!(c_karat.coeffs(), &expected[..]);
979 }
980}
981
982#[cfg(test)]
983mod proptests {
984 use super::*;
985 use ocas_domain::{Integer, IntegerDomain};
986 use proptest::prelude::*;
987
988 fn any_int_poly(
989 max_degree: usize,
990 ) -> impl Strategy<Value = DenseUnivariatePolynomial<IntegerDomain>> {
991 prop::collection::vec(any::<i64>(), 0..=max_degree).prop_map(|v| {
992 let domain = IntegerDomain;
993 DenseUnivariatePolynomial::from_coeffs(
994 domain,
995 v.into_iter().map(Integer::from).collect(),
996 )
997 })
998 }
999
1000 proptest! {
1001 #[test]
1002 fn addition_is_commutative(a in any_int_poly(8), b in any_int_poly(8)) {
1003 assert_eq!(a.add(&b), b.add(&a));
1004 }
1005
1006 #[test]
1007 fn multiplication_is_commutative(a in any_int_poly(5), b in any_int_poly(5)) {
1008 assert_eq!(a.mul(&b), b.mul(&a));
1009 }
1010
1011 #[test]
1012 fn derivative_reduces_degree_or_zero(a in any_int_poly(6)) {
1013 let da = a.derivative();
1014 match a.degree() {
1015 None => assert!(da.is_zero()),
1016 Some(0) => assert!(da.is_zero()),
1017 Some(d) => assert!(da.degree().unwrap_or(0) < d),
1018 }
1019 }
1020
1021 #[test]
1022 fn mul_then_div_exact_when_divisor_is_factor(
1023 a in any_int_poly(4),
1024 b in any_int_poly(4),
1025 ) {
1026 prop_assume!(!b.is_zero());
1028 let prod = a.mul(&b);
1029 let (q, r) = prod.div_rem(&b).unwrap();
1030 assert!(r.is_zero());
1031 assert_eq!(q, a);
1032 }
1033 }
1034}