he_ring/number_ring/hypercube/
isomorphism.rs

1use std::alloc::{Allocator, Global};
2use std::fs::File;
3use std::io::{BufReader, BufWriter};
4use std::sync::Arc;
5
6use feanor_math::algorithms::cyclotomic::cyclotomic_polynomial;
7use feanor_math::algorithms::convolution::fft::{FFTConvolution, FFTConvolutionZn};
8use feanor_math::algorithms::convolution::rns::{RNSConvolution, RNSConvolutionZn};
9use feanor_math::algorithms::convolution::{ConvolutionAlgorithm, STANDARD_CONVOLUTION};
10use feanor_math::algorithms::eea::signed_gcd;
11use feanor_math::algorithms::int_factor::is_prime_power;
12use feanor_math::algorithms::poly_gcd::hensel::hensel_lift_factorization;
13use feanor_math::algorithms::poly_gcd::local::PolyGCDLocallyIntermediateReductionMap;
14use feanor_math::computation::DontObserve;
15use feanor_math::field::Field;
16use feanor_math::rings::poly::sparse_poly::SparsePolyRing;
17use feanor_math::algorithms::linsolve::LinSolveRing;
18use feanor_math::pid::PrincipalIdealRingStore;
19use feanor_math::divisibility::{DivisibilityRing, DivisibilityRingStore};
20use feanor_math::rings::field::AsFieldBase;
21use feanor_math::homomorphism::*;
22use feanor_math::integer::*;
23use feanor_math::local::PrincipalLocalRing;
24use feanor_math::primitive_int::{StaticRing, StaticRingBase};
25use feanor_math::rings::extension::extension_impl::*;
26use feanor_math::rings::extension::galois_field::GaloisField;
27use feanor_math::rings::extension::{FreeAlgebra, FreeAlgebraStore};
28use feanor_math::rings::finite::FiniteRing;
29use feanor_math::rings::local::{AsLocalPIR, AsLocalPIRBase};
30use feanor_math::rings::poly::dense_poly::DensePolyRing;
31use feanor_math::rings::poly::PolyRingStore;
32use feanor_math::rings::zn::zn_64::*;
33use feanor_math::delegate::DelegateRing;
34use feanor_math::ring::*;
35use feanor_math::rings::zn::*;
36use feanor_math::seq::*;
37use feanor_math::serialization::SerializableElementRing;
38use serde::de::DeserializeSeed;
39use serde::Serialize;
40use tracing::instrument;
41
42use crate::cyclotomic::*;
43use crate::{log_time, ZZi64};
44use crate::ntt::dyn_convolution::*;
45use crate::number_ring::hypercube::interpolate::FastPolyInterpolation;
46use crate::number_ring::quotient::*;
47use crate::number_ring::hypercube::structure::*;
48
49pub use crate::number_ring::hypercube::serialization::{DeserializeSeedHypercubeIsomorphismWithoutRing, SerializableHypercubeIsomorphismWithoutRing};
50
51#[instrument(skip_all)]
52fn hensel_lift_factor<R1, R2, A1, A2, C1, C2>(from_ring: &DensePolyRing<R1, A1, C1>, to_ring: &DensePolyRing<R2, A2, C2>, f: &El<DensePolyRing<R1, A1, C1>>, g: El<DensePolyRing<R2, A2, C2>>) -> El<DensePolyRing<R1, A1, C1>>
53    where R1: RingStore,
54        R1::Type: ZnRing,
55        R2: RingStore,
56        R2::Type: ZnRing + Field + CanIsoFromTo<AsFieldBase<Zn>>,
57        A1: Allocator + Clone,
58        A2: Allocator + Clone,
59        C1: ConvolutionAlgorithm<R1::Type>,
60        C2: ConvolutionAlgorithm<R2::Type>,
61{
62    let ZZ = StaticRing::<i64>::RING;
63    let p = int_cast(to_ring.base_ring().integer_ring().clone_el(to_ring.base_ring().modulus()), ZZ, to_ring.base_ring().integer_ring());
64    let (from_p, e) = is_prime_power(ZZ, &int_cast(from_ring.base_ring().integer_ring().clone_el(from_ring.base_ring().modulus()), ZZ, from_ring.base_ring().integer_ring())).unwrap();
65    assert_eq!(p, from_p);
66
67    let Zpe = zn_big::Zn::new(BigIntRing::RING, BigIntRing::RING.pow(int_cast(p, BigIntRing::RING, ZZ), e));
68    let Zp = zn_big::Zn::new(BigIntRing::RING, int_cast(p, BigIntRing::RING, ZZ));
69    let Fp = Zn::new(p as u64).as_field().ok().unwrap();
70    let red_map = PolyGCDLocallyIntermediateReductionMap::new(ZZ.get_ring(), &p, &Zpe, e, &Zp, 1, 0);
71    let ZpeX = DensePolyRing::new(&Zpe, "X");
72    let FpX = DensePolyRing::new(&Fp, "X");
73    let to_ZpeX = ZpeX.lifted_hom(from_ring, ZnReductionMap::new(from_ring.base_ring(), ZpeX.base_ring()).unwrap());
74    let from_ZpeX = from_ring.lifted_hom(&ZpeX, ZnReductionMap::new(ZpeX.base_ring(), from_ring.base_ring()).unwrap());
75    let to_FpX = FpX.lifted_hom(to_ring, to_ring.base_ring().can_iso(FpX.base_ring()).unwrap());
76
77    let f_mod_p = FpX.lifted_hom(&ZpeX, Fp.can_hom(&Zp).unwrap().compose(&red_map)).compose(&to_ZpeX).map_ref(f);
78    let f_over_g = FpX.checked_div(&f_mod_p, &to_FpX.map_ref(&g)).unwrap();
79    let lifted = hensel_lift_factorization(&red_map, &ZpeX, &FpX, &to_ZpeX.map_ref(f), &[to_FpX.map(g), f_over_g][..], DontObserve);
80    return from_ZpeX.map(lifted.into_iter().next().unwrap());
81}
82
83#[instrument(skip_all)]
84fn get_prim_root_of_unity<R>(ring: R, m: usize) -> El<R>
85    where R: RingStore,
86        R::Type: FiniteRing + FreeAlgebra + DivisibilityRing,
87        <<R::Type as RingExtension>::BaseRing as RingStore>::Type: PrincipalLocalRing + ZnRing + CanHomFrom<StaticRingBase<i64>>
88{
89    let (p, e) = is_prime_power(&ZZi64, &ring.characteristic(&ZZi64).unwrap()).unwrap();
90    let galois_field = GaloisField::new_with(
91        Zn::new(p as u64).as_field().ok().unwrap(), 
92        ring.rank(), 
93        Global, 
94        create_convolution(ring.rank(), ring.base_ring().integer_ring().abs_log2_ceil(ring.base_ring().modulus()).unwrap())
95    );
96
97    let rou = feanor_math::algorithms::unity_root::get_prim_root_of_unity(&galois_field, m).unwrap();
98
99    let red_map = ZnReductionMap::new(ring.base_ring(), galois_field.base_ring()).unwrap();
100    let mut result = ring.from_canonical_basis(galois_field.wrt_canonical_basis(&rou).into_iter().map(|x| red_map.smallest_lift(x)));
101
102    // perform hensel lifting
103    for _ in 0..e {
104        let delta = ring.checked_div(
105            &ring.sub(ring.pow(ring.clone_el(&result), m), ring.one()),
106            &ring.inclusion().mul_map(ring.pow(ring.clone_el(&result), m - 1), ring.base_ring().coerce(&ZZi64, m as i64)) 
107        ).unwrap();
108        ring.sub_assign(&mut result, delta);
109    }
110    assert!(ring.is_one(&ring.pow(ring.clone_el(&result), m)));
111    return result;
112}
113
114fn create_convolution<R>(d: usize, log2_input_size: usize) -> DynConvolutionAlgorithmConvolution<R, Arc<dyn Send + Sync + DynConvolutionAlgorithm<R>>>
115    where R: ?Sized + ZnRing + CanHomFrom<BigIntRingBase> + CanHomFrom<StaticRingBase<i64>>
116{
117    let fft_convolution = FFTConvolution::new_with(Global);
118    let max_log2_len = ZZi64.abs_log2_ceil(&(d as i64)).unwrap() + 1;
119    if d <= 30 {
120        DynConvolutionAlgorithmConvolution::new(Arc::new(STANDARD_CONVOLUTION))
121    } else if fft_convolution.has_sufficient_precision(max_log2_len, log2_input_size) {
122        DynConvolutionAlgorithmConvolution::new(Arc::new(FFTConvolutionZn::from(fft_convolution)))
123    } else {
124        DynConvolutionAlgorithmConvolution::new(Arc::new(RNSConvolutionZn::from(RNSConvolution::new(max_log2_len))))
125    }
126}
127
128pub type SlotRingOver<R> = AsLocalPIR<FreeAlgebraImpl<R, Vec<El<R>>, Global, DynConvolutionAlgorithmConvolution<<R as RingStore>::Type, Arc<dyn Send + Sync + DynConvolutionAlgorithm<<R as RingStore>::Type>>>>>;
129pub type SlotRingOf<R> = SlotRingOver<RingValue<BaseRing<R>>>;
130
131pub type DefaultHypercube<'a, NumberRing, A = Global> = HypercubeIsomorphism<&'a NumberRingQuotient<NumberRing, Zn, A>>;
132
133pub type BaseRing<R> = <<<R as RingStore>::Type as RingExtension>::BaseRing as RingStore>::Type;
134pub type DecoratedBaseRing<R> = AsLocalPIR<RingValue<BaseRing<R>>>;
135
136///
137/// Represents the isomorphism
138/// ```text
139///   Fp[X]/(Phi_n(X)) -> F_(p^d)^((Z/nZ)*/<p>)
140/// ```
141/// where `d` is the order of `p` in `(Z/nZ)*`.
142/// The group `(Z/nZ)*/<p>` is represented by a [`HypercubeStructure`].
143/// 
144/// In fact, the more general case of `(Z/p^eZ)[X]/(Phi_n(X))` is supported.
145/// 
146/// # Serialization
147/// 
148/// There are two ways of serializing/deserializing a [`HypercubeIsomorphism`]
149///  - you can serialize the isomorphism including the implementation of `Fp[X]/(Phi_n(X))`
150///    (if the latter is serializable) using the implementation of [`serde::Serialize`] and
151///    [`serde::Deserialize`]
152///  - alternatively, you can serialize the isomorphism without the ring implementation
153///    using [`SerializableHypercubeIsomorphismWithoutRing`] and 
154///    [`DeserializeSeedHypercubeIsomorphismWithoutRing`]; 
155///    this of course requires that the ring is provided at deserialization time
156/// 
157pub struct HypercubeIsomorphism<R>
158    where R: RingStore,
159        R::Type: CyclotomicRing,
160        BaseRing<R>: Clone + ZnRing + CanHomFrom<StaticRingBase<i64>> + CanHomFrom<BigIntRingBase> + LinSolveRing + FromModulusCreateableZnRing,
161        AsFieldBase<DecoratedBaseRing<R>>: CanIsoFromTo<<DecoratedBaseRing<R> as RingStore>::Type> + SelfIso
162{
163    ring: R,
164    e: usize,
165    slot_rings: Vec<SlotRingOf<R>>,
166    slot_to_ring_interpolation: FastPolyInterpolation<DensePolyRing<DecoratedBaseRing<R>, Global>>,
167    hypercube_structure: HypercubeStructure,
168}
169
170impl<R> HypercubeIsomorphism<R>
171    where R: RingStore,
172        R::Type: CyclotomicRing,
173        <<R::Type as RingExtension>::BaseRing as RingStore>::Type: Clone + ZnRing + CanHomFrom<StaticRingBase<i64>> + CanHomFrom<BigIntRingBase> + LinSolveRing + FromModulusCreateableZnRing,
174        AsFieldBase<DecoratedBaseRing<R>>: CanIsoFromTo<<DecoratedBaseRing<R> as RingStore>::Type> + SelfIso
175{
176    pub fn new_cache_file<const LOG: bool>(ring: R, hypercube_structure: HypercubeStructure, dir: &str) -> Self
177        where BaseRing<R>: SerializableElementRing
178    {
179        let (p, e) = is_prime_power(&ZZi64, &ring.characteristic(&ZZi64).unwrap()).unwrap();
180        let filename = format!("{}/hypercube_n{}_p{}_e{}.json", dir, ring.n(), p, e);
181        if let Ok(file) = File::open(filename.as_str()) {
182            if LOG {
183                println!("Reading from file {}", filename);
184            }
185            let reader = serde_json::de::IoRead::new(BufReader::new(file));
186            let mut deserializer = serde_json::Deserializer::new(reader);
187            let deserialized = DeserializeSeedHypercubeIsomorphismWithoutRing::new(ring).deserialize(&mut deserializer).unwrap();
188            assert!(deserialized.hypercube() == &hypercube_structure);
189            return deserialized;
190        }
191        let result = Self::new::<LOG>(ring, hypercube_structure);
192        let file = File::create(filename.as_str()).unwrap();
193        let writer = BufWriter::new(file);
194        let mut serializer = serde_json::Serializer::new(writer);
195        SerializableHypercubeIsomorphismWithoutRing::new(&result).serialize(&mut serializer).unwrap();
196        return result;
197    }
198
199    pub fn new<const LOG: bool>(ring: R, hypercube_structure: HypercubeStructure) -> Self {      
200        let d = hypercube_structure.d();  
201        if d * d < hypercube_structure.n() {
202            return Self::new_small_slot_ring::<LOG>(ring, hypercube_structure);
203        } else {
204            return Self::new_large_slot_ring::<LOG>(ring, hypercube_structure);
205        }
206    }
207
208    ///
209    /// Creates a new [`HypercubeIsomorphism`], using algorithms that are
210    /// optimized for few large slots.
211    /// 
212    #[instrument(skip_all)]
213    fn new_large_slot_ring<const LOG: bool>(ring: R, hypercube_structure: HypercubeStructure) -> Self {
214        let n = ring.n() as usize;
215        let d = hypercube_structure.d();
216        let (p, _) = is_prime_power(&ZZi64, &ring.characteristic(&ZZi64).unwrap()).unwrap();
217        assert!(signed_gcd(n as i64, p, ZZi64) == 1, "currently the ramified case is not implemented");
218        let galois_group = hypercube_structure.galois_group();
219        assert_eq!(n, galois_group.n());
220        assert!(galois_group.eq_el(hypercube_structure.p(), galois_group.from_representative(p)));
221
222        let decorated_base_ring: DecoratedBaseRing<R> = AsLocalPIR::from_zn(RingValue::from(ring.base_ring().get_ring().clone())).unwrap();
223        let ZpeX = DensePolyRing::new_with(decorated_base_ring, "X", Global, STANDARD_CONVOLUTION);
224        let FpX = DensePolyRing::new_with(Zn::new(p as u64).as_field().ok().unwrap(), "X", Global, create_convolution(n as usize, ZZi64.abs_log2_ceil(&p).unwrap()));
225        let ZZX = SparsePolyRing::new(ZZi64, "X");
226        let Phi_n = cyclotomic_polynomial(&ZZX, n);
227        let Phi_n_mod_pe = ZpeX.lifted_hom(&ZZX, ZpeX.base_ring().can_hom(ZZX.base_ring()).unwrap()).map_ref(&Phi_n);
228        let Phi_n_mod_p = FpX.lifted_hom(&ZZX, FpX.base_ring().can_hom(ZZX.base_ring()).unwrap()).map(Phi_n);
229
230        let tmp_slot_ring = log_time::<_, _, LOG, _>("[HypercubeIsomorphism::new_small_slot_ring] Creating temporary slot ring", |[]| {
231            let base_ring = Zn::new(p as u64).as_field().ok().unwrap();
232            GaloisField::new_with(
233                base_ring, 
234                d, 
235                Global, 
236                create_convolution(d, ZZi64.abs_log2_ceil(&p).unwrap())
237            ).get_ring().galois_ring_with(
238                AsLocalPIR::from_zn(RingRef::new(ring.base_ring().get_ring())).unwrap(), 
239                Global, 
240                create_convolution(d, ring.base_ring().integer_ring().abs_log2_ceil(ring.base_ring().modulus()).unwrap())
241            )
242        });
243
244        let root_of_unity = log_time::<_, _, LOG, _>("[HypercubeIsomorphism::new_small_slot_ring] Computing root of unity", |[]| 
245            get_prim_root_of_unity(&tmp_slot_ring, n)
246        );
247
248        let slot_ring_modulus = log_time::<_, _, LOG, _>("[HypercubeIsomorphism::new_small_slot_ring] Computing single factor of cyclotomic polynomial", |[]| {
249            let SX = DensePolyRing::new(&tmp_slot_ring, "X");
250            let mut result = SX.prod((0..d).scan(
251                root_of_unity, 
252                |current_root_of_unity, _| {
253                    let result = SX.sub(SX.indeterminate(), SX.inclusion().map_ref(current_root_of_unity));
254                    *current_root_of_unity = tmp_slot_ring.pow(tmp_slot_ring.clone_el(current_root_of_unity), p as usize);
255                    return Some(result);
256                }
257            ));
258            let normalization_factor = SX.base_ring().invert(SX.lc(&result).unwrap()).unwrap();
259            SX.inclusion().mul_assign_map(&mut result, normalization_factor);
260    
261            let red_map = ZnReductionMap::new(SX.base_ring().base_ring(), FpX.base_ring()).unwrap();
262            return FpX.from_terms(SX.terms(&result).map(|(c, i)| {
263                let c_wrt_basis = tmp_slot_ring.wrt_canonical_basis(c);
264                debug_assert!(c_wrt_basis.iter().skip(1).all(|c| tmp_slot_ring.base_ring().is_zero(&c)));
265                return (red_map.map(c_wrt_basis.at(0)), i);
266            }));
267        });
268        drop(tmp_slot_ring);
269        
270        let slot_ring_moduli = log_time::<_, _, LOG, _>("[HypercubeIsomorphism::new_large_slot_ring] Computing complete factorization of cyclotomic polynomial", |[]| {
271            let mut result = Vec::new();
272            for g in hypercube_structure.element_iter() {
273                let slot_ring_modulus_mod_p = FpX.from_terms(
274                    FpX.terms(&slot_ring_modulus).map(|(c, i)| (
275                        *c,
276                        (galois_group.representative(g) * i as usize) % n
277                    ))
278                );
279                result.push(hensel_lift_factor(&ZpeX, &FpX, &Phi_n_mod_pe, FpX.normalize(FpX.ideal_gen(&slot_ring_modulus_mod_p, &Phi_n_mod_p))));
280            }
281            return result;
282        });
283
284        return Self::create::<LOG>(ring, hypercube_structure, ZpeX, slot_ring_moduli);
285    }
286
287    ///
288    /// Creates a new [`HypercubeIsomorphism`], using algorithms that are
289    /// optimized for many small slots.
290    /// 
291    #[instrument(skip_all)]
292    fn new_small_slot_ring<const LOG: bool>(ring: R, hypercube_structure: HypercubeStructure) -> Self {
293        let n = ring.n() as usize;
294        let d = hypercube_structure.d();
295        let (p, _) = is_prime_power(&ZZi64, &ring.characteristic(&ZZi64).unwrap()).unwrap();
296        let galois_group = hypercube_structure.galois_group();
297        assert_eq!(n, galois_group.n());
298        assert!(galois_group.eq_el(hypercube_structure.p(), galois_group.from_representative(p)));
299
300        // in this case, we use an "internal" approach, i.e. work only within
301        // the slot ring; since the slot ring is small, this is fast;
302        // The main idea is that we already know how the slot ring should look like,
303        // namely it is `GR(p, e, d)`. Once we find a root of unity in the slot
304        // ring, we can compute its minimal polynomial and find a factor of `Phi_n`, 
305        // without ever even computing `Phi_n`. Note however that this requires
306        // a lot of operations within the slot ring, and if that is large, this
307        // will be more expensive than an explicit factorization of `Phi_n`.
308
309        let tmp_slot_ring = log_time::<_, _, LOG, _>("[HypercubeIsomorphism::new_small_slot_ring] Creating temporary slot ring", |[]| {
310            let base_ring = Zn::new(p as u64).as_field().ok().unwrap();
311            GaloisField::new_with(
312                base_ring, 
313                d, 
314                Global, 
315                create_convolution(d, ZZi64.abs_log2_ceil(&p).unwrap())
316            ).get_ring().galois_ring_with(
317                AsLocalPIR::from_zn(RingRef::new(ring.base_ring().get_ring())).unwrap(), 
318                Global, 
319                create_convolution(d, ring.base_ring().integer_ring().abs_log2_ceil(ring.base_ring().modulus()).unwrap())
320            )
321        });
322
323        let root_of_unity = log_time::<_, _, LOG, _>("[HypercubeIsomorphism::new_small_slot_ring] Computing root of unity", |[]| 
324            get_prim_root_of_unity(&tmp_slot_ring, n)
325        );
326
327        let decorated_base_ring: DecoratedBaseRing<R> = AsLocalPIR::from_zn(RingValue::from(ring.base_ring().get_ring().clone())).unwrap();
328        let ZpeX = DensePolyRing::new_with(decorated_base_ring, "X", Global, STANDARD_CONVOLUTION);
329        let slot_ring_moduli = log_time::<_, _, LOG, _>("[HypercubeIsomorphism::new_small_slot_ring] Computing factorization of cyclotomic polynomial", |[]| {
330            let SX = DensePolyRing::new(&tmp_slot_ring, "X");
331            let mut slot_ring_moduli = Vec::new();
332            for g in hypercube_structure.element_iter() {
333                let mut result = SX.prod((0..d).scan(
334                    tmp_slot_ring.pow(tmp_slot_ring.clone_el(&root_of_unity), galois_group.representative(galois_group.invert(g))), 
335                    |current_root_of_unity, _| {
336                        let result = SX.sub(SX.indeterminate(), SX.inclusion().map_ref(current_root_of_unity));
337                        *current_root_of_unity = tmp_slot_ring.pow(tmp_slot_ring.clone_el(current_root_of_unity), p as usize);
338                        return Some(result);
339                    }
340                ));
341                let normalization_factor = SX.base_ring().invert(SX.lc(&result).unwrap()).unwrap();
342                SX.inclusion().mul_assign_map(&mut result, normalization_factor);
343    
344                slot_ring_moduli.push(ZpeX.from_terms(SX.terms(&result).map(|(c, i)| {
345                    let c_wrt_basis = tmp_slot_ring.wrt_canonical_basis(c);
346                    debug_assert!(c_wrt_basis.iter().skip(1).all(|c| tmp_slot_ring.base_ring().is_zero(&c)));
347                    return (ZpeX.base_ring().get_ring().rev_delegate(tmp_slot_ring.base_ring().get_ring().delegate(c_wrt_basis.at(0))), i);
348                })));
349            }
350            return slot_ring_moduli;
351        });
352        drop(tmp_slot_ring);
353
354        return Self::create::<LOG>(ring, hypercube_structure, ZpeX, slot_ring_moduli);
355    }
356
357    #[instrument(skip_all)]
358    pub fn create<const LOG: bool>(ring: R, hypercube_structure: HypercubeStructure, ZpeX: DensePolyRing<AsLocalPIR<RingValue<BaseRing<R>>>>, slot_ring_moduli: Vec<El<DensePolyRing<AsLocalPIR<RingValue<BaseRing<R>>>>>>) -> Self {
359        assert_eq!(ring.n(), hypercube_structure.n());
360        let (p, e) = is_prime_power(&ZZi64, &ring.characteristic(&ZZi64).unwrap()).unwrap();
361        assert!(hypercube_structure.galois_group().eq_el(hypercube_structure.galois_group().from_representative(p), hypercube_structure.frobenius(1)));
362        let d = hypercube_structure.d();
363
364        let slot_rings = log_time::<_, _, LOG, _>("[HypercubeIsomorphism::new_small_slot_ring] Computing slot rings", |[]| slot_ring_moduli.iter().map(|f| {
365            let modulus = (0..d).map(|i| ZpeX.base_ring().get_ring().delegate(ZpeX.base_ring().negate(ZpeX.base_ring().clone_el(ZpeX.coefficient_at(f, i))))).collect::<Vec<_>>();
366            let slot_ring = FreeAlgebraImpl::new_with(
367                RingValue::from(ring.base_ring().get_ring().clone()),
368                d,
369                modulus,
370                "𝝵",
371                Global,
372                create_convolution(d, ring.base_ring().integer_ring().abs_log2_ceil(ring.base_ring().modulus()).unwrap())
373            );
374            let max_ideal_gen = slot_ring.inclusion().map(slot_ring.base_ring().coerce(&ZZi64, p));
375            return AsLocalPIR::from(AsLocalPIRBase::promise_is_local_pir(slot_ring, max_ideal_gen, Some(e)));
376        }).collect::<Vec<_>>());
377
378        let interpolation = log_time::<_, _, LOG, _>("[HypercubeIsomorphism::new_small_slot_ring] Computing interpolation data", |[]|
379            FastPolyInterpolation::new(ZpeX, slot_ring_moduli)
380        );
381
382        return Self {
383            hypercube_structure: hypercube_structure,
384            ring: ring,
385            e: e,
386            slot_to_ring_interpolation: interpolation,
387            slot_rings: slot_rings
388        };
389    }
390
391    pub fn change_modulus<RNew>(&self, new_ring: RNew) -> HypercubeIsomorphism<RNew>
392        where RNew: RingStore,
393            RNew::Type: CyclotomicRing,
394            BaseRing<RNew>: Clone + ZnRing + CanHomFrom<StaticRingBase<i64>> + CanHomFrom<BigIntRingBase> + LinSolveRing + FromModulusCreateableZnRing,
395            AsFieldBase<DecoratedBaseRing<RNew>>: CanIsoFromTo<<DecoratedBaseRing<RNew> as RingStore>::Type> + SelfIso
396    {
397        let (p, e) = is_prime_power(&ZZi64, &new_ring.characteristic(&ZZi64).unwrap()).unwrap();
398        let d = self.hypercube().d();
399        let red_map = ZnReductionMap::new(self.ring().base_ring(), new_ring.base_ring()).unwrap();
400        let poly_ring = DensePolyRing::new(new_ring.base_ring(), "X");
401        let slot_rings = self.slot_rings.iter().map(|slot_ring| {
402            let gen_poly = slot_ring.generating_poly(&poly_ring, &red_map);
403            let new_slot_ring = FreeAlgebraImpl::new_with(
404                RingValue::from(new_ring.base_ring().get_ring().clone()),
405                d,
406                (0..d).map(|i| new_ring.base_ring().negate(new_ring.base_ring().clone_el(poly_ring.coefficient_at(&gen_poly, i)))).collect::<Vec<_>>(),
407                "𝝵",
408                Global,
409                create_convolution(d, ZZi64.abs_log2_ceil(&p).unwrap())
410            );
411            let max_ideal_gen = new_slot_ring.inclusion().map(new_slot_ring.base_ring().coerce(&ZZi64, p));
412            return AsLocalPIR::from(AsLocalPIRBase::promise_is_local_pir(new_slot_ring, max_ideal_gen, Some(e)));
413        }).collect::<Vec<_>>();
414
415        let decorated_base_ring: DecoratedBaseRing<RNew> = AsLocalPIR::from_zn(RingValue::from(new_ring.base_ring().get_ring().clone())).unwrap();
416        let base_poly_ring = DensePolyRing::new_with(decorated_base_ring, "X", Global, STANDARD_CONVOLUTION);
417        return HypercubeIsomorphism {
418            slot_to_ring_interpolation: self.slot_to_ring_interpolation.change_modulus(base_poly_ring),
419            e: e,
420            hypercube_structure: self.hypercube().clone(),
421            ring: new_ring,
422            slot_rings: slot_rings,
423        };
424    }
425
426    pub fn hypercube(&self) -> &HypercubeStructure {
427        &self.hypercube_structure
428    }
429
430    pub fn ring(&self) -> &R {
431        &self.ring
432    }
433
434    pub fn slot_ring_at<'a>(&'a self, i: usize) -> &'a SlotRingOf<R>
435        where R: 'a
436    {
437        &self.slot_rings[i]
438    }
439
440    pub fn slot_ring<'a>(&'a self) -> &'a SlotRingOf<R>
441        where R: 'a
442    {
443        self.slot_ring_at(0)
444    }
445
446    pub fn p(&self) -> i64 {
447        self.galois_group().representative(self.hypercube_structure.p()) as i64
448    }
449
450    pub fn e(&self) -> usize {
451        self.e
452    }
453
454    pub fn d(&self) -> usize {
455        self.hypercube_structure.d()
456    }
457
458    pub fn galois_group(&self) -> &CyclotomicGaloisGroup {
459        self.hypercube_structure.galois_group()
460    }
461
462    pub fn slot_count(&self) -> usize {
463        self.hypercube_structure.element_count()
464    }
465    
466    #[instrument(skip_all)]
467    pub fn get_slot_value(&self, el: &El<R>, slot_index: CyclotomicGaloisGroupEl) -> El<SlotRingOf<R>> {
468        let el = self.ring().apply_galois_action(el, self.galois_group().invert(slot_index));
469        let poly_ring = DensePolyRing::new(self.ring.base_ring(), "X");
470        let el_as_poly = self.ring().poly_repr(&poly_ring, &el, self.ring.base_ring().identity());
471        let poly_modulus = self.slot_ring().generating_poly(&poly_ring, self.ring.base_ring().identity());
472        let (_, rem) = poly_ring.div_rem_monic(el_as_poly, &poly_modulus);
473        self.slot_ring().from_canonical_basis((0..self.d()).map(|i| poly_ring.base_ring().clone_el(poly_ring.coefficient_at(&rem, i))))
474    }
475
476    #[instrument(skip_all)]
477    pub fn get_slot_values<'a>(&'a self, el: &'a El<R>) -> impl ExactSizeIterator<Item = El<SlotRingOf<R>>> + use<'a, R> {
478        self.hypercube_structure.element_iter().map(move |g| self.get_slot_value(el, g))
479    }
480
481    #[instrument(skip_all)]
482    pub fn from_slot_values<'a, I>(&self, values: I) -> El<R>
483        where I: IntoIterator<Item = El<SlotRingOf<R>>>
484    {
485        let poly_ring = self.slot_to_ring_interpolation.poly_ring();
486        let first_slot_ring: &SlotRingOf<R> = self.slot_ring();
487        let mut values_it = values.into_iter();
488        let wrap = LambdaHom::new(first_slot_ring.base_ring(), poly_ring.base_ring(), |from, to, x| to.get_ring().rev_delegate(from.clone_el(x)));
489        let unwrap = LambdaHom::new(poly_ring.base_ring(), first_slot_ring.base_ring(), |from, _to, x| from.get_ring().delegate(from.clone_el(x)));
490
491        let remainders = values_it.by_ref().zip(self.hypercube_structure.element_iter()).enumerate().map(|(i, (a, g))| {
492            let f = first_slot_ring.poly_repr(&poly_ring, &a, &wrap);
493            let local_slot_ring = self.slot_ring_at(i);
494            let image_zeta = local_slot_ring.pow(local_slot_ring.canonical_gen(), self.galois_group().representative(g));
495            return local_slot_ring.poly_repr(&poly_ring, &poly_ring.evaluate(&f, &image_zeta, local_slot_ring.inclusion().compose(&unwrap)), &wrap);
496        }).collect::<Vec<_>>();
497        assert!(values_it.next().is_none(), "iterator should only have {} elements", self.slot_count());
498        debug_assert!(remainders.iter().all(|r| poly_ring.degree(r).unwrap_or(0) < self.d()));
499
500        let unreduced_result = self.slot_to_ring_interpolation.interpolate_unreduced(remainders);
501        let unreduced_result = (0..=poly_ring.degree(&unreduced_result).unwrap_or(0)).map(|i| poly_ring.base_ring().clone_el(poly_ring.coefficient_at(&unreduced_result, i))).collect::<Vec<_>>();
502
503        let canonical_gen_pow_rank = self.ring().mul(self.ring().canonical_gen(), self.ring().from_canonical_basis((1..self.ring().rank()).map(|_| self.ring().base_ring().zero()).chain([self.ring().base_ring().one()].into_iter())));
504        let mut current = self.ring().one();
505        return <_ as RingStore>::sum(&self.ring, unreduced_result.chunks(self.ring.rank()).map(|chunk| self.ring.from_canonical_basis(
506            chunk.iter().map(|a| poly_ring.base_ring().clone_el(a)).chain((0..(self.ring.rank() - chunk.len())).map(|_| poly_ring.base_ring().zero()))
507                .map(|x| unwrap.map(x))
508        )).map(|x| {
509            let result = self.ring().mul_ref_snd(x, &current);
510            self.ring().mul_assign_ref(&mut current, &canonical_gen_pow_rank);
511            return result;
512        }));
513    }
514}
515
516#[cfg(test)]
517use feanor_math::rings::finite::*;
518#[cfg(test)]
519use crate::number_ring::odd_cyclotomic::CompositeCyclotomicNumberRing;
520#[cfg(test)]
521use crate::number_ring::pow2_cyclotomic::Pow2CyclotomicNumberRing;
522#[cfg(test)]
523use feanor_math::assert_el_eq;
524#[cfg(test)]
525use std::rc::Rc;
526#[cfg(test)]
527use std::ptr::Alignment;
528
529#[cfg(test)]
530fn test_ring1() -> (NumberRingQuotient<Pow2CyclotomicNumberRing, Zn>, HypercubeStructure) {
531    let galois_group = CyclotomicGaloisGroup::new(32);
532    let hypercube_structure = HypercubeStructure::new(
533        galois_group,
534        galois_group.from_representative(7),
535        4,
536        vec![4],
537        vec![galois_group.from_representative(5)]
538    );
539    let ring = NumberRingQuotientBase::new(Pow2CyclotomicNumberRing::new(32), Zn::new(7));
540    return (ring, hypercube_structure);
541}
542
543#[cfg(test)]
544fn test_ring2() -> (NumberRingQuotient<Pow2CyclotomicNumberRing, Zn>, HypercubeStructure) {
545    let galois_group = CyclotomicGaloisGroup::new(32);
546    let hypercube_structure = HypercubeStructure::new(
547        galois_group,
548        galois_group.from_representative(17),
549        2,
550        vec![4, 2],
551        vec![galois_group.from_representative(5), galois_group.from_representative(-1)]
552    );
553    let ring = NumberRingQuotientBase::new(Pow2CyclotomicNumberRing::new(32), Zn::new(17));
554    return (ring, hypercube_structure);
555}
556
557#[cfg(test)]
558fn test_ring3() -> (NumberRingQuotient<CompositeCyclotomicNumberRing, Zn>, HypercubeStructure) {
559    let galois_group = CyclotomicGaloisGroup::new(11 * 13);
560    let hypercube_structure = HypercubeStructure::new(
561        galois_group,
562        galois_group.from_representative(3),
563        15,
564        vec![2, 4],
565        vec![galois_group.from_representative(79), galois_group.from_representative(67)]
566    );
567    let ring = NumberRingQuotientBase::new(CompositeCyclotomicNumberRing::new(11, 13), Zn::new(3));
568    return (ring, hypercube_structure);
569}
570
571#[test]
572fn test_hypercube_isomorphism_from_to_slot_vector() {
573    let mut rng = oorandom::Rand64::new(1);
574
575    let (ring, hypercube) = test_ring1();
576    let isomorphism = HypercubeIsomorphism::new::<true>(ring, hypercube);
577    assert_eq!(4, isomorphism.slot_count());
578    for _ in 0..10 {
579        let slot_ring = isomorphism.slot_ring();
580        let expected = (0..isomorphism.slot_count()).map(|_| slot_ring.random_element(|| rng.rand_u64())).collect::<Vec<_>>();
581        let element = isomorphism.from_slot_values(expected.iter().map(|a| slot_ring.clone_el(a)));
582        let actual = isomorphism.get_slot_values(&element);
583        for (expected, actual) in expected.iter().zip(actual) {
584            assert_el_eq!(slot_ring, expected, actual);
585        }
586    }
587
588    let (ring, hypercube) = test_ring2();
589    let isomorphism = HypercubeIsomorphism::new::<true>(ring, hypercube);
590    assert_eq!(8, isomorphism.slot_count());
591    for _ in 0..10 {
592        let slot_ring = isomorphism.slot_ring();
593        let expected = (0..isomorphism.slot_count()).map(|_| slot_ring.random_element(|| rng.rand_u64())).collect::<Vec<_>>();
594        let element = isomorphism.from_slot_values(expected.iter().map(|a| slot_ring.clone_el(a)));
595        let actual = isomorphism.get_slot_values(&element);
596        for (expected, actual) in expected.iter().zip(actual) {
597            assert_el_eq!(slot_ring, expected, actual);
598        }
599    }
600
601    let (ring, hypercube) = test_ring3();
602    let isomorphism = HypercubeIsomorphism::new::<true>(ring, hypercube);
603    assert_eq!(8, isomorphism.slot_count());
604    for _ in 0..10 {
605        let slot_ring = isomorphism.slot_ring();
606        let expected = (0..isomorphism.slot_count()).map(|_| slot_ring.random_element(|| rng.rand_u64())).collect::<Vec<_>>();
607        let element = isomorphism.from_slot_values(expected.iter().map(|a| slot_ring.clone_el(a)));
608        let actual = isomorphism.get_slot_values(&element);
609        for (expected, actual) in expected.iter().zip(actual) {
610            assert_el_eq!(slot_ring, expected, actual);
611        }
612    }
613}
614
615#[test]
616fn test_hypercube_isomorphism_is_isomorphic() {
617    let mut rng = oorandom::Rand64::new(1);
618
619    let (ring, hypercube) = test_ring1();
620    let isomorphism = HypercubeIsomorphism::new::<true>(ring, hypercube);
621    for _ in 0..10 {
622        let slot_ring = isomorphism.slot_ring();
623        let lhs = (0..isomorphism.slot_count()).map(|_| slot_ring.random_element(|| rng.rand_u64())).collect::<Vec<_>>();
624        let rhs = (0..isomorphism.slot_count()).map(|_| slot_ring.random_element(|| rng.rand_u64())).collect::<Vec<_>>();
625        let expected = (0..isomorphism.slot_count()).map(|i| slot_ring.mul_ref(&lhs[i], &rhs[i])).collect::<Vec<_>>();
626        let element = isomorphism.ring().mul(
627            isomorphism.from_slot_values(lhs.iter().map(|a| slot_ring.clone_el(a))),
628            isomorphism.from_slot_values(rhs.iter().map(|a| slot_ring.clone_el(a)))
629        );
630        let actual = isomorphism.get_slot_values(&element);
631        for (expected, actual) in expected.iter().zip(actual) {
632            assert_el_eq!(slot_ring, expected, actual);
633        }
634    }
635
636    let (ring, hypercube) = test_ring2();
637    let isomorphism = HypercubeIsomorphism::new::<true>(ring, hypercube);
638    for _ in 0..10 {
639        let slot_ring = isomorphism.slot_ring();
640        let lhs = (0..isomorphism.slot_count()).map(|_| slot_ring.random_element(|| rng.rand_u64())).collect::<Vec<_>>();
641        let rhs = (0..isomorphism.slot_count()).map(|_| slot_ring.random_element(|| rng.rand_u64())).collect::<Vec<_>>();
642        let expected = (0..isomorphism.slot_count()).map(|i| slot_ring.mul_ref(&lhs[i], &rhs[i])).collect::<Vec<_>>();
643        let element = isomorphism.ring().mul(
644            isomorphism.from_slot_values(lhs.iter().map(|a| slot_ring.clone_el(a))),
645            isomorphism.from_slot_values(rhs.iter().map(|a| slot_ring.clone_el(a)))
646        );
647        let actual = isomorphism.get_slot_values(&element);
648        for (expected, actual) in expected.iter().zip(actual) {
649            assert_el_eq!(slot_ring, expected, actual);
650        }
651    }
652
653    let (ring, hypercube) = test_ring3();
654    let isomorphism = HypercubeIsomorphism::new::<true>(ring, hypercube);
655    for _ in 0..10 {
656        let slot_ring = isomorphism.slot_ring();
657        let lhs = (0..isomorphism.slot_count()).map(|_| slot_ring.random_element(|| rng.rand_u64())).collect::<Vec<_>>();
658        let rhs = (0..isomorphism.slot_count()).map(|_| slot_ring.random_element(|| rng.rand_u64())).collect::<Vec<_>>();
659        let expected = (0..isomorphism.slot_count()).map(|i| slot_ring.mul_ref(&lhs[i], &rhs[i])).collect::<Vec<_>>();
660        let element = isomorphism.ring().mul(
661            isomorphism.from_slot_values(lhs.iter().map(|a| slot_ring.clone_el(a))),
662            isomorphism.from_slot_values(rhs.iter().map(|a| slot_ring.clone_el(a)))
663        );
664        let actual = isomorphism.get_slot_values(&element);
665        for (expected, actual) in expected.iter().zip(actual) {
666            assert_el_eq!(slot_ring, expected, actual);
667        }
668    }
669}
670
671#[test]
672fn test_hypercube_isomorphism_rotation() {
673    let mut rng = oorandom::Rand64::new(1);
674
675    let (ring, hypercube) = test_ring1();
676    let isomorphism = HypercubeIsomorphism::new::<true>(ring, hypercube);
677    let ring = isomorphism.ring();
678    let hypercube = isomorphism.hypercube();
679    for _ in 0..10 {
680        let slot_ring = isomorphism.slot_ring();
681        let a = slot_ring.random_element(|| rng.rand_u64());
682
683        let mut input = (0..isomorphism.slot_count()).map(|_| slot_ring.zero()).collect::<Vec<_>>();
684        input[0] = slot_ring.clone_el(&a);
685
686        let mut expected = (0..isomorphism.slot_count()).map(|_| slot_ring.zero()).collect::<Vec<_>>();
687        expected[hypercube.m(0) - 1] = slot_ring.clone_el(&a);
688
689        let actual = ring.apply_galois_action(
690            &isomorphism.from_slot_values(input.into_iter()),
691            hypercube.galois_group().pow(hypercube.g(0), hypercube.m(0) as i64 - 1)
692        );
693        let actual = isomorphism.get_slot_values(&actual);
694        for (expected, actual) in expected.iter().zip(actual) {
695            assert_el_eq!(slot_ring, expected, actual);
696        }
697    }
698
699    let (ring, hypercube) = test_ring2();
700    let isomorphism = HypercubeIsomorphism::new::<true>(ring, hypercube);
701    let ring = isomorphism.ring();
702    let hypercube = isomorphism.hypercube();
703    for _ in 0..10 {
704        let slot_ring = isomorphism.slot_ring();
705        let a = slot_ring.random_element(|| rng.rand_u64());
706        
707        let mut input = (0..isomorphism.slot_count()).map(|_| slot_ring.zero()).collect::<Vec<_>>();
708        input[0] = slot_ring.clone_el(&a);
709
710        let mut expected = (0..isomorphism.slot_count()).map(|_| slot_ring.zero()).collect::<Vec<_>>();
711        expected[(hypercube.m(0) - 1) * hypercube.m(1)] = slot_ring.clone_el(&a);
712
713        let actual = ring.apply_galois_action(
714            &isomorphism.from_slot_values(input.into_iter()),
715            hypercube.galois_group().pow(hypercube.g(0), hypercube.m(0) as i64 - 1)
716        );
717        let actual = isomorphism.get_slot_values(&actual).collect::<Vec<_>>();
718        for (expected, actual) in expected.iter().zip(actual.iter()) {
719            assert_el_eq!(slot_ring, expected, actual);
720        }
721    }
722
723    let (ring, hypercube) = test_ring3();
724    let isomorphism = HypercubeIsomorphism::new::<true>(ring, hypercube);
725    let ring = isomorphism.ring();
726    let hypercube = isomorphism.hypercube();
727    for _ in 0..10 {
728        let slot_ring = isomorphism.slot_ring();
729        let a = slot_ring.random_element(|| rng.rand_u64());
730        
731        let mut input = (0..isomorphism.slot_count()).map(|_| slot_ring.zero()).collect::<Vec<_>>();
732        input[0] = slot_ring.clone_el(&a);
733
734        let mut expected = (0..isomorphism.slot_count()).map(|_| slot_ring.zero()).collect::<Vec<_>>();
735        expected[(hypercube.m(0) - 1) * hypercube.m(1)] = slot_ring.clone_el(&a);
736
737        let actual = ring.apply_galois_action(
738            &isomorphism.from_slot_values(input.into_iter()),
739            hypercube.galois_group().pow(hypercube.g(0), hypercube.m(0) as i64 - 1)
740        );
741        let actual = isomorphism.get_slot_values(&actual);
742        for (expected, actual) in expected.iter().zip(actual) {
743            assert_el_eq!(slot_ring, expected, actual);
744        }
745    }
746}
747
748#[test]
749#[ignore]
750fn time_from_slot_values_large() {
751    use tracing_subscriber::prelude::*;
752    let (chrome_layer, _guard) = tracing_chrome::ChromeLayerBuilder::new().build();
753    tracing_subscriber::registry().with(chrome_layer).init();
754
755    let mut rng = oorandom::Rand64::new(1);
756
757    let allocator = feanor_mempool::AllocRc(Rc::new(feanor_mempool::dynsize::DynLayoutMempool::<Global>::new(Alignment::of::<u64>())));
758    let ring = RingValue::from(NumberRingQuotientBase::new(CompositeCyclotomicNumberRing::new(337, 127), Zn::new(65536)).into().with_allocator(allocator));
759    let galois_group = CyclotomicGaloisGroup::new(337 * 127);
760    let hypercube = HypercubeStructure::new(
761        galois_group,
762        galois_group.from_representative(2),
763        21,
764        vec![16, 126],
765        vec![galois_group.from_representative(37085), galois_group.from_representative(25276)]
766    );
767    let H = HypercubeIsomorphism::new::<true>(ring, hypercube);
768    let slot_ring = H.slot_ring();
769
770    let value = log_time::<_, _, true, _>("from_slot_values", |[]| {
771        H.from_slot_values((0..H.slot_count()).map(|_| slot_ring.random_element(|| rng.rand_u64())))
772    });
773    std::hint::black_box(value);
774}
775
776#[test]
777fn test_serialization() {
778
779    fn test_with_test_ring<R>((ring, hypercube_structure): (R, HypercubeStructure))
780        where R: RingStore,
781            R::Type: CyclotomicRing,
782            BaseRing<R>: Clone + ZnRing + CanHomFrom<StaticRingBase<i64>> + CanHomFrom<BigIntRingBase> + LinSolveRing + FromModulusCreateableZnRing + SerializableElementRing,
783            AsFieldBase<DecoratedBaseRing<R>>: CanIsoFromTo<<DecoratedBaseRing<R> as RingStore>::Type> + SelfIso
784    {
785        let hypercube = HypercubeIsomorphism::new::<false>(&ring, hypercube_structure);
786        let serializer = serde_assert::Serializer::builder().is_human_readable(true).build();
787        let tokens = SerializableHypercubeIsomorphismWithoutRing::new(&hypercube).serialize(&serializer).unwrap();
788        let mut deserializer = serde_assert::Deserializer::builder(tokens).is_human_readable(true).build();
789        let deserialized_hypercube = DeserializeSeedHypercubeIsomorphismWithoutRing::new(&ring).deserialize(&mut deserializer).unwrap();
790        assert!(hypercube.slot_ring().get_ring() == deserialized_hypercube.slot_ring().get_ring());
791        assert_el_eq!(hypercube.ring(), 
792            hypercube.from_slot_values((0..hypercube.slot_count()).map(|i| hypercube.slot_ring().int_hom().map(i as i32))), 
793            deserialized_hypercube.from_slot_values((0..deserialized_hypercube.slot_count()).map(|i| deserialized_hypercube.slot_ring().int_hom().map(i as i32)))
794        );
795
796        let serializer = serde_assert::Serializer::builder().is_human_readable(false).build();
797        let tokens = SerializableHypercubeIsomorphismWithoutRing::new(&hypercube).serialize(&serializer).unwrap();
798        let mut deserializer = serde_assert::Deserializer::builder(tokens).is_human_readable(false).build();
799        let deserialized_hypercube = DeserializeSeedHypercubeIsomorphismWithoutRing::new(&ring).deserialize(&mut deserializer).unwrap();
800        assert!(hypercube.slot_ring().get_ring() == deserialized_hypercube.slot_ring().get_ring());
801        assert_el_eq!(hypercube.ring(), 
802            hypercube.from_slot_values((0..hypercube.slot_count()).map(|i| hypercube.slot_ring().int_hom().map(i as i32))), 
803            deserialized_hypercube.from_slot_values((0..deserialized_hypercube.slot_count()).map(|i| deserialized_hypercube.slot_ring().int_hom().map(i as i32)))
804        );
805    }
806    test_with_test_ring(test_ring1());
807    test_with_test_ring(test_ring2());
808    test_with_test_ring(test_ring3());
809}