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 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
136pub 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 #[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 #[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 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, ¤t);
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}