use std::f64::consts::SQRT_2;
use num_complex::Complex64;
use num_rational::Ratio;
use num_traits::{One, Zero};
use crate::define_integral_zz;
const ZZ16_Y: f64 = 2.0 + SQRT_2;
const ZZ32_Z: f64 = 2.0 + 1.847_759_065_022_573_5;
const SQRT_5: f64 = 2.236_067_977_499_79;
pub(crate) const ZZ10_Y: f64 = 2.0 * (5.0 - SQRT_5);
fn gpair(re: Ratio<i64>, im: Ratio<i64>) -> (Ratio<i64>, Ratio<i64>) {
(re, im)
}
fn format_gauss_pair(re: Ratio<i64>, im: Ratio<i64>) -> String {
let mut terms: Vec<String> = Vec::new();
if !re.is_zero() {
terms.push(format!("{re}"));
}
if !im.is_zero() {
terms.push(if im.is_one() {
"i".to_string()
} else if im == -Ratio::<i64>::one() {
"-i".to_string()
} else {
format!("{im}i")
});
}
if terms.is_empty() {
"0".to_string()
} else if terms.len() == 2 && im < Ratio::<i64>::zero() {
terms.join("")
} else {
terms.join("+")
}
}
macro_rules! impl_cell_floor_via_sign_verify {
($name:ident) => {
impl $crate::cyclotomic::CellFloor for $name {
#[inline]
fn cell_floor_exact(&self) -> (i64, i64) {
use $crate::cyclotomic::SymNum;
use $crate::cyclotomic::geometry::rect_signs;
let c = self.complex64();
let mut cx = c.re.floor() as i64;
let mut cy = c.im.floor() as i64;
loop {
let pos_min = <$name as From<(i64, i64)>>::from((cx, cy));
let pos_max = <$name as From<(i64, i64)>>::from((cx + 1, cy + 1));
let s = rect_signs(self, &pos_min, &pos_max);
let re_below = s[0] < 0;
let re_above = s[2] <= 0;
let im_below = s[1] < 0;
let im_above = s[3] <= 0;
if !re_below && !re_above && !im_below && !im_above {
return (cx, cy);
}
if re_below {
cx -= 1;
} else if re_above {
cx += 1;
}
if im_below {
cy -= 1;
} else if im_above {
cy += 1;
}
}
}
}
};
}
fn format_symbolic<const K: usize>(
coeffs: &[(Ratio<i64>, Ratio<i64>); K],
labels: &[&'static str; K],
f: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result {
let mut parts: Vec<String> = Vec::new();
for ((re, im), lbl) in coeffs.iter().zip(labels.iter()) {
let s = format_gauss_pair(*re, *im);
if s == "0" {
continue;
}
let is_real_unit = *lbl == "1";
let lbl_str = format!("sqrt({lbl})");
if s == "1" {
parts.push(if is_real_unit {
"1".to_string()
} else {
lbl_str
});
} else if is_real_unit {
parts.push(s);
} else {
parts.push(format!("({s})*{lbl_str}"));
}
}
let joined = parts.join(" + ");
if joined.is_empty() {
write!(f, "0")
} else {
write!(f, "{joined}")
}
}
#[inline]
fn zz4_complex64(coeffs: &[i64; 2]) -> Complex64 {
Complex64::new(coeffs[0] as f64, coeffs[1] as f64)
}
const ZZ4_CARTESIAN: [Complex64; 2] = [
Complex64::new(1.0, 0.0), Complex64::new(0.0, 1.0), ];
fn zz4_display(coeffs: &[i64; 2], f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
format_gauss_pair(
Ratio::<i64>::from_integer(coeffs[0]),
Ratio::<i64>::from_integer(coeffs[1]),
)
)
}
#[inline]
fn zz4_real_sign(x: &[i64; 1]) -> i8 {
x[0].signum() as i8
}
define_integral_zz! {
name: ZZ4,
n: 4,
phi: 2,
real_dim: 1,
reduction: [-1i64, 0],
re_decomp: [[1i64], [0]],
im_decomp: [[0i64], [1]],
cartesian: ZZ4_CARTESIAN,
one_in_real_basis: [1i64],
display_fn: zz4_display,
complex64_fn: zz4_complex64,
has: [HasZZ4Impl, IsZZ4Impl],
}
impl From<(i64, i64)> for ZZ4 {
#[inline]
fn from((re, im): (i64, i64)) -> Self {
Self::from_int_coeffs([re, im])
}
}
crate::impl_integral_units_via_basis!(ZZ4, 4);
crate::impl_integral_mul_via_basis!(ZZ4, 2);
crate::impl_integral_conj_via_basis!(ZZ4, 2);
crate::impl_integral_re_im_sign_via_basis!(ZZ4, 2, 1, zz4_real_sign);
crate::impl_integral_intersect_unit_segments_via_basis!(ZZ4, 2, 1, zz4_real_sign);
crate::impl_integral_within_radius_via_norm_sq!(ZZ4);
impl_cell_floor_via_sign_verify!(ZZ4);
crate::zz_integral_ring_tests!(name: ZZ4);
const ZZ6_CARTESIAN: [Complex64; 2] = {
const HALF_SQRT_3: f64 = 0.866_025_403_784_438_6_f64;
[
Complex64::new(1.0, 0.0),
Complex64::new(0.5, HALF_SQRT_3),
]
};
#[inline]
fn zz6_complex64(coeffs: &[i64; 2]) -> Complex64 {
const HALF_SQRT_3: f64 = 0.866_025_403_784_438_6_f64;
let [a, b] = *coeffs;
let (a, b) = (a as f64, b as f64);
let re = a + 0.5 * b;
let im = b * HALF_SQRT_3;
Complex64::new(re, im)
}
fn zz6_display(coeffs: &[i64; 2], f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let [a, b] = *coeffs;
let half = Ratio::<i64>::new_raw(1, 2);
let c0 = gpair(
Ratio::<i64>::from_integer(2 * a + b) * half,
Ratio::<i64>::from_integer(0),
);
let c1 = gpair(
Ratio::<i64>::from_integer(0),
Ratio::<i64>::from_integer(b) * half,
);
format_symbolic(&[c0, c1], &["1", "3"], f)
}
#[inline]
fn zz6_real_sign(x: &[i64; 2]) -> i8 {
sign_m_plus_n_sqrt3(x[0], x[1])
}
define_integral_zz! {
name: ZZ6,
n: 6,
phi: 2,
real_dim: 2,
reduction: [-1i64, 1],
re_decomp: [[2i64, 0], [1, 0]],
im_decomp: [[0i64, 0], [0, 1]],
cartesian: ZZ6_CARTESIAN,
one_in_real_basis: [2i64, 0],
display_fn: zz6_display,
complex64_fn: zz6_complex64,
has: [HasZZ6Impl],
}
crate::impl_integral_units_via_basis!(ZZ6, 6);
crate::impl_integral_mul_via_basis!(ZZ6, 2);
crate::impl_integral_conj_via_basis!(ZZ6, 2);
crate::impl_integral_re_im_sign_via_basis!(ZZ6, 2, 2, zz6_real_sign);
crate::impl_integral_intersect_unit_segments_via_basis!(ZZ6, 2, 2, zz6_real_sign);
crate::impl_integral_within_radius_via_norm_sq!(ZZ6);
impl crate::cyclotomic::CellFloor for ZZ6 {
#[inline]
fn cell_floor_exact(&self) -> (i64, i64) {
use crate::cyclotomic::SymNum;
let [a, b] = self.int_coeffs();
let cx = a + b.div_euclid(2);
let cf = self.complex64();
let mut cy = cf.im.floor() as i64;
while sign_m_plus_n_sqrt3(-2 * cy, b) < 0 {
cy -= 1;
}
while sign_m_plus_n_sqrt3(-2 * (cy + 1), b) >= 0 {
cy += 1;
}
(cx, cy)
}
}
crate::zz_integral_ring_tests!(name: ZZ6);
const HALF_SQRT_2: f64 = std::f64::consts::SQRT_2 * 0.5;
#[inline]
fn zz8_complex64(coeffs: &[i64; 4]) -> Complex64 {
let [a, b, c, d] = *coeffs;
let (a, b, c, d) = (a as f64, b as f64, c as f64, d as f64);
let re = a + (b - d) * HALF_SQRT_2;
let im = (b + d) * HALF_SQRT_2 + c;
Complex64::new(re, im)
}
const ZZ8_CARTESIAN: [Complex64; 4] = [
Complex64::new(1.0, 0.0),
Complex64::new(HALF_SQRT_2, HALF_SQRT_2),
Complex64::new(0.0, 1.0),
Complex64::new(-HALF_SQRT_2, HALF_SQRT_2),
];
fn zz8_display(coeffs: &[i64; 4], f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let [a, b, c, d] = *coeffs;
let half = Ratio::<i64>::new_raw(1, 2);
let c0 = gpair(Ratio::<i64>::from_integer(a), Ratio::<i64>::from_integer(c));
let c1 = gpair(
Ratio::<i64>::from_integer(b - d) * half,
Ratio::<i64>::from_integer(b + d) * half,
);
format_symbolic(&[c0, c1], &["1", "2"], f)
}
#[inline]
fn zz8_real_sign(x: &[i64; 2]) -> i8 {
crate::cyclotomic::sign::signum_sum_sqrt_expr_2::<i128>(x[0] as i128, 1, x[1] as i128, 2) as i8
}
define_integral_zz! {
name: ZZ8,
n: 8,
phi: 4,
real_dim: 2,
reduction: [-1i64, 0, 0, 0],
re_decomp: [[2i64, 0], [0, 1], [0, 0], [0, -1]],
im_decomp: [[0i64, 0], [0, 1], [2, 0], [0, 1]],
cartesian: ZZ8_CARTESIAN,
one_in_real_basis: [2i64, 0],
display_fn: zz8_display,
complex64_fn: zz8_complex64,
has: [HasZZ4Impl, HasZZ8Impl],
}
impl From<(i64, i64)> for ZZ8 {
#[inline]
fn from((re, im): (i64, i64)) -> Self {
Self::from_int_coeffs([re, 0, im, 0])
}
}
crate::impl_integral_units_via_basis!(ZZ8, 8);
crate::impl_integral_mul_via_basis!(ZZ8, 4);
crate::impl_integral_conj_via_basis!(ZZ8, 4);
crate::impl_integral_re_im_sign_via_basis!(ZZ8, 4, 2, zz8_real_sign);
crate::impl_integral_intersect_unit_segments_via_basis!(ZZ8, 4, 2, zz8_real_sign);
crate::impl_integral_within_radius_via_norm_sq!(ZZ8);
impl_cell_floor_via_sign_verify!(ZZ8);
crate::zz_integral_ring_tests!(name: ZZ8);
#[inline]
fn zz12_complex64(coeffs: &[i64; 4]) -> Complex64 {
const HALF_SQRT_3: f64 = 0.866_025_403_784_438_6_f64;
let [a, b, c, d] = *coeffs;
let (a, b, c, d) = (a as f64, b as f64, c as f64, d as f64);
let re = a + b * HALF_SQRT_3 + 0.5 * c;
let im = 0.5 * b + c * HALF_SQRT_3 + d;
Complex64::new(re, im)
}
const ZZ12_CARTESIAN: [Complex64; 4] = {
const HALF_SQRT_3: f64 = 0.866_025_403_784_438_6_f64;
[
Complex64::new(1.0, 0.0),
Complex64::new(HALF_SQRT_3, 0.5),
Complex64::new(0.5, HALF_SQRT_3),
Complex64::new(0.0, 1.0),
]
};
fn zz12_display(coeffs: &[i64; 4], f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let [a, b, c, d] = *coeffs;
let half = Ratio::<i64>::new_raw(1, 2);
let c0 = gpair(
Ratio::<i64>::from_integer(2 * a + c) * half,
Ratio::<i64>::from_integer(b + 2 * d) * half,
);
let c1 = gpair(
Ratio::<i64>::from_integer(b) * half,
Ratio::<i64>::from_integer(c) * half,
);
format_symbolic(&[c0, c1], &["1", "3"], f)
}
define_integral_zz! {
name: ZZ12,
n: 12,
phi: 4,
real_dim: 2,
reduction: [-1i64, 0, 1, 0],
re_decomp: [[2i64, 0], [0, 1], [1, 0], [0, 0]],
im_decomp: [[0i64, 0], [1, 0], [0, 1], [2, 0]],
cartesian: ZZ12_CARTESIAN,
one_in_real_basis: [2i64, 0],
display_fn: zz12_display,
complex64_fn: zz12_complex64,
has: [HasZZ4Impl, HasZZ6Impl, HasZZ12Impl],
}
impl From<(i64, i64)> for ZZ12 {
#[inline]
fn from((re, im): (i64, i64)) -> Self {
Self::from_int_coeffs([re, 0, 0, im])
}
}
impl std::ops::Mul<ZZ12> for ZZ12 {
type Output = Self;
#[inline]
fn mul(self, other: Self) -> Self {
let [a, b, c, d] = self.int_coeffs();
let [e, f, g, h] = other.int_coeffs();
let r0 = a * e - b * h - c * g - d * f - d * h;
let r1 = a * f + b * e - c * h - d * g;
let r2 = a * g + b * f + c * e + b * h + c * g + d * f;
let r3 = a * h + b * g + c * f + d * e + c * h + d * g;
Self::from_int_coeffs([r0, r1, r2, r3])
}
}
impl crate::cyclotomic::Conj for ZZ12 {
#[inline]
fn conj(&self) -> Self {
let [a, b, c, d] = self.int_coeffs();
Self::from_int_coeffs([a + c, b, -c, -b - d])
}
}
impl crate::cyclotomic::Units for ZZ12 {
#[inline]
fn unit(angle: i8) -> Self {
static UNIT_TABLE: [[i64; 4]; 12] = [
[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1], [-1, 0, 1, 0], [0, -1, 0, 1], [-1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, -1], [1, 0, -1, 0], [0, 1, 0, -1], ];
let idx = angle.rem_euclid(12) as usize;
Self::from_int_coeffs(UNIT_TABLE[idx])
}
}
#[inline]
fn sign_m_plus_n_sqrt3(m: i64, n: i64) -> i8 {
if m == 0 && n == 0 {
return 0;
}
if m >= 0 && n >= 0 {
return 1;
}
if m <= 0 && n <= 0 {
return -1;
}
let m_sq = m
.checked_mul(m)
.expect("ZZ12 sign_m_plus_n_sqrt3 overflow: m*m");
let n_sq = n
.checked_mul(n)
.expect("ZZ12 sign_m_plus_n_sqrt3 overflow: n*n");
let three_n_sq = 3i64
.checked_mul(n_sq)
.expect("ZZ12 sign_m_plus_n_sqrt3 overflow: 3*n*n");
if m > 0 {
match m_sq.cmp(&three_n_sq) {
std::cmp::Ordering::Less => -1,
std::cmp::Ordering::Equal => 0,
std::cmp::Ordering::Greater => 1,
}
} else {
match three_n_sq.cmp(&m_sq) {
std::cmp::Ordering::Less => -1,
std::cmp::Ordering::Equal => 0,
std::cmp::Ordering::Greater => 1,
}
}
}
impl crate::cyclotomic::ReImSign for ZZ12 {
#[inline]
fn re_sign(&self) -> i8 {
let [a, b, c, _] = self.int_coeffs();
sign_m_plus_n_sqrt3(2 * a + c, b)
}
#[inline]
fn im_sign(&self) -> i8 {
let [_, b, c, d] = self.int_coeffs();
sign_m_plus_n_sqrt3(b + 2 * d, c)
}
}
impl crate::cyclotomic::WithinRadius for ZZ12 {
#[inline]
fn within_radius(&self, radius: i64) -> bool {
let [a, b, c, d] = self.int_coeffs();
let s = 2 * a + c;
let t = b + 2 * d;
let m = s * s + 3 * b * b + t * t + 3 * c * c;
let n = 2 * (s * b + t * c);
let four_r_sq = 4 * radius * radius;
sign_m_plus_n_sqrt3(m - four_r_sq, n) <= 0
}
}
fn zz12_real_sign(x: &[i64; 2]) -> i8 {
sign_m_plus_n_sqrt3(x[0], x[1])
}
crate::impl_integral_intersect_unit_segments_via_basis!(ZZ12, 4, 2, zz12_real_sign);
impl crate::cyclotomic::CellFloor for ZZ12 {
#[inline]
fn cell_floor_exact(&self) -> (i64, i64) {
use crate::cyclotomic::SymNum;
let [a, b, c, d] = self.int_coeffs();
let re_m = 2 * a + c;
let re_n = b;
let im_m = b + 2 * d;
let im_n = c;
let cf = self.complex64();
let mut cx = cf.re.floor() as i64;
let mut cy = cf.im.floor() as i64;
while sign_m_plus_n_sqrt3(re_m - 2 * cx, re_n) < 0 {
cx -= 1;
}
while sign_m_plus_n_sqrt3(re_m - 2 * (cx + 1), re_n) >= 0 {
cx += 1;
}
while sign_m_plus_n_sqrt3(im_m - 2 * cy, im_n) < 0 {
cy -= 1;
}
while sign_m_plus_n_sqrt3(im_m - 2 * (cy + 1), im_n) >= 0 {
cy += 1;
}
(cx, cy)
}
}
crate::zz_integral_ring_tests!(name: ZZ12);
const ZZ14_CARTESIAN: [Complex64; 6] = {
[
Complex64::new(1.0, 0.0), Complex64::new(0.9009688679024191, 0.4338837391175581), Complex64::new(0.6234898018587336, 0.7818314824680298), Complex64::new(0.22252093395631434, 0.9749279121818236), Complex64::new(-0.22252093395631434, 0.9749279121818236), Complex64::new(-0.6234898018587336, 0.7818314824680298), ]
};
#[inline]
fn zz14_complex64(coeffs: &[i64; 6]) -> Complex64 {
let mut re = 0.0_f64;
let mut im = 0.0_f64;
for (k, &ck) in coeffs.iter().enumerate() {
let ck = ck as f64;
re += ck * ZZ14_CARTESIAN[k].re;
im += ck * ZZ14_CARTESIAN[k].im;
}
Complex64::new(re, im)
}
fn zz14_display(coeffs: &[i64; 6], f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let [c0, c1, c2, c3, c4, c5] = *coeffs;
let half = Ratio::<i64>::new_raw(1, 2);
let m0 = Ratio::<i64>::from_integer(2 * c0 - 2 * c2 - c3 + c4 + 2 * c5) * half;
let m1 = Ratio::<i64>::from_integer(c1 - c3 + c4) * half;
let m2 = Ratio::<i64>::from_integer(c2 + c3 - c4 - c5) * half;
let n0 = Ratio::<i64>::from_integer(c1 - c3 - c4) * half;
let n1 = Ratio::<i64>::from_integer(c2 + c5) * half;
let n2 = Ratio::<i64>::from_integer(c3 + c4) * half;
let c_pair_1 = gpair(m0, Ratio::<i64>::from_integer(0));
let c_pair_c = gpair(m1, Ratio::<i64>::from_integer(0));
let c_pair_c2 = gpair(m2, Ratio::<i64>::from_integer(0));
let c_pair_s = gpair(Ratio::<i64>::from_integer(0), n0);
let c_pair_cs = gpair(Ratio::<i64>::from_integer(0), n1);
let c_pair_c2s = gpair(Ratio::<i64>::from_integer(0), n2);
format_symbolic(
&[
c_pair_1, c_pair_c, c_pair_c2, c_pair_s, c_pair_cs, c_pair_c2s,
],
&["1", "c7", "c7^2", "s7", "c7*s7", "c7^2*s7"],
f,
)
}
const ZZ14_MINPOLY: [i64; 4] = [1, -2, -1, 1];
const ZZ14_ISO_LO: (i64, i64) = (1, 1);
const ZZ14_ISO_HI: (i64, i64) = (2, 1);
fn zz14_real_sign(x: &[i64; 6]) -> i8 {
let [a, b, d, e, f, g] = *x;
let real_zero = a == 0 && b == 0 && d == 0;
let s_zero = e == 0 && f == 0 && g == 0;
if real_zero && s_zero {
return 0;
}
if s_zero {
return crate::cyclotomic::sign::sign_at_cubic_root_in_interval(
[a, b, d],
ZZ14_MINPOLY,
ZZ14_ISO_LO,
ZZ14_ISO_HI,
);
}
if real_zero {
return crate::cyclotomic::sign::sign_at_cubic_root_in_interval(
[e, f, g],
ZZ14_MINPOLY,
ZZ14_ISO_LO,
ZZ14_ISO_HI,
);
}
panic!(
"zz14_real_sign called with mixed real+imaginary K-vector: {:?} -- \
the DFS shouldn't produce this; please investigate the caller",
x
);
}
define_integral_zz! {
name: ZZ14,
n: 14,
phi: 6,
real_dim: 6,
reduction: [-1i64, 1, -1, 1, -1, 1],
re_decomp: [
[2i64, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0],
[-2, 0, 1, 0, 0, 0],
[-1, -1, 1, 0, 0, 0],
[1, 1, -1, 0, 0, 0],
[2, 0, -1, 0, 0, 0],
],
im_decomp: [
[0i64, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 1, 0],
[0, 0, 0, -1, 0, 1],
[0, 0, 0, -1, 0, 1],
[0, 0, 0, 0, 1, 0],
],
cartesian: ZZ14_CARTESIAN,
one_in_real_basis: [2i64, 0, 0, 0, 0, 0],
display_fn: zz14_display,
complex64_fn: zz14_complex64,
has: [],
}
crate::impl_integral_units_via_basis!(ZZ14, 14);
crate::impl_integral_mul_via_basis!(ZZ14, 6);
crate::impl_integral_conj_via_basis!(ZZ14, 6);
crate::impl_integral_re_im_sign_via_basis!(ZZ14, 6, 6, zz14_real_sign);
crate::impl_integral_intersect_unit_segments_via_basis!(ZZ14, 6, 6, zz14_real_sign);
crate::impl_integral_within_radius_via_norm_sq!(ZZ14);
impl crate::cyclotomic::CellFloor for ZZ14 {
#[inline]
fn cell_floor_exact(&self) -> (i64, i64) {
use crate::cyclotomic::SymNum;
use crate::cyclotomic::sign::{sign_at_cubic_root_in_interval, sign_at_s_times_x_minus_k};
let [c0, c1, c2, c3, c4, c5] = self.int_coeffs();
let m0 = 2 * c0 - 2 * c2 - c3 + c4 + 2 * c5;
let m1 = c1 - c3 + c4;
let m2 = c2 + c3 - c4 - c5;
let n0 = c1 - c3 - c4;
let n1 = c2 + c5;
let n2 = c3 + c4;
let cf = self.complex64();
let mut cx = cf.re.floor() as i64;
let mut cy = cf.im.floor() as i64;
while sign_at_cubic_root_in_interval(
[m0 - 2 * cx, m1, m2],
ZZ14_MINPOLY,
ZZ14_ISO_LO,
ZZ14_ISO_HI,
) < 0
{
cx -= 1;
}
while sign_at_cubic_root_in_interval(
[m0 - 2 * (cx + 1), m1, m2],
ZZ14_MINPOLY,
ZZ14_ISO_LO,
ZZ14_ISO_HI,
) >= 0
{
cx += 1;
}
while sign_at_s_times_x_minus_k(
[n0, n1, n2],
2 * cy,
ZZ14_MINPOLY,
ZZ14_ISO_LO,
ZZ14_ISO_HI,
) < 0
{
cy -= 1;
}
while sign_at_s_times_x_minus_k(
[n0, n1, n2],
2 * (cy + 1),
ZZ14_MINPOLY,
ZZ14_ISO_LO,
ZZ14_ISO_HI,
) >= 0
{
cy += 1;
}
(cx, cy)
}
#[inline]
fn cell_floor(&self) -> (i64, i64) {
self.cell_floor_exact()
}
}
crate::zz_integral_ring_tests!(name: ZZ14);
const ZZ18_CARTESIAN: [Complex64; 6] = {
const HALF_SQRT_3: f64 = 0.866_025_403_784_438_6_f64;
[
Complex64::new(1.0, 0.0), Complex64::new(0.9396926207859084, 0.3420201433256687), Complex64::new(0.766044443118978, 0.6427876096865393), Complex64::new(0.5, HALF_SQRT_3), Complex64::new(0.17364817766693041, 0.984807753012208), Complex64::new(-0.17364817766693036, 0.984807753012208), ]
};
#[inline]
fn zz18_complex64(coeffs: &[i64; 6]) -> Complex64 {
let mut re = 0.0_f64;
let mut im = 0.0_f64;
for (k, &ck) in coeffs.iter().enumerate() {
let ck = ck as f64;
re += ck * ZZ18_CARTESIAN[k].re;
im += ck * ZZ18_CARTESIAN[k].im;
}
Complex64::new(re, im)
}
fn zz18_display(coeffs: &[i64; 6], f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let [c0, c1, c2, c3, c4, c5] = *coeffs;
let half = Ratio::<i64>::new_raw(1, 2);
let m0 = Ratio::<i64>::from_integer(2 * c0 - 2 * c2 + c3 + 2 * c4 - 2 * c5) * half;
let m1 = Ratio::<i64>::from_integer(c1 + c4 - c5) * half;
let m2 = Ratio::<i64>::from_integer(c2 - c4 + c5) * half;
let n0 = Ratio::<i64>::from_integer(c1 - c3 + c4 + c5) * half;
let n1 = Ratio::<i64>::from_integer(c2 + c4 + c5) * half;
let n2 = Ratio::<i64>::from_integer(c3) * half;
let c_pair_1 = gpair(m0, Ratio::<i64>::from_integer(0));
let c_pair_c = gpair(m1, Ratio::<i64>::from_integer(0));
let c_pair_c2 = gpair(m2, Ratio::<i64>::from_integer(0));
let c_pair_s = gpair(Ratio::<i64>::from_integer(0), n0);
let c_pair_cs = gpair(Ratio::<i64>::from_integer(0), n1);
let c_pair_c2s = gpair(Ratio::<i64>::from_integer(0), n2);
format_symbolic(
&[
c_pair_1, c_pair_c, c_pair_c2, c_pair_s, c_pair_cs, c_pair_c2s,
],
&["1", "c9", "c9^2", "s9", "c9*s9", "c9^2*s9"],
f,
)
}
const ZZ18_MINPOLY: [i64; 4] = [-1, -3, 0, 1];
const ZZ18_ISO_LO: (i64, i64) = (1, 1);
const ZZ18_ISO_HI: (i64, i64) = (2, 1);
fn zz18_real_sign(x: &[i64; 6]) -> i8 {
let [a, b, d, e, f, g] = *x;
let real_zero = a == 0 && b == 0 && d == 0;
let s_zero = e == 0 && f == 0 && g == 0;
if real_zero && s_zero {
return 0;
}
if s_zero {
return crate::cyclotomic::sign::sign_at_cubic_root_in_interval(
[a, b, d],
ZZ18_MINPOLY,
ZZ18_ISO_LO,
ZZ18_ISO_HI,
);
}
if real_zero {
return crate::cyclotomic::sign::sign_at_cubic_root_in_interval(
[e, f, g],
ZZ18_MINPOLY,
ZZ18_ISO_LO,
ZZ18_ISO_HI,
);
}
panic!(
"zz18_real_sign called with mixed real+imaginary K-vector: {:?} -- \
the DFS shouldn't produce this; please investigate the caller",
x
);
}
define_integral_zz! {
name: ZZ18,
n: 18,
phi: 6,
real_dim: 6,
reduction: [-1i64, 0, 0, 1, 0, 0],
re_decomp: [
[2i64, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0],
[-2, 0, 1, 0, 0, 0],
[1, 0, 0, 0, 0, 0],
[2, 1, -1, 0, 0, 0],
[-2, -1, 1, 0, 0, 0],
],
im_decomp: [
[0i64, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 1, 0],
[0, 0, 0, -1, 0, 1],
[0, 0, 0, 1, 1, 0],
[0, 0, 0, 1, 1, 0],
],
cartesian: ZZ18_CARTESIAN,
one_in_real_basis: [2i64, 0, 0, 0, 0, 0],
display_fn: zz18_display,
complex64_fn: zz18_complex64,
has: [HasZZ6Impl],
}
crate::impl_integral_units_via_basis!(ZZ18, 18);
crate::impl_integral_mul_via_basis!(ZZ18, 6);
crate::impl_integral_conj_via_basis!(ZZ18, 6);
crate::impl_integral_re_im_sign_via_basis!(ZZ18, 6, 6, zz18_real_sign);
crate::impl_integral_intersect_unit_segments_via_basis!(ZZ18, 6, 6, zz18_real_sign);
crate::impl_integral_within_radius_via_norm_sq!(ZZ18);
impl crate::cyclotomic::CellFloor for ZZ18 {
#[inline]
fn cell_floor_exact(&self) -> (i64, i64) {
use crate::cyclotomic::SymNum;
use crate::cyclotomic::sign::{sign_at_cubic_root_in_interval, sign_at_s_times_x_minus_k};
let [c0, c1, c2, c3, c4, c5] = self.int_coeffs();
let m0 = 2 * c0 - 2 * c2 + c3 + 2 * c4 - 2 * c5;
let m1 = c1 + c4 - c5;
let m2 = c2 - c4 + c5;
let n0 = c1 - c3 + c4 + c5;
let n1 = c2 + c4 + c5;
let n2 = c3;
let cf = self.complex64();
let mut cx = cf.re.floor() as i64;
let mut cy = cf.im.floor() as i64;
while sign_at_cubic_root_in_interval(
[m0 - 2 * cx, m1, m2],
ZZ18_MINPOLY,
ZZ18_ISO_LO,
ZZ18_ISO_HI,
) < 0
{
cx -= 1;
}
while sign_at_cubic_root_in_interval(
[m0 - 2 * (cx + 1), m1, m2],
ZZ18_MINPOLY,
ZZ18_ISO_LO,
ZZ18_ISO_HI,
) >= 0
{
cx += 1;
}
while sign_at_s_times_x_minus_k(
[n0, n1, n2],
2 * cy,
ZZ18_MINPOLY,
ZZ18_ISO_LO,
ZZ18_ISO_HI,
) < 0
{
cy -= 1;
}
while sign_at_s_times_x_minus_k(
[n0, n1, n2],
2 * (cy + 1),
ZZ18_MINPOLY,
ZZ18_ISO_LO,
ZZ18_ISO_HI,
) >= 0
{
cy += 1;
}
(cx, cy)
}
#[inline]
fn cell_floor(&self) -> (i64, i64) {
self.cell_floor_exact()
}
}
crate::zz_integral_ring_tests!(name: ZZ18);
#[inline]
fn zz24_complex64(coeffs: &[i64; 8]) -> Complex64 {
let [a, b, c, d, e, f_, g, h] = *coeffs;
let (a, b, c, d, e, f_, g, h) = (
a as f64, b as f64, c as f64, d as f64, e as f64, f_ as f64, g as f64, h as f64,
);
let sqrt2 = std::f64::consts::SQRT_2;
let sqrt3 = 3.0_f64.sqrt();
let sqrt6 = 6.0_f64.sqrt();
let r0 = 4.0 * a + 2.0 * e;
let r1 = b + 2.0 * d - f_ + h;
let r2 = 2.0 * c;
let r3 = b + f_ - h;
let re = (r0 + r1 * sqrt2 + r2 * sqrt3 + r3 * sqrt6) * 0.25;
let i0 = 2.0 * c + 4.0 * g;
let i1 = -b + 2.0 * d + f_ + h;
let i2 = 2.0 * e;
let i3 = b + f_ + h;
let im = (i0 + i1 * sqrt2 + i2 * sqrt3 + i3 * sqrt6) * 0.25;
Complex64::new(re, im)
}
const ZZ24_CARTESIAN: [Complex64; 8] = {
const COS_15: f64 = 0.9659258262890683;
const SIN_15: f64 = 0.25881904510252074;
const HALF_SQRT_3: f64 = 0.8660254037844386;
const HALF_SQRT_2: f64 = std::f64::consts::SQRT_2 * 0.5;
[
Complex64::new(1.0, 0.0),
Complex64::new(COS_15, SIN_15),
Complex64::new(HALF_SQRT_3, 0.5),
Complex64::new(HALF_SQRT_2, HALF_SQRT_2),
Complex64::new(0.5, HALF_SQRT_3),
Complex64::new(SIN_15, COS_15),
Complex64::new(0.0, 1.0),
Complex64::new(-SIN_15, COS_15),
]
};
fn zz24_display(coeffs: &[i64; 8], f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let [a, b, c, d, e, f_, g, h] = *coeffs;
let quarter = Ratio::<i64>::new_raw(1, 4);
let c0 = gpair(
Ratio::<i64>::from_integer(4 * a + 2 * e) * quarter,
Ratio::<i64>::from_integer(2 * c + 4 * g) * quarter,
);
let c1 = gpair(
Ratio::<i64>::from_integer(b + 2 * d - f_ + h) * quarter,
Ratio::<i64>::from_integer(-b + 2 * d + f_ + h) * quarter,
);
let c2 = gpair(
Ratio::<i64>::from_integer(2 * c) * quarter,
Ratio::<i64>::from_integer(2 * e) * quarter,
);
let c3 = gpair(
Ratio::<i64>::from_integer(b + f_ - h) * quarter,
Ratio::<i64>::from_integer(b + f_ + h) * quarter,
);
format_symbolic(&[c0, c1, c2, c3], &["1", "2", "3", "6"], f)
}
#[inline]
fn zz24_real_sign(x: &[i64; 4]) -> i8 {
crate::cyclotomic::sign::signum_sum_sqrt_expr_4::<i128>(
x[0] as i128,
1,
x[1] as i128,
2,
x[2] as i128,
3,
x[3] as i128,
6,
) as i8
}
define_integral_zz! {
name: ZZ24,
n: 24,
phi: 8,
real_dim: 4,
reduction: [-1i64, 0, 0, 0, 1, 0, 0, 0],
re_decomp: [
[4i64, 0, 0, 0], [0, 1, 0, 1], [0, 0, 2, 0], [0, 2, 0, 0],
[2, 0, 0, 0], [0, -1, 0, 1], [0, 0, 0, 0], [0, 1, 0, -1],
],
im_decomp: [
[0i64, 0, 0, 0], [0, -1, 0, 1], [2, 0, 0, 0], [0, 2, 0, 0],
[0, 0, 2, 0], [0, 1, 0, 1], [4, 0, 0, 0], [0, 1, 0, 1],
],
cartesian: ZZ24_CARTESIAN,
one_in_real_basis: [4i64, 0, 0, 0],
display_fn: zz24_display,
complex64_fn: zz24_complex64,
has: [HasZZ4Impl, HasZZ6Impl, HasZZ8Impl, HasZZ12Impl],
}
impl From<(i64, i64)> for ZZ24 {
#[inline]
fn from((re, im): (i64, i64)) -> Self {
Self::from_int_coeffs([re, 0, 0, 0, 0, 0, im, 0])
}
}
crate::impl_integral_units_via_basis!(ZZ24, 24);
crate::impl_integral_mul_via_basis!(ZZ24, 8);
crate::impl_integral_conj_via_basis!(ZZ24, 8);
crate::impl_integral_re_im_sign_via_basis!(ZZ24, 8, 4, zz24_real_sign);
crate::impl_integral_intersect_unit_segments_via_basis!(ZZ24, 8, 4, zz24_real_sign);
crate::impl_integral_within_radius_via_norm_sq!(ZZ24);
impl_cell_floor_via_sign_verify!(ZZ24);
crate::zz_integral_ring_tests!(name: ZZ24);
#[inline]
fn zz20_complex64(coeffs: &[i64; 8]) -> Complex64 {
let [a, b, c, d, e, f_, g, h] = *coeffs;
let (a, b, c, d, e, f_, g, h) = (
a as f64, b as f64, c as f64, d as f64, e as f64, f_ as f64, g as f64, h as f64,
);
let sq5 = 5.0_f64.sqrt();
let sq_y = ZZ10_Y.sqrt();
let sq_5y = (5.0 * ZZ10_Y).sqrt();
let r0 = 8.0 * a + 2.0 * c - 2.0 * e + 2.0 * g;
let r1 = 2.0 * c + 2.0 * e - 2.0 * g;
let r2 = b + 2.0 * d - 2.0 * h;
let r3 = b;
let re = (r0 + r1 * sq5 + r2 * sq_y + r3 * sq_5y) * 0.125;
let i0 = -2.0 * b + 2.0 * d + 8.0 * f_ + 2.0 * h;
let i1 = 2.0 * b + 2.0 * d + 2.0 * h;
let i2 = 2.0 * c + e + g;
let i3 = e + g;
let im = (i0 + i1 * sq5 + i2 * sq_y + i3 * sq_5y) * 0.125;
Complex64::new(re, im)
}
const ZZ20_CARTESIAN: [Complex64; 8] = {
const COS_18: f64 = 0.9510565162951535;
const SIN_18: f64 = 0.30901699437494745;
const COS_36: f64 = 0.8090169943749475;
const SIN_36: f64 = 0.5877852522924731;
const COS_54: f64 = 0.5877852522924731;
const SIN_54: f64 = 0.8090169943749475;
const COS_72: f64 = 0.30901699437494745;
const SIN_72: f64 = 0.9510565162951535;
[
Complex64::new(1.0, 0.0),
Complex64::new(COS_18, SIN_18),
Complex64::new(COS_36, SIN_36),
Complex64::new(COS_54, SIN_54),
Complex64::new(COS_72, SIN_72),
Complex64::new(0.0, 1.0),
Complex64::new(-COS_72, SIN_72),
Complex64::new(-COS_54, SIN_54),
]
};
fn zz20_display(coeffs: &[i64; 8], f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let [a, b, c, d, e, f_, g, h] = *coeffs;
let eighth = Ratio::<i64>::new_raw(1, 8);
let c0 = gpair(
Ratio::<i64>::from_integer(8 * a + 2 * c - 2 * e + 2 * g) * eighth,
Ratio::<i64>::from_integer(-2 * b + 2 * d + 8 * f_ + 2 * h) * eighth,
);
let c1 = gpair(
Ratio::<i64>::from_integer(2 * c + 2 * e - 2 * g) * eighth,
Ratio::<i64>::from_integer(2 * b + 2 * d + 2 * h) * eighth,
);
let c2 = gpair(
Ratio::<i64>::from_integer(b + 2 * d - 2 * h) * eighth,
Ratio::<i64>::from_integer(2 * c + e + g) * eighth,
);
let c3 = gpair(
Ratio::<i64>::from_integer(b) * eighth,
Ratio::<i64>::from_integer(e + g) * eighth,
);
format_symbolic(
&[c0, c1, c2, c3],
&["1", "5", "2(5-sqrt(5))", "10(5-sqrt(5))"],
f,
)
}
#[inline]
fn zz20_real_sign(x: &[i64; 4]) -> i8 {
crate::cyclotomic::sign::signum_sum_sqrt_expr_4_pentagonal::<i128>(
x[0] as i128,
x[1] as i128,
x[2] as i128,
x[3] as i128,
) as i8
}
define_integral_zz! {
name: ZZ20,
n: 20,
phi: 8,
real_dim: 4,
reduction: [-1i64, 0, 1, 0, -1, 0, 1, 0],
re_decomp: [
[8i64, 0, 0, 0], [0, 0, 1, 1], [2, 2, 0, 0], [0, 0, 2, 0],
[-2, 2, 0, 0], [0, 0, 0, 0], [2, -2, 0, 0], [0, 0, -2, 0],
],
im_decomp: [
[0i64, 0, 0, 0], [-2, 2, 0, 0], [0, 0, 2, 0], [2, 2, 0, 0],
[0, 0, 1, 1], [8, 0, 0, 0], [0, 0, 1, 1], [2, 2, 0, 0],
],
cartesian: ZZ20_CARTESIAN,
one_in_real_basis: [8i64, 0, 0, 0],
display_fn: zz20_display,
complex64_fn: zz20_complex64,
has: [HasZZ4Impl, HasZZ10Impl],
}
impl From<(i64, i64)> for ZZ20 {
#[inline]
fn from((re, im): (i64, i64)) -> Self {
Self::from_int_coeffs([re, 0, 0, 0, 0, im, 0, 0])
}
}
crate::impl_integral_units_via_basis!(ZZ20, 20);
crate::impl_integral_mul_via_basis!(ZZ20, 8);
crate::impl_integral_conj_via_basis!(ZZ20, 8);
crate::impl_integral_re_im_sign_via_basis!(ZZ20, 8, 4, zz20_real_sign);
crate::impl_integral_intersect_unit_segments_via_basis!(ZZ20, 8, 4, zz20_real_sign);
crate::impl_integral_within_radius_via_norm_sq!(ZZ20);
impl_cell_floor_via_sign_verify!(ZZ20);
crate::zz_integral_ring_tests!(name: ZZ20);
#[inline]
fn zz10_complex64(coeffs: &[i64; 4]) -> Complex64 {
let [a, b, c, d] = *coeffs;
let (a, b, c, d) = (a as f64, b as f64, c as f64, d as f64);
let sq5 = 5.0_f64.sqrt();
let sq_y = ZZ10_Y.sqrt();
let sq_5y = (5.0 * ZZ10_Y).sqrt();
let r0 = 8.0 * a + 2.0 * b - 2.0 * c + 2.0 * d;
let r1 = 2.0 * b + 2.0 * c - 2.0 * d;
let re = (r0 + r1 * sq5) * 0.125;
let i2 = 2.0 * b + c + d;
let i3 = c + d;
let im = (i2 * sq_y + i3 * sq_5y) * 0.125;
Complex64::new(re, im)
}
const ZZ10_CARTESIAN: [Complex64; 4] = {
const COS_36: f64 = 0.8090169943749475;
const SIN_36: f64 = 0.5877852522924731;
const COS_72: f64 = 0.30901699437494745;
const SIN_72: f64 = 0.9510565162951535;
[
Complex64::new(1.0, 0.0),
Complex64::new(COS_36, SIN_36),
Complex64::new(COS_72, SIN_72),
Complex64::new(-COS_72, SIN_72),
]
};
fn zz10_display(coeffs: &[i64; 4], f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let [a, b, c, d] = *coeffs;
let eighth = Ratio::<i64>::new_raw(1, 8);
let c0 = gpair(
Ratio::<i64>::from_integer(8 * a + 2 * b - 2 * c + 2 * d) * eighth,
Ratio::<i64>::from_integer(0) * eighth,
);
let c1 = gpair(
Ratio::<i64>::from_integer(2 * b + 2 * c - 2 * d) * eighth,
Ratio::<i64>::from_integer(0) * eighth,
);
let c2 = gpair(
Ratio::<i64>::from_integer(0) * eighth,
Ratio::<i64>::from_integer(2 * b + c + d) * eighth,
);
let c3 = gpair(
Ratio::<i64>::from_integer(0) * eighth,
Ratio::<i64>::from_integer(c + d) * eighth,
);
format_symbolic(
&[c0, c1, c2, c3],
&["1", "5", "2(5-sqrt(5))", "10(5-sqrt(5))"],
f,
)
}
define_integral_zz! {
name: ZZ10,
n: 10,
phi: 4,
real_dim: 4,
reduction: [-1i64, 1, -1, 1],
re_decomp: [
[8i64, 0, 0, 0], [2, 2, 0, 0], [-2, 2, 0, 0], [2, -2, 0, 0],
],
im_decomp: [
[0i64, 0, 0, 0], [0, 0, 2, 0], [0, 0, 1, 1], [0, 0, 1, 1],
],
cartesian: ZZ10_CARTESIAN,
one_in_real_basis: [8i64, 0, 0, 0],
display_fn: zz10_display,
complex64_fn: zz10_complex64,
has: [HasZZ10Impl],
}
#[inline]
fn sign_m_plus_n_sqrt5(m: i64, n: i64) -> i8 {
if m == 0 && n == 0 {
return 0;
}
if m >= 0 && n >= 0 {
return 1;
}
if m <= 0 && n <= 0 {
return -1;
}
let m_sq = (m as i128) * (m as i128);
let n_sq = (n as i128) * (n as i128);
let five_n_sq = 5i128 * n_sq;
if m > 0 {
match m_sq.cmp(&five_n_sq) {
std::cmp::Ordering::Less => -1,
std::cmp::Ordering::Equal => 0,
std::cmp::Ordering::Greater => 1,
}
} else {
match five_n_sq.cmp(&m_sq) {
std::cmp::Ordering::Less => -1,
std::cmp::Ordering::Equal => 0,
std::cmp::Ordering::Greater => 1,
}
}
}
#[inline]
fn sign_p_b2_plus_q_b3(p: i64, q: i64) -> i8 {
if p == 0 && q == 0 {
return 0;
}
if p >= 0 && q >= 0 {
return 1;
}
if p <= 0 && q <= 0 {
return -1;
}
let p_sq = (p as i128) * (p as i128);
let q_sq = (q as i128) * (q as i128);
if p_sq <= q_sq {
return if p > 0 { -1 } else { 1 };
}
let poly = p_sq * p_sq - 3 * p_sq * q_sq + q_sq * q_sq;
if poly > 0 {
if p > 0 { 1 } else { -1 }
} else {
if p > 0 { -1 } else { 1 }
}
}
#[inline]
fn sign_p_b2_plus_q_b3_minus_k(p: i64, q: i64, big_k: i64) -> i8 {
let sign_b = sign_p_b2_plus_q_b3(p, q);
let sign_a = big_k.signum() as i8;
if sign_a == 0 {
return sign_b;
}
if sign_b == 0 {
return -sign_a;
}
if sign_a != sign_b {
return sign_b;
}
let p_sq = p * p;
let q_sq = q * q;
let k_sq = big_k * big_k;
let m = 10 * (p_sq + q_sq) - k_sq;
let n = 2 * (q_sq - p_sq + 4 * p * q);
let sign_diff = sign_m_plus_n_sqrt5(m, n);
if sign_a > 0 { sign_diff } else { -sign_diff }
}
#[inline]
fn re_components_zz10(coeffs: &[i64; 4]) -> (i64, i64) {
let [a, b, c, d] = *coeffs;
(4 * a + b - c + d, b + c - d)
}
#[inline]
fn im_components_zz10(coeffs: &[i64; 4]) -> (i64, i64) {
let [_, b, c, d] = *coeffs;
(b, c + d)
}
impl std::ops::Mul<ZZ10> for ZZ10 {
type Output = Self;
#[inline]
fn mul(self, other: Self) -> Self {
let [a, b, c, d] = self.int_coeffs();
let [e, f, g, h] = other.int_coeffs();
let p4 = b * h + c * g + d * f;
let r0 = a * e - p4 - c * h - d * g;
let r1 = a * f + b * e + p4 - d * h;
let r2 = a * g + b * f + c * e - p4;
let r3 = a * h + b * g + c * f + d * e + p4;
Self::from_int_coeffs([r0, r1, r2, r3])
}
}
impl crate::cyclotomic::Conj for ZZ10 {
#[inline]
fn conj(&self) -> Self {
let [a, b, c, d] = self.int_coeffs();
Self::from_int_coeffs([a + b, -b, b - d, -b - c])
}
}
impl crate::cyclotomic::Units for ZZ10 {
#[inline]
fn unit(angle: i8) -> Self {
static UNIT_TABLE: [[i64; 4]; 10] = [
[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1], [-1, 1, -1, 1], [-1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, -1], [1, -1, 1, -1], ];
let idx = angle.rem_euclid(10) as usize;
Self::from_int_coeffs(UNIT_TABLE[idx])
}
}
impl crate::cyclotomic::WithinRadius for ZZ10 {
#[inline]
fn within_radius(&self, radius: i64) -> bool {
let [a, b, c, d] = self.int_coeffs();
let m_re = 4 * a + b - c + d;
let n_re = b + c - d;
let s = c + d;
let a_part = m_re * m_re + 5 * n_re * n_re + 10 * (b * b + s * s);
let b_part = 2 * m_re * n_re + 2 * (s * s - b * b + 4 * b * s);
let sixteen_r_sq = 16 * radius * radius;
sign_m_plus_n_sqrt5(a_part - sixteen_r_sq, b_part) <= 0
}
}
impl crate::cyclotomic::ReImSign for ZZ10 {
#[inline]
fn re_sign(&self) -> i8 {
let (m, n) = re_components_zz10(&self.int_coeffs());
sign_m_plus_n_sqrt5(m, n)
}
#[inline]
fn im_sign(&self) -> i8 {
let (p, q) = im_components_zz10(&self.int_coeffs());
sign_p_b2_plus_q_b3(p, q)
}
}
fn zz10_real_sign(x: &[i64; 4]) -> i8 {
const P: u32 = 50;
const R5: i128 = 2517588727560788; const R1: i128 = 2647149443198255; const R3: i128 = 5919206101592016; let [a, b, c, d] = [x[0] as i128, x[1] as i128, x[2] as i128, x[3] as i128];
let v = (a << P) + b * R5 + c * R1 + d * R3;
if 2 * v.abs() > b.abs() + c.abs() + d.abs() {
return v.signum() as i8;
}
crate::cyclotomic::sign::signum_sum_sqrt_expr_4_pentagonal::<i128>(a, b, c, d) as i8
}
crate::impl_integral_intersect_unit_segments_via_basis!(ZZ10, 4, 4, zz10_real_sign);
impl crate::cyclotomic::CellFloor for ZZ10 {
#[inline]
fn cell_floor_exact(&self) -> (i64, i64) {
use crate::cyclotomic::SymNum;
let coeffs = self.int_coeffs();
let (re_m, re_n) = re_components_zz10(&coeffs);
let (im_p, im_q) = im_components_zz10(&coeffs);
let cf = self.complex64();
let mut cx = cf.re.floor() as i64;
let mut cy = cf.im.floor() as i64;
while sign_m_plus_n_sqrt5(re_m - 4 * cx, re_n) < 0 {
cx -= 1;
}
while sign_m_plus_n_sqrt5(re_m - 4 * (cx + 1), re_n) >= 0 {
cx += 1;
}
while sign_p_b2_plus_q_b3_minus_k(im_p, im_q, 4 * cy) < 0 {
cy -= 1;
}
while sign_p_b2_plus_q_b3_minus_k(im_p, im_q, 4 * (cy + 1)) >= 0 {
cy += 1;
}
(cx, cy)
}
}
crate::zz_integral_ring_tests!(name: ZZ10);
#[cfg(test)]
mod zz10_real_sign_fast_path {
use super::zz10_real_sign;
use crate::cyclotomic::sign::signum_sum_sqrt_expr_4_pentagonal;
#[test]
fn fast_path_matches_exact_pentagonal_small_grid() {
for a in -6..=6 {
for b in -6..=6 {
for c in -6..=6 {
for d in -6..=6 {
let got = zz10_real_sign(&[a, b, c, d]);
let want = signum_sum_sqrt_expr_4_pentagonal::<i128>(
a as i128, b as i128, c as i128, d as i128,
) as i8;
assert_eq!(
got, want,
"zz10_real_sign disagrees with exact at [{a},{b},{c},{d}]"
);
}
}
}
}
}
}
#[inline]
fn zz16_complex64(coeffs: &[i64; 8]) -> Complex64 {
let [a, b, c, d, e, f_, g, h] = *coeffs;
let (a, b, c, d, e, f_, g, h) = (
a as f64, b as f64, c as f64, d as f64, e as f64, f_ as f64, g as f64, h as f64,
);
let sqrt2 = std::f64::consts::SQRT_2;
let sq_y = ZZ16_Y.sqrt();
let sq_2y = (2.0 * ZZ16_Y).sqrt();
let r0 = 2.0 * a;
let r1 = c - g;
let r2 = b - d + f_ - h;
let r3 = d - f_;
let re = (r0 + r1 * sqrt2 + r2 * sq_y + r3 * sq_2y) * 0.5;
let i0 = 2.0 * e;
let i1 = c + g;
let i2 = -b + d + f_ - h;
let i3 = b + h;
let im = (i0 + i1 * sqrt2 + i2 * sq_y + i3 * sq_2y) * 0.5;
Complex64::new(re, im)
}
const ZZ16_CARTESIAN: [Complex64; 8] = {
const COS_22_5: f64 = 0.9238795325112867;
const SIN_22_5: f64 = 0.3826834323650898;
const HALF_SQRT_2: f64 = std::f64::consts::SQRT_2 * 0.5;
[
Complex64::new(1.0, 0.0),
Complex64::new(COS_22_5, SIN_22_5),
Complex64::new(HALF_SQRT_2, HALF_SQRT_2),
Complex64::new(SIN_22_5, COS_22_5),
Complex64::new(0.0, 1.0),
Complex64::new(-SIN_22_5, COS_22_5),
Complex64::new(-HALF_SQRT_2, HALF_SQRT_2),
Complex64::new(-COS_22_5, SIN_22_5),
]
};
fn zz16_display(coeffs: &[i64; 8], f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let [a, b, c, d, e, f_, g, h] = *coeffs;
let half = Ratio::<i64>::new_raw(1, 2);
let c0 = gpair(
Ratio::<i64>::from_integer(2 * a) * half,
Ratio::<i64>::from_integer(2 * e) * half,
);
let c1 = gpair(
Ratio::<i64>::from_integer(c - g) * half,
Ratio::<i64>::from_integer(c + g) * half,
);
let c2 = gpair(
Ratio::<i64>::from_integer(b - d + f_ - h) * half,
Ratio::<i64>::from_integer(-b + d + f_ - h) * half,
);
let c3 = gpair(
Ratio::<i64>::from_integer(d - f_) * half,
Ratio::<i64>::from_integer(b + h) * half,
);
format_symbolic(
&[c0, c1, c2, c3],
&["1", "2", "2+sqrt(2)", "2(2+sqrt(2))"],
f,
)
}
#[inline]
fn zz16_real_sign(x: &[i64; 4]) -> i8 {
crate::cyclotomic::sign::signum_sum_sqrt_expr_4_zz16::<i128>(
x[0] as i128,
x[1] as i128,
x[2] as i128,
x[3] as i128,
) as i8
}
define_integral_zz! {
name: ZZ16,
n: 16,
phi: 8,
real_dim: 4,
reduction: [-1i64, 0, 0, 0, 0, 0, 0, 0],
re_decomp: [
[2i64, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, -1, 1],
[0, 0, 0, 0], [0, 0, 1, -1], [0, -1, 0, 0], [0, 0, -1, 0],
],
im_decomp: [
[0i64, 0, 0, 0], [0, 0, -1, 1], [0, 1, 0, 0], [0, 0, 1, 0],
[2, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, -1, 1],
],
cartesian: ZZ16_CARTESIAN,
one_in_real_basis: [2i64, 0, 0, 0],
display_fn: zz16_display,
complex64_fn: zz16_complex64,
has: [HasZZ4Impl, HasZZ8Impl],
}
impl From<(i64, i64)> for ZZ16 {
#[inline]
fn from((re, im): (i64, i64)) -> Self {
Self::from_int_coeffs([re, 0, 0, 0, im, 0, 0, 0])
}
}
crate::impl_integral_units_via_basis!(ZZ16, 16);
crate::impl_integral_mul_via_basis!(ZZ16, 8);
crate::impl_integral_conj_via_basis!(ZZ16, 8);
crate::impl_integral_re_im_sign_via_basis!(ZZ16, 8, 4, zz16_real_sign);
crate::impl_integral_intersect_unit_segments_via_basis!(ZZ16, 8, 4, zz16_real_sign);
crate::impl_integral_within_radius_via_norm_sq!(ZZ16);
impl_cell_floor_via_sign_verify!(ZZ16);
crate::zz_integral_ring_tests!(name: ZZ16);
#[inline]
fn zz60_complex64(coeffs: &[i64; 16]) -> Complex64 {
let sq3 = 3.0_f64.sqrt();
let sq5 = 5.0_f64.sqrt();
let sq15 = 15.0_f64.sqrt();
let sqy = ZZ10_Y.sqrt();
let sq3y = (3.0 * ZZ10_Y).sqrt();
let sq5y = (5.0 * ZZ10_Y).sqrt();
let sq15y = (15.0 * ZZ10_Y).sqrt();
let proj = |table: &[[i64; 8]; 16]| -> f64 {
let mut acc = [0i64; 8];
for k in 0..16 {
let ck = coeffs[k];
if ck == 0 {
continue;
}
for j in 0..8 {
acc[j] += ck * table[k][j];
}
}
(acc[0] as f64
+ (acc[1] as f64) * sq3
+ (acc[2] as f64) * sq5
+ (acc[3] as f64) * sqy
+ (acc[4] as f64) * sq15
+ (acc[5] as f64) * sq3y
+ (acc[6] as f64) * sq5y
+ (acc[7] as f64) * sq15y)
/ 16.0
};
let re = proj(&ZZ60_RE_DECOMP);
let im = proj(&ZZ60_IM_DECOMP);
Complex64::new(re, im)
}
const ZZ60_CARTESIAN: [Complex64; 16] = {
const fn placeholder() -> Complex64 {
Complex64::new(0.0, 0.0)
}
let mut out = [placeholder(); 16];
out[0] = Complex64::new(1.0, 0.0);
out
};
const ZZ60_RE_DECOMP: [[i64; 8]; 16] = [
[16, 0, 0, 0, 0, 0, 0, 0],
[0, 2, 0, 2, 2, 0, 0, 0],
[-2, 0, 2, 0, 0, 1, 0, 1],
[0, 0, 0, 2, 0, 0, 2, 0],
[2, 0, 2, 0, 0, 2, 0, 0],
[0, 8, 0, 0, 0, 0, 0, 0],
[4, 0, 4, 0, 0, 0, 0, 0],
[0, -2, 0, 1, 2, 0, 1, 0],
[2, 0, -2, 0, 0, 1, 0, 1],
[0, 0, 0, 4, 0, 0, 0, 0],
[8, 0, 0, 0, 0, 0, 0, 0],
[0, 2, 0, -2, 2, 0, 0, 0],
[-4, 0, 4, 0, 0, 0, 0, 0],
[0, 2, 0, 1, -2, 0, 1, 0],
[-2, 0, -2, 0, 0, 2, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
];
const ZZ60_IM_DECOMP: [[i64; 8]; 16] = [
[0, 0, 0, 0, 0, 0, 0, 0],
[-2, 0, -2, 0, 0, 2, 0, 0],
[0, 2, 0, 1, -2, 0, 1, 0],
[-4, 0, 4, 0, 0, 0, 0, 0],
[0, 2, 0, -2, 2, 0, 0, 0],
[8, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 4, 0, 0, 0, 0],
[2, 0, -2, 0, 0, 1, 0, 1],
[0, -2, 0, 1, 2, 0, 1, 0],
[4, 0, 4, 0, 0, 0, 0, 0],
[0, 8, 0, 0, 0, 0, 0, 0],
[2, 0, 2, 0, 0, 2, 0, 0],
[0, 0, 0, 2, 0, 0, 2, 0],
[-2, 0, 2, 0, 0, 1, 0, 1],
[0, 2, 0, 2, 2, 0, 0, 0],
[16, 0, 0, 0, 0, 0, 0, 0],
];
fn zz60_display(coeffs: &[i64; 16], f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let sixteenth = Ratio::<i64>::new_raw(1, 16);
let mut k_coeffs: [(Ratio<i64>, Ratio<i64>); 8] =
[gpair(Ratio::<i64>::from_integer(0), Ratio::<i64>::from_integer(0)); 8];
for j in 0..8 {
let mut re_acc: i64 = 0;
let mut im_acc: i64 = 0;
for k in 0..16 {
re_acc += coeffs[k] * ZZ60_RE_DECOMP[k][j];
im_acc += coeffs[k] * ZZ60_IM_DECOMP[k][j];
}
k_coeffs[j] = gpair(
Ratio::<i64>::from_integer(re_acc) * sixteenth,
Ratio::<i64>::from_integer(im_acc) * sixteenth,
);
}
format_symbolic(
&k_coeffs,
&[
"1",
"3",
"5",
"2(5-sqrt(5))",
"15",
"6(5-sqrt(5))",
"10(5-sqrt(5))",
"30(5-sqrt(5))",
],
f,
)
}
#[inline]
fn zz60_real_sign(x: &[i64; 8]) -> i8 {
crate::cyclotomic::sign::signum_sum_sqrt_expr_8_zz60::<i128>(
x[0] as i128,
x[1] as i128,
x[2] as i128,
x[3] as i128,
x[4] as i128,
x[5] as i128,
x[6] as i128,
x[7] as i128,
) as i8
}
define_integral_zz! {
name: ZZ60,
n: 60,
phi: 16,
real_dim: 8,
reduction: [-1i64, 0, -1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, -1, 0],
re_decomp: ZZ60_RE_DECOMP,
im_decomp: ZZ60_IM_DECOMP,
cartesian: ZZ60_CARTESIAN,
one_in_real_basis: [16i64, 0, 0, 0, 0, 0, 0, 0],
display_fn: zz60_display,
complex64_fn: zz60_complex64,
has: [HasZZ4Impl, HasZZ6Impl, HasZZ10Impl, HasZZ12Impl],
}
impl From<(i64, i64)> for ZZ60 {
#[inline]
fn from((re, im): (i64, i64)) -> Self {
let mut c = [0i64; 16];
c[0] = re;
c[15] = im;
Self::from_int_coeffs(c)
}
}
crate::impl_integral_units_via_basis!(ZZ60, 60);
crate::impl_integral_mul_via_basis!(ZZ60, 16);
crate::impl_integral_conj_via_basis!(ZZ60, 16);
crate::impl_integral_re_im_sign_via_basis!(ZZ60, 16, 8, zz60_real_sign);
crate::impl_integral_intersect_unit_segments_via_basis!(ZZ60, 16, 8, zz60_real_sign);
crate::impl_integral_within_radius_via_norm_sq!(ZZ60);
impl_cell_floor_via_sign_verify!(ZZ60);
crate::zz_integral_ring_tests!(name: ZZ60);
#[inline]
fn zz32_complex64(coeffs: &[i64; 16]) -> Complex64 {
let sq2 = std::f64::consts::SQRT_2;
let sq_y = ZZ16_Y.sqrt(); let sq_z = ZZ32_Z.sqrt(); let sq_2y = (2.0 * ZZ16_Y).sqrt();
let sq_2z = (2.0 * ZZ32_Z).sqrt();
let sq_yz = (ZZ16_Y * ZZ32_Z).sqrt();
let sq_2yz = (2.0 * ZZ16_Y * ZZ32_Z).sqrt();
let proj = |table: &[[i64; 8]; 16]| -> f64 {
let mut acc = [0i64; 8];
for k in 0..16 {
let ck = coeffs[k];
if ck == 0 {
continue;
}
for j in 0..8 {
acc[j] += ck * table[k][j];
}
}
(acc[0] as f64
+ (acc[1] as f64) * sq2
+ (acc[2] as f64) * sq_y
+ (acc[3] as f64) * sq_z
+ (acc[4] as f64) * sq_2y
+ (acc[5] as f64) * sq_2z
+ (acc[6] as f64) * sq_yz
+ (acc[7] as f64) * sq_2yz)
* 0.5
};
let re = proj(&ZZ32_RE_DECOMP);
let im = proj(&ZZ32_IM_DECOMP);
Complex64::new(re, im)
}
const ZZ32_CARTESIAN: [Complex64; 16] = {
let mut out = [Complex64::new(0.0, 0.0); 16];
out[0] = Complex64::new(1.0, 0.0);
out
};
const ZZ32_RE_DECOMP: [[i64; 8]; 16] = [
[2, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, -1, 0, 0, 1, 0],
[0, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 1, -1, 0],
[0, 0, -1, 0, 1, 0, 0, 0],
[0, 0, 0, -1, 0, -1, 0, 1],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 1, 0, -1],
[0, 0, 1, 0, -1, 0, 0, 0],
[0, 0, 0, -1, 0, -1, 1, 0],
[0, -1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, -1, 0],
[0, 0, -1, 0, 0, 0, 0, 0],
[0, 0, 0, -1, 0, 0, 0, 0],
];
const ZZ32_IM_DECOMP: [[i64; 8]; 16] = [
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, -1, 0, -1, 0, 1],
[0, 0, -1, 0, 1, 0, 0, 0],
[0, 0, 0, 1, 0, 1, -1, 0],
[0, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, -1, 0, 0, 1, 0],
[0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0],
[2, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, -1, 0, 0, 1, 0],
[0, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 1, -1, 0],
[0, 0, -1, 0, 1, 0, 0, 0],
[0, 0, 0, -1, 0, -1, 0, 1],
];
fn zz32_display(coeffs: &[i64; 16], f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let half = Ratio::<i64>::new_raw(1, 2);
let mut k_coeffs: [(Ratio<i64>, Ratio<i64>); 8] =
[gpair(Ratio::<i64>::from_integer(0), Ratio::<i64>::from_integer(0)); 8];
for j in 0..8 {
let mut re_acc: i64 = 0;
let mut im_acc: i64 = 0;
for k in 0..16 {
re_acc += coeffs[k] * ZZ32_RE_DECOMP[k][j];
im_acc += coeffs[k] * ZZ32_IM_DECOMP[k][j];
}
k_coeffs[j] = gpair(
Ratio::<i64>::from_integer(re_acc) * half,
Ratio::<i64>::from_integer(im_acc) * half,
);
}
format_symbolic(
&k_coeffs,
&[
"1",
"2",
"2+sqrt(2)",
"2+sqrt(2+sqrt(2))",
"2(2+sqrt(2))",
"2(2+sqrt(2+sqrt(2)))",
"(2+sqrt(2))(2+sqrt(2+sqrt(2)))",
"2(2+sqrt(2))(2+sqrt(2+sqrt(2)))",
],
f,
)
}
#[inline]
fn zz32_real_sign(x: &[i64; 8]) -> i8 {
crate::cyclotomic::sign::signum_sum_sqrt_expr_8_zz32::<i128>(
x[0] as i128,
x[1] as i128,
x[2] as i128,
x[3] as i128,
x[4] as i128,
x[5] as i128,
x[6] as i128,
x[7] as i128,
) as i8
}
define_integral_zz! {
name: ZZ32,
n: 32,
phi: 16,
real_dim: 8,
reduction: [-1i64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
re_decomp: ZZ32_RE_DECOMP,
im_decomp: ZZ32_IM_DECOMP,
cartesian: ZZ32_CARTESIAN,
one_in_real_basis: [2i64, 0, 0, 0, 0, 0, 0, 0],
display_fn: zz32_display,
complex64_fn: zz32_complex64,
has: [HasZZ4Impl, HasZZ8Impl],
}
impl From<(i64, i64)> for ZZ32 {
#[inline]
fn from((re, im): (i64, i64)) -> Self {
let mut c = [0i64; 16];
c[0] = re;
c[8] = im;
Self::from_int_coeffs(c)
}
}
crate::impl_integral_units_via_basis!(ZZ32, 32);
crate::impl_integral_mul_via_basis!(ZZ32, 16);
crate::impl_integral_conj_via_basis!(ZZ32, 16);
crate::impl_integral_re_im_sign_via_basis!(ZZ32, 16, 8, zz32_real_sign);
crate::impl_integral_intersect_unit_segments_via_basis!(ZZ32, 16, 8, zz32_real_sign);
crate::impl_integral_within_radius_via_norm_sq!(ZZ32);
impl_cell_floor_via_sign_verify!(ZZ32);
crate::zz_integral_ring_tests!(name: ZZ32);
#[cfg(test)]
mod tests {
use super::*;
use crate::cyclotomic::constants::zz_units_sum;
use crate::cyclotomic::traits::OneImag;
use crate::cyclotomic::{Ccw, ZZComplex};
use num_traits::{One, Pow, Zero};
#[test]
fn test_display() {
let x = ZZ24::zero();
assert_eq!(format!("{x}"), "0");
let x = ZZ24::one();
assert_eq!(format!("{x}"), "1");
let x = ZZ24::one() + ZZ24::one();
assert_eq!(format!("{x}"), "2");
let x = -ZZ24::one();
assert_eq!(format!("{x}"), "-1");
let x = ZZ24::one() + (ZZ24::ccw()).pow(2i8);
assert_eq!(format!("{x}"), "1+1/2i + (1/2)*sqrt(3)");
let x: ZZ10 = zz_units_sum();
assert_eq!(
format!("{x}"),
"-5 + (-15/4i)*sqrt(2(5-sqrt(5))) + (-5/4i)*sqrt(10(5-sqrt(5)))"
);
}
#[test]
fn test_zz12_zeta_cubed_is_i() {
let z = ZZ12::ccw();
let i = z * z * z;
assert_eq!(i, ZZ12::one_i());
assert_eq!(i * i, -ZZ12::one());
}
#[test]
fn test_zz12_units_sum_is_complex() {
let p: ZZ12 = zz_units_sum();
assert!(p.is_complex());
}
#[test]
fn zz14_cell_floor_exact_matches_f64_off_boundary() {
use crate::cyclotomic::{CellFloor, SymNum};
let mut seed: u64 = 0x9E3779B97F4A7C15;
let mut next = || {
seed ^= seed << 13;
seed ^= seed >> 7;
seed ^= seed << 17;
seed
};
let mut checked = 0u32;
for _ in 0..6_000 {
let coeffs: [i64; 6] = std::array::from_fn(|_| (next() % 41) as i64 - 20);
let z = ZZ14::from_int_coeffs(coeffs);
let c = z.complex64();
let re_frac = c.re - c.re.floor();
let im_frac = c.im - c.im.floor();
let near_edge = |f: f64| !(1e-6..=1.0 - 1e-6).contains(&f);
if near_edge(re_frac) || near_edge(im_frac) {
continue;
}
assert_eq!(
z.cell_floor_exact(),
(c.re.floor() as i64, c.im.floor() as i64),
"ZZ14 cell_floor_exact disagrees with f64 off-boundary for coeffs {coeffs:?} \
-- a miswired Re/Im decomposition coefficient",
);
checked += 1;
}
assert!(checked > 2_000, "too few off-boundary samples ({checked})");
}
}