use feanor_math::algorithms::discrete_log::*;
use feanor_math::algorithms::eea::{signed_gcd, signed_lcm};
use feanor_math::divisibility::DivisibilityRingStore;
use feanor_math::group::*;
use feanor_math::integer::{int_cast, BigIntRing, IntegerRingStore};
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::zn::zn_64::*;
use feanor_math::homomorphism::*;
use feanor_math::pid::*;
use feanor_math::rings::zn::*;
use feanor_math::rings::zn::zn_rns;
use feanor_math::seq::*;
use serde::{Deserialize, Serialize};
use crate::{euler_phi, ZZbig, ZZi64};
use crate::number_ring::galois::*;
#[derive(Clone)]
pub struct HypercubeStructure {
pub(super) galois_group: Subgroup<CyclotomicGaloisGroup>,
pub(super) frobenius: GaloisGroupEl,
pub(super) d: usize,
pub(super) ls: Vec<usize>,
pub(super) ord_gs: Vec<usize>,
pub(super) gs: Vec<GaloisGroupEl>,
pub(super) representations: Vec<(GaloisGroupEl, /* 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.get_group() == other.galois_group.get_group() &&
self.galois_group.eq_el(self.p(), &other.p()) &&
self.d == other.d &&
self.ls == other.ls &&
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: Subgroup<CyclotomicGaloisGroup>, frobenius: GaloisGroupEl, d: usize, ls: Vec<usize>, gs: Vec<GaloisGroupEl>) -> Self {
assert_eq!(ls.len(), gs.len());
assert_eq!(d, galois_group.element_order(&frobenius));
assert!(galois_group.contains(&frobenius));
assert!(gs.iter().all(|g| galois_group.contains(g)));
let mut all_elements = multi_cartesian_product([(0..d)].into_iter().chain(ls.iter().map(|l_i| 0..*l_i)), |idxs| (
idxs.iter().zip([&frobenius].into_iter().chain(gs.iter()))
.map(|(i, g)| galois_group.pow(g, &int_cast(*i as i64, ZZbig, ZZi64)))
.fold(galois_group.identity(), |current, next| galois_group.op(current, next)),
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!(int_cast(galois_group.subgroup_order(), ZZi64, ZZbig) as usize, all_elements.len());
let ord_gs = gs.iter().map(|g| galois_group.element_order(g)).collect();
return Self {
galois_group: galois_group,
frobenius: frobenius,
d: d,
ls,
ord_gs: ord_gs,
gs: gs,
choice: HypercubeTypeData::Generic,
representations: all_elements
};
}
pub fn default_pow2_hypercube(galois_group: &Subgroup<CyclotomicGaloisGroup>, p: El<BigIntRing>) -> Self {
let m = galois_group.m() as i64;
let log2_m = ZZi64.abs_log2_ceil(&m).unwrap();
assert!(m == 1 << log2_m, "m must be a power of two for default_pow2_hypercube()");
let p_mod_m = int_cast(ZZbig.euclidean_rem(p, &int_cast(m, ZZbig, ZZi64)), ZZi64, ZZbig);
assert!(signed_gcd(m, p_mod_m, ZZi64) == 1, "m and p must be coprime");
let frobenius = galois_group.from_representative(p_mod_m);
let Gal = galois_group.parent();
let order = galois_group.group_order();
let d = galois_group.element_order(&frobenius);
let g1 = Gal.from_representative(5);
let g2 = Gal.from_representative(-1);
if order == ZZi64.pow(2, log2_m - 1) as usize || galois_group.contains(&g2) {
if p_mod_m % 4 == 3 {
let g1_pow_2k = Gal.pow(&g1, &int_cast(ZZi64.checked_div(&ZZi64.pow(2, log2_m - 1), &(order as i64)).unwrap(), ZZbig, ZZi64));
return Self::new(
galois_group.clone(),
frobenius,
d,
vec![order / d],
vec![g1_pow_2k]
);
} else {
let g1_pow_2k = Gal.pow(&g1, &int_cast(ZZi64.checked_div(&ZZi64.pow(2, log2_m - 1), &(order as i64)).unwrap(), ZZbig, ZZi64));
return Self::new(
galois_group.clone(),
frobenius,
d,
vec![order / d / 2, 2],
vec![g1_pow_2k, g2]
);
}
}
let k = ZZi64.checked_div(&ZZi64.pow(2, log2_m - 2), &(order as i64)).unwrap();
let g1_pow_k = Gal.pow(&g1, &int_cast(k, ZZbig, ZZi64));
if galois_group.contains(&g1_pow_k) {
return Self::new(
galois_group.clone(),
frobenius,
d,
vec![order / d],
vec![g1_pow_k]
);
} else {
let g2_g1_pow_k = Gal.op(g1_pow_k, g2);
debug_assert!(galois_group.contains(&g2_g1_pow_k));
return Self::new(
galois_group.clone(),
frobenius,
d,
vec![order / d],
vec![g2_g1_pow_k]
);
}
}
pub fn halevi_shoup_hypercube(galois_group: &Subgroup<CyclotomicGaloisGroup>, p: El<BigIntRing>) -> Self {
assert!(galois_group.is_full_cyclotomic_galois_group());
assert!(galois_group.m() % 2 == 1, "the halevi-shoup hypercube structure only exists for odd m");
let galois_group = galois_group.parent().clone();
struct HypercubeDimension {
g_main: ZnEl,
order_of_projected_p: i64,
group_order: i64,
factor_m: (i64, usize)
}
let m = galois_group.m() as i64;
let frobenius = int_cast(ZZbig.euclidean_rem(p, &int_cast(m, ZZbig, ZZi64)), ZZi64, ZZbig);
assert!(signed_gcd(m, frobenius, ZZi64) == 1, "m and p must be coprime");
let mut factorization = factor(ZZi64, m);
factorization.sort_unstable_by_key(|(p, _)| *p);
let zm_rns = zn_rns::Zn::new(factorization.iter().map(|(q, k)| Zn::new(ZZi64.pow(*q, *k) as u64)).collect(), ZZi64);
let zm = Zn::new(m as u64);
let iso = zm.into_can_hom(zn_big::Zn::new(ZZi64, m)).ok().unwrap().compose((&zm_rns).into_can_iso(zn_big::Zn::new(ZZi64, m)).ok().unwrap());
let from_crt = |index: usize, value: ZnEl| iso.map(zm_rns.from_congruence((0..factorization.len()).map(|j| if j == index { value } else { zm_rns.at(j).one() })));
let mut dimensions = Vec::new();
for (i, (q, k)) in factorization.iter().enumerate() {
let Zqk = zm_rns.at(i);
assert!(*q != 2);
let g = get_multiplicative_generator(*Zqk);
let ord_g = euler_phi(&[(*q, *k)]);
let logg_p = unit_group_dlog(Zqk, g, Zqk.can_hom(&ZZi64).unwrap().map(frobenius)).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_m: (*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;
len as usize
}).collect::<Vec<_>>();
let p_repr = galois_group.from_representative(frobenius);
let gs = dimensions.iter().map(|dim| galois_group.from_ring_el(dim.g_main)).collect();
let mut result = Self::new(
galois_group.into().full_subgroup(),
p_repr,
current_d as usize,
lengths,
gs
);
result.choice = HypercubeTypeData::CyclotomicTensorProductHypercube(dimensions.iter().map(|dim| dim.factor_m).collect());
return result;
}
pub fn map_1d(&self, dim_idx: usize, steps: i64) -> GaloisGroupEl {
assert!(dim_idx < self.dim_count());
self.galois_group.pow(&self.gs[dim_idx], &int_cast(steps, ZZbig, ZZi64))
}
pub fn map(&self, idxs: &[i64]) -> GaloisGroupEl {
assert_eq!(self.ls.len(), idxs.len());
idxs.iter().zip(self.gs.iter()).map(|(i, g)| self.galois_group.pow(g, &int_cast(*i, ZZbig, ZZi64)))
.fold(self.galois_group().identity(), |current, next| self.galois_group().op(current, next))
}
pub fn map_incl_frobenius(&self, idxs: &[i64]) -> GaloisGroupEl {
assert_eq!(self.ls.len() + 1, idxs.len());
self.galois_group.op(self.map(&idxs[1..]), self.frobenius(idxs[0]))
}
pub fn map_usize(&self, idxs: &[usize]) -> GaloisGroupEl {
assert_eq!(self.ls.len(), idxs.len());
idxs.iter().zip(self.gs.iter()).map(|(i, g)| self.galois_group.pow(g, &int_cast(*i as i64, ZZbig, ZZi64)))
.fold(self.galois_group().identity(), |current, next| self.galois_group().op(current, next))
}
pub fn std_preimage(&self, g: &GaloisGroupEl) -> &[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 is_halevi_shoup_hypercube(&self) -> bool {
self.is_tensor_product_compatible()
}
pub fn factor_of_m(&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) -> &GaloisGroupEl {
&self.frobenius
}
pub fn frobenius(&self, e: i64) -> GaloisGroupEl {
self.galois_group.pow(self.p(), &int_cast(e, ZZbig, ZZi64))
}
pub fn d(&self) -> usize {
self.d
}
pub fn dim_length(&self, i: usize) -> usize {
assert!(i < self.ls.len());
self.ls[i]
}
pub fn dim_generator(&self, i: usize) -> &GaloisGroupEl {
assert!(i < self.ls.len());
&self.gs[i]
}
pub fn ord_generator(&self, i: usize) -> usize {
assert!(i < self.ls.len());
let result = self.ord_gs[i];
debug_assert!(result % self.dim_length(i) == 0);
return result;
}
pub fn dim_count(&self) -> usize {
self.gs.len()
}
pub fn galois_group(&self) -> &Subgroup<CyclotomicGaloisGroup> {
&self.galois_group
}
pub fn element_count(&self) -> usize {
ZZi64.prod(self.ls.iter().map(|m_i| *m_i 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.ls.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 = GaloisGroupEl> + use<'b> {
self.hypercube_iter(|idxs| self.map_usize(idxs))
}
}
pub fn unit_group_dlog(ring: &Zn, base: ZnEl, value: ZnEl) -> Option<i64> {
finite_field_discrete_log(
value,
base,
ring
)
}
#[test]
fn test_halevi_shoup_hypercube() {
let galois_group = CyclotomicGaloisGroupBase::new(11 * 31).into().full_subgroup();
let hypercube_structure = HypercubeStructure::halevi_shoup_hypercube(&galois_group, int_cast(2, ZZbig, ZZi64));
assert_eq!(10, hypercube_structure.d());
assert_eq!(2, hypercube_structure.dim_count());
assert_eq!(1, hypercube_structure.dim_length(0));
assert_eq!(30, hypercube_structure.dim_length(1));
}
#[test]
fn test_pow2_hypercube() {
let galois_group = CyclotomicGaloisGroupBase::new(32).into().full_subgroup();
let hypercube_structure = HypercubeStructure::default_pow2_hypercube(&galois_group, int_cast(7, ZZbig, ZZi64));
assert_eq!(4, hypercube_structure.d());
assert_eq!(1, hypercube_structure.dim_count());
assert_eq!(4, hypercube_structure.dim_length(0));
let galois_group = CyclotomicGaloisGroupBase::new(32).into().full_subgroup();
let hypercube_structure = HypercubeStructure::default_pow2_hypercube(&galois_group, int_cast(17, ZZbig, ZZi64));
assert_eq!(2, hypercube_structure.d());
assert_eq!(2, hypercube_structure.dim_count());
assert_eq!(4, hypercube_structure.dim_length(0));
assert_eq!(2, hypercube_structure.dim_length(1));
}
#[test]
fn test_serialization() {
for hypercube in [
HypercubeStructure::halevi_shoup_hypercube(&CyclotomicGaloisGroupBase::new(11 * 31).into().full_subgroup(), int_cast(2, ZZbig, ZZi64)),
HypercubeStructure::default_pow2_hypercube(&CyclotomicGaloisGroupBase::new(32).into().full_subgroup(), int_cast(7, ZZbig, ZZi64)),
HypercubeStructure::default_pow2_hypercube(&CyclotomicGaloisGroupBase::new(32).into().full_subgroup(), int_cast(17, ZZbig, ZZi64))
] {
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().get_group() == deserialized_hypercube.galois_group().get_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.dim_length(i), deserialized_hypercube.dim_length(i));
assert!(hypercube.galois_group().eq_el(hypercube.dim_generator(i), deserialized_hypercube.dim_generator(i)));
assert_eq!(hypercube.ord_generator(i), deserialized_hypercube.ord_generator(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().get_group() == deserialized_hypercube.galois_group().get_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.dim_length(i), deserialized_hypercube.dim_length(i));
assert!(hypercube.galois_group().eq_el(hypercube.dim_generator(i), deserialized_hypercube.dim_generator(i)));
assert_eq!(hypercube.ord_generator(i), deserialized_hypercube.ord_generator(i));
}
}
}