use crate::cyclotomic::{IsRing, Units};
pub struct ShadowPrune {
pub n: i64,
pub exps: Vec<i64>,
}
impl ShadowPrune {
pub fn for_ring(ring: u8) -> Self {
let n = ring as i64;
let exps = (2..=n / 2).filter(|&g| gcd(g, n) == 1).collect();
ShadowPrune { n, exps }
}
#[inline]
pub fn allows_closure<ZZ: IsRing>(&self, point: &ZZ, remaining: i64) -> bool {
if self.exps.is_empty() {
return true;
}
let nsq = *point * point.conj();
let coeffs = nsq.int_coeffs_slice();
let r_sq = <ZZ as From<i64>>::from(remaining * remaining);
for &g in &self.exps {
let mut s = ZZ::zero();
for (j, &c) in coeffs.iter().enumerate() {
if c != 0 {
let k = (g * j as i64).rem_euclid(self.n) as i8;
s = s + <ZZ as Units>::unit(k).scale(c);
}
}
if (r_sq - s).re_sign() < 0 {
return false;
}
}
true
}
}
fn gcd(mut a: i64, mut b: i64) -> i64 {
while b != 0 {
let t = b;
b = a % b;
a = t;
}
a.abs()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cyclotomic::{ZZ12, ZZ24, ZZ32};
#[test]
fn shadow_exps_are_the_nonphysical_conjugate_reps() {
assert_eq!(ShadowPrune::for_ring(12).exps, vec![5]);
assert_eq!(ShadowPrune::for_ring(24).exps, vec![5, 7, 11]);
assert_eq!(ShadowPrune::for_ring(32).exps, vec![3, 5, 7, 9, 11, 13, 15]);
assert!(ShadowPrune::for_ring(4).exps.is_empty());
assert!(ShadowPrune::for_ring(6).exps.is_empty());
}
fn agrees_with_direct_embedding<ZZ: IsRing>(ring: u8) {
let sp = ShadowPrune::for_ring(ring);
let n = ring as i64;
let dirs: Vec<ZZ> = (0..ring as i8).map(|d| <ZZ as Units>::unit(d)).collect();
let mut pts: Vec<ZZ> = vec![ZZ::zero()];
for &a in &dirs {
for &b in &dirs {
pts.push(a + b);
pts.push(a + b + <ZZ as Units>::unit(0));
}
}
for p in &pts {
let coeffs = p.int_coeffs_slice();
for radius in 0..6i64 {
let mut ok = true;
for &g in &sp.exps {
let (mut re, mut im) = (0.0f64, 0.0f64);
for (j, &c) in coeffs.iter().enumerate() {
let ang =
std::f64::consts::TAU * (g * j as i64).rem_euclid(n) as f64 / n as f64;
re += c as f64 * ang.cos();
im += c as f64 * ang.sin();
}
if (re * re + im * im).sqrt() > radius as f64 + 1e-9 {
ok = false;
}
}
assert_eq!(
sp.allows_closure(p, radius),
ok,
"ZZ{ring}: shadow prune disagrees with direct embedding for \
coeffs={coeffs:?} radius={radius}"
);
}
}
}
#[test]
fn allows_closure_matches_direct_embedding_zz12() {
agrees_with_direct_embedding::<ZZ12>(12);
}
#[test]
fn allows_closure_matches_direct_embedding_zz24() {
agrees_with_direct_embedding::<ZZ24>(24);
}
#[test]
fn allows_closure_matches_direct_embedding_zz32() {
agrees_with_direct_embedding::<ZZ32>(32);
}
}