Skip to main content

feanor_math/rings/zn/
mod.rs

1use super::field::AsFieldBase;
2use super::finite::FiniteRing;
3use crate::algorithms;
4use crate::divisibility::{DivisibilityRing, DivisibilityRingStore};
5use crate::homomorphism::*;
6use crate::integer::*;
7use crate::ordered::*;
8use crate::pid::{EuclideanRingStore, PrincipalIdealRing, *};
9use crate::primitive_int::StaticRing;
10use crate::ring::*;
11use crate::rings::finite::FiniteRingStore;
12
13/// This module contains [`zn_64::Zn`], the new, heavily optimized implementation of `Z/nZ`
14/// for moduli `n` of size slightly smaller than 64 bits.
15pub mod zn_64;
16/// This module contains [`zn_big::Zn`], a general-purpose implementation of
17/// Barett reduction. It is relatively slow when instantiated with small fixed-size
18/// integer type.
19pub mod zn_big;
20/// This module contains [`zn_rns::Zn`], a residue number system (RNS) implementation of
21/// `Z/nZ` for highly composite `n`.
22pub mod zn_rns;
23/// This module contains [`zn_static::Zn`], an implementation of `Z/nZ` for a small `n`
24/// that is known at compile-time.
25pub mod zn_static;
26
27/// Trait for all rings that represent a quotient of the integers `Z/nZ` for some integer `n`.
28pub trait ZnRing: PrincipalIdealRing + FiniteRing + CanHomFrom<Self::IntegerRingBase> {
29    /// there seems to be a problem with associated type bounds, hence we cannot use `Integers:
30    /// IntegerRingStore` or `Integers: RingStore<Type: IntegerRing>`
31    type IntegerRingBase: IntegerRing + ?Sized;
32    type IntegerRing: RingStore<Type = Self::IntegerRingBase>;
33
34    fn integer_ring(&self) -> &Self::IntegerRing;
35    fn modulus(&self) -> &El<Self::IntegerRing>;
36
37    /// Computes the smallest positive lift for some `x` in `Z/nZ`, i.e. the smallest positive
38    /// integer `m` such that `m = x mod n`.
39    ///
40    /// This will be one of `0, 1, ..., n - 1`. If an integer in `-(n - 1)/2, ..., -1, 0, 1, ..., (n
41    /// - 1)/2` (for odd `n`) is needed instead, use [`ZnRing::smallest_lift()`].
42    fn smallest_positive_lift(&self, el: Self::Element) -> El<Self::IntegerRing>;
43
44    /// Computes any lift for some `x` in `Z/nZ`, i.e. the some integer `m` such that `m = x mod n`.
45    ///
46    /// The only requirement is that `m` is a valid element of the integer ring, in particular that
47    /// it fits within the required amount of bits, if [`ZnRing::IntegerRing`] is a fixed-size
48    /// integer ring.
49    fn any_lift(&self, el: Self::Element) -> El<Self::IntegerRing> { self.smallest_positive_lift(el) }
50
51    /// If the given integer is within `{ 0, ..., n - 1 }`, returns the corresponding
52    /// element in `Z/nZ`. Any other input is considered a logic error.
53    ///
54    /// Unless the context is absolutely performance-critical, it might be safer to use
55    /// the homomorphism provided by [`CanHomFrom`] which performs proper modular reduction
56    /// of the input.
57    ///
58    /// This function never causes undefined behavior, but an invalid input leads to
59    /// a logic error. In particular, the result in such a case does not have to be
60    /// congruent to the input mod `n`, nor does it even have to be a valid element
61    /// of the ring (i.e. operations involving it may not follow the ring axioms).
62    /// Implementors are strongly encouraged to check the element during debug builds.
63    fn from_int_promise_reduced(&self, x: El<Self::IntegerRing>) -> Self::Element;
64
65    /// Computes the smallest lift for some `x` in `Z/nZ`, i.e. the smallest integer `m` such that
66    /// `m = x mod n`.
67    ///
68    /// This will be one of `-(n - 1)/2, ..., -1, 0, 1, ..., (n - 1)/2` (for odd `n`). If an integer
69    /// in `0, 1, ..., n - 1` is needed instead, use [`ZnRing::smallest_positive_lift()`].
70    fn smallest_lift(&self, el: Self::Element) -> El<Self::IntegerRing> {
71        let result = self.smallest_positive_lift(el);
72        let mut mod_half = self.integer_ring().clone_el(self.modulus());
73        self.integer_ring().euclidean_div_pow_2(&mut mod_half, 1);
74        if self.integer_ring().is_gt(&result, &mod_half) {
75            return self.integer_ring().sub_ref_snd(result, self.modulus());
76        } else {
77            return result;
78        }
79    }
80
81    /// Returns whether this ring is a field, i.e. whether `n` is prime.
82    fn is_field(&self) -> bool { algorithms::miller_rabin::is_prime_base(RingRef::new(self), 10) }
83}
84
85/// Trait for implementations of [`ZnRing`] that can be created (possibly with a
86/// default configuration) from just the integer modulus.
87///
88/// I am not yet sure whether to use this trait, or opt for a factory trait (which
89/// would then offer more flexibility).
90#[stability::unstable(feature = "enable")]
91pub trait FromModulusCreateableZnRing: Sized + ZnRing {
92    fn from_modulus<F, E>(create_modulus: F) -> Result<Self, E>
93    where
94        F: FnOnce(&Self::IntegerRingBase) -> Result<El<Self::IntegerRing>, E>;
95}
96
97pub mod generic_impls {
98    use std::alloc::Global;
99    use std::marker::PhantomData;
100
101    use crate::algorithms::convolution::STANDARD_CONVOLUTION;
102    use crate::algorithms::int_bisect;
103    use crate::divisibility::DivisibilityRingStore;
104    use crate::field::*;
105    use crate::integer::{IntegerRing, IntegerRingStore};
106    use crate::ordered::*;
107    use crate::primitive_int::{StaticRing, StaticRingBase};
108    use crate::rings::extension::galois_field::{GaloisField, GaloisFieldOver};
109    use crate::rings::zn::*;
110
111    /// A generic `ZZ -> Z/nZ` homomorphism. Optimized for the case that values of `ZZ` can be very
112    /// large, but allow for efficient estimation of their approximate size.
113
114    pub struct BigIntToZnHom<I: ?Sized + IntegerRing, J: ?Sized + IntegerRing, R: ?Sized + ZnRing>
115    where
116        I: CanIsoFromTo<R::IntegerRingBase> + CanIsoFromTo<J>,
117    {
118        highbit_mod: usize,
119        highbit_bound: usize,
120        int_ring: PhantomData<I>,
121        to_large_int_ring: PhantomData<J>,
122        hom: <I as CanHomFrom<R::IntegerRingBase>>::Homomorphism,
123        iso: <I as CanIsoFromTo<R::IntegerRingBase>>::Isomorphism,
124        iso2: <I as CanIsoFromTo<J>>::Isomorphism,
125    }
126
127    /// See [`map_in_from_bigint()`].
128    ///
129    /// This will only ever return `None` if one of the integer ring `has_canonical_hom/iso` returns
130    /// `None`.
131    #[stability::unstable(feature = "enable")]
132    pub fn has_canonical_hom_from_bigint<I: ?Sized + IntegerRing, J: ?Sized + IntegerRing, R: ?Sized + ZnRing>(
133        from: &I,
134        to: &R,
135        to_large_int_ring: &J,
136        bounded_reduce_bound: Option<&J::Element>,
137    ) -> Option<BigIntToZnHom<I, J, R>>
138    where
139        I: CanIsoFromTo<R::IntegerRingBase> + CanIsoFromTo<J>,
140    {
141        if let Some(bound) = bounded_reduce_bound {
142            Some(BigIntToZnHom {
143                highbit_mod: to.integer_ring().abs_highest_set_bit(to.modulus()).unwrap(),
144                highbit_bound: to_large_int_ring.abs_highest_set_bit(bound).unwrap(),
145                int_ring: PhantomData,
146                to_large_int_ring: PhantomData,
147                hom: from.has_canonical_hom(to.integer_ring().get_ring())?,
148                iso: from.has_canonical_iso(to.integer_ring().get_ring())?,
149                iso2: from.has_canonical_iso(to_large_int_ring)?,
150            })
151        } else {
152            Some(BigIntToZnHom {
153                highbit_mod: to.integer_ring().abs_highest_set_bit(to.modulus()).unwrap(),
154                highbit_bound: usize::MAX,
155                int_ring: PhantomData,
156                to_large_int_ring: PhantomData,
157                hom: from.has_canonical_hom(to.integer_ring().get_ring())?,
158                iso: from.has_canonical_iso(to.integer_ring().get_ring())?,
159                iso2: from.has_canonical_iso(to_large_int_ring)?,
160            })
161        }
162    }
163
164    /// A parameterized, generic variant of the reduction `Z -> Z/nZ`.
165    /// It considers the following situations:
166    ///  - the source ring `Z` might not be large enough to represent `n`
167    ///  - the integer ring associated to the destination ring `Z/nZ` might not be large enough to
168    ///    represent the input
169    ///  - the destination ring might use Barett reductions (or similar) for fast modular reduction
170    ///    if the input is bounded by some fixed bound `B`
171    ///  - general modular reduction modulo `n` is only performed in the source ring if necessary
172    ///
173    /// In particular, we use the following additional parameters:
174    ///  - `to_large_int_ring`: an integer ring that can represent all integers for which we can
175    ///    perform fast modular reduction (i.e. those bounded by `B`)
176    ///  - `from_positive_representative_exact`: a function that performs the restricted reduction
177    ///    `{0, ..., n - 1} -> Z/nZ`
178    ///  - `from_positive_representative_bounded`: a function that performs the restricted reduction
179    ///    `{0, ..., B - 1} -> Z/nZ`
180    ///
181    /// It first estimates the size of numbers by their bitlength, so don't use this for small
182    /// integers (i.e. `ixx`-types), as the estimation is likely to take longer than the actual
183    /// modular reduction.
184    ///
185    /// Note that the input size estimates consider only the bitlength of numbers, and so there is a
186    /// small margin in which a reduction method for larger numbers than necessary is used.
187    /// Furthermore, if the integer rings used can represent some but not all positive numbers of a
188    /// certain bitlength, there might be rare edge cases with panics/overflows.
189    ///
190    /// In particular, if the input integer ring `Z` can represent the input `x`, but not `n` AND
191    /// `x` and `n` have the same bitlength, this function might decide that we have to perform
192    /// generic modular reduction (even though `x < n`), and try to map `n` into `Z`. This is never
193    /// a problem if the primitive integer rings `StaticRing::<ixx>::RING` are used, or if `B >=
194    /// 2n`.
195    #[stability::unstable(feature = "enable")]
196    pub fn map_in_from_bigint<I: ?Sized + IntegerRing, J: ?Sized + IntegerRing, R: ?Sized + ZnRing, F, G>(
197        from: &I,
198        to: &R,
199        to_large_int_ring: &J,
200        el: I::Element,
201        hom: &BigIntToZnHom<I, J, R>,
202        from_positive_representative_exact: F,
203        from_positive_representative_bounded: G,
204    ) -> R::Element
205    where
206        I: CanIsoFromTo<R::IntegerRingBase> + CanIsoFromTo<J>,
207        F: FnOnce(El<R::IntegerRing>) -> R::Element,
208        G: FnOnce(J::Element) -> R::Element,
209    {
210        let (neg, n) = if from.is_neg(&el) {
211            (true, from.negate(el))
212        } else {
213            (false, el)
214        };
215        let ZZ = to.integer_ring().get_ring();
216        let highbit_el = from.abs_highest_set_bit(&n).unwrap_or(0);
217
218        let reduced = if highbit_el < hom.highbit_mod {
219            from_positive_representative_exact(from.map_out(ZZ, n, &hom.iso))
220        } else if highbit_el < hom.highbit_bound {
221            from_positive_representative_bounded(from.map_out(to_large_int_ring, n, &hom.iso2))
222        } else {
223            from_positive_representative_exact(from.map_out(
224                ZZ,
225                from.euclidean_rem(n, &from.map_in_ref(ZZ, to.modulus(), &hom.hom)),
226                &hom.iso,
227            ))
228        };
229        if neg { to.negate(reduced) } else { reduced }
230    }
231
232    /// Generates a uniformly random element of `Z/nZ` using the randomness of `rng`.
233    /// Designed to be used when implementing
234    /// [`crate::rings::finite::FiniteRing::random_element()`].
235    #[stability::unstable(feature = "enable")]
236    pub fn random_element<R: ZnRing, G: FnMut() -> u64>(ring: &R, rng: G) -> R::Element {
237        ring.map_in(
238            ring.integer_ring().get_ring(),
239            ring.integer_ring().get_uniformly_random(ring.modulus(), rng),
240            &ring.has_canonical_hom(ring.integer_ring().get_ring()).unwrap(),
241        )
242    }
243
244    /// Computes the checked division in `Z/nZ`. Designed to be used when implementing
245    /// [`crate::divisibility::DivisibilityRing::checked_left_div()`].
246    #[stability::unstable(feature = "enable")]
247    pub fn checked_left_div<R: ZnRingStore>(ring: R, lhs: &El<R>, rhs: &El<R>) -> Option<El<R>>
248    where
249        R::Type: ZnRing,
250    {
251        if ring.is_zero(lhs) {
252            return Some(ring.zero());
253        }
254        let int_ring = ring.integer_ring();
255        let lhs_lift = ring.smallest_positive_lift(ring.clone_el(lhs));
256        let rhs_lift = ring.smallest_positive_lift(ring.clone_el(rhs));
257        let (s, _, d) = int_ring.extended_ideal_gen(&rhs_lift, ring.modulus());
258        if let Some(quotient) = int_ring.checked_div(&lhs_lift, &d) {
259            Some(ring.mul(ring.coerce(int_ring, quotient), ring.coerce(int_ring, s)))
260        } else {
261            None
262        }
263    }
264
265    #[stability::unstable(feature = "enable")]
266    pub fn checked_div_min<R: ZnRingStore>(ring: R, lhs: &El<R>, rhs: &El<R>) -> Option<El<R>>
267    where
268        R::Type: ZnRing,
269    {
270        if ring.is_zero(lhs) && ring.is_zero(rhs) {
271            return Some(ring.one());
272        }
273        assert!(ring.is_noetherian());
274        let int_ring = ring.integer_ring();
275        let rhs_ann = int_ring
276            .checked_div(
277                ring.modulus(),
278                &int_ring.ideal_gen(ring.modulus(), &ring.smallest_positive_lift(ring.clone_el(rhs))),
279            )
280            .unwrap();
281        let some_sol = ring.smallest_positive_lift(ring.checked_div(lhs, rhs)?);
282        let minimal_solution = int_ring.euclidean_rem(some_sol, &rhs_ann);
283        if int_ring.is_zero(&minimal_solution) {
284            return Some(ring.coerce(&int_ring, rhs_ann));
285        } else {
286            return Some(ring.coerce(&int_ring, minimal_solution));
287        }
288    }
289
290    #[stability::unstable(feature = "enable")]
291    pub fn interpolation_ring<R: ZnRingStore>(ring: R, count: usize) -> GaloisFieldOver<R>
292    where
293        R: Clone,
294        R::Type: ZnRing + Field + SelfIso + CanHomFrom<StaticRingBase<i64>>,
295    {
296        let ZZbig = BigIntRing::RING;
297        let modulus = int_cast(ring.integer_ring().clone_el(ring.modulus()), ZZbig, ring.integer_ring());
298        let count = int_cast(count.try_into().unwrap(), ZZbig, StaticRing::<i64>::RING);
299        let degree = int_bisect::find_root_floor(StaticRing::<i64>::RING, 1, |d| {
300            if *d > 0 && ZZbig.is_gt(&ZZbig.pow(ZZbig.clone_el(&modulus), *d as usize), &count) {
301                1
302            } else {
303                -1
304            }
305        }) + 1;
306        assert!(degree >= 1);
307        return GaloisField::new_with_convolution(ring, degree as usize, Global, STANDARD_CONVOLUTION);
308    }
309}
310
311/// The [`crate::ring::RingStore`] corresponding to [`ZnRing`].
312pub trait ZnRingStore: FiniteRingStore
313where
314    Self::Type: ZnRing,
315{
316    delegate! { ZnRing, fn integer_ring(&self) -> &<Self::Type as ZnRing>::IntegerRing }
317    delegate! { ZnRing, fn modulus(&self) -> &El<<Self::Type as ZnRing>::IntegerRing> }
318    delegate! { ZnRing, fn smallest_positive_lift(&self, el: El<Self>) -> El<<Self::Type as ZnRing>::IntegerRing> }
319    delegate! { ZnRing, fn smallest_lift(&self, el: El<Self>) -> El<<Self::Type as ZnRing>::IntegerRing> }
320    delegate! { ZnRing, fn any_lift(&self, el: El<Self>) -> El<<Self::Type as ZnRing>::IntegerRing> }
321    delegate! { ZnRing, fn is_field(&self) -> bool }
322
323    fn as_field(self) -> Result<RingValue<AsFieldBase<Self>>, Self>
324    where
325        Self: Sized,
326    {
327        if self.is_field() {
328            Ok(RingValue::from(AsFieldBase::promise_is_perfect_field(self)))
329        } else {
330            Err(self)
331        }
332    }
333}
334
335impl<R: RingStore> ZnRingStore for R where R::Type: ZnRing {}
336
337/// Trait for algorithms that require some implementation of
338/// `Z/nZ`, but do not care which.
339///
340/// See [`choose_zn_impl()`] for details.
341pub trait ZnOperation {
342    type Output<'a>
343    where
344        Self: 'a;
345
346    fn call<'a, R>(self, ring: R) -> Self::Output<'a>
347    where
348        Self: 'a,
349        R: 'a + RingStore + Send + Sync,
350        R::Type: ZnRing,
351        El<R>: Send;
352}
353
354/// Calls the given function with some implementation of the ring
355/// `Z/nZ`, chosen depending on `n` to provide best performance.
356///
357/// It is currently necessary to write all the boilerplate code that
358/// comes with manually implementing [`ZnOperation`]. I experimented with
359/// macros, but currently something simple seems like the best solution.
360///
361/// # Example
362/// ```rust
363/// # use feanor_math::ring::*;
364/// # use feanor_math::homomorphism::*;
365/// # use feanor_math::rings::zn::*;
366/// # use feanor_math::primitive_int::*;
367/// # use feanor_math::integer::*;
368/// # use feanor_math::assert_el_eq;
369///
370/// let int_value = 4;
371/// // work in Z/17Z without explicitly choosing an implementation
372/// struct DoStuff {
373///     int_value: i64,
374/// }
375/// impl ZnOperation for DoStuff {
376///     type Output<'a>
377///         = ()
378///     where
379///         Self: 'a;
380///
381///     fn call<'a, R>(self, Zn: R) -> ()
382///     where
383///         Self: 'a,
384///         R: 'a + RingStore,
385///         R::Type: ZnRing,
386///     {
387///         let value = Zn.coerce(
388///             Zn.integer_ring(),
389///             int_cast(self.int_value, Zn.integer_ring(), &StaticRing::<i64>::RING),
390///         );
391///         assert_el_eq!(Zn, Zn.int_hom().map(-1), Zn.mul_ref(&value, &value));
392///     }
393/// }
394/// choose_zn_impl(StaticRing::<i64>::RING, 17, DoStuff { int_value });
395/// ```
396pub fn choose_zn_impl<'a, I, F>(ZZ: I, n: El<I>, f: F) -> F::Output<'a>
397where
398    I: 'a + RingStore,
399    I::Type: IntegerRing,
400    F: ZnOperation,
401{
402    if ZZ.abs_highest_set_bit(&n).unwrap_or(0) < 57 {
403        f.call(zn_64::Zn::new(StaticRing::<i64>::RING.coerce(&ZZ, n) as u64))
404    } else {
405        f.call(zn_big::Zn::new(BigIntRing::RING, int_cast(n, &BigIntRing::RING, &ZZ)))
406    }
407}
408
409#[test]
410fn test_choose_zn_impl() {
411    let int_value = 4;
412    // work in Z/17Z without explicitly choosing an implementation
413    struct DoStuff {
414        int_value: i64,
415    }
416    impl ZnOperation for DoStuff {
417        type Output<'a>
418            = ()
419        where
420            Self: 'a;
421
422        fn call<'a, R>(self, Zn: R)
423        where
424            R: 'a + RingStore,
425            R::Type: ZnRing,
426        {
427            let value = Zn.coerce(
428                Zn.integer_ring(),
429                int_cast(self.int_value, Zn.integer_ring(), &StaticRing::<i64>::RING),
430            );
431            assert_el_eq!(Zn, Zn.int_hom().map(-1), Zn.mul_ref(&value, &value));
432        }
433    }
434    choose_zn_impl(StaticRing::<i64>::RING, 17, DoStuff { int_value });
435}
436
437#[derive(Debug, Clone, Copy, PartialEq, Eq)]
438enum ReductionMapRequirements {
439    SmallestLift,
440    ExplicitReduce,
441}
442
443/// The homomorphism `Z/nZ -> Z/mZ` that exists whenever `m | n`. In
444/// addition to the map, this also provides a function [`ZnReductionMap::smallest_lift()`]
445/// that computes the "smallest" preimage under the map, and a function
446/// [`ZnReductionMap::mul_quotient_fraction()`], that computes the multiplication
447/// with `n/m` while also changing from `Z/mZ` to `Z/nZ`. This is very
448/// useful in many number theoretic applications, where one often has to switch
449/// between `Z/nZ` and `Z/mZ`.
450///
451/// Furthermore, many implementations of `ZnRing` currently do not support
452/// [`CanHomFrom`]-homomorphisms when the moduli are different (but divide each
453/// other).
454pub struct ZnReductionMap<R, S>
455where
456    R: RingStore,
457    R::Type: ZnRing,
458    S: RingStore,
459    S::Type: ZnRing,
460{
461    from: R,
462    to: S,
463    fraction_of_quotients: El<R>,
464    to_modulus: El<<R::Type as ZnRing>::IntegerRing>,
465    to_from_int: <S::Type as CanHomFrom<<S::Type as ZnRing>::IntegerRingBase>>::Homomorphism,
466    from_from_int: <R::Type as CanHomFrom<<R::Type as ZnRing>::IntegerRingBase>>::Homomorphism,
467    map_forward_requirement: ReductionMapRequirements,
468}
469
470impl<R, S> ZnReductionMap<R, S>
471where
472    R: RingStore,
473    R::Type: ZnRing,
474    S: RingStore,
475    S::Type: ZnRing,
476{
477    pub fn new(from: R, to: S) -> Option<Self> {
478        let from_char = from.characteristic(&BigIntRing::RING).unwrap();
479        let to_char = to.characteristic(&BigIntRing::RING).unwrap();
480        if let Some(frac) = BigIntRing::RING.checked_div(&from_char, &to_char) {
481            let map_forward_requirement: ReductionMapRequirements =
482                if to.integer_ring().get_ring().representable_bits().is_none()
483                    || BigIntRing::RING.is_lt(
484                        &from_char,
485                        &BigIntRing::RING.power_of_two(to.integer_ring().get_ring().representable_bits().unwrap()),
486                    )
487                {
488                    ReductionMapRequirements::SmallestLift
489                } else {
490                    ReductionMapRequirements::ExplicitReduce
491                };
492            Some(Self {
493                map_forward_requirement,
494                to_modulus: int_cast(
495                    to.integer_ring().clone_el(to.modulus()),
496                    from.integer_ring(),
497                    to.integer_ring(),
498                ),
499                to_from_int: to.get_ring().has_canonical_hom(to.integer_ring().get_ring()).unwrap(),
500                from_from_int: from
501                    .get_ring()
502                    .has_canonical_hom(from.integer_ring().get_ring())
503                    .unwrap(),
504                fraction_of_quotients: from.can_hom(from.integer_ring()).unwrap().map(int_cast(
505                    frac,
506                    from.integer_ring(),
507                    BigIntRing::RING,
508                )),
509                from,
510                to,
511            })
512        } else {
513            None
514        }
515    }
516
517    /// Computes the additive group homomorphism `Z/mZ -> Z/nZ, x -> (n/m)x`.
518    ///
519    /// # Example
520    /// ```rust
521    /// # use feanor_math::assert_el_eq;
522    /// # use feanor_math::ring::*;
523    /// # use feanor_math::homomorphism::*;
524    /// # use feanor_math::rings::zn::*;
525    /// # use feanor_math::rings::zn::zn_64::*;
526    /// let Z5 = Zn::new(5);
527    /// let Z25 = Zn::new(25);
528    /// let f = ZnReductionMap::new(&Z25, &Z5).unwrap();
529    /// assert_el_eq!(
530    ///     Z25,
531    ///     Z25.int_hom().map(15),
532    ///     f.mul_quotient_fraction(Z5.int_hom().map(3))
533    /// );
534    /// ```
535    pub fn mul_quotient_fraction(&self, x: El<S>) -> El<R> {
536        self.from.mul_ref_snd(self.any_preimage(x), &self.fraction_of_quotients)
537    }
538
539    /// Computes the smallest preimage under the reduction map `Z/nZ -> Z/mZ`, where
540    /// "smallest" refers to the element that has the smallest lift to `Z`.
541    ///
542    /// # Example
543    /// ```rust
544    /// # use feanor_math::assert_el_eq;
545    /// # use feanor_math::ring::*;
546    /// # use feanor_math::homomorphism::*;
547    /// # use feanor_math::rings::zn::*;
548    /// # use feanor_math::rings::zn::zn_64::*;
549    /// let Z5 = Zn::new(5);
550    /// let Z25 = Zn::new(25);
551    /// let f = ZnReductionMap::new(&Z25, &Z5).unwrap();
552    /// assert_el_eq!(
553    ///     Z25,
554    ///     Z25.int_hom().map(-2),
555    ///     f.smallest_lift(Z5.int_hom().map(3))
556    /// );
557    /// ```
558    pub fn smallest_lift(&self, x: El<S>) -> El<R> {
559        self.from.get_ring().map_in(
560            self.from.integer_ring().get_ring(),
561            int_cast(
562                self.to.smallest_lift(x),
563                self.from.integer_ring(),
564                self.to.integer_ring(),
565            ),
566            &self.from_from_int,
567        )
568    }
569
570    pub fn any_preimage(&self, x: El<S>) -> El<R> {
571        // the problem is that we don't know if `to.any_lift(x)` will fit into
572        // `from.integer_ring()`; furthermore, profiling indicates that it won't help a lot
573        // anyway, since taking the smallest lift now will usually make reduction cheaper
574        // later
575        self.smallest_lift(x)
576    }
577
578    pub fn smallest_lift_ref(&self, x: &El<S>) -> El<R> { self.smallest_lift(self.codomain().clone_el(x)) }
579}
580
581impl<R, S> Homomorphism<R::Type, S::Type> for ZnReductionMap<R, S>
582where
583    R: RingStore,
584    R::Type: ZnRing,
585    S: RingStore,
586    S::Type: ZnRing,
587{
588    type CodomainStore = S;
589    type DomainStore = R;
590
591    fn map(&self, x: El<R>) -> El<S> {
592        let value = match self.map_forward_requirement {
593            ReductionMapRequirements::SmallestLift => self.from.smallest_lift(x),
594            ReductionMapRequirements::ExplicitReduce => self
595                .from
596                .integer_ring()
597                .euclidean_rem(self.from.any_lift(x), &self.to_modulus),
598        };
599        self.to.get_ring().map_in(
600            self.to.integer_ring().get_ring(),
601            int_cast(value, self.to.integer_ring(), self.from.integer_ring()),
602            &self.to_from_int,
603        )
604    }
605
606    fn codomain(&self) -> &Self::CodomainStore { &self.to }
607
608    fn domain(&self) -> &Self::DomainStore { &self.from }
609}
610
611#[cfg(any(test, feature = "generic_tests"))]
612pub mod generic_tests {
613
614    use super::*;
615    use crate::primitive_int::{StaticRing, StaticRingBase};
616
617    pub fn test_zn_axioms<R: RingStore>(R: R)
618    where
619        R::Type: ZnRing,
620        <R::Type as ZnRing>::IntegerRingBase: CanIsoFromTo<StaticRingBase<i128>> + CanIsoFromTo<StaticRingBase<i32>>,
621    {
622        let ZZ = R.integer_ring();
623        let n = R.modulus();
624
625        assert!(R.is_zero(&R.coerce(ZZ, ZZ.clone_el(n))));
626        assert!(R.is_field() == algorithms::miller_rabin::is_prime(ZZ, n, 10));
627
628        let mut k = ZZ.one();
629        while ZZ.is_lt(&k, &n) {
630            assert!(!R.is_zero(&R.coerce(ZZ, ZZ.clone_el(&k))));
631            ZZ.add_assign(&mut k, ZZ.one());
632        }
633
634        let all_elements = R.elements().collect::<Vec<_>>();
635        assert_eq!(
636            int_cast(ZZ.clone_el(n), &StaticRing::<i128>::RING, &ZZ) as usize,
637            all_elements.len()
638        );
639        for (i, x) in all_elements.iter().enumerate() {
640            for (j, y) in all_elements.iter().enumerate() {
641                assert!(i == j || !R.eq_el(x, y));
642            }
643        }
644    }
645
646    pub fn test_map_in_large_int<R: RingStore>(R: R)
647    where
648        <R as RingStore>::Type: ZnRing + CanHomFrom<BigIntRingBase>,
649    {
650        let ZZ_big = BigIntRing::RING;
651        let n = ZZ_big.power_of_two(1000);
652        let x = R.coerce(&ZZ_big, n);
653        assert!(R.eq_el(&R.pow(R.int_hom().map(2), 1000), &x));
654    }
655}
656
657#[test]
658fn test_reduction_map_large_value() {
659    let ring1 = zn_64::Zn::new(1 << 42);
660    let ring2 = zn_big::Zn::new(BigIntRing::RING, BigIntRing::RING.power_of_two(666));
661    let reduce = ZnReductionMap::new(&ring2, ring1).unwrap();
662    assert_el_eq!(ring1, ring1.zero(), reduce.map(ring2.pow(ring2.int_hom().map(2), 665)));
663}
664
665#[test]
666fn test_reduction_map() {
667    let ring1 = zn_64::Zn::new(257);
668    let ring2 = zn_big::Zn::new(StaticRing::<i128>::RING, 257 * 7);
669
670    crate::homomorphism::generic_tests::test_homomorphism_axioms(
671        ZnReductionMap::new(&ring2, &ring1).unwrap(),
672        ring2.elements().step_by(8),
673    );
674
675    let ring1 = zn_big::Zn::new(StaticRing::<i16>::RING, 3);
676    let ring2 = zn_big::Zn::new(BigIntRing::RING, BigIntRing::RING.int_hom().map(65537 * 3));
677
678    crate::homomorphism::generic_tests::test_homomorphism_axioms(
679        ZnReductionMap::new(&ring2, &ring1).unwrap(),
680        ring2.elements().step_by(1024),
681    );
682}
683
684#[test]
685fn test_generic_impl_checked_div_min() {
686    let ring = zn_64::Zn::new(5 * 7 * 11 * 13);
687    let actual = ring.annihilator(&ring.int_hom().map(1001));
688    let expected = ring.int_hom().map(5);
689    assert!(ring.checked_div(&expected, &actual).is_some());
690    assert!(ring.checked_div(&actual, &expected).is_some());
691
692    let actual = ring.annihilator(&ring.zero());
693    let expected = ring.one();
694    assert!(ring.checked_div(&expected, &actual).is_some());
695    assert!(ring.checked_div(&actual, &expected).is_some());
696}