use std::cmp::max;
use feanor_math::algorithms::discrete_log::discrete_log;
use feanor_math::algorithms::eea::{signed_gcd, signed_lcm};
use feanor_math::divisibility::DivisibilityRingStore;
use feanor_math::iters::clone_slice;
use feanor_math::algorithms::int_factor::factor;
use feanor_math::iters::multi_cartesian_product;
use feanor_math::ring::*;
use feanor_math::rings::finite::FiniteRingStore;
use feanor_math::rings::zn::zn_64::*;
use feanor_math::homomorphism::*;
use feanor_math::rings::zn::*;
use feanor_math::rings::zn::zn_rns;
use feanor_math::seq::*;
use feanor_math::wrapper::RingElementWrapper;
use serde::{Deserialize, Serialize};
use crate::{cyclotomic::*, ZZi64};
use crate::euler_phi;
#[derive(Clone)]
pub struct HypercubeStructure {
pub(super) galois_group: CyclotomicGaloisGroup,
pub(super) p: CyclotomicGaloisGroupEl,
pub(super) d: usize,
pub(super) ms: Vec<usize>,
pub(super) ord_gs: Vec<usize>,
pub(super) gs: Vec<CyclotomicGaloisGroupEl>,
pub(super) representations: Vec<(CyclotomicGaloisGroupEl, /* first element is frobenius */ Box<[usize]>)>,
pub(super) choice: HypercubeTypeData
}
#[derive(Clone, Serialize, Deserialize, PartialEq)]
pub enum HypercubeTypeData {
Generic,
CyclotomicTensorProductHypercube(Vec<(i64, usize)>)
}
impl PartialEq for HypercubeStructure {
fn eq(&self, other: &Self) -> bool {
self.galois_group == other.galois_group &&
self.galois_group.eq_el(self.p, other.p) &&
self.d == other.d &&
self.ms == other.ms &&
self.gs.iter().zip(other.gs.iter()).all(|(l, r)| self.galois_group.eq_el(*l, *r)) &&
self.choice == other.choice
}
}
impl HypercubeStructure {
pub fn new(galois_group: CyclotomicGaloisGroup, p: CyclotomicGaloisGroupEl, d: usize, ms: Vec<usize>, gs: Vec<CyclotomicGaloisGroupEl>) -> Self {
assert_eq!(ms.len(), gs.len());
assert!(galois_group.is_identity(galois_group.pow(p, d as i64)));
for (factor, _) in factor(ZZi64, d as i64) {
assert!(!galois_group.is_identity(galois_group.pow(p, d as i64 / factor)));
}
let mut all_elements = multi_cartesian_product([(0..d)].into_iter().chain(ms.iter().map(|mi| 0..*mi)), |idxs| (
galois_group.prod(idxs.iter().zip([&p].into_iter().chain(gs.iter())).map(|(i, g)| galois_group.pow(*g, *i as i64))),
clone_slice(idxs)
), |_, x| *x).collect::<Vec<_>>();
all_elements.sort_unstable_by_key(|(g, _)| galois_group.representative(*g));
assert!((1..all_elements.len()).all(|i| !galois_group.eq_el(all_elements[i - 1].0, all_elements[i].0)), "not a bijection");
assert_eq!(galois_group.group_order(), all_elements.len());
return Self {
galois_group: galois_group,
p: p,
d: d,
ms: ms,
ord_gs: gs.iter().map(|g| galois_group.element_order(*g)).collect(),
gs: gs,
choice: HypercubeTypeData::Generic,
representations: all_elements
};
}
pub fn halevi_shoup_hypercube(galois_group: CyclotomicGaloisGroup, p: i64) -> Self {
struct HypercubeDimension {
g_main: ZnEl,
order_of_projected_p: i64,
group_order: i64,
factor_n: (i64, usize)
}
let n = galois_group.n() as i64;
assert!(signed_gcd(n, p, ZZi64) == 1, "n and p must be coprime");
let mut factorization = factor(ZZi64, n);
factorization.sort_unstable_by_key(|(p, _)| *p);
let Zn_rns = zn_rns::Zn::new(factorization.iter().map(|(q, k)| Zn::new(ZZi64.pow(*q, *k) as u64)).collect(), ZZi64);
let Zn = Zn::new(n as u64);
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());
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() })));
let mut dimensions = Vec::new();
for (i, (q, k)) in factorization.iter().enumerate() {
let Zqk = Zn_rns.at(i);
if *q == 2 {
if *k == 1 {
continue;
} else if *k == 2 {
unimplemented!()
} else {
let g1 = Zqk.int_hom().map(5);
let ord_g1 = ZZi64.pow(*q, *k as usize - 2);
let g2 = Zqk.can_hom(&ZZi64).unwrap().map(-1);
if p % 4 == 1 {
let logg1_p = unit_group_dlog(Zqk, g1, ord_g1, Zqk.can_hom(&ZZi64).unwrap().map(p)).unwrap();
dimensions.push(HypercubeDimension {
order_of_projected_p: ord_g1 / signed_gcd(logg1_p, ord_g1, &ZZi64),
group_order: ord_g1,
g_main: from_crt(i, g1),
factor_n: (2, *k),
});
dimensions.push(HypercubeDimension {
order_of_projected_p: 1,
group_order: 2,
g_main: from_crt(i, g2),
factor_n: (2, *k),
});
} else {
let logg1_pg2 = unit_group_dlog(Zqk, g1, ord_g1, Zqk.mul(Zqk.can_hom(&ZZi64).unwrap().map(p), g2)).unwrap();
dimensions.push(HypercubeDimension {
order_of_projected_p: max(ord_g1 / signed_gcd(logg1_pg2, ord_g1, &ZZi64), 2),
group_order: 2 * ord_g1,
g_main: from_crt(i, g1),
factor_n: (2, *k)
});
}
}
} else {
let g = get_multiplicative_generator(*Zqk, &[(*q, *k)]);
let ord_g = euler_phi(&[(*q, *k)]);
let logg_p = unit_group_dlog(Zqk, g, ord_g, Zqk.can_hom(&ZZi64).unwrap().map(p)).unwrap();
let ord_p = ord_g / signed_gcd(logg_p, ord_g, ZZi64);
dimensions.push(HypercubeDimension {
order_of_projected_p: ord_p,
group_order: ord_g,
g_main: from_crt(i, g),
factor_n: (*q, *k)
});
}
}
dimensions.sort_by_key(|dim| -(dim.order_of_projected_p as i64));
let mut current_d = 1;
let lengths = dimensions.iter().map(|dim| {
let new_d = signed_lcm(current_d, dim.order_of_projected_p as i64, ZZi64);
let len = dim.group_order as i64 / (new_d / current_d);
current_d = new_d;
return len as usize;
}).collect::<Vec<_>>();
let mut result = Self::new(
galois_group,
galois_group.from_representative(p),
current_d as usize,
lengths,
dimensions.iter().map(|dim| galois_group.from_ring_el(dim.g_main)).collect()
);
result.choice = HypercubeTypeData::CyclotomicTensorProductHypercube(dimensions.iter().map(|dim| dim.factor_n).collect());
return result;
}
pub fn map_1d(&self, dim_idx: usize, steps: i64) -> CyclotomicGaloisGroupEl {
assert!(dim_idx < self.dim_count());
self.galois_group.pow(self.gs[dim_idx], steps)
}
pub fn map(&self, idxs: &[i64]) -> CyclotomicGaloisGroupEl {
assert_eq!(self.ms.len(), idxs.len());
self.galois_group.prod(idxs.iter().zip(self.gs.iter()).map(|(i, g)| self.galois_group.pow(*g, *i)))
}
pub fn map_usize(&self, idxs: &[usize]) -> CyclotomicGaloisGroupEl {
assert_eq!(self.ms.len(), idxs.len());
self.galois_group.prod(idxs.iter().zip(self.gs.iter()).map(|(i, g)| self.galois_group.pow(*g, *i as i64)))
}
pub fn std_preimage(&self, g: CyclotomicGaloisGroupEl) -> &[usize] {
let idx = self.representations.binary_search_by_key(&self.galois_group.representative(g), |(g, _)| self.galois_group.representative(*g)).unwrap();
return &self.representations[idx].1;
}
pub fn is_tensor_product_compatible(&self) -> bool {
match self.choice {
HypercubeTypeData::CyclotomicTensorProductHypercube(_) => true,
HypercubeTypeData::Generic => false
}
}
pub fn factor_of_n(&self, dim_idx: usize) -> Option<i64> {
assert!(dim_idx < self.dim_count());
match &self.choice {
HypercubeTypeData::CyclotomicTensorProductHypercube(factors_n) => Some(ZZi64.pow(factors_n[dim_idx].0, factors_n[dim_idx].1)),
HypercubeTypeData::Generic => None
}
}
pub fn p(&self) -> CyclotomicGaloisGroupEl {
self.p
}
pub fn frobenius(&self, power: i64) -> CyclotomicGaloisGroupEl {
self.galois_group.pow(self.p(), power)
}
pub fn d(&self) -> usize {
self.d
}
pub fn m(&self, i: usize) -> usize {
assert!(i < self.ms.len());
self.ms[i]
}
pub fn g(&self, i: usize) -> CyclotomicGaloisGroupEl {
assert!(i < self.ms.len());
self.gs[i]
}
pub fn ord_g(&self, i: usize) -> usize {
assert!(i < self.ms.len());
self.ord_gs[i]
}
pub fn n(&self) -> usize {
self.galois_group().n()
}
pub fn dim_count(&self) -> usize {
self.gs.len()
}
pub fn galois_group(&self) -> &CyclotomicGaloisGroup {
&self.galois_group
}
pub fn element_count(&self) -> usize {
ZZi64.prod(self.ms.iter().map(|mi| *mi as i64)) as usize
}
pub fn hypercube_iter<'b, G, T>(&'b self, for_slot: G) -> impl ExactSizeIterator<Item = T> + use<'b, G, T>
where G: 'b + Clone + FnMut(&[usize]) -> T,
T: 'b
{
let mut it = multi_cartesian_product(
self.ms.iter().map(|l| (0..*l)),
for_slot,
|_, x| *x
);
(0..self.element_count()).map(move |_| it.next().unwrap())
}
pub fn element_iter<'b>(&'b self) -> impl ExactSizeIterator<Item = CyclotomicGaloisGroupEl> + use<'b> {
self.hypercube_iter(|idxs| self.map_usize(idxs))
}
}
pub fn get_multiplicative_generator(ring: Zn, factorization: &[(i64, usize)]) -> ZnEl {
assert_eq!(*ring.modulus(), ZZi64.prod(factorization.iter().map(|(p, e)| ZZi64.pow(*p, *e))));
assert!(*ring.modulus() % 2 == 1, "for even n, Z/nZ* is either equal to Z/(n/2)Z* or not cyclic");
let mut rng = oorandom::Rand64::new(ring.integer_ring().default_hash(ring.modulus()) as u128);
let order = euler_phi(factorization);
let order_factorization = factor(&ZZi64, order);
'test_generator: loop {
let potential_generator = ring.random_element(|| rng.rand_u64());
if !ring.is_unit(&potential_generator) {
continue 'test_generator;
}
for (p, _) in &order_factorization {
if ring.is_one(&ring.pow(potential_generator, (order / p) as usize)) {
continue 'test_generator;
}
}
return potential_generator;
}
}
pub fn unit_group_dlog(ring: &Zn, base: ZnEl, order: i64, value: ZnEl) -> Option<i64> {
discrete_log(
RingElementWrapper::new(&ring, value),
&RingElementWrapper::new(&ring, base),
order,
|x, y| x * y,
RingElementWrapper::new(&ring, ring.one())
)
}
#[test]
fn test_halevi_shoup_hypercube() {
let galois_group = CyclotomicGaloisGroup::new(11 * 31);
let hypercube_structure = HypercubeStructure::halevi_shoup_hypercube(galois_group, 2);
assert_eq!(10, hypercube_structure.d());
assert_eq!(2, hypercube_structure.dim_count());
assert_eq!(1, hypercube_structure.m(0));
assert_eq!(30, hypercube_structure.m(1));
let galois_group = CyclotomicGaloisGroup::new(32);
let hypercube_structure = HypercubeStructure::halevi_shoup_hypercube(galois_group, 7);
assert_eq!(4, hypercube_structure.d());
assert_eq!(1, hypercube_structure.dim_count());
assert_eq!(4, hypercube_structure.m(0));
let galois_group = CyclotomicGaloisGroup::new(32);
let hypercube_structure = HypercubeStructure::halevi_shoup_hypercube(galois_group, 17);
assert_eq!(2, hypercube_structure.d());
assert_eq!(2, hypercube_structure.dim_count());
assert_eq!(4, hypercube_structure.m(0));
assert_eq!(2, hypercube_structure.m(1));
}
#[test]
fn test_serialization() {
for hypercube in [
HypercubeStructure::halevi_shoup_hypercube(CyclotomicGaloisGroup::new(11 * 31), 2),
HypercubeStructure::halevi_shoup_hypercube(CyclotomicGaloisGroup::new(32), 7),
HypercubeStructure::halevi_shoup_hypercube(CyclotomicGaloisGroup::new(32), 17)
] {
let serializer = serde_assert::Serializer::builder().is_human_readable(true).build();
let tokens = hypercube.serialize(&serializer).unwrap();
let mut deserializer = serde_assert::Deserializer::builder(tokens).is_human_readable(true).build();
let deserialized_hypercube = HypercubeStructure::deserialize(&mut deserializer).unwrap();
assert!(hypercube.galois_group() == deserialized_hypercube.galois_group());
assert_eq!(hypercube.dim_count(), deserialized_hypercube.dim_count());
assert_eq!(hypercube.is_tensor_product_compatible(), deserialized_hypercube.is_tensor_product_compatible());
for i in 0..hypercube.dim_count() {
assert_eq!(hypercube.m(i), deserialized_hypercube.m(i));
assert!(hypercube.galois_group().eq_el(hypercube.g(i), deserialized_hypercube.g(i)));
assert_eq!(hypercube.ord_g(i), deserialized_hypercube.ord_g(i));
}
let serializer = serde_assert::Serializer::builder().is_human_readable(false).build();
let tokens = hypercube.serialize(&serializer).unwrap();
let mut deserializer = serde_assert::Deserializer::builder(tokens).is_human_readable(false).build();
let deserialized_hypercube = HypercubeStructure::deserialize(&mut deserializer).unwrap();
assert!(hypercube.galois_group() == deserialized_hypercube.galois_group());
assert_eq!(hypercube.dim_count(), deserialized_hypercube.dim_count());
assert_eq!(hypercube.is_tensor_product_compatible(), deserialized_hypercube.is_tensor_product_compatible());
for i in 0..hypercube.dim_count() {
assert_eq!(hypercube.m(i), deserialized_hypercube.m(i));
assert!(hypercube.galois_group().eq_el(hypercube.g(i), deserialized_hypercube.g(i)));
assert_eq!(hypercube.ord_g(i), deserialized_hypercube.ord_g(i));
}
}
}