1use num_bigint::BigInt;
16use num_traits::One;
17use ocas_domain::{
18 Domain, FiniteField, FiniteFieldElement, Integer, IntegerDomain, Rational, RationalDomain,
19};
20
21use crate::dense::DenseUnivariatePolynomial;
22use crate::factor::hensel;
23use crate::sparse::{Lex, MonomialOrder, SparseMultivariatePolynomial};
24
25pub type ZMPoly = SparseMultivariatePolynomial<IntegerDomain, Lex>;
27
28pub type QPoly = DenseUnivariatePolynomial<RationalDomain>;
30
31pub type ZPoly = DenseUnivariatePolynomial<IntegerDomain>;
33
34pub type FpPoly = DenseUnivariatePolynomial<FiniteField>;
36
37pub type FpMPoly = SparseMultivariatePolynomial<FiniteField, Lex>;
39
40fn degree_in_var<D: Domain, O: MonomialOrder>(
43 poly: &SparseMultivariatePolynomial<D, O>,
44 var_index: usize,
45) -> usize {
46 poly.terms_ref()
47 .keys()
48 .map(|e| e.get(var_index).copied().unwrap_or(0))
49 .max()
50 .unwrap_or(0)
51}
52
53fn eval_to_univariate(poly: &ZMPoly, y_var: usize, value: &Integer) -> ZPoly {
56 let evaluated = poly.eval(y_var, value);
57 let mut coeffs = Vec::new();
58 for (exp, c) in evaluated.terms_ref() {
59 let idx = exp.first().copied().unwrap_or(0);
60 if idx >= coeffs.len() {
61 coeffs.resize(idx + 1, IntegerDomain.zero());
62 }
63 coeffs[idx] = c.clone();
64 }
65 ZPoly::from_coeffs(IntegerDomain, coeffs)
66}
67
68fn univariate_to_bivariate(g: &ZPoly, n_vars: usize, x_var: usize) -> ZMPoly {
71 let mut terms = Vec::new();
72 for (i, c) in g.coeffs().iter().enumerate() {
73 if !IntegerDomain.is_zero(c) {
74 let mut exp = vec![0usize; n_vars];
75 exp[x_var] = i;
76 terms.push((exp, c.clone()));
77 }
78 }
79 ZMPoly::from_terms(IntegerDomain, n_vars, terms)
80}
81
82fn univariate_times_y_minus_alpha_k(
85 g: &ZPoly,
86 k: usize,
87 alpha: &Integer,
88 n_vars: usize,
89 x_var: usize,
90 y_var: usize,
91) -> ZMPoly {
92 let mut terms = Vec::new();
93 for (i, c) in g.coeffs().iter().enumerate() {
94 if IntegerDomain.is_zero(c) {
95 continue;
96 }
97 for j in 0..=k {
98 let mut exp = vec![0usize; n_vars];
99 exp[x_var] = i;
100 exp[y_var] = j;
101 let sign = if (k - j) % 2 == 0 { 1i64 } else { -1i64 };
102 let binom = Integer::from(binomial(k, j) as i64);
103 let alpha_pow = alpha.pow_u32((k - j) as u32);
104 let sign_int = Integer::from(sign);
105 let coeff = IntegerDomain.mul(&IntegerDomain.mul(&binom, &sign_int), &alpha_pow);
106 let prod = IntegerDomain.mul(c, &coeff);
107 terms.push((exp, prod));
108 }
109 }
110 ZMPoly::from_terms(IntegerDomain, n_vars, terms)
111}
112
113fn binomial(n: usize, k: usize) -> u64 {
115 if k > n {
116 return 0;
117 }
118 if k == 0 || k == n {
119 return 1;
120 }
121 let k = k.min(n - k);
122 let mut num = 1u64;
123 let mut den = 1u64;
124 for i in 0..k {
125 num *= (n - i) as u64;
126 den *= (i + 1) as u64;
127 }
128 num / den
129}
130
131fn derivative_in_var(poly: &ZMPoly, var_index: usize) -> ZMPoly {
133 let mut result = ZMPoly::new(IntegerDomain, poly.n_vars());
134 for (exp, coeff) in poly.terms_ref() {
135 let power = exp.get(var_index).copied().unwrap_or(0);
136 if power == 0 {
137 continue;
138 }
139 let mut new_exp = exp.to_vec();
140 new_exp[var_index] = power - 1;
141 let scalar = IntegerDomain.cast_u64(power as u64);
142 let new_coeff = IntegerDomain.mul(coeff, &scalar);
143 result.set_term_external(new_exp, new_coeff);
144 }
145 result
146}
147
148fn taylor_coeffs_in_y(poly: &ZMPoly, y_var: usize, alpha: &Integer, max_k: usize) -> Vec<ZPoly> {
152 let mut coeffs = Vec::with_capacity(max_k + 1);
153 let mut current = poly.clone();
154 for k in 0..=max_k {
155 let value = eval_to_univariate(¤t, y_var, alpha);
156 coeffs.push(divide_by_k_factorial(value, k));
157 current = derivative_in_var(¤t, y_var);
158 }
159 coeffs
160}
161
162fn divide_by_k_factorial(poly: ZPoly, k: usize) -> ZPoly {
164 let mut fact = BigInt::one();
165 for i in 1..=k {
166 fact *= BigInt::from(i);
167 }
168 let fact_int = Integer::from(fact);
169 let coeffs = poly
170 .coeffs()
171 .iter()
172 .map(|c| IntegerDomain.div(c, &fact_int).unwrap_or_else(|| c.clone()))
173 .collect();
174 ZPoly::from_coeffs(IntegerDomain, coeffs)
175}
176
177fn monic_zpoly(f: &ZPoly) -> ZPoly {
180 if f.is_zero() {
181 return f.clone();
182 }
183 let lc = f.leading_coeff().cloned().unwrap();
184 if lc.is_negative() {
185 f.mul_scalar(&Integer::from(-1))
186 } else {
187 f.clone()
188 }
189}
190
191fn factor_univariate_z(f: &ZPoly) -> Vec<ZPoly> {
194 hensel::factor_primitive(f)
195 .into_iter()
196 .map(|(g, _)| monic_zpoly(&g))
197 .collect()
198}
199
200fn hensel_lift_bivariate(
204 f: &ZMPoly,
205 alpha: &Integer,
206 univariate_factors: &[ZPoly],
207 x_var: usize,
208 y_var: usize,
209) -> Option<Vec<ZMPoly>> {
210 let n_vars = f.n_vars();
211 let d_y = degree_in_var(f, y_var);
212
213 let c_f = taylor_coeffs_in_y(f, y_var, alpha, d_y);
214 let q_factors: Vec<QPoly> = univariate_factors.iter().map(zpoly_to_qpoly).collect();
215 let bezout_q = bezout_coefficients_q(&q_factors);
216
217 let mut lifted: Vec<ZMPoly> = univariate_factors
218 .iter()
219 .map(|g| univariate_to_bivariate(g, n_vars, x_var))
220 .collect();
221
222 for k in 1..=d_y {
223 let mut product = ZMPoly::from_terms(
224 IntegerDomain,
225 n_vars,
226 vec![(vec![0; n_vars], Integer::from(1))],
227 );
228 for g in &lifted {
229 product = product.mul(g);
230 }
231
232 let c_product = taylor_coeffs_in_y(&product, y_var, alpha, d_y);
233 let error = c_f[k].sub(&c_product[k]);
234 let error_q = zpoly_to_qpoly(&error);
235
236 for i in 0..lifted.len() {
237 let delta_q = error_q.mul(&bezout_q[i]);
238 let (_q, remainder_q) = delta_q.div_rem(&q_factors[i]).unwrap();
239 let remainder_z = qpoly_to_zpoly(&remainder_q)?;
240 let correction =
241 univariate_times_y_minus_alpha_k(&remainder_z, k, alpha, n_vars, x_var, y_var);
242 lifted[i] = lifted[i].add(&correction);
243 }
244 }
245
246 Some(lifted)
247}
248
249fn choose_evaluation_points(f: &ZMPoly, y_var: usize) -> Vec<(Integer, Vec<ZPoly>)> {
257 let candidates: [i64; 11] = [0, 1, -1, 2, -2, 3, -3, 4, -4, 5, -5];
258 let mut best: Vec<(Integer, Vec<ZPoly>)> = Vec::new();
259 for alpha in candidates {
260 let alpha_int = Integer::from(alpha);
261 let image = eval_to_univariate(f, y_var, &alpha_int);
262 if image.degree().unwrap_or(0) < 1 || !image.is_square_free() {
263 continue;
264 }
265 let factors = factor_univariate_z(&image);
266 if factors.len() < 2 {
267 continue;
268 }
269 let insert_pos = best
271 .binary_search_by(|(_, b)| b.len().cmp(&factors.len()))
272 .unwrap_or_else(|e| e);
273 best.insert(insert_pos, (alpha_int, factors));
274 }
275 best
276}
277
278fn is_one_mpoly(f: &ZMPoly) -> bool {
280 f.terms_ref().len() == 1
281 && f.terms_ref()
282 .iter()
283 .next()
284 .map(|(e, c)| e.iter().all(|&p| p == 0) && IntegerDomain.is_one(c))
285 .unwrap_or(false)
286}
287
288fn bivariate_factor_square_free_monic(f: &ZMPoly, x_var: usize, y_var: usize) -> Vec<ZMPoly> {
290 if degree_in_var(f, x_var) == 0 || degree_in_var(f, y_var) == 0 {
291 return vec![f.clone()];
292 }
293
294 let candidates = choose_evaluation_points(f, y_var);
295 if candidates.is_empty() {
296 return vec![f.clone()];
297 }
298
299 for (alpha, mut univariate_factors) in candidates {
300 if univariate_factors.len() <= 1 {
301 continue;
302 }
303
304 univariate_factors.sort_by_key(|b| std::cmp::Reverse(b.degree().unwrap_or(0)));
305
306 let lifted = match hensel_lift_bivariate(f, &alpha, &univariate_factors, x_var, y_var) {
307 Some(v) => v,
308 None => continue,
309 };
310
311 let mut product = ZMPoly::from_terms(
312 IntegerDomain,
313 f.n_vars(),
314 vec![(vec![0; f.n_vars()], Integer::from(1))],
315 );
316 for g in &lifted {
317 product = product.mul(g);
318 }
319 if product == f.clone() || product == f.neg() {
320 return lifted;
321 }
322 }
323
324 vec![f.clone()]
325}
326
327fn lc_x_is_constant(f: &ZMPoly, x_var: usize) -> bool {
330 let deg_x = degree_in_var(f, x_var);
331 if deg_x == 0 {
332 return true;
333 }
334 for exp in f.terms_ref().keys() {
335 if exp.get(x_var).copied().unwrap_or(0) == deg_x {
336 for (i, &e) in exp.iter().enumerate() {
337 if i != x_var && e != 0 {
338 return false;
339 }
340 }
341 }
342 }
343 true
344}
345
346fn bivariate_factor_square_free(f: &ZMPoly, x_var: usize, y_var: usize) -> Vec<ZMPoly> {
349 if !lc_x_is_constant(f, x_var) {
350 return vec![f.clone()];
351 }
352 bivariate_factor_square_free_monic(f, x_var, y_var)
353}
354
355pub fn bivariate_factor_z(f: &ZMPoly, x_var: usize, y_var: usize) -> Vec<(ZMPoly, usize)> {
363 if f.is_zero() || f.total_degree() == Some(0) {
364 return Vec::new();
365 }
366
367 let content = f.content();
368 let mut result = Vec::new();
369 if !IntegerDomain.is_one(&content) {
370 result.push((
371 ZMPoly::from_terms(
372 IntegerDomain,
373 f.n_vars(),
374 vec![(vec![0; f.n_vars()], content)],
375 ),
376 1,
377 ));
378 }
379
380 let primitive = f.primitive_part();
381 if primitive.total_degree() == Some(0) {
382 return result;
383 }
384
385 let sqfree = square_free_factorization_bivariate(&primitive, x_var, y_var);
386 for (g, m) in sqfree {
387 if is_one_mpoly(&g) {
388 continue;
389 }
390 for irr in bivariate_factor_square_free(&g, x_var, y_var) {
391 result.push((irr, m));
392 }
393 }
394
395 result
396}
397
398fn square_free_factorization_bivariate(
401 f: &ZMPoly,
402 x_var: usize,
403 y_var: usize,
404) -> Vec<(ZMPoly, usize)> {
405 let f_deriv = derivative_in_var(f, x_var);
406 let mut g = crate::multivariate_gcd::bivariate_gcd(f, &f_deriv)
407 .unwrap_or_else(|| one_mpoly(f.n_vars()));
408 let mut w = divide_bivariate_by_gcd(f, &g, x_var, y_var);
409
410 let mut result = Vec::new();
411 let mut k = 1usize;
412 while !is_one_mpoly(&w) && w.total_degree() != Some(0) {
413 let h =
414 crate::multivariate_gcd::bivariate_gcd(&w, &g).unwrap_or_else(|| one_mpoly(f.n_vars()));
415 let z = divide_bivariate_by_gcd(&w, &h, x_var, y_var);
416 if !is_one_mpoly(&z) && z.total_degree() != Some(0) {
417 result.push((z, k));
418 }
419 w = h;
420 g = divide_bivariate_by_gcd(&g, &w, x_var, y_var);
421 k += 1;
422 }
423 result
424}
425
426fn one_mpoly(n_vars: usize) -> ZMPoly {
428 ZMPoly::from_terms(
429 IntegerDomain,
430 n_vars,
431 vec![(vec![0; n_vars], Integer::from(1))],
432 )
433}
434
435fn divide_bivariate_by_gcd(a: &ZMPoly, b: &ZMPoly, x_var: usize, y_var: usize) -> ZMPoly {
438 if b.is_zero() || is_one_mpoly(b) {
439 return a.clone();
440 }
441 if a.is_zero() {
442 return a.clone();
443 }
444 let deg_y_a = degree_in_var(a, y_var);
445 let deg_y_b = degree_in_var(b, y_var);
446 let n_points = deg_y_a.max(deg_y_b) + 2;
447
448 let mut images: Vec<(Integer, ZPoly)> = Vec::new();
449 let mut eval_point = Integer::from(0);
450 for _ in 0..n_points + 10 {
451 if images.len() >= n_points {
452 break;
453 }
454 let a_eval = eval_to_univariate(a, y_var, &eval_point);
455 let b_eval = eval_to_univariate(b, y_var, &eval_point);
456 if b_eval.is_zero() || a_eval.is_zero() {
457 eval_point = IntegerDomain.add(&eval_point, &Integer::from(1));
458 continue;
459 }
460 let (q, r) = a_eval.div_rem(&b_eval).unwrap();
461 if !r.is_zero() {
462 eval_point = IntegerDomain.add(&eval_point, &Integer::from(1));
463 continue;
464 }
465 images.push((eval_point.clone(), q));
466 eval_point = IntegerDomain.add(&eval_point, &Integer::from(1));
467 }
468
469 if images.len() < n_points {
470 return a.clone();
471 }
472
473 interpolate_bivariate_quotient(&images, a.n_vars(), x_var, y_var)
474}
475
476fn interpolate_bivariate_quotient(
478 images: &[(Integer, ZPoly)],
479 n_vars: usize,
480 x_var: usize,
481 y_var: usize,
482) -> ZMPoly {
483 let mut result = ZMPoly::new(IntegerDomain, n_vars);
484 if images.is_empty() {
485 return result;
486 }
487 let max_x_deg = images
488 .iter()
489 .map(|(_, g)| g.degree().unwrap_or(0))
490 .max()
491 .unwrap_or(0);
492 for x_pow in 0..=max_x_deg {
493 let mut y_points: Vec<(Integer, Integer)> = Vec::new();
494 for (y_val, g) in images {
495 if let Some(c) = g.coeff(x_pow) {
496 y_points.push((y_val.clone(), c.clone()));
497 }
498 }
499 if y_points.len() < 2 {
500 continue;
501 }
502 let y_poly = lagrange_interpolate(&y_points);
503 for (y_pow, c) in y_poly.coeffs().iter().enumerate() {
504 if !IntegerDomain.is_zero(c) {
505 let mut exp = vec![0; n_vars];
506 exp[x_var] = x_pow;
507 exp[y_var] = y_pow;
508 result.set_term_external(exp, c.clone());
509 }
510 }
511 }
512 result
513}
514
515fn lagrange_interpolate(points: &[(Integer, Integer)]) -> ZPoly {
518 let n = points.len();
519 let mut result = ZPoly::from_coeffs(IntegerDomain, Vec::new());
520 for i in 0..n {
521 let (y_i, v_i) = &points[i];
522 let mut numerator = ZPoly::from_coeffs(IntegerDomain, vec![Integer::from(1)]);
523 let mut denom = Integer::from(1);
524 for (j, (y_j, _v_j)) in points.iter().enumerate().take(n) {
525 if i == j {
526 continue;
527 }
528 let factor = ZPoly::from_coeffs(
529 IntegerDomain,
530 vec![IntegerDomain.neg(y_j), Integer::from(1)],
531 );
532 numerator = numerator.mul(&factor);
533 denom = IntegerDomain.mul(&denom, &IntegerDomain.sub(y_i, y_j));
534 }
535 let q = IntegerDomain
536 .div(v_i, &denom)
537 .expect("lagrange_interpolate: non-exact division");
538 result = result.add(&numerator.mul_scalar(&q));
539 }
540 result
541}
542
543fn zpoly_to_qpoly(f: &ZPoly) -> QPoly {
544 QPoly::from_coeffs(
545 RationalDomain,
546 f.coeffs()
547 .iter()
548 .map(|c| Rational::from_integer(c.clone()))
549 .collect(),
550 )
551}
552
553fn qpoly_to_zpoly(f: &QPoly) -> Option<ZPoly> {
554 let mut coeffs = Vec::new();
555 for r in f.coeffs() {
556 let d = r.denom();
557 if !IntegerDomain.is_one(&d) {
558 return None;
559 }
560 coeffs.push(r.numer());
561 }
562 Some(ZPoly::from_coeffs(IntegerDomain, coeffs))
563}
564
565fn monic_qpoly(f: &QPoly) -> QPoly {
566 if f.is_zero() {
567 return f.clone();
568 }
569 let lc = f.leading_coeff().unwrap();
570 let inv = RationalDomain.inv(lc).unwrap();
571 f.mul_scalar(&inv)
572}
573
574fn extended_gcd_qpoly(a: &QPoly, b: &QPoly) -> (QPoly, QPoly, QPoly) {
575 if b.is_zero() {
576 let monic_a = monic_qpoly(a);
577 let lc = a.leading_coeff().unwrap();
578 let inv = RationalDomain.inv(lc).unwrap();
579 let s = QPoly::from_coeffs(RationalDomain, vec![inv]);
580 return (monic_a, s, QPoly::new(RationalDomain));
581 }
582 if a.degree().unwrap_or(0) < b.degree().unwrap_or(0) {
583 let (g, s, t) = extended_gcd_qpoly(b, a);
584 return (g, t, s);
585 }
586 let (q, r) = a.div_rem(b).expect("Q is a field");
587 let (g, s1, t1) = extended_gcd_qpoly(b, &r);
588 let s = t1.clone();
589 let t = s1.sub(&q.mul(&t1));
590 (g, s, t)
591}
592
593fn bezout_coefficients_q(factors: &[QPoly]) -> Vec<QPoly> {
594 let n = factors.len();
595 if n == 1 {
596 return vec![factors[0].one()];
597 }
598 let mut result = vec![factors[0].zero(); n];
599 result[0] = factors[0].one();
600 let mut accum = factors[0].clone();
601 for i in 1..n {
602 let (_g, s, t) = extended_gcd_qpoly(&accum, &factors[i]);
603 for res in result.iter_mut().take(i) {
604 *res = res.mul(&t);
605 }
606 result[i] = s;
607 accum = accum.mul(&factors[i]);
608 }
609 result
610}
611
612fn eval_to_univariate_fp(poly: &FpMPoly, y_var: usize, value: &FiniteFieldElement) -> FpPoly {
613 let evaluated = poly.eval(y_var, value);
614 let mut coeffs = Vec::new();
615 for (exp, c) in evaluated.terms_ref() {
616 let idx = exp.first().copied().unwrap_or(0);
617 if idx >= coeffs.len() {
618 coeffs.resize(idx + 1, poly.domain().zero());
619 }
620 coeffs[idx] = c.clone();
621 }
622 FpPoly::from_coeffs(poly.domain().clone(), coeffs)
623}
624
625fn univariate_to_bivariate_fp(g: &FpPoly, n_vars: usize, x_var: usize) -> FpMPoly {
626 let mut terms = Vec::new();
627 for (i, c) in g.coeffs().iter().enumerate() {
628 if !g.domain().is_zero(c) {
629 let mut exp = vec![0usize; n_vars];
630 exp[x_var] = i;
631 terms.push((exp, c.clone()));
632 }
633 }
634 FpMPoly::from_terms(g.domain().clone(), n_vars, terms)
635}
636
637fn derivative_in_var_fp(poly: &FpMPoly, var_index: usize) -> FpMPoly {
638 let mut result = FpMPoly::new(poly.domain().clone(), poly.n_vars());
639 for (exp, coeff) in poly.terms_ref() {
640 let power = exp.get(var_index).copied().unwrap_or(0);
641 if power == 0 {
642 continue;
643 }
644 let mut new_exp = exp.to_vec();
645 new_exp[var_index] = power - 1;
646 let scalar = poly.domain().cast_u64(power as u64);
647 let new_coeff = poly.domain().mul(coeff, &scalar);
648 result.set_term_external(new_exp, new_coeff);
649 }
650 result
651}
652
653fn divide_by_k_factorial_fp(poly: FpPoly, k: usize) -> FpPoly {
654 let domain = poly.domain().clone();
655 let mut fact = domain.one();
656 for i in 1..=k {
657 fact = domain.mul(&fact, &domain.cast_u64(i as u64));
658 }
659 let fact_inv = domain.inv(&fact).expect("k! must be invertible mod p");
660 let coeffs = poly
661 .coeffs()
662 .iter()
663 .map(|c| domain.mul(c, &fact_inv))
664 .collect();
665 FpPoly::from_coeffs(domain, coeffs)
666}
667
668fn taylor_coeffs_in_y_fp(
669 poly: &FpMPoly,
670 y_var: usize,
671 alpha: &FiniteFieldElement,
672 max_k: usize,
673) -> Vec<FpPoly> {
674 let mut coeffs = Vec::with_capacity(max_k + 1);
675 let mut current = poly.clone();
676 for k in 0..=max_k {
677 let value = eval_to_univariate_fp(¤t, y_var, alpha);
678 coeffs.push(divide_by_k_factorial_fp(value, k));
679 current = derivative_in_var_fp(¤t, y_var);
680 }
681 coeffs
682}
683
684fn monic_fppoly(f: &FpPoly) -> FpPoly {
685 if f.is_zero() {
686 return f.clone();
687 }
688 let lc = f.leading_coeff().cloned().unwrap();
689 let inv = f.domain().inv(&lc).expect("nonzero leading coefficient");
690 f.mul_scalar(&inv)
691}
692
693fn extended_gcd_fppoly(a: &FpPoly, b: &FpPoly) -> (FpPoly, FpPoly, FpPoly) {
694 if b.is_zero() {
695 return (a.clone(), a.one(), a.zero());
696 }
697 if a.degree().unwrap_or(0) < b.degree().unwrap_or(0) {
698 let (g, s, t) = extended_gcd_fppoly(b, a);
699 return (g, t, s);
700 }
701 let (q, r) = a.div_rem(b).expect("field division");
702 let (g, s1, t1) = extended_gcd_fppoly(b, &r);
703 let s = t1.clone();
704 let t = s1.sub(&q.mul(&t1));
705 (g, s, t)
706}
707
708fn bezout_coefficients_fp(factors: &[FpPoly]) -> Vec<FpPoly> {
709 let n = factors.len();
710 if n == 1 {
711 return vec![factors[0].one()];
712 }
713 let mut result = vec![factors[0].zero(); n];
714 result[0] = factors[0].one();
715 let mut accum = factors[0].clone();
716 for i in 1..n {
717 let (_g, s, t) = extended_gcd_fppoly(&accum, &factors[i]);
718 for res in result.iter_mut().take(i) {
719 *res = res.mul(&t);
720 }
721 result[i] = s;
722 accum = accum.mul(&factors[i]);
723 }
724 result
725}
726
727fn hensel_lift_bivariate_fp(
728 f: &FpMPoly,
729 alpha: &FiniteFieldElement,
730 univariate_factors: &[FpPoly],
731 x_var: usize,
732 y_var: usize,
733) -> Vec<FpMPoly> {
734 let n_vars = f.n_vars();
735 let d_y = degree_in_var(f, y_var);
736
737 let c_f = taylor_coeffs_in_y_fp(f, y_var, alpha, d_y);
738 let bezout = bezout_coefficients_fp(univariate_factors);
739
740 let mut lifted: Vec<FpMPoly> = univariate_factors
741 .iter()
742 .map(|g| univariate_to_bivariate_fp(g, n_vars, x_var))
743 .collect();
744
745 for k in 1..=d_y {
746 let mut product = FpMPoly::from_terms(
747 f.domain().clone(),
748 n_vars,
749 vec![(vec![0; n_vars], f.domain().one())],
750 );
751 for g in &lifted {
752 product = product.mul(g);
753 }
754
755 let c_product = taylor_coeffs_in_y_fp(&product, y_var, alpha, d_y);
756 let error = c_f[k].sub(&c_product[k]);
757
758 for i in 0..lifted.len() {
759 let delta = error.mul(&bezout[i]);
760 let (_q, remainder) = delta.div_rem(&univariate_factors[i]).unwrap();
761 let correction =
762 univariate_times_y_minus_alpha_k_fp(&remainder, k, alpha, n_vars, x_var, y_var);
763 lifted[i] = lifted[i].add(&correction);
764 }
765 }
766
767 lifted
768}
769
770fn univariate_times_y_minus_alpha_k_fp(
771 g: &FpPoly,
772 k: usize,
773 alpha: &FiniteFieldElement,
774 n_vars: usize,
775 x_var: usize,
776 y_var: usize,
777) -> FpMPoly {
778 let domain = g.domain().clone();
779 let mut terms = Vec::new();
780 for (i, c) in g.coeffs().iter().enumerate() {
781 if domain.is_zero(c) {
782 continue;
783 }
784 for j in 0..=k {
785 let mut exp = vec![0usize; n_vars];
786 exp[x_var] = i;
787 exp[y_var] = j;
788 let alpha_pow = domain.pow(alpha, (k - j) as u64);
789 let binom = domain.cast_u64(binomial(k, j));
790 let sign = if (k - j) % 2 == 0 {
791 domain.one()
792 } else {
793 domain.neg(&domain.one())
794 };
795 let coeff = domain.mul(c, &domain.mul(&binom, &domain.mul(&sign, &alpha_pow)));
796 terms.push((exp, coeff));
797 }
798 }
799 FpMPoly::from_terms(domain, n_vars, terms)
800}
801
802fn choose_evaluation_point_fp(
803 f: &FpMPoly,
804 y_var: usize,
805) -> Option<(FiniteFieldElement, Vec<FpPoly>)> {
806 let domain = f.domain().clone();
807 let p = domain.prime().clone();
808 let mut best: Option<(FiniteFieldElement, Vec<FpPoly>)> = None;
809 for a in 0i64..20 {
810 if BigInt::from(a) >= p {
811 break;
812 }
813 let alpha = domain.element(a);
814 let image = eval_to_univariate_fp(f, y_var, &alpha);
815 if image.degree().unwrap_or(0) < 1 || !image.is_square_free() {
816 continue;
817 }
818 let mut factors = image.factor();
819 factors.sort_by(|a, b| b.0.degree().unwrap_or(0).cmp(&a.0.degree().unwrap_or(0)));
820 let factors: Vec<FpPoly> = factors.into_iter().map(|(g, _)| monic_fppoly(&g)).collect();
821 if factors.len() < 2 {
822 continue;
823 }
824 match &best {
825 None => best = Some((alpha.clone(), factors)),
826 Some((_, best_factors)) => {
827 if factors.len() < best_factors.len() {
828 best = Some((alpha.clone(), factors));
829 }
830 }
831 }
832 }
833 best
834}
835
836fn lc_x_is_constant_fp(f: &FpMPoly, x_var: usize) -> bool {
837 let deg_x = degree_in_var(f, x_var);
838 if deg_x == 0 {
839 return true;
840 }
841 for exp in f.terms_ref().keys() {
842 if exp.get(x_var).copied().unwrap_or(0) == deg_x {
843 for (i, &e) in exp.iter().enumerate() {
844 if i != x_var && e != 0 {
845 return false;
846 }
847 }
848 }
849 }
850 true
851}
852
853fn bivariate_factor_square_free_monic_fp(f: &FpMPoly, x_var: usize, y_var: usize) -> Vec<FpMPoly> {
854 if degree_in_var(f, x_var) == 0 || degree_in_var(f, y_var) == 0 {
855 return vec![f.clone()];
856 }
857
858 let (alpha, univariate_factors) = match choose_evaluation_point_fp(f, y_var) {
859 Some(v) => v,
860 None => return vec![f.clone()],
861 };
862
863 if univariate_factors.len() <= 1 {
864 return vec![f.clone()];
865 }
866
867 let lifted = hensel_lift_bivariate_fp(f, &alpha, &univariate_factors, x_var, y_var);
868
869 let mut product = FpMPoly::from_terms(
870 f.domain().clone(),
871 f.n_vars(),
872 vec![(vec![0; f.n_vars()], f.domain().one())],
873 );
874 for g in &lifted {
875 product = product.mul(g);
876 }
877 if product == f.clone() {
878 return lifted;
879 }
880
881 vec![f.clone()]
882}
883
884fn bivariate_factor_square_free_fp(f: &FpMPoly, x_var: usize, y_var: usize) -> Vec<FpMPoly> {
888 if !lc_x_is_constant_fp(f, x_var) {
889 return vec![f.clone()];
890 }
891 bivariate_factor_square_free_monic_fp(f, x_var, y_var)
892}
893
894pub fn bivariate_factor_fp(f: &FpMPoly, x_var: usize, y_var: usize) -> Vec<(FpMPoly, usize)> {
901 if f.is_zero() || f.total_degree() == Some(0) {
902 return Vec::new();
903 }
904
905 let content = f.content();
906 let mut result = Vec::new();
907 if !f.domain().is_one(&content) {
908 result.push((
909 FpMPoly::from_terms(
910 f.domain().clone(),
911 f.n_vars(),
912 vec![(vec![0; f.n_vars()], content)],
913 ),
914 1,
915 ));
916 }
917
918 let primitive = f.primitive_part();
919 if primitive.total_degree() == Some(0) {
920 return result;
921 }
922
923 let deriv = derivative_in_var_fp(&primitive, x_var);
925 if !deriv.is_zero() {
926 for irr in bivariate_factor_square_free_fp(&primitive, x_var, y_var) {
927 result.push((irr, 1));
928 }
929 } else {
930 result.push((primitive, 1));
931 }
932
933 result
934}
935
936#[cfg(test)]
937mod tests {
938 use super::*;
939 use num_bigint::BigInt;
940 use ocas_domain::{FiniteField, Integer};
941
942 fn mpoly_from_str(coeffs: &[((usize, usize), i64)]) -> ZMPoly {
943 let terms: Vec<(Vec<usize>, Integer)> = coeffs
944 .iter()
945 .map(|((x, y), c)| (vec![*x, *y], Integer::from(*c)))
946 .collect();
947 ZMPoly::from_terms(IntegerDomain, 2, terms)
948 }
949
950 fn fpoly_from_str(coeffs: &[((usize, usize), i64)], p: i64) -> FpMPoly {
951 let domain = FiniteField::new(BigInt::from(p));
952 let terms: Vec<(Vec<usize>, FiniteFieldElement)> = coeffs
953 .iter()
954 .map(|((x, y), c)| (vec![*x, *y], domain.element(*c)))
955 .collect();
956 FpMPoly::from_terms(domain, 2, terms)
957 }
958
959 fn one_mpoly_fp(n_vars: usize, domain: &FiniteField) -> FpMPoly {
960 FpMPoly::from_terms(
961 domain.clone(),
962 n_vars,
963 vec![(vec![0; n_vars], domain.one())],
964 )
965 }
966
967 #[test]
968 fn factor_monic_bivariate() {
969 let f = mpoly_from_str(&[
972 ((3, 0), 1),
973 ((2, 1), 1),
974 ((2, 0), 2),
975 ((1, 1), 1),
976 ((1, 0), 1),
977 ((0, 2), 1),
978 ((0, 1), 3),
979 ((0, 0), 2),
980 ]);
981 let factors = bivariate_factor_z(&f, 0, 1);
982 let mut product = one_mpoly(2);
983 for (g, m) in &factors {
984 for _ in 0..*m {
985 product = product.mul(g);
986 }
987 }
988 assert!(
989 product == f || product == f.neg(),
990 "product did not reconstruct f"
991 );
992 assert!(factors.len() >= 2, "expected at least two factors");
993 }
994
995 #[test]
996 #[ignore = "non-monic leading coefficient requires Wang LC handling"]
997 fn factor_textbook_bivariate_non_monic() {
998 let f = mpoly_from_str(&[
1002 ((3, 0), 3),
1003 ((2, 2), 1),
1004 ((2, 0), 7),
1005 ((1, 2), 1),
1006 ((1, 1), 3),
1007 ((1, 0), 7),
1008 ((0, 3), 1),
1009 ((0, 2), 1),
1010 ((0, 1), 4),
1011 ((0, 0), 4),
1012 ]);
1013 let factors = bivariate_factor_z(&f, 0, 1);
1014 let mut product = one_mpoly(2);
1015 for (g, m) in &factors {
1016 for _ in 0..*m {
1017 product = product.mul(g);
1018 }
1019 }
1020 assert!(
1021 product == f || product == f.neg(),
1022 "product did not reconstruct f"
1023 );
1024 assert!(factors.len() >= 2, "expected at least two factors");
1025 }
1026
1027 #[test]
1028 fn factor_monic_bivariate_over_finite_field() {
1029 let f = fpoly_from_str(
1032 &[
1033 ((3, 0), 1),
1034 ((2, 1), 1),
1035 ((2, 0), 2),
1036 ((1, 1), 1),
1037 ((1, 0), 1),
1038 ((0, 2), 1),
1039 ((0, 1), 3),
1040 ((0, 0), 2),
1041 ],
1042 5,
1043 );
1044 let factors = bivariate_factor_fp(&f, 0, 1);
1045 let mut product = one_mpoly_fp(2, f.domain());
1046 for (g, m) in &factors {
1047 for _ in 0..*m {
1048 product = product.mul(g);
1049 }
1050 }
1051 assert_eq!(product, f, "product did not reconstruct f");
1052 assert!(factors.len() >= 2, "expected at least two factors");
1053 }
1054}