Skip to main content

feanor_math/rings/extension/
galois_field.rs

1use std::alloc::{Allocator, Global};
2use std::marker::PhantomData;
3
4use extension_impl::FreeAlgebraImplBase;
5use feanor_serde::newtype_struct::*;
6use serde::de::DeserializeSeed;
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8use sparse::SparseMapVector;
9use zn_64::Zn;
10
11use crate::algorithms::convolution::fft::*;
12use crate::algorithms::convolution::*;
13use crate::algorithms::eea::signed_gcd;
14use crate::algorithms::int_factor::{factor, is_prime_power};
15use crate::algorithms::matmul::{ComputeInnerProduct, StrassenHint};
16use crate::algorithms::poly_factor::cantor_zassenhaus;
17use crate::algorithms::poly_gcd::PolyTFracGCDRing;
18use crate::algorithms::poly_gcd::finite::poly_squarefree_part_finite_field;
19use crate::algorithms::unity_root::*;
20use crate::computation::DontObserve;
21use crate::delegate::{DelegateRing, DelegateRingImplFiniteRing};
22use crate::divisibility::{DivisibilityRingStore, Domain};
23use crate::field::*;
24use crate::integer::*;
25use crate::pid::{EuclideanRing, PrincipalIdealRing};
26use crate::primitive_int::{StaticRing, StaticRingBase};
27use crate::ring::*;
28use crate::rings::extension::extension_impl::FreeAlgebraImpl;
29use crate::rings::extension::*;
30use crate::rings::field::{AsField, AsFieldBase};
31use crate::rings::finite::*;
32use crate::rings::local::{AsLocalPIR, AsLocalPIRBase};
33use crate::rings::poly::PolyRing;
34use crate::rings::poly::dense_poly::DensePolyRing;
35use crate::rings::zn::*;
36use crate::serialization::*;
37
38fn filter_irreducible<R, P>(poly_ring: P, mod_f_ring: R, degree: usize) -> Option<El<P>>
39where
40    P: RingStore,
41    P::Type: PolyRing + EuclideanRing,
42    <<P::Type as RingExtension>::BaseRing as RingStore>::Type: ZnRing + FiniteRing + Field,
43    R: RingStore,
44    R::Type: FreeAlgebra,
45    <R::Type as RingExtension>::BaseRing: RingStore<Type = <<P::Type as RingExtension>::BaseRing as RingStore>::Type>,
46{
47    let f = mod_f_ring.generating_poly(&poly_ring, &poly_ring.base_ring().identity());
48    let squarefree_part = poly_squarefree_part_finite_field(&poly_ring, &f, DontObserve);
49    if poly_ring.degree(&squarefree_part) != Some(degree) {
50        return None;
51    }
52    if !cantor_zassenhaus::squarefree_is_irreducible_base(&poly_ring, &mod_f_ring) {
53        return None;
54    }
55    return Some(mod_f_ring.generating_poly(&poly_ring, &poly_ring.base_ring().identity()));
56}
57
58/// Tries to find a "small" irreducible polynomial of given degree.
59///
60/// "small" is not well-defined, but can mean sparse, or having a small `deg(f - lt(f))`.
61/// Both will lead to more efficient arithmetic in the ring `k[X]/(f)`. However, in general
62/// finding a very small `f` is hard, thus we use heuristics.
63fn find_small_irreducible_poly_base<P, C>(
64    poly_ring: P,
65    degree: usize,
66    convolution: C,
67    rng: &mut oorandom::Rand64,
68) -> El<P>
69where
70    P: RingStore,
71    P::Type: PolyRing + EuclideanRing,
72    <P::Type as RingExtension>::BaseRing: Copy,
73    <<P::Type as RingExtension>::BaseRing as RingStore>::Type:
74        ZnRing + Field + SelfIso + CanHomFrom<StaticRingBase<i64>>,
75    C: ConvolutionAlgorithm<<<P::Type as RingExtension>::BaseRing as RingStore>::Type>,
76{
77    let Fp = *poly_ring.base_ring();
78    let create_mod_f_ring = |f: &El<P>| {
79        let mut f_body = SparseMapVector::new(degree, poly_ring.base_ring());
80        for (c, i) in poly_ring.terms(f) {
81            if i != degree {
82                *f_body.at_mut(i) = poly_ring.base_ring().negate(poly_ring.base_ring().clone_el(c));
83            }
84        }
85        return FreeAlgebraImpl::new_with_convolution(Fp, degree, f_body, "θ", Global, &convolution);
86    };
87
88    if degree > 3 {
89        // first try trinomials, they seem to have a good chance of being irreducible
90        for _ in 0..16 {
91            let i = (StaticRing::<i64>::RING
92                .get_uniformly_random(&(TryInto::<i64>::try_into(degree).unwrap() - 1), || rng.rand_u64())
93                + 1) as usize;
94            let a = Fp.random_element(|| rng.rand_u64());
95            let b = Fp.random_element(|| rng.rand_u64());
96            let f = poly_ring.from_terms([(a, 0), (b, i), (Fp.one(), degree)]);
97            if let Some(result) = filter_irreducible(&poly_ring, create_mod_f_ring(&f), degree) {
98                return result;
99            }
100        }
101
102        // next, check for cases where we can take `small_poly(X^high_power)`; these cases are
103        // characterized by the fact that we have `degree = small_d * large_d` with `large_d
104        // | #F_(q^small_d)*`
105        let ZZbig = BigIntRing::RING;
106        let ZZ = StaticRing::<i64>::RING;
107
108        let p = Fp.size(&ZZbig).unwrap();
109        let mut small_d = 1;
110        let Fq_star_order = ZZbig.sub(ZZbig.pow(ZZbig.clone_el(&p), small_d as usize), ZZbig.one());
111        let mut large_d = int_cast(
112            signed_gcd(
113                Fq_star_order,
114                int_cast(
115                    TryInto::<i64>::try_into(degree).unwrap() / small_d,
116                    ZZbig,
117                    StaticRing::<i64>::RING,
118                ),
119                ZZbig,
120            ),
121            ZZ,
122            ZZbig,
123        );
124        let mut increased_small_d = true;
125        while increased_small_d {
126            increased_small_d = false;
127            // use a greedy approach, just add a factor to small_d if it makes large_d larger
128            for (k, _) in factor(&ZZ, TryInto::<i64>::try_into(degree).unwrap() / small_d) {
129                let new_small_d = small_d * k;
130                let Fq_star_order = ZZbig.sub(ZZbig.pow(ZZbig.clone_el(&p), new_small_d as usize), ZZbig.one());
131                let new_large_d = int_cast(
132                    signed_gcd(
133                        Fq_star_order,
134                        int_cast(
135                            TryInto::<i64>::try_into(degree).unwrap() / new_small_d,
136                            ZZbig,
137                            StaticRing::<i64>::RING,
138                        ),
139                        ZZbig,
140                    ),
141                    ZZ,
142                    ZZbig,
143                );
144                if new_large_d > large_d {
145                    small_d = new_small_d;
146                    large_d = new_large_d;
147                    increased_small_d = true;
148                    break;
149                }
150            }
151        }
152        small_d = TryInto::<i64>::try_into(degree).unwrap() / large_d;
153        if large_d != 1 {
154            let Fq_star_order = ZZbig.sub(ZZbig.pow(ZZbig.clone_el(&p), small_d as usize), ZZbig.one());
155            // careful here to not get an infinite generic argument recursion
156            let Fq = GaloisField::new_internal(Fp, small_d as usize, Global, &convolution);
157            // the primitive element of `Fq` clearly has no `k`-th root, for every `k | large_d`
158            // since `large_d | #Fq*`; hence we can use
159            // `MinPoly(primitive_element)(X^large_d)`
160            let primitive_element = if is_prim_root_of_unity_gen(&Fq, &Fq.canonical_gen(), &Fq_star_order, ZZbig) {
161                Fq.canonical_gen()
162            } else {
163                get_prim_root_of_unity_gen(&Fq, &Fq_star_order, ZZbig, &Fq_star_order).unwrap()
164            };
165            // I thought for a while that it would be enough to have a primitive `lcm(Fq_star_order,
166            // large_d^inf)`-th root of unity, however it is not guaranteed that this
167            // would indeed generate the field
168            let FqX = DensePolyRing::new(&Fq, "X");
169            let minpoly = FqX.prod((0..small_d).map(|i| {
170                FqX.from_terms([
171                    (
172                        Fq.negate(Fq.pow_gen(
173                            Fq.clone_el(&primitive_element),
174                            &ZZbig.pow(ZZbig.clone_el(&p), i as usize),
175                            ZZbig,
176                        )),
177                        0,
178                    ),
179                    (Fq.one(), 1),
180                ])
181            }));
182            let minpoly_Fp = poly_ring.from_terms(FqX.terms(&minpoly).map(|(c, i)| {
183                let c_wrt_basis = Fq.wrt_canonical_basis(c);
184                assert!(c_wrt_basis.iter().skip(1).all(|x| Fp.is_zero(&x)));
185                return (c_wrt_basis.at(0), i);
186            }));
187            let f = poly_ring.evaluate(
188                &minpoly_Fp,
189                &poly_ring.from_terms([(Fp.one(), large_d as usize)]),
190                poly_ring.inclusion(),
191            );
192            return f;
193        }
194    }
195
196    // fallback, just generate a random irreducible polynomial
197    loop {
198        let f = poly_ring.from_terms(
199            (0..degree)
200                .map(|i| (Fp.random_element(|| rng.rand_u64()), i))
201                .chain([(Fp.one(), degree)]),
202        );
203        if let Some(result) = filter_irreducible(&poly_ring, create_mod_f_ring(&f), degree) {
204            return result;
205        }
206    }
207}
208
209fn find_small_irreducible_poly<P>(poly_ring: P, degree: usize, rng: &mut oorandom::Rand64) -> El<P>
210where
211    P: RingStore,
212    P::Type: PolyRing + EuclideanRing,
213    <P::Type as RingExtension>::BaseRing: Copy,
214    <<P::Type as RingExtension>::BaseRing as RingStore>::Type:
215        ZnRing + Field + SelfIso + CanHomFrom<StaticRingBase<i64>>,
216{
217    static_assert_impls!(<<P::Type as RingExtension>::BaseRing as RingStore>::Type: PolyTFracGCDRing);
218
219    let log2_modulus = poly_ring
220        .base_ring()
221        .integer_ring()
222        .abs_log2_ceil(poly_ring.base_ring().modulus())
223        .unwrap();
224    let fft_convolution = FFTConvolution::new_with_alloc(Global);
225    if fft_convolution.has_sufficient_precision(
226        StaticRing::<i64>::RING
227            .abs_log2_ceil(&degree.try_into().unwrap())
228            .unwrap()
229            + 1,
230        log2_modulus,
231    ) {
232        find_small_irreducible_poly_base(
233            &poly_ring,
234            degree,
235            <FFTConvolutionZn as From<_>>::from(fft_convolution),
236            rng,
237        )
238    } else {
239        find_small_irreducible_poly_base(&poly_ring, degree, STANDARD_CONVOLUTION, rng)
240    }
241}
242
243/// Implementation of a galois field `GF(p^e)`; Also known as galois field, and sometimes denoted by
244/// `Fq` where `q = p^e`.
245///
246/// There exists a finite field with `q` elements if and only if `q = p^e` is a prime power. In
247/// these cases, this struct provides an implementation of arithmetic in these fields. Note that
248/// since those fields are always finite-degree extensions of `Z/pZ`, they can also be used by
249/// creating a suitable instance of [`super::extension_impl::FreeAlgebraImpl`]. In fact, this is the
250/// way the old implementation of galois fields was designed. However, providing a new type can
251/// provide both ergonomic and performance benefits, and also gives better encapsulation.
252///
253/// # Example
254///
255/// The easiest way to create a Galois field is by using `new()`:
256/// ```rust
257/// # use feanor_math::ring::*;
258/// # use feanor_math::rings::extension::*;
259/// # use feanor_math::rings::finite::*;
260/// # use feanor_math::homomorphism::*;
261/// # use feanor_math::primitive_int::*;
262/// # use feanor_math::rings::extension::galois_field::*;
263/// let F25: GaloisField = GaloisField::new(5, 2);
264/// assert_eq!(5, F25.characteristic(&StaticRing::<i64>::RING).unwrap());
265/// assert_eq!(2, F25.rank());
266/// ```
267/// More configurations are possible using [`GaloisField::new_with_convolution()`] or
268/// [`GaloisField::create()`].
269///
270/// We also support conversion to and from a plain [`super::extension_impl::FreeAlgebraImpl`]
271/// representation.
272/// ```rust
273/// # use feanor_math::ring::*;
274/// # use feanor_math::rings::extension::*;
275/// # use feanor_math::rings::extension::extension_impl::*;
276/// # use feanor_math::rings::field::*;
277/// # use feanor_math::rings::finite::*;
278/// # use feanor_math::homomorphism::*;
279/// # use feanor_math::primitive_int::*;
280/// # use feanor_math::rings::extension::galois_field::*;
281/// let F25: GaloisField = GaloisField::new(5, 2);
282/// let raw_F25: AsField<FreeAlgebraImpl<_, _>> = F25.clone().into().unwrap_self();
283/// assert!(F25.can_iso(&raw_F25).is_some());
284/// ```
285/// The other way is slightly more dangerous, since at some point we either have to check, or assume
286/// that the extension ring is indeed a field.
287/// ```rust
288/// # use feanor_math::ring::*;
289/// # use feanor_math::rings::extension::*;
290/// # use feanor_math::rings::extension::extension_impl::*;
291/// # use feanor_math::rings::field::*;
292/// # use feanor_math::rings::finite::*;
293/// # use feanor_math::homomorphism::*;
294/// # use feanor_math::rings::zn::zn_64::*;
295/// # use feanor_math::rings::zn::*;
296/// # use feanor_math::primitive_int::*;
297/// # use feanor_math::rings::extension::galois_field::*;
298/// let base_ring = Zn::new(5).as_field().ok().unwrap();
299/// let raw_F25: FreeAlgebraImpl<_, _> =
300///     FreeAlgebraImpl::new(base_ring, 2, [base_ring.int_hom().map(2)]);
301/// let asfield_F25 = raw_F25.clone().as_field().ok().unwrap();
302/// // alternatively, you can ensure yourself that the ring is a field and use `promise_is_field` to
303/// // avoid the check at runtime; be careful when doing this!
304/// let asfield_F25 = AsField::from(AsFieldBase::promise_is_field(raw_F25).ok().unwrap());
305/// let F25 = GaloisField::create(asfield_F25);
306/// assert!(F25.can_iso(&raw_F25).is_some());
307/// ```
308///
309/// # Choice of blanket implementations of [`CanHomFrom`]
310///
311/// As opposed to the more generic [`DelegateRing`]s, here I chose a "ping-pong" way
312/// of implementing [`CanHomFrom`] and [`CanIsoFromTo`] for [`GaloisFieldBase`] that is
313/// very powerful when we use the standard `Impl = AsField<FreeAlgebraImpl<_, _, _, _>>`.
314/// In particular, we implement `GaloisFieldBase<Impl>: CanHomFrom<S>` for all `S` with
315/// `Impl: CanHomFrom<S>`. It is great that we can provide such a large class of blanket impls,
316/// however this impl will then conflict with (almost) all other impls - which is the reason
317/// why we don't do it e.g. for [`AsFieldBase`].
318///
319/// Instead, we complement it with just the impls `FreeAlgebraImplBase<_, _, _, _>:
320/// CanHomFrom<GaloisFieldBase<_>>` and `AsFieldBase<FreeAlgebraImpl<_, _, _, _>>:
321/// CanHomFrom<GaloisFieldBase<_>>`. This means that if `Impl = AsFieldBase<FreeAlgebraImpl<_, _, _,
322/// _>>`, together with the blanket implementation, it gives the very much desired self-isomorphism
323/// `GaloisFieldBase<_>: CanHomFrom<GaloisFieldBase<_>>`. The only downside of this approach is that
324/// `GaloisFieldBase` does not have a canonical self-isomorphism anymore if a nonstandard `Impl` is
325/// chosen - which I believe will be very rarely the case in practice.
326#[repr(transparent)]
327#[derive(Debug)]
328pub struct GaloisFieldBase<Impl>
329where
330    Impl: RingStore,
331    Impl::Type: Field + FreeAlgebra + FiniteRing,
332    <<Impl::Type as RingExtension>::BaseRing as RingStore>::Type: ZnRing + Field,
333{
334    base: Impl,
335}
336
337/// The default implementation of a finite field extension of a prime field,
338/// based on [`Zn`].
339pub type DefaultGaloisFieldImpl =
340    AsField<FreeAlgebraImpl<AsField<Zn>, SparseMapVector<AsField<Zn>>, Global, KaratsubaAlgorithm>>;
341
342/// Implementation of finite/galois fields.
343///
344/// This is the [`RingStore`] corresponding to [`GaloisFieldBase`]. For more details, see
345/// [`GaloisFieldBase`].
346pub type GaloisField<Impl = DefaultGaloisFieldImpl> = RingValue<GaloisFieldBase<Impl>>;
347
348/// Type alias for the most common instantiation of [`GaloisField`], which
349/// uses [`FreeAlgebraImpl`] to compute ring arithmetic.
350pub type GaloisFieldOver<R, A = Global, C = KaratsubaAlgorithm> = RingValue<GaloisFieldBaseOver<R, A, C>>;
351
352/// Type alias for the most common instantiation of [`GaloisFieldBase`], which
353/// uses [`FreeAlgebraImpl`] to compute ring arithmetic.
354pub type GaloisFieldBaseOver<R, A = Global, C = KaratsubaAlgorithm> =
355    GaloisFieldBase<AsField<FreeAlgebraImpl<R, SparseMapVector<R>, A, C>>>;
356
357impl GaloisField {
358    /// Creates a new instance of the finite/galois field `GF(p^degree)`.
359    ///
360    /// If you need more options for configuration, consider using
361    /// [`GaloisField::new_with_convolution()`] or the most general [`GaloisField::create()`].
362    ///
363    /// # Example
364    /// ```rust
365    /// # use feanor_math::ring::*;
366    /// # use feanor_math::rings::extension::*;
367    /// # use feanor_math::rings::finite::*;
368    /// # use feanor_math::homomorphism::*;
369    /// # use feanor_math::rings::extension::galois_field::*;
370    /// let F25 = GaloisField::new(5, 2);
371    /// let generator = F25.canonical_gen();
372    /// let norm = F25.mul_ref_fst(&generator, F25.pow(F25.clone_el(&generator), 5));
373    /// let inclusion = F25.inclusion();
374    /// // the norm must be an element of the prime field
375    /// assert!(
376    ///     F25.base_ring()
377    ///         .elements()
378    ///         .any(|x| { F25.eq_el(&norm, &inclusion.map(x)) })
379    /// );
380    /// ```
381    ///
382    /// # Panics
383    ///
384    /// Panics if `Z/pZ` is not a field, namely if `p` is not prime.
385    pub fn new(p: i64, degree: usize) -> Self {
386        let field = match Zn::new(p as u64).as_field() {
387            Ok(field) => field,
388            Err(_) => panic!("characteristic {p} is not a prime"),
389        };
390
391        Self::new_with_convolution(field, degree, Global, STANDARD_CONVOLUTION)
392    }
393}
394
395impl<R, A, C> GaloisFieldOver<R, A, C>
396where
397    R: RingStore + Clone,
398    R::Type: ZnRing + Field + SelfIso + CanHomFrom<StaticRingBase<i64>>,
399    C: ConvolutionAlgorithm<R::Type>,
400    A: Allocator + Clone,
401{
402    /// Creates a new instance of a finite/galois field, as given-degree extension of the
403    /// given base ring. The base ring must have prime characteristic.
404    ///
405    /// If you need to specify the minimal polynomial used, see also [`GaloisField::create()`].
406    ///
407    /// # Example
408    ///
409    /// Sometimes it is useful to have the base ring also be a field. This can e.g. be achieved
410    /// by
411    /// ```rust
412    /// #![feature(allocator_api)]
413    /// # use std::alloc::Global;
414    /// # use feanor_math::ring::*;
415    /// # use feanor_math::rings::extension::*;
416    /// # use feanor_math::rings::finite::*;
417    /// # use feanor_math::homomorphism::*;
418    /// # use feanor_math::rings::zn::zn_64::*;
419    /// # use feanor_math::rings::zn::*;
420    /// # use feanor_math::algorithms::convolution::*;
421    /// # use feanor_math::rings::extension::galois_field::*;
422    /// let F25 = GaloisField::new_with_convolution(
423    ///     Zn::new(5).as_field().ok().unwrap(),
424    ///     2,
425    ///     Global,
426    ///     STANDARD_CONVOLUTION,
427    /// );
428    /// let generator = F25.canonical_gen();
429    /// let norm = F25.mul_ref_fst(&generator, F25.pow(F25.clone_el(&generator), 5));
430    /// let inclusion = F25.inclusion();
431    /// // the norm must be an element of the prime field
432    /// assert!(
433    ///     F25.base_ring()
434    ///         .elements()
435    ///         .any(|x| { F25.eq_el(&norm, &inclusion.map(x)) })
436    /// );
437    /// ```
438    #[stability::unstable(feature = "enable")]
439    pub fn new_with_convolution(base_field: R, degree: usize, allocator: A, convolution_algorithm: C) -> Self {
440        assert!(degree >= 1);
441        let poly_ring = DensePolyRing::new(&base_field, "X");
442        let mut rng = oorandom::Rand64::new(
443            poly_ring
444                .base_ring()
445                .integer_ring()
446                .default_hash(poly_ring.base_ring().modulus()) as u128,
447        );
448        let modulus = find_small_irreducible_poly(&poly_ring, degree, &mut rng);
449        let mut modulus_vec = SparseMapVector::new(degree, base_field.clone());
450        for (c, i) in poly_ring.terms(&modulus) {
451            if i != degree {
452                *modulus_vec.at_mut(i) = base_field.negate(base_field.clone_el(c));
453            }
454        }
455        return RingValue::from(GaloisFieldBase {
456            base: AsField::from(AsFieldBase::promise_is_perfect_field(
457                FreeAlgebraImpl::new_with_convolution(
458                    base_field,
459                    degree,
460                    modulus_vec,
461                    "θ",
462                    allocator,
463                    convolution_algorithm,
464                ),
465            )),
466        });
467    }
468
469    /// Instantiates the galois field by calling `find_small_irreducible_poly()` with a poly ring
470    /// whose base ring is exactly `R`; this prevents infinite generic argument recursion, which
471    /// might otherwise happen if each recursive call adds a reference `&` to `R`.
472    fn new_internal(base_ring: R, degree: usize, allocator: A, convolution_algorithm: C) -> Self
473    where
474        R: Copy,
475    {
476        assert!(degree >= 1);
477        let poly_ring = DensePolyRing::new(base_ring, "X");
478        let mut rng = oorandom::Rand64::new(
479            poly_ring
480                .base_ring()
481                .integer_ring()
482                .default_hash(poly_ring.base_ring().modulus()) as u128,
483        );
484        let modulus = find_small_irreducible_poly(&poly_ring, degree, &mut rng);
485        let mut modulus_vec = SparseMapVector::new(degree, base_ring);
486        for (c, i) in poly_ring.terms(&modulus) {
487            if i != degree {
488                *modulus_vec.at_mut(i) = base_ring.negate(base_ring.clone_el(c));
489            }
490        }
491        return RingValue::from(GaloisFieldBase {
492            base: AsField::from(AsFieldBase::promise_is_perfect_field(
493                FreeAlgebraImpl::new_with_convolution(
494                    base_ring,
495                    degree,
496                    modulus_vec,
497                    "θ",
498                    allocator,
499                    convolution_algorithm,
500                ),
501            )),
502        });
503    }
504}
505
506impl<A> GaloisFieldBase<AsField<FreeAlgebraImpl<AsField<Zn>, SparseMapVector<AsField<Zn>>, A, KaratsubaAlgorithm>>>
507where
508    A: Allocator + Clone,
509{
510    /// Creates the galois ring `GR(p, e, degree)`, mirroring the structure of this galois field.
511    ///
512    /// A galois ring is similar to a galois field, but not a field anymore since it has prime power
513    /// characteristic instead of prime characteristic. It can be constructed as
514    /// `(Z/p^eZ)[X]/(f)` where `f` is a monic polynomial of degree `degree` such that `f mod p`
515    /// is irreducible. When using this function, the generating polynomial of the resulting
516    /// galois ring will be the coefficient-wise shortest lift of the generating polynomial of this
517    /// galois field.
518    ///
519    /// For more configuration options, use [`GaloisFieldBase::galois_ring_with()`].
520    #[stability::unstable(feature = "enable")]
521    pub fn galois_ring(&self, e: usize) -> AsLocalPIR<FreeAlgebraImpl<Zn, SparseMapVector<Zn>, A, KaratsubaAlgorithm>> {
522        self.galois_ring_with(
523            Zn::new(StaticRing::<i64>::RING.pow(*self.base_ring().modulus(), e) as u64),
524            self.base.get_ring().get_delegate().allocator().clone(),
525            STANDARD_CONVOLUTION,
526        )
527    }
528}
529
530impl<Impl> GaloisFieldBase<Impl>
531where
532    Impl: RingStore,
533    Impl::Type: Field + FreeAlgebra + FiniteRing,
534    <<Impl::Type as RingExtension>::BaseRing as RingStore>::Type: ZnRing + Field,
535{
536    /// Creates the galois ring `GR(p, e, degree)`, mirroring the structure of this galois field.
537    ///
538    /// A galois ring is similar to a galois field, but not a field anymore since it has prime power
539    /// characteristic instead of prime characteristic. It can be constructed as
540    /// `(Z/p^eZ)[X]/(f)` where `f` is a monic polynomial of degree `degree` such that `f mod p`
541    /// is irreducible. When using this function, the generating polynomial of the resulting
542    /// galois ring will be the coefficient-wise shortest lift of the generating polynomial of this
543    /// galois field.
544    ///
545    /// See also [`GaloisFieldBase::galois_ring()`] for a simpler version of this function.
546    #[stability::unstable(feature = "enable")]
547    pub fn galois_ring_with<S, A2, C2>(
548        &self,
549        new_base_ring: S,
550        allocator: A2,
551        convolution_algorithm: C2,
552    ) -> AsLocalPIR<FreeAlgebraImpl<S, SparseMapVector<S>, A2, C2>>
553    where
554        S: RingStore + Clone,
555        S::Type: ZnRing
556            + CanHomFrom<<<<Impl::Type as RingExtension>::BaseRing as RingStore>::Type as ZnRing>::IntegerRingBase>,
557        C2: ConvolutionAlgorithm<S::Type>,
558        A2: Allocator + Clone,
559    {
560        let (p, e) = is_prime_power(&BigIntRing::RING, &new_base_ring.size(&BigIntRing::RING).unwrap()).unwrap();
561        assert!(BigIntRing::RING.eq_el(&p, &self.base_ring().size(&BigIntRing::RING).unwrap()));
562        let mut modulus_vec = SparseMapVector::new(self.rank(), new_base_ring.clone());
563        let x_pow_deg = RingRef::new(self).pow(self.canonical_gen(), self.rank());
564        let x_pow_deg = self.wrt_canonical_basis(&x_pow_deg);
565        let hom = new_base_ring.can_hom(self.base_ring().integer_ring()).unwrap();
566        for i in 0..self.rank() {
567            if !self.base_ring().is_zero(&x_pow_deg.at(i)) {
568                *modulus_vec.at_mut(i) = hom.map(self.base_ring().smallest_lift(x_pow_deg.at(i)));
569            }
570        }
571        let result = FreeAlgebraImpl::new_with_convolution(
572            new_base_ring,
573            self.rank(),
574            modulus_vec,
575            "θ",
576            allocator,
577            convolution_algorithm,
578        );
579        let hom = result.base_ring().can_hom(self.base_ring().integer_ring()).unwrap();
580        let max_ideal_gen = result.inclusion().map(hom.map_ref(self.base_ring().modulus()));
581        return AsLocalPIR::from(AsLocalPIRBase::promise_is_local_pir(result, max_ideal_gen, Some(e)));
582    }
583
584    #[stability::unstable(feature = "enable")]
585    pub fn unwrap_self(self) -> Impl { self.base }
586}
587
588impl<Impl> GaloisField<Impl>
589where
590    Impl: RingStore,
591    Impl::Type: Field + FreeAlgebra + FiniteRing,
592    <<Impl::Type as RingExtension>::BaseRing as RingStore>::Type: ZnRing + Field,
593{
594    /// Most generic function to create a finite/galois field.
595    ///
596    /// It allows specifying all associated data. Note also that the passed implementation
597    /// must indeed be a field, and this is not checked at runtime. Passing a non-field
598    /// is impossible, unless [`AsFieldBase::promise_is_field()`] is used.
599    #[stability::unstable(feature = "enable")]
600    pub fn create(base: Impl) -> Self { RingValue::from(GaloisFieldBase { base }) }
601}
602
603impl<Impl> Clone for GaloisFieldBase<Impl>
604where
605    Impl: RingStore + Clone,
606    Impl::Type: Field + FreeAlgebra + FiniteRing,
607    <<Impl::Type as RingExtension>::BaseRing as RingStore>::Type: ZnRing + Field,
608{
609    fn clone(&self) -> Self {
610        Self {
611            base: self.base.clone(),
612        }
613    }
614}
615
616impl<Impl> Copy for GaloisFieldBase<Impl>
617where
618    Impl: RingStore + Copy,
619    Impl::Type: Field + FreeAlgebra + FiniteRing,
620    <<Impl::Type as RingExtension>::BaseRing as RingStore>::Type: ZnRing + Field,
621    El<Impl>: Copy,
622{
623}
624
625impl<Impl> PartialEq for GaloisFieldBase<Impl>
626where
627    Impl: RingStore,
628    Impl::Type: Field + FreeAlgebra + FiniteRing,
629    <<Impl::Type as RingExtension>::BaseRing as RingStore>::Type: ZnRing + Field,
630{
631    fn eq(&self, other: &Self) -> bool { self.base.get_ring() == other.base.get_ring() }
632}
633
634impl<Impl> DelegateRing for GaloisFieldBase<Impl>
635where
636    Impl: RingStore,
637    Impl::Type: Field + FreeAlgebra + FiniteRing,
638    <<Impl::Type as RingExtension>::BaseRing as RingStore>::Type: ZnRing + Field,
639{
640    type Base = Impl::Type;
641    type Element = El<Impl>;
642
643    fn delegate(&self, el: Self::Element) -> <Self::Base as RingBase>::Element { el }
644    fn delegate_mut<'a>(&self, el: &'a mut Self::Element) -> &'a mut <Self::Base as RingBase>::Element { el }
645    fn delegate_ref<'a>(&self, el: &'a Self::Element) -> &'a <Self::Base as RingBase>::Element { el }
646    fn rev_delegate(&self, el: <Self::Base as RingBase>::Element) -> Self::Element { el }
647
648    fn get_delegate(&self) -> &Self::Base { self.base.get_ring() }
649}
650
651impl<Impl> DelegateRingImplFiniteRing for GaloisFieldBase<Impl>
652where
653    Impl: RingStore,
654    Impl::Type: Field + FreeAlgebra + FiniteRing,
655    <<Impl::Type as RingExtension>::BaseRing as RingStore>::Type: ZnRing + Field,
656{
657}
658
659impl<Impl> Domain for GaloisFieldBase<Impl>
660where
661    Impl: RingStore,
662    Impl::Type: Field + FreeAlgebra + FiniteRing,
663    <<Impl::Type as RingExtension>::BaseRing as RingStore>::Type: ZnRing + Field,
664{
665}
666
667impl<Impl> Field for GaloisFieldBase<Impl>
668where
669    Impl: RingStore,
670    Impl::Type: Field + FreeAlgebra + FiniteRing,
671    <<Impl::Type as RingExtension>::BaseRing as RingStore>::Type: ZnRing + Field,
672{
673}
674
675impl<Impl> PerfectField for GaloisFieldBase<Impl>
676where
677    Impl: RingStore,
678    Impl::Type: Field + FreeAlgebra + FiniteRing,
679    <<Impl::Type as RingExtension>::BaseRing as RingStore>::Type: ZnRing + Field,
680{
681}
682
683// impl<Impl> LinSolveRing for GaloisFieldBase<Impl>
684//     where Impl: RingStore,
685//         Impl::Type: Field + FreeAlgebra + FiniteRing,
686//         <<Impl::Type as RingExtension>::BaseRing as RingStore>::Type: ZnRing + Field
687// {
688//     fn solve_right<V1, V2, V3, A>(&self, lhs: crate::matrix::SubmatrixMut<V1, Self::Element>,
689// rhs: crate::matrix::SubmatrixMut<V2, Self::Element>, out: crate::matrix::SubmatrixMut<V3,
690// Self::Element>, allocator: A) -> crate::algorithms::linsolve::SolveResult         where V1:
691// crate::matrix::AsPointerToSlice<Self::Element>,             V2:
692// crate::matrix::AsPointerToSlice<Self::Element>,             V3:
693// crate::matrix::AsPointerToSlice<Self::Element>,             A: Allocator
694//     {
695//         unimplemented!()
696//     }
697// }
698
699impl<Impl> EuclideanRing for GaloisFieldBase<Impl>
700where
701    Impl: RingStore,
702    Impl::Type: Field + FreeAlgebra + FiniteRing,
703    <<Impl::Type as RingExtension>::BaseRing as RingStore>::Type: ZnRing + Field,
704{
705    fn euclidean_div_rem(&self, lhs: Self::Element, rhs: &Self::Element) -> (Self::Element, Self::Element) {
706        assert!(!self.is_zero(rhs));
707        (self.base.checked_div(&lhs, rhs).unwrap(), self.zero())
708    }
709
710    fn euclidean_deg(&self, val: &Self::Element) -> Option<usize> { if self.is_zero(val) { Some(0) } else { Some(1) } }
711
712    fn euclidean_rem(&self, _: Self::Element, rhs: &Self::Element) -> Self::Element {
713        assert!(!self.is_zero(rhs));
714        self.zero()
715    }
716}
717
718impl<Impl> PrincipalIdealRing for GaloisFieldBase<Impl>
719where
720    Impl: RingStore,
721    Impl::Type: Field + FreeAlgebra + FiniteRing,
722    <<Impl::Type as RingExtension>::BaseRing as RingStore>::Type: ZnRing + Field,
723{
724    fn checked_div_min(&self, lhs: &Self::Element, rhs: &Self::Element) -> Option<Self::Element> {
725        if self.is_zero(lhs) && self.is_zero(rhs) {
726            Some(self.one())
727        } else {
728            self.checked_left_div(lhs, rhs)
729        }
730    }
731
732    fn extended_ideal_gen(
733        &self,
734        lhs: &Self::Element,
735        rhs: &Self::Element,
736    ) -> (Self::Element, Self::Element, Self::Element) {
737        if self.is_zero(lhs) {
738            (self.zero(), self.one(), self.clone_el(rhs))
739        } else {
740            (self.one(), self.zero(), self.clone_el(lhs))
741        }
742    }
743}
744
745impl<Impl> Serialize for GaloisFieldBase<Impl>
746where
747    Impl: RingStore + Serialize,
748    Impl::Type: Field + FreeAlgebra + FiniteRing + SerializableElementRing,
749    <<Impl::Type as RingExtension>::BaseRing as RingStore>::Type: ZnRing + Field,
750{
751    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
752    where
753        S: Serializer,
754    {
755        SerializableNewtypeStruct::new("GaloisField", &self.base).serialize(serializer)
756    }
757}
758
759impl<'de, Impl> Deserialize<'de> for GaloisFieldBase<Impl>
760where
761    Impl: RingStore + Deserialize<'de>,
762    Impl::Type: Field + FreeAlgebra + FiniteRing + SerializableElementRing,
763    <<Impl::Type as RingExtension>::BaseRing as RingStore>::Type: ZnRing + Field,
764{
765    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
766    where
767        D: Deserializer<'de>,
768    {
769        DeserializeSeedNewtypeStruct::new("GaloisField", PhantomData::<Impl>)
770            .deserialize(deserializer)
771            .map(|res| GaloisField::create(res).into())
772    }
773}
774
775impl<Impl> KaratsubaHint for GaloisFieldBase<Impl>
776where
777    Impl: RingStore,
778    Impl::Type: Field + FreeAlgebra + FiniteRing,
779    <<Impl::Type as RingExtension>::BaseRing as RingStore>::Type: ZnRing + Field,
780{
781    fn karatsuba_threshold(&self) -> usize { self.get_delegate().karatsuba_threshold() }
782}
783
784impl<Impl> ComputeInnerProduct for GaloisFieldBase<Impl>
785where
786    Impl: RingStore,
787    Impl::Type: Field + FreeAlgebra + FiniteRing,
788    <<Impl::Type as RingExtension>::BaseRing as RingStore>::Type: ZnRing + Field,
789{
790    fn inner_product<I: Iterator<Item = (Self::Element, Self::Element)>>(&self, els: I) -> Self::Element {
791        self.rev_delegate(
792            self.get_delegate()
793                .inner_product(els.map(|(a, b)| (self.delegate(a), self.delegate(b)))),
794        )
795    }
796
797    fn inner_product_ref<'a, I: Iterator<Item = (&'a Self::Element, &'a Self::Element)>>(&self, els: I) -> Self::Element
798    where
799        Self::Element: 'a,
800        Self: 'a,
801    {
802        self.rev_delegate(
803            self.get_delegate()
804                .inner_product_ref(els.map(|(a, b)| (self.delegate_ref(a), self.delegate_ref(b)))),
805        )
806    }
807
808    fn inner_product_ref_fst<'a, I: Iterator<Item = (&'a Self::Element, Self::Element)>>(&self, els: I) -> Self::Element
809    where
810        Self::Element: 'a,
811        Self: 'a,
812    {
813        self.rev_delegate(
814            self.get_delegate()
815                .inner_product_ref_fst(els.map(|(a, b)| (self.delegate_ref(a), self.delegate(b)))),
816        )
817    }
818}
819
820impl<Impl> StrassenHint for GaloisFieldBase<Impl>
821where
822    Impl: RingStore,
823    Impl::Type: Field + FreeAlgebra + FiniteRing,
824    <<Impl::Type as RingExtension>::BaseRing as RingStore>::Type: ZnRing + Field,
825{
826    fn strassen_threshold(&self) -> usize { self.get_delegate().strassen_threshold() }
827}
828
829/// For the rationale which blanket implementations I chose, see the [`GaloisFieldBase`].
830impl<Impl, S> CanHomFrom<S> for GaloisFieldBase<Impl>
831where
832    Impl: RingStore,
833    Impl::Type: Field + FreeAlgebra + FiniteRing + CanHomFrom<S>,
834    <<Impl::Type as RingExtension>::BaseRing as RingStore>::Type: ZnRing + Field,
835    S: ?Sized + RingBase,
836{
837    type Homomorphism = <Impl::Type as CanHomFrom<S>>::Homomorphism;
838
839    fn has_canonical_hom(&self, from: &S) -> Option<Self::Homomorphism> { self.base.get_ring().has_canonical_hom(from) }
840
841    fn map_in(&self, from: &S, el: <S as RingBase>::Element, hom: &Self::Homomorphism) -> Self::Element {
842        self.base.get_ring().map_in(from, el, hom)
843    }
844}
845
846/// For the rationale which blanket implementations I chose, see the [`GaloisFieldBase`].
847impl<Impl, R, A, V, C> CanHomFrom<GaloisFieldBase<Impl>> for FreeAlgebraImplBase<R, V, A, C>
848where
849    Impl: RingStore,
850    Impl::Type: Field + FreeAlgebra + FiniteRing,
851    <<Impl::Type as RingExtension>::BaseRing as RingStore>::Type: ZnRing + Field,
852    R: RingStore,
853    V: VectorView<El<R>>,
854    C: ConvolutionAlgorithm<R::Type>,
855    A: Allocator + Clone,
856    FreeAlgebraImplBase<R, V, A, C>: CanHomFrom<Impl::Type>,
857{
858    type Homomorphism = <FreeAlgebraImplBase<R, V, A, C> as CanHomFrom<Impl::Type>>::Homomorphism;
859
860    fn has_canonical_hom(&self, from: &GaloisFieldBase<Impl>) -> Option<Self::Homomorphism> {
861        self.has_canonical_hom(from.base.get_ring())
862    }
863
864    fn map_in(
865        &self,
866        from: &GaloisFieldBase<Impl>,
867        el: <GaloisFieldBase<Impl> as RingBase>::Element,
868        hom: &Self::Homomorphism,
869    ) -> Self::Element {
870        self.map_in(from.base.get_ring(), el, hom)
871    }
872}
873
874/// For the rationale which blanket implementations I chose, see the [`GaloisFieldBase`].
875impl<Impl, R, A, V, C> CanHomFrom<GaloisFieldBase<Impl>> for AsFieldBase<FreeAlgebraImpl<R, V, A, C>>
876where
877    Impl: RingStore,
878    Impl::Type: Field + FreeAlgebra + FiniteRing,
879    <<Impl::Type as RingExtension>::BaseRing as RingStore>::Type: ZnRing + Field,
880    R: RingStore,
881    R::Type: LinSolveRing,
882    V: VectorView<El<R>>,
883    C: ConvolutionAlgorithm<R::Type>,
884    A: Allocator + Clone,
885    FreeAlgebraImplBase<R, V, A, C>: CanHomFrom<Impl::Type>,
886{
887    type Homomorphism = <FreeAlgebraImplBase<R, V, A, C> as CanHomFrom<Impl::Type>>::Homomorphism;
888
889    fn has_canonical_hom(&self, from: &GaloisFieldBase<Impl>) -> Option<Self::Homomorphism> {
890        self.get_delegate().has_canonical_hom(from.base.get_ring())
891    }
892
893    fn map_in(
894        &self,
895        from: &GaloisFieldBase<Impl>,
896        el: <GaloisFieldBase<Impl> as RingBase>::Element,
897        hom: &Self::Homomorphism,
898    ) -> Self::Element {
899        self.rev_delegate(self.get_delegate().map_in(from.base.get_ring(), el, hom))
900    }
901}
902
903/// For the rationale which blanket implementations I chose, see the [`GaloisFieldBase`].
904impl<Impl, S> CanIsoFromTo<S> for GaloisFieldBase<Impl>
905where
906    Impl: RingStore,
907    Impl::Type: Field + FreeAlgebra + FiniteRing + CanIsoFromTo<S>,
908    <<Impl::Type as RingExtension>::BaseRing as RingStore>::Type: ZnRing + Field,
909    S: ?Sized + RingBase,
910{
911    type Isomorphism = <Impl::Type as CanIsoFromTo<S>>::Isomorphism;
912
913    fn has_canonical_iso(&self, from: &S) -> Option<Self::Isomorphism> { self.base.get_ring().has_canonical_iso(from) }
914
915    fn map_out(&self, from: &S, el: Self::Element, iso: &Self::Isomorphism) -> <S as RingBase>::Element {
916        self.base.get_ring().map_out(from, el, iso)
917    }
918}
919
920/// For the rationale which blanket implementations I chose, see the [`GaloisFieldBase`].
921impl<Impl, R, A, V, C> CanIsoFromTo<GaloisFieldBase<Impl>> for FreeAlgebraImplBase<R, V, A, C>
922where
923    Impl: RingStore,
924    Impl::Type: Field + FreeAlgebra + FiniteRing,
925    <<Impl::Type as RingExtension>::BaseRing as RingStore>::Type: ZnRing + Field,
926    R: RingStore,
927    V: VectorView<El<R>>,
928    C: ConvolutionAlgorithm<R::Type>,
929    A: Allocator + Clone,
930    FreeAlgebraImplBase<R, V, A, C>: CanIsoFromTo<Impl::Type>,
931{
932    type Isomorphism = <FreeAlgebraImplBase<R, V, A, C> as CanIsoFromTo<Impl::Type>>::Isomorphism;
933
934    fn has_canonical_iso(&self, from: &GaloisFieldBase<Impl>) -> Option<Self::Isomorphism> {
935        self.has_canonical_iso(from.base.get_ring())
936    }
937
938    fn map_out(
939        &self,
940        from: &GaloisFieldBase<Impl>,
941        el: Self::Element,
942        iso: &Self::Isomorphism,
943    ) -> <GaloisFieldBase<Impl> as RingBase>::Element {
944        self.map_out(from.base.get_ring(), el, iso)
945    }
946}
947
948/// For the rationale which blanket implementations I chose, see the [`GaloisFieldBase`].
949impl<Impl, R, A, V, C> CanIsoFromTo<GaloisFieldBase<Impl>> for AsFieldBase<FreeAlgebraImpl<R, V, A, C>>
950where
951    Impl: RingStore,
952    Impl::Type: Field + FreeAlgebra + FiniteRing,
953    <<Impl::Type as RingExtension>::BaseRing as RingStore>::Type: ZnRing + Field,
954    R: RingStore,
955    R::Type: LinSolveRing,
956    V: VectorView<El<R>>,
957    C: ConvolutionAlgorithm<R::Type>,
958    A: Allocator + Clone,
959    FreeAlgebraImplBase<R, V, A, C>: CanIsoFromTo<Impl::Type>,
960{
961    type Isomorphism = <FreeAlgebraImplBase<R, V, A, C> as CanIsoFromTo<Impl::Type>>::Isomorphism;
962
963    fn has_canonical_iso(&self, from: &GaloisFieldBase<Impl>) -> Option<Self::Isomorphism> {
964        self.get_delegate().has_canonical_iso(from.base.get_ring())
965    }
966
967    fn map_out(
968        &self,
969        from: &GaloisFieldBase<Impl>,
970        el: Self::Element,
971        iso: &Self::Isomorphism,
972    ) -> <GaloisFieldBase<Impl> as RingBase>::Element {
973        self.get_delegate()
974            .map_out(from.base.get_ring(), self.delegate(el), iso)
975    }
976}
977
978#[cfg(test)]
979use std::time::Instant;
980
981#[cfg(test)]
982use test::Bencher;
983
984#[test]
985fn test_can_hom_from() {
986    #[allow(unused)]
987    fn assert_impl_CanHomFrom<From, To>()
988    where
989        To: ?Sized + CanHomFrom<From>,
990        From: ?Sized + RingBase,
991    {
992    }
993
994    #[allow(unused)]
995    fn FreeAlgebraImpl_wrap_unwrap_homs<R, V, A, C>()
996    where
997        R: RingStore,
998        R::Type: SelfIso + LinSolveRing,
999        V: VectorView<El<R>>,
1000        A: Allocator + Clone,
1001        C: ConvolutionAlgorithm<R::Type>,
1002    {
1003        assert_impl_CanHomFrom::<FreeAlgebraImplBase<R, V, A, C>, AsFieldBase<FreeAlgebraImpl<R, V, A, C>>>();
1004    }
1005
1006    #[allow(unused)]
1007    fn FreeAlgebraImpl_from_GaloisField<R, V, A, C>()
1008    where
1009        R: RingStore,
1010        R::Type: SelfIso + LinSolveRing + FiniteRing + Field + ZnRing,
1011        V: VectorView<El<R>>,
1012        A: Allocator + Clone,
1013        C: ConvolutionAlgorithm<R::Type>,
1014    {
1015        assert_impl_CanHomFrom::<
1016            GaloisFieldBase<AsField<FreeAlgebraImpl<R, V, A, C>>>,
1017            AsFieldBase<FreeAlgebraImpl<R, V, A, C>>,
1018        >();
1019    }
1020
1021    #[allow(unused)]
1022    fn GaloisField_from_GaloisField<R, V, A, C>()
1023    where
1024        R: RingStore,
1025        R::Type: SelfIso + LinSolveRing + FiniteRing + Field + ZnRing,
1026        V: VectorView<El<R>>,
1027        A: Allocator + Clone,
1028        C: ConvolutionAlgorithm<R::Type>,
1029    {
1030        assert_impl_CanHomFrom::<
1031            GaloisFieldBase<AsField<FreeAlgebraImpl<R, V, A, C>>>,
1032            GaloisFieldBase<AsField<FreeAlgebraImpl<R, V, A, C>>>,
1033        >();
1034    }
1035}
1036
1037#[test]
1038fn test_galois_field() {
1039    let field = GaloisField::new(3, 1);
1040    assert_eq!(3, field.elements().count());
1041    crate::ring::generic_tests::test_ring_axioms(&field, field.elements());
1042    crate::ring::generic_tests::test_self_iso(&field, field.elements());
1043    crate::field::generic_tests::test_field_axioms(&field, field.elements());
1044
1045    let field = GaloisField::new(3, 2);
1046    assert_eq!(9, field.elements().count());
1047    crate::ring::generic_tests::test_ring_axioms(&field, field.elements());
1048    crate::ring::generic_tests::test_self_iso(&field, field.elements());
1049    crate::field::generic_tests::test_field_axioms(&field, field.elements());
1050}
1051
1052#[test]
1053fn test_principal_ideal_ring_axioms() {
1054    let field = GaloisField::new(3, 2);
1055    crate::pid::generic_tests::test_principal_ideal_ring_axioms(&field, field.elements());
1056    crate::pid::generic_tests::test_euclidean_ring_axioms(&field, field.elements());
1057}
1058
1059#[test]
1060fn test_galois_field_even() {
1061    for degree in 1..=9 {
1062        let field = GaloisField::new_with_convolution(
1063            Zn::new(2).as_field().ok().unwrap(),
1064            degree,
1065            Global,
1066            STANDARD_CONVOLUTION,
1067        );
1068        assert_eq!(degree, field.rank());
1069        assert!(field.into().unwrap_self().into().unwrap_self().as_field().is_ok());
1070    }
1071}
1072
1073#[test]
1074fn test_galois_field_odd() {
1075    for degree in 1..=9 {
1076        let field = GaloisField::new_with_convolution(
1077            Zn::new(3).as_field().ok().unwrap(),
1078            degree,
1079            Global,
1080            STANDARD_CONVOLUTION,
1081        );
1082        assert_eq!(degree, field.rank());
1083        assert!(field.into().unwrap_self().into().unwrap_self().as_field().is_ok());
1084    }
1085
1086    for degree in 1..=9 {
1087        let field = GaloisField::new_with_convolution(
1088            Zn::new(5).as_field().ok().unwrap(),
1089            degree,
1090            Global,
1091            STANDARD_CONVOLUTION,
1092        );
1093        assert_eq!(degree, field.rank());
1094        assert!(field.into().unwrap_self().into().unwrap_self().as_field().is_ok());
1095    }
1096}
1097
1098#[test]
1099fn test_galois_field_no_trinomial() {
1100    let field =
1101        GaloisField::new_with_convolution(Zn::new(2).as_field().ok().unwrap(), 24, Global, STANDARD_CONVOLUTION);
1102    assert_eq!(24, field.rank());
1103    let poly_ring = DensePolyRing::new(field.base_ring(), "X");
1104    poly_ring.println(&field.generating_poly(&poly_ring, &poly_ring.base_ring().identity()));
1105    assert!(field.into().unwrap_self().into().unwrap_self().as_field().is_ok());
1106
1107    let field =
1108        GaloisField::new_with_convolution(Zn::new(3).as_field().ok().unwrap(), 30, Global, STANDARD_CONVOLUTION);
1109    assert_eq!(30, field.rank());
1110    let poly_ring = DensePolyRing::new(field.base_ring(), "X");
1111    poly_ring.println(&field.generating_poly(&poly_ring, &poly_ring.base_ring().identity()));
1112    assert!(field.into().unwrap_self().into().unwrap_self().as_field().is_ok());
1113
1114    let field =
1115        GaloisField::new_with_convolution(Zn::new(17).as_field().ok().unwrap(), 32, Global, STANDARD_CONVOLUTION);
1116    assert_eq!(32, field.rank());
1117    let poly_ring = DensePolyRing::new(field.base_ring(), "X");
1118    poly_ring.println(&field.generating_poly(&poly_ring, &poly_ring.base_ring().identity()));
1119    assert!(field.into().unwrap_self().into().unwrap_self().as_field().is_ok());
1120}
1121
1122#[bench]
1123fn bench_create_galois_ring_2_14_96(bencher: &mut Bencher) {
1124    bencher.iter(|| {
1125        let field = GaloisField::new(2, 96);
1126        let ring = field.get_ring().galois_ring(14);
1127        assert_eq!(96, ring.rank());
1128    });
1129}
1130
1131#[test]
1132#[ignore]
1133fn test_galois_field_huge() {
1134    let start = Instant::now();
1135    let field = GaloisField::new(17, 2048);
1136    _ = std::hint::black_box(field);
1137    let end = Instant::now();
1138    println!("Created GF(17^2048) in {} ms", (end - start).as_millis());
1139}