1use std::cmp::max;
2
3use feanor_math::algorithms::discrete_log::discrete_log;
4use feanor_math::algorithms::eea::{signed_gcd, signed_lcm};
5use feanor_math::divisibility::DivisibilityRingStore;
6use feanor_math::iters::clone_slice;
7use feanor_math::algorithms::int_factor::factor;
8use feanor_math::iters::multi_cartesian_product;
9use feanor_math::ring::*;
10use feanor_math::rings::finite::FiniteRingStore;
11use feanor_math::rings::zn::zn_64::*;
12use feanor_math::homomorphism::*;
13use feanor_math::rings::zn::*;
14use feanor_math::rings::zn::zn_rns;
15use feanor_math::seq::*;
16use feanor_math::wrapper::RingElementWrapper;
17use serde::{Deserialize, Serialize};
18
19use crate::{cyclotomic::*, ZZi64};
20use crate::euler_phi;
21
22#[derive(Clone)]
37pub struct HypercubeStructure {
38 pub(super) galois_group: CyclotomicGaloisGroup,
39 pub(super) p: CyclotomicGaloisGroupEl,
40 pub(super) d: usize,
41 pub(super) ms: Vec<usize>,
42 pub(super) ord_gs: Vec<usize>,
43 pub(super) gs: Vec<CyclotomicGaloisGroupEl>,
44 pub(super) representations: Vec<(CyclotomicGaloisGroupEl, Box<[usize]>)>,
45 pub(super) choice: HypercubeTypeData
46}
47
48#[derive(Clone, Serialize, Deserialize, PartialEq)]
49pub enum HypercubeTypeData {
50 Generic,
51 CyclotomicTensorProductHypercube(Vec<(i64, usize)>)
55}
56
57impl PartialEq for HypercubeStructure {
58 fn eq(&self, other: &Self) -> bool {
59 self.galois_group == other.galois_group &&
60 self.galois_group.eq_el(self.p, other.p) &&
61 self.d == other.d &&
62 self.ms == other.ms &&
63 self.gs.iter().zip(other.gs.iter()).all(|(l, r)| self.galois_group.eq_el(*l, *r)) &&
64
65 self.choice == other.choice
66 }
67}
68
69impl HypercubeStructure {
70
71 pub fn new(galois_group: CyclotomicGaloisGroup, p: CyclotomicGaloisGroupEl, d: usize, ms: Vec<usize>, gs: Vec<CyclotomicGaloisGroupEl>) -> Self {
72 assert_eq!(ms.len(), gs.len());
73 assert!(galois_group.is_identity(galois_group.pow(p, d as i64)));
75 for (factor, _) in factor(ZZi64, d as i64) {
76 assert!(!galois_group.is_identity(galois_group.pow(p, d as i64 / factor)));
77 }
78 let mut all_elements = multi_cartesian_product([(0..d)].into_iter().chain(ms.iter().map(|mi| 0..*mi)), |idxs| (
80 galois_group.prod(idxs.iter().zip([&p].into_iter().chain(gs.iter())).map(|(i, g)| galois_group.pow(*g, *i as i64))),
81 clone_slice(idxs)
82 ), |_, x| *x).collect::<Vec<_>>();
83 all_elements.sort_unstable_by_key(|(g, _)| galois_group.representative(*g));
84 assert!((1..all_elements.len()).all(|i| !galois_group.eq_el(all_elements[i - 1].0, all_elements[i].0)), "not a bijection");
85 assert_eq!(galois_group.group_order(), all_elements.len());
86
87 return Self {
88 galois_group: galois_group,
89 p: p,
90 d: d,
91 ms: ms,
92 ord_gs: gs.iter().map(|g| galois_group.element_order(*g)).collect(),
93 gs: gs,
94 choice: HypercubeTypeData::Generic,
95 representations: all_elements
96 };
97 }
98
99 pub fn halevi_shoup_hypercube(galois_group: CyclotomicGaloisGroup, p: i64) -> Self {
106
107 struct HypercubeDimension {
117 g_main: ZnEl,
118 order_of_projected_p: i64,
119 group_order: i64,
120 factor_n: (i64, usize)
121 }
122
123 let n = galois_group.n() as i64;
124 assert!(signed_gcd(n, p, ZZi64) == 1, "n and p must be coprime");
125
126 let mut factorization = factor(ZZi64, n);
129 factorization.sort_unstable_by_key(|(p, _)| *p);
131 let Zn_rns = zn_rns::Zn::new(factorization.iter().map(|(q, k)| Zn::new(ZZi64.pow(*q, *k) as u64)).collect(), ZZi64);
132 let Zn = Zn::new(n as u64);
133 let iso = Zn.into_can_hom(zn_big::Zn::new(ZZi64, n)).ok().unwrap().compose((&Zn_rns).into_can_iso(zn_big::Zn::new(ZZi64, n)).ok().unwrap());
134 let from_crt = |index: usize, value: ZnEl| iso.map(Zn_rns.from_congruence((0..factorization.len()).map(|j| if j == index { value } else { Zn_rns.at(j).one() })));
135
136 let mut dimensions = Vec::new();
137 for (i, (q, k)) in factorization.iter().enumerate() {
138 let Zqk = Zn_rns.at(i);
139 if *q == 2 {
140 if *k == 1 {
142 continue;
143 } else if *k == 2 {
144 unimplemented!()
145 } else {
146 let g1 = Zqk.int_hom().map(5);
148 let ord_g1 = ZZi64.pow(*q, *k as usize - 2);
149 let g2 = Zqk.can_hom(&ZZi64).unwrap().map(-1);
150 if p % 4 == 1 {
151 let logg1_p = unit_group_dlog(Zqk, g1, ord_g1, Zqk.can_hom(&ZZi64).unwrap().map(p)).unwrap();
153 dimensions.push(HypercubeDimension {
154 order_of_projected_p: ord_g1 / signed_gcd(logg1_p, ord_g1, &ZZi64),
155 group_order: ord_g1,
156 g_main: from_crt(i, g1),
157 factor_n: (2, *k),
158 });
159 dimensions.push(HypercubeDimension {
160 order_of_projected_p: 1,
161 group_order: 2,
162 g_main: from_crt(i, g2),
163 factor_n: (2, *k),
164 });
165 } else {
166 let logg1_pg2 = unit_group_dlog(Zqk, g1, ord_g1, Zqk.mul(Zqk.can_hom(&ZZi64).unwrap().map(p), g2)).unwrap();
168 dimensions.push(HypercubeDimension {
169 order_of_projected_p: max(ord_g1 / signed_gcd(logg1_pg2, ord_g1, &ZZi64), 2),
170 group_order: 2 * ord_g1,
171 g_main: from_crt(i, g1),
172 factor_n: (2, *k)
173 });
174 }
175 }
176 } else {
177 let g = get_multiplicative_generator(*Zqk, &[(*q, *k)]);
179 let ord_g = euler_phi(&[(*q, *k)]);
180 let logg_p = unit_group_dlog(Zqk, g, ord_g, Zqk.can_hom(&ZZi64).unwrap().map(p)).unwrap();
181 let ord_p = ord_g / signed_gcd(logg_p, ord_g, ZZi64);
182 dimensions.push(HypercubeDimension {
183 order_of_projected_p: ord_p,
184 group_order: ord_g,
185 g_main: from_crt(i, g),
186 factor_n: (*q, *k)
187 });
188 }
189 }
190
191 dimensions.sort_by_key(|dim| -(dim.order_of_projected_p as i64));
192 let mut current_d = 1;
193 let lengths = dimensions.iter().map(|dim| {
194 let new_d = signed_lcm(current_d, dim.order_of_projected_p as i64, ZZi64);
195 let len = dim.group_order as i64 / (new_d / current_d);
196 current_d = new_d;
197 return len as usize;
198 }).collect::<Vec<_>>();
199
200 let mut result = Self::new(
201 galois_group,
202 galois_group.from_representative(p),
203 current_d as usize,
204 lengths,
205 dimensions.iter().map(|dim| galois_group.from_ring_el(dim.g_main)).collect()
206 );
207 result.choice = HypercubeTypeData::CyclotomicTensorProductHypercube(dimensions.iter().map(|dim| dim.factor_n).collect());
208 return result;
209 }
210
211 pub fn map_1d(&self, dim_idx: usize, steps: i64) -> CyclotomicGaloisGroupEl {
217 assert!(dim_idx < self.dim_count());
218 self.galois_group.pow(self.gs[dim_idx], steps)
219 }
220
221 pub fn map(&self, idxs: &[i64]) -> CyclotomicGaloisGroupEl {
233 assert_eq!(self.ms.len(), idxs.len());
234 self.galois_group.prod(idxs.iter().zip(self.gs.iter()).map(|(i, g)| self.galois_group.pow(*g, *i)))
235 }
236
237 pub fn map_usize(&self, idxs: &[usize]) -> CyclotomicGaloisGroupEl {
242 assert_eq!(self.ms.len(), idxs.len());
243 self.galois_group.prod(idxs.iter().zip(self.gs.iter()).map(|(i, g)| self.galois_group.pow(*g, *i as i64)))
244 }
245
246 pub fn std_preimage(&self, g: CyclotomicGaloisGroupEl) -> &[usize] {
253 let idx = self.representations.binary_search_by_key(&self.galois_group.representative(g), |(g, _)| self.galois_group.representative(*g)).unwrap();
254 return &self.representations[idx].1;
255 }
256
257 pub fn is_tensor_product_compatible(&self) -> bool {
264 match self.choice {
265 HypercubeTypeData::CyclotomicTensorProductHypercube(_) => true,
266 HypercubeTypeData::Generic => false
267 }
268 }
269
270 pub fn factor_of_n(&self, dim_idx: usize) -> Option<i64> {
277 assert!(dim_idx < self.dim_count());
278 match &self.choice {
279 HypercubeTypeData::CyclotomicTensorProductHypercube(factors_n) => Some(ZZi64.pow(factors_n[dim_idx].0, factors_n[dim_idx].1)),
280 HypercubeTypeData::Generic => None
281 }
282 }
283
284 pub fn p(&self) -> CyclotomicGaloisGroupEl {
288 self.p
289 }
290
291 pub fn frobenius(&self, power: i64) -> CyclotomicGaloisGroupEl {
296 self.galois_group.pow(self.p(), power)
297 }
298
299 pub fn d(&self) -> usize {
303 self.d
304 }
305
306 pub fn m(&self, i: usize) -> usize {
310 assert!(i < self.ms.len());
311 self.ms[i]
312 }
313
314 pub fn g(&self, i: usize) -> CyclotomicGaloisGroupEl {
318 assert!(i < self.ms.len());
319 self.gs[i]
320 }
321
322 pub fn ord_g(&self, i: usize) -> usize {
326 assert!(i < self.ms.len());
327 self.ord_gs[i]
328 }
329
330 pub fn n(&self) -> usize {
334 self.galois_group().n()
335 }
336
337 pub fn dim_count(&self) -> usize {
341 self.gs.len()
342 }
343
344 pub fn galois_group(&self) -> &CyclotomicGaloisGroup {
349 &self.galois_group
350 }
351
352 pub fn element_count(&self) -> usize {
358 ZZi64.prod(self.ms.iter().map(|mi| *mi as i64)) as usize
359 }
360
361 pub fn hypercube_iter<'b, G, T>(&'b self, for_slot: G) -> impl ExactSizeIterator<Item = T> + use<'b, G, T>
369 where G: 'b + Clone + FnMut(&[usize]) -> T,
370 T: 'b
371 {
372 let mut it = multi_cartesian_product(
373 self.ms.iter().map(|l| (0..*l)),
374 for_slot,
375 |_, x| *x
376 );
377 (0..self.element_count()).map(move |_| it.next().unwrap())
378 }
379
380 pub fn element_iter<'b>(&'b self) -> impl ExactSizeIterator<Item = CyclotomicGaloisGroupEl> + use<'b> {
387 self.hypercube_iter(|idxs| self.map_usize(idxs))
388 }
389}
390
391pub fn get_multiplicative_generator(ring: Zn, factorization: &[(i64, usize)]) -> ZnEl {
392 assert_eq!(*ring.modulus(), ZZi64.prod(factorization.iter().map(|(p, e)| ZZi64.pow(*p, *e))));
393 assert!(*ring.modulus() % 2 == 1, "for even n, Z/nZ* is either equal to Z/(n/2)Z* or not cyclic");
394 let mut rng = oorandom::Rand64::new(ring.integer_ring().default_hash(ring.modulus()) as u128);
395 let order = euler_phi(factorization);
396 let order_factorization = factor(&ZZi64, order);
397 'test_generator: loop {
398 let potential_generator = ring.random_element(|| rng.rand_u64());
399 if !ring.is_unit(&potential_generator) {
400 continue 'test_generator;
401 }
402 for (p, _) in &order_factorization {
403 if ring.is_one(&ring.pow(potential_generator, (order / p) as usize)) {
404 continue 'test_generator;
405 }
406 }
407 return potential_generator;
408 }
409}
410
411pub fn unit_group_dlog(ring: &Zn, base: ZnEl, order: i64, value: ZnEl) -> Option<i64> {
412 discrete_log(
413 RingElementWrapper::new(&ring, value),
414 &RingElementWrapper::new(&ring, base),
415 order,
416 |x, y| x * y,
417 RingElementWrapper::new(&ring, ring.one())
418 )
419}
420
421#[test]
422fn test_halevi_shoup_hypercube() {
423 let galois_group = CyclotomicGaloisGroup::new(11 * 31);
424 let hypercube_structure = HypercubeStructure::halevi_shoup_hypercube(galois_group, 2);
425 assert_eq!(10, hypercube_structure.d());
426 assert_eq!(2, hypercube_structure.dim_count());
427
428 assert_eq!(1, hypercube_structure.m(0));
429 assert_eq!(30, hypercube_structure.m(1));
430
431 let galois_group = CyclotomicGaloisGroup::new(32);
432 let hypercube_structure = HypercubeStructure::halevi_shoup_hypercube(galois_group, 7);
433 assert_eq!(4, hypercube_structure.d());
434 assert_eq!(1, hypercube_structure.dim_count());
435
436 assert_eq!(4, hypercube_structure.m(0));
437
438 let galois_group = CyclotomicGaloisGroup::new(32);
439 let hypercube_structure = HypercubeStructure::halevi_shoup_hypercube(galois_group, 17);
440 assert_eq!(2, hypercube_structure.d());
441 assert_eq!(2, hypercube_structure.dim_count());
442
443 assert_eq!(4, hypercube_structure.m(0));
444 assert_eq!(2, hypercube_structure.m(1));
445}
446
447#[test]
448fn test_serialization() {
449 for hypercube in [
450 HypercubeStructure::halevi_shoup_hypercube(CyclotomicGaloisGroup::new(11 * 31), 2),
451 HypercubeStructure::halevi_shoup_hypercube(CyclotomicGaloisGroup::new(32), 7),
452 HypercubeStructure::halevi_shoup_hypercube(CyclotomicGaloisGroup::new(32), 17)
453 ] {
454 let serializer = serde_assert::Serializer::builder().is_human_readable(true).build();
455 let tokens = hypercube.serialize(&serializer).unwrap();
456 let mut deserializer = serde_assert::Deserializer::builder(tokens).is_human_readable(true).build();
457 let deserialized_hypercube = HypercubeStructure::deserialize(&mut deserializer).unwrap();
458
459 assert!(hypercube.galois_group() == deserialized_hypercube.galois_group());
460 assert_eq!(hypercube.dim_count(), deserialized_hypercube.dim_count());
461 assert_eq!(hypercube.is_tensor_product_compatible(), deserialized_hypercube.is_tensor_product_compatible());
462 for i in 0..hypercube.dim_count() {
463 assert_eq!(hypercube.m(i), deserialized_hypercube.m(i));
464 assert!(hypercube.galois_group().eq_el(hypercube.g(i), deserialized_hypercube.g(i)));
465 assert_eq!(hypercube.ord_g(i), deserialized_hypercube.ord_g(i));
466 }
467
468 let serializer = serde_assert::Serializer::builder().is_human_readable(false).build();
469 let tokens = hypercube.serialize(&serializer).unwrap();
470 let mut deserializer = serde_assert::Deserializer::builder(tokens).is_human_readable(false).build();
471 let deserialized_hypercube = HypercubeStructure::deserialize(&mut deserializer).unwrap();
472
473 assert!(hypercube.galois_group() == deserialized_hypercube.galois_group());
474 assert_eq!(hypercube.dim_count(), deserialized_hypercube.dim_count());
475 assert_eq!(hypercube.is_tensor_product_compatible(), deserialized_hypercube.is_tensor_product_compatible());
476 for i in 0..hypercube.dim_count() {
477 assert_eq!(hypercube.m(i), deserialized_hypercube.m(i));
478 assert!(hypercube.galois_group().eq_el(hypercube.g(i), deserialized_hypercube.g(i)));
479 assert_eq!(hypercube.ord_g(i), deserialized_hypercube.ord_g(i));
480 }
481 }
482}