use std::cmp::max;
use std::mem::swap;
use crate::algorithms::int_factor::is_prime_power;
use crate::algorithms::matmul::strassen::{dispatch_strassen_impl, strassen_mem_size};
use crate::algorithms::poly_div::{PolyDivRemReducedError, poly_div_rem_finite_reduced};
use crate::computation::*;
use crate::divisibility::*;
use crate::field::*;
use crate::homomorphism::*;
use crate::integer::*;
use crate::matrix::*;
use crate::pid::*;
use crate::primitive_int::*;
use crate::ring::*;
use crate::rings::finite::*;
use crate::rings::poly::*;
#[stability::unstable(feature = "enable")]
pub fn poly_power_decomposition_finite_field<P, Controller>(
poly_ring: P,
poly: &El<P>,
controller: Controller,
) -> Vec<(El<P>, usize)>
where
P: RingStore + Copy,
P::Type: PolyRing + EuclideanRing,
<<P::Type as RingExtension>::BaseRing as RingStore>::Type: FiniteRing + Field,
Controller: ComputationController,
{
assert!(!poly_ring.is_zero(poly));
let squarefree_part = poly_squarefree_part_finite_field(poly_ring, poly, controller.clone());
if poly_ring.degree(&squarefree_part).unwrap() == poly_ring.degree(poly).unwrap() {
return vec![(squarefree_part, 1)];
} else {
let square_part = poly_ring.checked_div(poly, &squarefree_part).unwrap();
let square_part_decomposition = poly_power_decomposition_finite_field(poly_ring, &square_part, controller);
let mut result = square_part_decomposition;
let mut degree = 0;
for (g, k) in &mut result {
*k += 1;
degree += poly_ring.degree(g).unwrap() * *k;
}
if degree != poly_ring.degree(poly).unwrap() {
let remaining_part = poly_ring
.checked_div(
poly,
&poly_ring.prod(result.iter().map(|(g, e)| poly_ring.pow(poly_ring.clone_el(g), *e))),
)
.unwrap();
result.insert(0, (poly_ring.normalize(remaining_part), 1));
}
return result;
}
}
#[stability::unstable(feature = "enable")]
pub fn poly_squarefree_part_finite_field<P, Controller>(poly_ring: P, poly: &El<P>, controller: Controller) -> El<P>
where
P: RingStore,
P::Type: PolyRing + PrincipalIdealRing,
<<P::Type as RingExtension>::BaseRing as RingStore>::Type: FiniteRing + Field,
Controller: ComputationController,
{
assert!(!poly_ring.is_zero(poly));
if poly_ring.degree(poly).unwrap() == 0 {
return poly_ring.one();
}
let derivate = derive_poly(&poly_ring, poly);
if poly_ring.is_zero(&derivate) {
let q = poly_ring.base_ring().size(&BigIntRing::RING).unwrap();
let (p, e) = is_prime_power(BigIntRing::RING, &q).unwrap();
let p = int_cast(p, StaticRing::<i64>::RING, BigIntRing::RING) as usize;
assert!(p > 0);
let undo_frobenius = Frobenius::new(poly_ring.base_ring(), e - 1);
let base_poly = poly_ring.from_terms(poly_ring.terms(poly).map(|(c, i)| {
debug_assert!(i % p == 0);
(undo_frobenius.map_ref(c), i / p)
}));
return poly_squarefree_part_finite_field(poly_ring, &base_poly, controller);
} else {
let square_part = poly_ring.ideal_gen(poly, &derivate);
let result = poly_ring.checked_div(poly, &square_part).unwrap();
return poly_ring.normalize(result);
}
}
const FAST_POLY_EEA_THRESHOLD: usize = 32;
fn partial_eea<P>(ring: P, lhs: El<P>, rhs: El<P>, target_deg: usize) -> ([El<P>; 4], [El<P>; 2])
where
P: RingStore + Copy,
P::Type: PolyRing + EuclideanRing,
<<P::Type as RingExtension>::BaseRing as RingStore>::Type: Field,
{
if ring.is_zero(&lhs) || ring.is_zero(&rhs) {
return ([ring.one(), ring.zero(), ring.zero(), ring.one()], [lhs, rhs]);
}
let (mut a, mut b) = (ring.clone_el(&lhs), ring.clone_el(&rhs));
let (mut sa, mut ta) = (ring.one(), ring.zero());
let (mut sb, mut tb) = (ring.zero(), ring.one());
if ring.degree(&a).unwrap() < ring.degree(&b).unwrap() {
swap(&mut a, &mut b);
swap(&mut sa, &mut sb);
swap(&mut ta, &mut tb);
}
while ring.degree(&a).unwrap() > target_deg && !ring.is_zero(&b) {
debug_assert!(ring.eq_el(&a, &ring.add(ring.mul_ref(&sa, &lhs), ring.mul_ref(&ta, &rhs))));
debug_assert!(ring.eq_el(&b, &ring.add(ring.mul_ref(&sb, &lhs), ring.mul_ref(&tb, &rhs))));
let (quo, rem) = ring.euclidean_div_rem(a, &b);
ta = ring.sub(ta, ring.mul_ref(&quo, &tb));
sa = ring.sub(sa, ring.mul_ref_snd(quo, &sb));
a = rem;
swap(&mut a, &mut b);
swap(&mut sa, &mut sb);
swap(&mut ta, &mut tb);
debug_assert_eq!(
ring.degree(&sb).unwrap(),
ring.degree(&rhs).unwrap() - ring.degree(&a).unwrap()
);
debug_assert_eq!(
ring.degree(&tb).unwrap(),
ring.degree(&lhs).unwrap() - ring.degree(&a).unwrap()
);
}
return ([sa, ta, sb, tb], [a, b]);
}
#[stability::unstable(feature = "enable")]
pub fn fast_poly_eea<P, Controller>(
poly_ring: P,
lhs: El<P>,
rhs: El<P>,
controller: Controller,
) -> (El<P>, El<P>, El<P>)
where
P: RingStore + Copy,
P::Type: PolyRing + EuclideanRing,
<<P::Type as RingExtension>::BaseRing as RingStore>::Type: Field,
Controller: ComputationController,
{
fn fast_poly_eea_impl<P, Controller>(
poly_ring: P,
lhs: El<P>,
rhs: El<P>,
target_deg: usize,
controller: Controller,
memory: &mut [El<P>],
) -> ([El<P>; 4], [El<P>; 2])
where
P: RingStore + Copy,
P::Type: PolyRing + EuclideanRing,
<<P::Type as RingExtension>::BaseRing as RingStore>::Type: Field,
Controller: ComputationController,
{
if poly_ring.is_zero(&lhs) || poly_ring.is_zero(&rhs) {
return (
[poly_ring.one(), poly_ring.zero(), poly_ring.zero(), poly_ring.one()],
[lhs, rhs],
);
}
let ldeg = poly_ring.degree(&lhs).unwrap();
let rdeg = poly_ring.degree(&rhs).unwrap();
if ldeg < target_deg + FAST_POLY_EEA_THRESHOLD || rdeg < target_deg + FAST_POLY_EEA_THRESHOLD {
log_progress!(controller, ".");
return partial_eea(poly_ring, lhs, rhs, target_deg);
} else if ldeg >= 2 * rdeg {
let (mut q, r) = poly_ring.euclidean_div_rem(lhs, &rhs);
poly_ring.negate_inplace(&mut q);
let (transform, rest) = fast_poly_eea_impl(poly_ring, r, rhs, target_deg, controller, memory);
let mut transform: (_, _, _, _) = transform.into();
transform.1 = poly_ring.fma(&q, &transform.0, transform.1);
transform.3 = poly_ring.fma(&q, &transform.2, transform.3);
return (transform.into(), rest);
} else if rdeg >= 2 * ldeg {
let (transform, rest) = fast_poly_eea_impl(poly_ring, rhs, lhs, target_deg, controller, memory);
let transform: (_, _, _, _) = transform.into();
return ([transform.1, transform.0, transform.3, transform.2], rest);
}
let split_deg = max(ldeg, rdeg) / 3;
assert!(2 * split_deg + 1 < max(ldeg, rdeg));
let part_target_deg = max(split_deg, target_deg.saturating_sub(split_deg));
let lhs_upper = poly_ring.from_terms(
poly_ring
.terms(&lhs)
.filter(|(_, i)| *i >= split_deg)
.map(|(c, i)| (poly_ring.base_ring().clone_el(c), i - split_deg)),
);
let mut lhs_lower = lhs;
poly_ring.truncate_monomials(&mut lhs_lower, split_deg);
let rhs_upper = poly_ring.from_terms(
poly_ring
.terms(&rhs)
.filter(|(_, i)| *i >= split_deg)
.map(|(c, i)| (poly_ring.base_ring().clone_el(c), i - split_deg)),
);
let mut rhs_lower = rhs;
poly_ring.truncate_monomials(&mut rhs_lower, split_deg);
log_progress!(
controller,
"({},{})",
max(
poly_ring.degree(&lhs_upper).unwrap(),
poly_ring.degree(&rhs_upper).unwrap()
),
part_target_deg
);
let (fst_transform, [mut lhs_rest, mut rhs_rest]) = fast_poly_eea_impl(
poly_ring,
lhs_upper,
rhs_upper,
part_target_deg,
controller.clone(),
memory,
);
poly_ring.mul_assign_monomial(&mut lhs_rest, split_deg);
poly_ring.mul_assign_monomial(&mut rhs_rest, split_deg);
lhs_rest = poly_ring.fma(&fst_transform[0], &lhs_lower, lhs_rest);
lhs_rest = poly_ring.fma(&fst_transform[1], &rhs_lower, lhs_rest);
rhs_rest = poly_ring.fma(&fst_transform[2], &lhs_lower, rhs_rest);
rhs_rest = poly_ring.fma(&fst_transform[3], &rhs_lower, rhs_rest);
log_progress!(
controller,
"({},{})",
max(
poly_ring.degree(&lhs_rest).unwrap_or(0),
poly_ring.degree(&rhs_rest).unwrap_or(0)
),
target_deg
);
let (snd_transform, rest) = fast_poly_eea_impl(poly_ring, lhs_rest, rhs_rest, target_deg, controller, memory);
let mut result = [poly_ring.zero(), poly_ring.zero(), poly_ring.zero(), poly_ring.zero()];
dispatch_strassen_impl::<_, _, _, _, false, _, _, _>(
1,
0,
TransposableSubmatrix::from(Submatrix::from_1d(&snd_transform, 2, 2)),
TransposableSubmatrix::from(Submatrix::from_1d(&fst_transform, 2, 2)),
TransposableSubmatrixMut::from(SubmatrixMut::from_1d(&mut result, 2, 2)),
poly_ring,
memory,
);
return (result, rest);
}
if poly_ring.is_zero(&lhs) {
return (poly_ring.zero(), poly_ring.one(), rhs);
} else if poly_ring.is_zero(&rhs) {
return (poly_ring.one(), poly_ring.zero(), lhs);
}
controller.run_computation(
format_args!(
"fast_poly_eea(ldeg={}, rdeg={})",
poly_ring.degree(&lhs).unwrap(),
poly_ring.degree(&rhs).unwrap()
),
|controller| {
let ([s1, t1, s2, t2], [a1, a2]) = fast_poly_eea_impl(
poly_ring,
lhs,
rhs,
0,
controller,
&mut (0..strassen_mem_size(false, 2, 0))
.map(|_| poly_ring.zero())
.collect::<Vec<_>>(),
);
if poly_ring.is_zero(&a1) {
return (s2, t2, a2);
} else {
assert!(poly_ring.is_zero(&a2));
return (s1, t1, a1);
}
},
)
}
#[stability::unstable(feature = "enable")]
pub fn poly_gcd_finite_reduced<P>(
poly_ring: P,
mut lhs: El<P>,
mut rhs: El<P>,
) -> Result<El<P>, El<<P::Type as RingExtension>::BaseRing>>
where
P: RingStore + Copy,
P::Type: PolyRing,
<<P::Type as RingExtension>::BaseRing as RingStore>::Type: FiniteRing + PrincipalIdealRing,
{
while !poly_ring.is_zero(&rhs) {
match poly_div_rem_finite_reduced(poly_ring, poly_ring.clone_el(&lhs), &rhs) {
Ok((_q, r)) => {
lhs = r;
std::mem::swap(&mut lhs, &mut rhs);
}
Err(PolyDivRemReducedError::NotReduced(nilpotent)) => return Err(nilpotent),
Err(PolyDivRemReducedError::NotDivisibleByContent(content_rhs)) => {
let content_ann = poly_ring.base_ring().annihilator(&content_rhs);
if !poly_ring
.base_ring()
.is_unit(&poly_ring.base_ring().ideal_gen(&content_rhs, &content_ann))
{
return Err(poly_ring
.base_ring()
.annihilator(&poly_ring.base_ring().ideal_gen(&content_rhs, &content_ann)));
}
let mod_content_gcd =
poly_gcd_finite_reduced(poly_ring, poly_ring.inclusion().mul_ref_map(&lhs, &content_rhs), rhs)?;
debug_assert!(
poly_ring
.terms(&mod_content_gcd)
.all(|(c, _)| poly_ring.base_ring().divides(c, &content_rhs))
);
return Ok(poly_ring.add(poly_ring.inclusion().mul_ref_map(&lhs, &content_ann), mod_content_gcd));
}
}
}
return Ok(lhs);
}
#[cfg(test)]
use crate::algorithms::poly_div::poly_checked_div_finite_reduced;
#[cfg(test)]
use crate::rings::poly::dense_poly::DensePolyRing;
#[cfg(test)]
use crate::rings::zn::zn_64;
#[cfg(test)]
use crate::rings::zn::zn_rns::*;
#[cfg(test)]
use crate::rings::zn::*;
#[cfg(test)]
use crate::seq::VectorView;
#[test]
fn test_poly_gcd_finite_reduced() {
let base_ring = Zn::new(
[5, 7, 11, 13]
.into_iter()
.map(|p| zn_64::Zn::new(p).as_field().ok().unwrap())
.collect(),
StaticRing::<i64>::RING,
);
let poly_ring = DensePolyRing::new(&base_ring, "X");
let component_poly_rings: [_; 4] = std::array::from_fn(|i| DensePolyRing::new(base_ring.at(i), "X"));
let [f0, g0, expected0] = component_poly_rings[0].with_wrapped_indeterminate(|X| {
[
(X.pow_ref(2) + 2) * (X.pow_ref(3) + X + 1),
(X + 1) * (X + 2) * (X.pow_ref(3) + X + 1),
X.pow_ref(3) + X + 1,
]
});
let f1 = component_poly_rings[1].zero();
let [g1, expected1] =
component_poly_rings[1].with_wrapped_indeterminate(|X| [X.pow_ref(3) + X + 1, X.pow_ref(3) + X + 1]);
let f2 = component_poly_rings[2].int_hom().map(1);
let g2 = component_poly_rings[2].zero();
let expected2 = component_poly_rings[2].one();
let f3 = component_poly_rings[3].zero();
let g3 = component_poly_rings[3].zero();
let expected3 = component_poly_rings[3].zero();
fn reconstruct<'a, 'b, R>(
polys: [El<DensePolyRing<&'a R>>; 4],
poly_rings: &[DensePolyRing<&'a R>; 4],
poly_ring: &DensePolyRing<&'b Zn<R, StaticRing<i64>>>,
) -> El<DensePolyRing<&'b Zn<R, StaticRing<i64>>>>
where
R: RingStore,
R::Type: ZnRing + CanHomFrom<StaticRingBase<i64>>,
{
poly_ring.from_terms((0..10).map(|i| {
(
poly_ring.base_ring().from_congruence(
polys
.iter()
.zip(poly_rings.iter())
.map(|(f, P)| P.base_ring().clone_el(P.coefficient_at(f, i))),
),
i,
)
}))
}
let f = reconstruct([f0, f1, f2, f3], &component_poly_rings, &poly_ring);
let g = reconstruct([g0, g1, g2, g3], &component_poly_rings, &poly_ring);
let expected = reconstruct(
[expected0, expected1, expected2, expected3],
&component_poly_rings,
&poly_ring,
);
let actual = poly_gcd_finite_reduced(&poly_ring, poly_ring.clone_el(&f), poly_ring.clone_el(&g))
.ok()
.unwrap();
assert!(
poly_checked_div_finite_reduced(&poly_ring, poly_ring.clone_el(&actual), poly_ring.clone_el(&expected))
.ok()
.unwrap()
.is_some()
);
assert!(
poly_checked_div_finite_reduced(&poly_ring, poly_ring.clone_el(&expected), poly_ring.clone_el(&actual))
.ok()
.unwrap()
.is_some()
);
}
#[test]
fn test_partial_eea() {
let field = zn_64::Zn::new(65537).as_field().ok().unwrap();
let poly_ring = DensePolyRing::new(field, "X");
let [f, g] = poly_ring.with_wrapped_indeterminate(|X| {
[
X.pow_ref(9) - X.pow_ref(7) + 3 * X.pow_ref(2) - 1,
X.pow_ref(10) + X.pow_ref(6) + 1,
]
});
for k in (1..10).rev() {
let ([s1, t1, s2, t2], [a, b]) = partial_eea(&poly_ring, poly_ring.clone_el(&f), poly_ring.clone_el(&g), k);
assert_el_eq!(
&poly_ring,
poly_ring.add(poly_ring.mul_ref(&s1, &f), poly_ring.mul_ref(&t1, &g)),
&a
);
assert_el_eq!(
&poly_ring,
poly_ring.add(poly_ring.mul_ref(&s2, &f), poly_ring.mul_ref(&t2, &g)),
&b
);
assert_eq!(k, poly_ring.degree(&a).unwrap());
assert_eq!(k - 1, poly_ring.degree(&b).unwrap());
assert!(poly_ring.degree(&s1).is_none() || poly_ring.degree(&s1).unwrap() < 10 - poly_ring.degree(&a).unwrap());
assert!(poly_ring.degree(&t1).is_none() || poly_ring.degree(&t1).unwrap() < 9 - poly_ring.degree(&a).unwrap());
assert!(poly_ring.degree(&s2).is_none() || poly_ring.degree(&s2).unwrap() < 10 - poly_ring.degree(&b).unwrap());
assert!(poly_ring.degree(&t2).is_none() || poly_ring.degree(&t2).unwrap() < 9 - poly_ring.degree(&b).unwrap());
}
}
#[test]
fn test_fast_poly_eea() {
let field = zn_64::Zn::new(65537).as_field().ok().unwrap();
let poly_ring = DensePolyRing::new(field, "X");
let [f, g] = poly_ring.with_wrapped_indeterminate(|X| {
[
X.pow_ref(90) - X.pow_ref(70) + 3 * X.pow_ref(20) - 1,
X.pow_ref(100) + X.pow_ref(60) + 1,
]
});
let (s, t, d) = fast_poly_eea(&poly_ring, poly_ring.clone_el(&f), poly_ring.clone_el(&g), LOG_PROGRESS);
assert!(poly_ring.is_unit(&d));
assert_el_eq!(
&poly_ring,
&d,
poly_ring.add(poly_ring.mul_ref(&s, &f), poly_ring.mul_ref(&t, &g))
);
let [f, g] = poly_ring.with_wrapped_indeterminate(|X| {
[
X.pow_ref(9) - X.pow_ref(7) + 3 * X.pow_ref(2) - 1,
X.pow_ref(100) + X.pow_ref(60) + 1,
]
});
let (s, t, d) = fast_poly_eea(&poly_ring, poly_ring.clone_el(&f), poly_ring.clone_el(&g), LOG_PROGRESS);
assert!(poly_ring.is_unit(&d));
assert_el_eq!(
&poly_ring,
&d,
poly_ring.add(poly_ring.mul_ref(&s, &f), poly_ring.mul_ref(&t, &g))
);
}