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