feanor_math/algorithms/poly_factor/
extension.rs

1use crate::algorithms;
2use crate::algorithms::poly_factor::FactorPolyField;
3use crate::algorithms::poly_gcd::PolyTFracGCDRing;
4use crate::compute_locally::InterpolationBaseRing;
5use crate::field::*;
6use crate::homomorphism::*;
7use crate::ordered::OrderedRingStore;
8use crate::pid::EuclideanRing;
9use crate::primitive_int::StaticRing;
10use crate::ring::*;
11use crate::rings::extension::FreeAlgebra;
12use crate::rings::extension::FreeAlgebraStore;
13use crate::rings::poly::dense_poly::DensePolyRing;
14use crate::rings::poly::{PolyRing, PolyRingStore};
15use crate::integer::*;
16use crate::MAX_PROBABILISTIC_REPETITIONS;
17use crate::specialization::FiniteRingSpecializable;
18
19#[stability::unstable(feature = "enable")]
20pub struct ProbablyNotSquarefree;
21
22///
23/// Factors a polynomial with coefficients in a field `K` that is a simple, finite-degree
24/// field extension of a base field that supports polynomial factorization. 
25/// 
26/// If the given polynomial is square-free, this will succeed, except with probability
27/// `2^-attempts`. If it is not square-free, this will always fail (i.e. return `Err`). 
28/// 
29#[stability::unstable(feature = "enable")]
30pub fn poly_factor_squarefree_extension<P>(LX: P, f: &El<P>, attempts: usize) -> Result<Vec<El<P>>, ProbablyNotSquarefree>
31    where P: RingStore,
32        P::Type: PolyRing + EuclideanRing,
33        <<P::Type as RingExtension>::BaseRing as RingStore>::Type: Field + FreeAlgebra + PolyTFracGCDRing,
34        <<<<P::Type as RingExtension>::BaseRing as RingStore>::Type as RingExtension>::BaseRing as RingStore>::Type: PerfectField + PolyTFracGCDRing + FactorPolyField + InterpolationBaseRing + FiniteRingSpecializable
35{
36    let L = LX.base_ring();
37    let K = L.base_ring();
38
39    assert!(LX.base_ring().is_one(LX.lc(f).unwrap()));
40
41    let KX = DensePolyRing::new(K, "X");
42    // Y will take the role of the ring generator `theta`, and `X` remains the indeterminate
43    let KXY = DensePolyRing::new(KX.clone(), "Y");
44
45    let Norm = |f: El<P>| {
46        let f_over_KY = <_ as RingStore>::sum(&KXY,
47            LX.terms(&f).map(|(c, i)| {
48                let mut result = L.poly_repr(&KXY, c, KX.inclusion());
49                KXY.inclusion().mul_assign_map(&mut result, KX.pow(KX.indeterminate(), i));
50                result
51            })
52        );
53        let gen_poly = L.generating_poly(&KXY, KX.inclusion());
54    
55        return algorithms::resultant::resultant_local::<&DensePolyRing<_>>(&KXY, f_over_KY, gen_poly);
56    };
57
58    // we want to find `k` such that `N(f(X + k theta))` remains square-free, where `theta` generates `L`;
59    // there are only finitely many `k` that don't work, by the following idea:
60    // Write `f = f1 ... fr` for the factorization of `f`, where `r <= d`. Then `N(f(X + k theta))` is not
61    // square-free, iff there exist `i != j` and `sigma` such that `fi(X + k theta) = sigma(fj(X + k theta)) = sigma(fj)(X + k sigma(theta))`.
62    // Now note that for given `i, j, sigma`, `fi(X + k theta) = sigma(fj)(X + k sigma(theta))` for at most one `k`, as otherwise
63    // `fi(a (k1 - k2) theta) sigma(theta)) = sigma(fj)(a (k1 - k2) sigma(theta))` for all `a in Z` (evaluation is ring hom).
64    //
65    // Now note that `fi(X) - sigma(fj(X))` is a linear combination of `X^m` and `sigma(X^m)`, i.e. of `deg(fi) + deg(fj) <= d`
66    // basic functions. The vectors `(a1 theta)^m, ..., (al theta)^m` for `m <= deg(fi)` and `sigma(a1 theta)^m, ..., sigma(al theta)^m`
67    // for `m <= deg(fj)` are all linearly independent (assuming `a1, ..., al` distinct and `l >= deg(fi) + deg(fj) + 2`), 
68    // thus `char(K) >= d + 2` (or `char(K) = 0`) implies that `fi = fj = 0`, a contradiction.
69    //
70    // Thus we find that there are at most `d(d + 1)/2 * [L : K]` many `k` such that `N(f(X + k theta))` is not squarefree.
71    // This would even work if `k` is not an integer, but any element of `K`. Note that we still require `char(K) >= d + 2` for
72    // the previous paragraph to work.
73
74    let ZZbig = BigIntRing::RING;
75    let characteristic = K.characteristic(&ZZbig).unwrap();
76    // choose bound about twice as large as necessary, so the probability of succeeding is almost 1/2
77    let bound = LX.degree(f).unwrap() * LX.degree(f).unwrap() * L.rank();
78    assert!(ZZbig.is_zero(&characteristic) || ZZbig.is_geq(&characteristic, &int_cast(bound as i64, ZZbig, StaticRing::<i64>::RING)));
79
80    let mut rng = oorandom::Rand64::new(1);
81
82    for _ in 0..attempts {
83        let k = StaticRing::<i32>::RING.get_uniformly_random(&(bound as i32), || rng.rand_u64());
84        let lin_transform = LX.from_terms([(L.mul(L.canonical_gen(), L.int_hom().map(k)), 0), (L.one(), 1)].into_iter());
85        let f_transformed = LX.evaluate(f, &lin_transform, &LX.inclusion());
86
87        let norm_f_transformed = Norm(LX.clone_el(&f_transformed));
88        let degree = KX.degree(&norm_f_transformed).unwrap();
89        let squarefree_part = <_ as PolyTFracGCDRing>::squarefree_part(&KX, &norm_f_transformed);
90
91        if KX.degree(&squarefree_part).unwrap() == degree {
92            let lin_transform_rev = LX.from_terms([(L.mul(L.canonical_gen(), L.int_hom().map(-k)), 0), (L.one(), 1)].into_iter());
93            let (factorization, _unit) = <_ as FactorPolyField>::factor_poly(&KX, &squarefree_part);
94            
95            return Ok(factorization.into_iter().map(|(factor, e)| {
96                assert!(e == 1);
97                let f_factor = LX.normalize(<_ as PolyTFracGCDRing>::gcd(&LX, &f_transformed, &LX.lifted_hom(&KX, L.inclusion()).map(factor)));
98                return LX.evaluate(&f_factor, &lin_transform_rev, &LX.inclusion());
99            }).collect());
100        }
101    }
102    return Err(ProbablyNotSquarefree);
103}
104
105///
106/// Factors a polynomial with coefficients in a field `K` that is a simple, finite-degree
107/// field extension of a base field that supports polynomial factorization. 
108/// 
109#[stability::unstable(feature = "enable")]
110pub fn poly_factor_extension<P>(poly_ring: P, f: &El<P>) -> (Vec<(El<P>, usize)>, El<<P::Type as RingExtension>::BaseRing>)
111    where P: RingStore,
112        P::Type: PolyRing + EuclideanRing,
113        <<P::Type as RingExtension>::BaseRing as RingStore>::Type: FreeAlgebra + PerfectField + FiniteRingSpecializable + PolyTFracGCDRing,
114        <<<<P::Type as RingExtension>::BaseRing as RingStore>::Type as RingExtension>::BaseRing as RingStore>::Type: PerfectField + PolyTFracGCDRing + FactorPolyField + InterpolationBaseRing + FiniteRingSpecializable
115{
116    let KX = &poly_ring;
117    let K = KX.base_ring();
118
119    // We use the approach outlined in Cohen's "a course in computational algebraic number theory".
120    //  - Use square-free reduction to assume wlog that `f` is square-free
121    //  - Observe that the factorization of `f` is the product over `gcd(f, g)` where `g` runs
122    //    through the factors of `N(f)` over `QQ[X]` - assuming that `N(f)` is square-free!
123    //    Here `N(f)` is the "norm" of `f`, i.e.
124    //    the product `prod_sigma sigma(f)` where `sigma` runs through the embeddings `K -> CC`.
125    //  - It is now left to actually compute `N(f)`, which is not so simple as we do not known the
126    //    `sigma`. As it turns out, this is the resultant of `f` and `MiPo(theta)` where `theta`
127    //    generates `K`
128
129    assert!(!KX.is_zero(f));
130    let mut result: Vec<(El<P>, usize)> = Vec::new();
131    for (non_irred_factor, k) in <_ as PolyTFracGCDRing>::power_decomposition(KX, f) {
132        for factor in poly_factor_squarefree_extension(KX, &non_irred_factor, MAX_PROBABILISTIC_REPETITIONS).ok().unwrap() {
133            if let Some((i, _)) = result.iter().enumerate().filter(|(_, f)| KX.eq_el(&f.0, &factor)).next() {
134                result[i].1 += k;
135            } else {
136                result.push((factor, k));
137            }
138        }
139    }
140    return (result, K.clone_el(KX.coefficient_at(f, 0)));
141}