use std::marker::PhantomData;
use super::DensePolyRing;
use crate::algorithms::int_factor::is_prime_power;
use crate::computation::{ComputationController, DontObserve};
use crate::divisibility::*;
use crate::homomorphism::*;
use crate::pid::*;
use crate::reduce_lift::poly_factor_gcd::*;
use crate::ring::*;
use crate::rings::poly::*;
use crate::rings::zn::{FromModulusCreateableZnRing, *};
use crate::seq::*;
#[cfg(test)]
fn hensel_lift_linear<'ring, 'data, 'local, R, P1, P2, Controller>(
reduction_map: &PolyGCDLocallyIntermediateReductionMap<'ring, 'data, 'local, R>,
target_poly_ring: P1,
base_poly_ring: P2,
f: &El<P1>,
factors: (&El<P2>, &El<P2>),
controller: Controller,
) -> (El<P1>, El<P1>)
where
R: ?Sized + PolyGCDLocallyDomain,
P1: RingStore,
P1::Type: PolyRing,
<P1::Type as RingExtension>::BaseRing: RingStore<Type = R::LocalRingBase<'ring>>,
P2: RingStore,
P2::Type: PolyRing + PrincipalIdealRing,
<P2::Type as RingExtension>::BaseRing: RingStore<Type = R::LocalFieldBase<'ring>>,
Controller: ComputationController,
{
assert!(target_poly_ring.base_ring().is_one(target_poly_ring.lc(f).unwrap()));
assert!(base_poly_ring.base_ring().is_one(base_poly_ring.lc(factors.0).unwrap()));
assert!(base_poly_ring.base_ring().is_one(base_poly_ring.lc(factors.1).unwrap()));
assert!(target_poly_ring.base_ring().get_ring() == reduction_map.domain().get_ring());
let prime_field = base_poly_ring.base_ring();
let prime_ring = reduction_map.codomain();
let prime_ring_iso = PolyGCDLocallyBaseRingToFieldIso::new(
reduction_map.parent_ring().into(),
reduction_map.ideal(),
prime_ring.get_ring(),
prime_field.get_ring(),
reduction_map.max_ideal_idx(),
);
let (g, h) = factors;
let (mut s, mut t, d) = base_poly_ring.extended_ideal_gen(g, h);
assert!(base_poly_ring.degree(&d).unwrap() == 0);
let d_inv = prime_field.invert(base_poly_ring.coefficient_at(&d, 0)).unwrap();
base_poly_ring.inclusion().mul_assign_ref_map(&mut s, &d_inv);
base_poly_ring.inclusion().mul_assign_map(&mut t, d_inv);
let lift_to_target_poly_ring = |f| {
target_poly_ring.from_terms(base_poly_ring.terms(f).map(|(c, i)| {
(
reduction_map.parent_ring().get_ring().lift_partial(
reduction_map.ideal(),
(reduction_map.codomain().get_ring(), reduction_map.to_e()),
(reduction_map.domain().get_ring(), reduction_map.from_e()),
reduction_map.max_ideal_idx(),
prime_ring_iso.inv().map_ref(c),
),
i,
)
}))
};
let lifted_s = lift_to_target_poly_ring(&s);
let lifted_t = lift_to_target_poly_ring(&t);
let mut current_g = lift_to_target_poly_ring(g);
let mut current_h = lift_to_target_poly_ring(h);
let P = target_poly_ring;
for _ in reduction_map.to_e()..reduction_map.from_e() {
log_progress!(controller, ".");
let delta = P.sub_ref_fst(f, P.mul_ref(¤t_g, ¤t_h));
let mut delta_g = P.mul_ref(&lifted_t, &delta);
let mut delta_h = P.mul_ref(&lifted_s, &delta);
delta_g = P.div_rem_monic(delta_g, ¤t_g).1;
delta_h = P.div_rem_monic(delta_h, ¤t_h).1;
P.add_assign(&mut current_g, delta_g);
P.add_assign(&mut current_h, delta_h);
debug_assert!(P.degree(¤t_g).unwrap() == base_poly_ring.degree(&g).unwrap());
debug_assert!(P.degree(¤t_h).unwrap() == base_poly_ring.degree(&h).unwrap());
}
assert_el_eq!(P, f, P.mul_ref(¤t_g, ¤t_h));
return (current_g, current_h);
}
struct HenselLiftableBarrettReducer<P: ?Sized + PolyRing> {
ring: PhantomData<Box<P>>,
n: usize,
neg_Xn_div_poly: P::Element,
poly: P::Element,
poly_deg: usize,
e: usize,
}
impl<P: ?Sized + PolyRing> HenselLiftableBarrettReducer<P> {
fn div_rem_poly<S>(&self, poly_ring: S, poly: El<S>) -> (El<S>, El<S>)
where
S: RingStore<Type = P>,
{
assert!(poly_ring.degree(&poly).unwrap_or(0) <= self.n);
let scaled_quotient = poly_ring.mul_ref(&poly, &self.neg_Xn_div_poly);
let quotient = poly_ring.from_terms(
poly_ring
.terms(&scaled_quotient)
.filter(|(_, i)| *i >= self.n)
.map(|(c, i)| (poly_ring.base_ring().clone_el(c), i - self.n)),
);
let remainder = poly_ring.add(poly, poly_ring.mul_ref("ient, &self.poly));
let truncated_remainder = poly_ring.from_terms(
poly_ring
.terms(&remainder)
.filter(|(_, i)| *i < self.poly_deg)
.map(|(c, i)| (poly_ring.base_ring().clone_el(c), i)),
);
return (poly_ring.negate(quotient), truncated_remainder);
}
fn new<S>(poly_ring: S, poly: El<S>, other_d: usize, start_e: usize) -> Self
where
S: RingStore<Type = P> + Copy,
{
let poly_deg = poly_ring.degree(&poly).unwrap();
let n = poly_deg + other_d;
assert!(poly_ring.base_ring().is_one(poly_ring.lc(&poly).unwrap()));
let neg_Xn_div_poly = poly_ring
.div_rem_monic(poly_ring.from_terms([(poly_ring.base_ring().neg_one(), n)]), &poly)
.0;
return Self {
ring: PhantomData,
n,
e: start_e,
poly,
poly_deg,
neg_Xn_div_poly,
};
}
fn lift<S>(&mut self, poly_ring: S, delta_poly: El<S>, new_e: usize)
where
S: RingStore<Type = P> + Copy,
{
assert!(new_e <= 2 * self.e);
self.e = new_e;
let new_f = poly_ring.add_ref(&self.poly, &delta_poly);
let delta_quo = self
.div_rem_poly(
poly_ring,
poly_ring.add(
poly_ring.from_terms([(poly_ring.base_ring().one(), self.n)]),
poly_ring.mul_ref(&self.neg_Xn_div_poly, &new_f),
),
)
.0;
self.poly = new_f;
poly_ring.sub_assign(&mut self.neg_Xn_div_poly, delta_quo);
}
}
fn hensel_lift_quadratic<'ring, 'data, 'local, R, P1, P2, Controller>(
reduction_map: &PolyGCDLocallyIntermediateReductionMap<'ring, 'data, 'local, R>,
target_poly_ring: P1,
base_poly_ring: P2,
f: &El<P1>,
factors: (&El<P2>, &El<P2>),
controller: Controller,
) -> (El<P1>, El<P1>)
where
R: ?Sized + PolyGCDLocallyDomain,
P1: RingStore,
P1::Type: PolyRing,
<P1::Type as RingExtension>::BaseRing: RingStore<Type = R::LocalRingBase<'ring>>,
P2: RingStore,
P2::Type: PolyRing + PrincipalIdealRing,
<P2::Type as RingExtension>::BaseRing: RingStore<Type = R::LocalFieldBase<'ring>>,
Controller: ComputationController,
{
assert!(target_poly_ring.base_ring().is_one(target_poly_ring.lc(f).unwrap()));
assert!(base_poly_ring.base_ring().is_one(base_poly_ring.lc(factors.0).unwrap()));
assert!(base_poly_ring.base_ring().is_one(base_poly_ring.lc(factors.1).unwrap()));
assert!(target_poly_ring.base_ring().get_ring() == reduction_map.domain().get_ring());
let prime_field = base_poly_ring.base_ring();
let prime_ring = reduction_map.codomain();
let prime_ring_iso = PolyGCDLocallyBaseRingToFieldIso::new(
reduction_map.parent_ring().into(),
reduction_map.ideal(),
prime_ring.get_ring(),
prime_field.get_ring(),
reduction_map.max_ideal_idx(),
);
assert_el_eq!(
base_poly_ring,
base_poly_ring
.lifted_hom(&target_poly_ring, (&prime_ring_iso).compose(&reduction_map))
.map_ref(f),
base_poly_ring.mul_ref(factors.0, factors.1)
);
let (g, h) = factors;
let (mut s, mut t, d) = base_poly_ring.extended_ideal_gen(g, h);
assert!(base_poly_ring.degree(&d).unwrap() == 0);
let d_inv = prime_field.invert(base_poly_ring.coefficient_at(&d, 0)).unwrap();
base_poly_ring.inclusion().mul_assign_ref_map(&mut s, &d_inv);
base_poly_ring.inclusion().mul_assign_map(&mut t, d_inv);
let lift_to_target_poly_ring = |f| {
target_poly_ring.from_terms(base_poly_ring.terms(f).map(|(c, i)| {
(
reduction_map.parent_ring().get_ring().lift_partial(
reduction_map.ideal(),
(reduction_map.codomain().get_ring(), reduction_map.to_e()),
(reduction_map.domain().get_ring(), reduction_map.from_e()),
reduction_map.max_ideal_idx(),
prime_ring_iso.inv().map_ref(c),
),
i,
)
}))
};
let mut current_s = lift_to_target_poly_ring(&s);
let mut current_t = lift_to_target_poly_ring(&t);
let degree_delta_bound = base_poly_ring.degree(g).unwrap() + base_poly_ring.degree(h).unwrap();
let mut current_g =
HenselLiftableBarrettReducer::new(&target_poly_ring, lift_to_target_poly_ring(g), degree_delta_bound, 1);
let mut current_h =
HenselLiftableBarrettReducer::new(&target_poly_ring, lift_to_target_poly_ring(h), degree_delta_bound, 1);
log_progress!(controller, "(setup)");
let mut current_e = 1;
let P = target_poly_ring;
while current_e < reduction_map.from_e() {
let delta = P.sub_ref_fst(f, P.mul_ref(¤t_g.poly, ¤t_h.poly));
debug_assert!(P.degree(&delta).is_none() || P.degree(&delta).unwrap() < degree_delta_bound);
let mut delta_g = P.mul_ref(¤t_t, &delta);
let mut delta_h = P.mul_ref(¤t_s, &delta);
delta_g = current_g.div_rem_poly(&P, delta_g).1;
delta_h = current_h.div_rem_poly(&P, delta_h).1;
current_g.lift(&P, delta_g, 2 * current_e);
current_h.lift(&P, delta_h, 2 * current_e);
debug_assert!(P.degree(¤t_g.poly).unwrap() == base_poly_ring.degree(g).unwrap());
debug_assert!(P.degree(¤t_h.poly).unwrap() == base_poly_ring.degree(h).unwrap());
let bezout_value = P.add(
P.mul_ref(¤t_s, ¤t_g.poly),
P.mul_ref(¤t_t, ¤t_h.poly),
);
debug_assert!(P.degree(&bezout_value).is_none() || P.degree(&bezout_value).unwrap() < degree_delta_bound);
P.mul_assign(&mut current_s, P.sub_ref_snd(P.int_hom().map(2), &bezout_value));
P.mul_assign(&mut current_t, P.sub_ref_snd(P.int_hom().map(2), &bezout_value));
assert!(
P.degree(¤t_s).is_none()
|| P.degree(¤t_s).unwrap() < base_poly_ring.degree(h).unwrap() + degree_delta_bound
);
assert!(
P.degree(¤t_t).is_none()
|| P.degree(¤t_t).unwrap() < base_poly_ring.degree(g).unwrap() + degree_delta_bound
);
current_s = current_h.div_rem_poly(&P, current_s).1;
current_t = current_g.div_rem_poly(&P, current_t).1;
debug_assert!(
P.degree(¤t_s).is_none() || P.degree(¤t_s).unwrap() < base_poly_ring.degree(h).unwrap()
);
debug_assert!(
P.degree(¤t_t).is_none() || P.degree(¤t_t).unwrap() < base_poly_ring.degree(g).unwrap()
);
current_e *= 2;
log_progress!(controller, ".");
}
debug_assert!(P.eq_el(f, &P.mul_ref(¤t_g.poly, ¤t_h.poly)));
return (current_g.poly, current_h.poly);
}
fn hensel_lift_bezout_identity_quadratic<'ring, 'data, 'local, R, P1, P2, Controller>(
reduction_map: &PolyGCDLocallyIntermediateReductionMap<'ring, 'data, 'local, R>,
target_poly_ring: P1,
base_poly_ring: P2,
f: &El<P1>,
g: &El<P1>,
(s, t): (&El<P2>, &El<P2>),
controller: Controller,
) -> (El<P1>, El<P1>)
where
R: ?Sized + PolyGCDLocallyDomain,
P1: RingStore,
P1::Type: PolyRing,
<P1::Type as RingExtension>::BaseRing: RingStore<Type = R::LocalRingBase<'ring>>,
P2: RingStore,
P2::Type: PolyRing + PrincipalIdealRing,
<P2::Type as RingExtension>::BaseRing: RingStore<Type = R::LocalFieldBase<'ring>>,
Controller: ComputationController,
{
assert!(target_poly_ring.base_ring().is_one(target_poly_ring.lc(f).unwrap()));
assert!(target_poly_ring.base_ring().is_one(target_poly_ring.lc(g).unwrap()));
assert!(target_poly_ring.base_ring().get_ring() == reduction_map.domain().get_ring());
let prime_field = base_poly_ring.base_ring();
let prime_ring = reduction_map.codomain();
let prime_ring_iso = PolyGCDLocallyBaseRingToFieldIso::new(
reduction_map.parent_ring().into(),
reduction_map.ideal(),
prime_ring.get_ring(),
prime_field.get_ring(),
reduction_map.max_ideal_idx(),
);
let poly_hom = base_poly_ring.lifted_hom(&target_poly_ring, (&prime_ring_iso).compose(&reduction_map));
assert_el_eq!(
base_poly_ring,
base_poly_ring.one(),
base_poly_ring.add(poly_hom.mul_ref_map(s, f), poly_hom.mul_ref_map(t, g))
);
let f_base = base_poly_ring
.lifted_hom(&target_poly_ring, (&prime_ring_iso).compose(reduction_map))
.map_ref(f);
let g_base = base_poly_ring
.lifted_hom(&target_poly_ring, (&prime_ring_iso).compose(reduction_map))
.map_ref(g);
assert!(
base_poly_ring
.is_one(&base_poly_ring.add(base_poly_ring.mul_ref(&f_base, s), base_poly_ring.mul_ref(&g_base, t)))
);
let lift_to_target_poly_ring = |f| {
target_poly_ring.from_terms(base_poly_ring.terms(f).map(|(c, i)| {
(
reduction_map.parent_ring().get_ring().lift_partial(
reduction_map.ideal(),
(reduction_map.codomain().get_ring(), reduction_map.to_e()),
(reduction_map.domain().get_ring(), reduction_map.from_e()),
reduction_map.max_ideal_idx(),
prime_ring_iso.inv().map_ref(c),
),
i,
)
}))
};
let mut current_s = lift_to_target_poly_ring(s);
let mut current_t = lift_to_target_poly_ring(t);
let mut current_e = 1;
let P = target_poly_ring;
while current_e < reduction_map.from_e() {
log_progress!(controller, ".");
let bezout_value = P.add(P.mul_ref(¤t_s, f), P.mul_ref(¤t_t, g));
P.mul_assign(&mut current_s, P.sub_ref_snd(P.int_hom().map(2), &bezout_value));
P.mul_assign(&mut current_t, P.sub_ref_snd(P.int_hom().map(2), &bezout_value));
current_s = P.div_rem_monic(current_s, g).1;
current_t = P.div_rem_monic(current_t, f).1;
current_e *= 2;
}
debug_assert!(P.is_one(&P.add(P.mul_ref(f, ¤t_s), P.mul_ref(g, ¤t_t))));
return (current_s, current_t);
}
#[stability::unstable(feature = "enable")]
pub fn local_zn_ring_bezout_identity<P>(poly_ring: P, f: &El<P>, g: &El<P>) -> Option<(El<P>, El<P>)>
where
P: RingStore,
P::Type: PolyRing,
<<P::Type as RingExtension>::BaseRing as RingStore>::Type: SelfIso + ZnRing + FromModulusCreateableZnRing + Clone,
{
if poly_ring.is_zero(f) {
if poly_ring.is_one(g) {
return Some((poly_ring.zero(), poly_ring.one()));
} else {
return None;
}
} else if poly_ring.is_zero(g) {
if poly_ring.is_one(f) {
return Some((poly_ring.one(), poly_ring.zero()));
} else {
return None;
}
}
let Zpe = poly_ring.base_ring();
let ZZ = Zpe.integer_ring();
let (p, e) = is_prime_power(ZZ, Zpe.modulus()).unwrap();
let wrapped_ring: IntegersWithLocalZnQuotient<<<P::Type as RingExtension>::BaseRing as RingStore>::Type> =
IntegersWithLocalZnQuotient::new(ZZ, p);
let reduction_context = wrapped_ring.reduction_context(e);
let Zpe_to_Zp = reduction_context.intermediate_ring_to_field_reduction(0);
let Fp = wrapped_ring.local_field_at(Zpe_to_Zp.ideal(), 0);
let FpX = DensePolyRing::new(&Fp, "X");
let Zpe_to_Fp = reduction_context.base_ring_to_field_iso(0).compose(&Zpe_to_Zp);
let ZpeX_to_FpX = FpX.lifted_hom(&poly_ring, &Zpe_to_Fp);
let (mut s_base, mut t_base, d_base) = FpX.extended_ideal_gen(&ZpeX_to_FpX.map_ref(f), &ZpeX_to_FpX.map_ref(g));
if FpX.degree(&d_base).unwrap() > 0 {
return None;
}
let scale = Fp.invert(FpX.coefficient_at(&d_base, 0)).unwrap();
FpX.inclusion().mul_assign_ref_map(&mut s_base, &scale);
FpX.inclusion().mul_assign_ref_map(&mut t_base, &scale);
let (s, t) =
hensel_lift_bezout_identity_quadratic(&Zpe_to_Zp, &poly_ring, &FpX, f, g, (&s_base, &t_base), DontObserve);
return Some((s, t));
}
fn hensel_lift_factorization_internal<'ring, 'data, 'local, R, P1, P2, V, Controller>(
reduction_map: &PolyGCDLocallyIntermediateReductionMap<'ring, 'data, 'local, R>,
target_poly_ring: P1,
base_poly_ring: P2,
f: &El<P1>,
factors: V,
controller: Controller,
) -> Vec<El<P1>>
where
R: ?Sized + PolyGCDLocallyDomain,
P1: RingStore + Copy,
P1::Type: PolyRing,
<P1::Type as RingExtension>::BaseRing: RingStore<Type = R::LocalRingBase<'ring>>,
P2: RingStore + Copy,
P2::Type: PolyRing + PrincipalIdealRing,
<P2::Type as RingExtension>::BaseRing: RingStore<Type = R::LocalFieldBase<'ring>>,
V: SelfSubvectorView<El<P2>>,
Controller: ComputationController,
{
if factors.len() == 1 {
return vec![target_poly_ring.clone_el(f)];
}
let (g, h) = (
factors.at(0),
base_poly_ring.prod(factors.as_iter().skip(1).map(|h| base_poly_ring.clone_el(h))),
);
let (g_lifted, h_lifted) = hensel_lift_quadratic(
reduction_map,
target_poly_ring,
base_poly_ring,
f,
(g, &h),
controller.clone(),
);
let mut result = hensel_lift_factorization_internal(
reduction_map,
target_poly_ring,
base_poly_ring,
&h_lifted,
factors.restrict(1..),
controller,
);
result.insert(0, g_lifted);
return result;
}
#[stability::unstable(feature = "enable")]
pub fn hensel_lift_factorization<'ring, 'data, 'local, R, P1, P2, V, Controller>(
reduction_map: &PolyGCDLocallyIntermediateReductionMap<'ring, 'data, 'local, R>,
target_poly_ring: P1,
base_poly_ring: P2,
f: &El<P1>,
factors: V,
controller: Controller,
) -> Vec<El<P1>>
where
R: ?Sized + PolyGCDLocallyDomain,
P1: RingStore + Copy,
P1::Type: PolyRing,
<P1::Type as RingExtension>::BaseRing: RingStore<Type = R::LocalRingBase<'ring>>,
P2: RingStore + Copy,
P2::Type: PolyRing + PrincipalIdealRing,
<P2::Type as RingExtension>::BaseRing: RingStore<Type = R::LocalFieldBase<'ring>>,
V: SelfSubvectorView<El<P2>>,
Controller: ComputationController,
{
assert!(target_poly_ring.base_ring().is_one(target_poly_ring.lc(f).unwrap()));
assert!(
factors
.as_iter()
.all(|f| { base_poly_ring.base_ring().is_one(base_poly_ring.lc(f).unwrap()) })
);
assert!(target_poly_ring.base_ring().get_ring() == reduction_map.domain().get_ring());
let result = controller.run_computation(
format_args!(
"hensel_lift(deg={}, to={})",
target_poly_ring.degree(f).unwrap(),
reduction_map.from_e()
),
|controller| {
hensel_lift_factorization_internal(reduction_map, target_poly_ring, base_poly_ring, f, factors, controller)
},
);
return result;
}
#[cfg(test)]
use crate::integer::*;
#[test]
fn test_hensel_lift() {
let ZZ = BigIntRing::RING;
let prime = 5;
let Zp = ZZ.get_ring().local_ring_at(&prime, 1, 0);
let Fp = ZZ.get_ring().local_field_at(&prime, 0);
let Zpe = ZZ.get_ring().local_ring_at(&prime, 6, 0);
let Zpe_to_Zp = PolyGCDLocallyIntermediateReductionMap::new(ZZ.get_ring(), &prime, &Zpe, 6, &Zp, 1, 0);
let ZpeX = DensePolyRing::new(&Zpe, "X");
let FpX = DensePolyRing::new(&Fp, "X");
let ZpeX_to_ZpX = FpX.lifted_hom(&ZpeX, Fp.can_hom(&Zp).unwrap().compose(&Zpe_to_Zp));
let [f, g] = ZpeX.with_wrapped_indeterminate(|X| [X.pow_ref(2) + 3, X + 1]);
let h = ZpeX.mul_ref(&f, &g);
let (actual_f, actual_g) = hensel_lift_linear(
&Zpe_to_Zp,
&ZpeX,
&FpX,
&h,
(&ZpeX_to_ZpX.map_ref(&f), &ZpeX_to_ZpX.map_ref(&g)),
DontObserve,
);
assert_el_eq!(&ZpeX, &f, &actual_f);
assert_el_eq!(&ZpeX, &g, &actual_g);
let (actual_f, actual_g) = hensel_lift_quadratic(
&Zpe_to_Zp,
&ZpeX,
&FpX,
&h,
(&ZpeX_to_ZpX.map_ref(&f), &ZpeX_to_ZpX.map_ref(&g)),
DontObserve,
);
assert_el_eq!(&ZpeX, &f, &actual_f);
assert_el_eq!(&ZpeX, &g, &actual_g);
let [f, g] = ZpeX.with_wrapped_indeterminate(|X| [X.pow_ref(2) + 25 * X + 3 + 625, X + 1 + 125]);
let h = ZpeX.mul_ref(&f, &g);
let (actual_f, actual_g) = hensel_lift_linear(
&Zpe_to_Zp,
&ZpeX,
&FpX,
&h,
(&ZpeX_to_ZpX.map_ref(&f), &ZpeX_to_ZpX.map_ref(&g)),
DontObserve,
);
assert_el_eq!(&ZpeX, &f, &actual_f);
assert_el_eq!(&ZpeX, &g, &actual_g);
let (actual_f, actual_g) = hensel_lift_quadratic(
&Zpe_to_Zp,
&ZpeX,
&FpX,
&h,
(&ZpeX_to_ZpX.map_ref(&f), &ZpeX_to_ZpX.map_ref(&g)),
DontObserve,
);
assert_el_eq!(&ZpeX, &f, &actual_f);
assert_el_eq!(&ZpeX, &g, &actual_g);
}
#[test]
fn test_hensel_lift_bezout_identity() {
let ZZ = BigIntRing::RING;
let prime = 5;
let Zp = ZZ.get_ring().local_ring_at(&prime, 1, 0);
let Fp = ZZ.get_ring().local_field_at(&prime, 0);
let Zpe = ZZ.get_ring().local_ring_at(&prime, 6, 0);
let Zpe_to_Zp = PolyGCDLocallyIntermediateReductionMap::new(ZZ.get_ring(), &prime, &Zpe, 6, &Zp, 1, 0);
let ZpeX = DensePolyRing::new(&Zpe, "X");
let FpX = DensePolyRing::new(&Fp, "X");
let [f, g] = ZpeX.with_wrapped_indeterminate(|X| [X.pow_ref(2) - 6 * X + 2, X.pow_ref(2) + 11]);
let [s_base, t_base] = FpX.with_wrapped_indeterminate(|X| [3 * X + 3, 2 * X]);
let (s, t) =
hensel_lift_bezout_identity_quadratic(&Zpe_to_Zp, &ZpeX, &FpX, &f, &g, (&s_base, &t_base), DontObserve);
assert_eq!(1, ZpeX.degree(&s).unwrap());
assert_eq!(1, ZpeX.degree(&t).unwrap());
assert_el_eq!(&ZpeX, ZpeX.one(), ZpeX.add(ZpeX.mul_ref(&f, &s), ZpeX.mul_ref(&g, &t)));
assert_el_eq!(
&FpX,
&s_base,
FpX.lifted_hom(&ZpeX, Fp.can_hom(&Zp).unwrap().compose(&Zpe_to_Zp))
.map_ref(&s)
);
assert_el_eq!(
&FpX,
&t_base,
FpX.lifted_hom(&ZpeX, Fp.can_hom(&Zp).unwrap().compose(&Zpe_to_Zp))
.map_ref(&t)
);
}