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