use std::ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign};
use num::Zero;
use num::rational::Ratio;
use super::{Lattice, ShortVectorError};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct RootLatticeD<const N: usize> {
pub coeffs: [i128; N],
}
impl<const N: usize> RootLatticeD<N> {
#[must_use = "The coeffs are now inside the lattice point"]
pub fn new(coeffs: [i128; N]) -> Self {
Self { coeffs }
}
}
impl<const N: usize> Add for RootLatticeD<N> {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self::new(core::array::from_fn(|i| self.coeffs[i] + rhs.coeffs[i]))
}
}
impl<const N: usize> Sub for RootLatticeD<N> {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
Self::new(core::array::from_fn(|i| self.coeffs[i] - rhs.coeffs[i]))
}
}
impl<const N: usize> AddAssign for RootLatticeD<N> {
fn add_assign(&mut self, rhs: Self) {
for i in 0..N {
self.coeffs[i] += rhs.coeffs[i];
}
}
}
impl<const N: usize> SubAssign for RootLatticeD<N> {
fn sub_assign(&mut self, rhs: Self) {
for i in 0..N {
self.coeffs[i] -= rhs.coeffs[i];
}
}
}
impl<const N: usize> Mul<i128> for RootLatticeD<N> {
type Output = Self;
fn mul(self, rhs: i128) -> Self {
Self::new(core::array::from_fn(|i| self.coeffs[i] * rhs))
}
}
impl<const N: usize> MulAssign<i128> for RootLatticeD<N> {
fn mul_assign(&mut self, rhs: i128) {
for i in 0..N {
self.coeffs[i] *= rhs;
}
}
}
impl<const N: usize> Zero for RootLatticeD<N> {
fn zero() -> Self {
Self::new([0; N])
}
fn is_zero(&self) -> bool {
self.coeffs.iter().all(|c| *c == 0)
}
}
impl<const N: usize> Lattice<N> for RootLatticeD<N> {
type DualLattice = DualRootLatticeD<N>;
fn inner_product(&self, other: &Self) -> Ratio<i128> {
let mut total = 0i128;
for i in 0..N {
total += 2 * self.coeffs[i] * other.coeffs[i];
}
for k in 0..N.saturating_sub(2) {
total -= self.coeffs[k] * other.coeffs[k + 1] + self.coeffs[k + 1] * other.coeffs[k];
}
if N >= 3 {
let (a, b) = (N - 3, N - 1);
total -= self.coeffs[a] * other.coeffs[b] + self.coeffs[b] * other.coeffs[a];
}
Ratio::from_integer(total)
}
fn is_integral() -> bool {
true
}
fn is_even() -> bool {
true
}
fn is_self_dual() -> bool {
N == 0
}
fn signature() -> (usize, usize, usize) {
(N, 0, 0)
}
fn discriminant() -> Ratio<u128> {
if N == 0 {
Ratio::from_integer(1)
} else {
Ratio::from_integer(4)
}
}
fn short_vectors() -> Result<Vec<Self>, ShortVectorError> {
if N < 2 {
return Ok(Vec::new());
}
let mut roots = Vec::with_capacity(2 * N * (N - 1));
for i in 0..N {
for j in (i + 1)..N {
let mut minus = [0i128; N];
minus[i..j].fill(1);
let minus_root = Self::new(minus);
roots.push(minus_root);
roots.push(minus_root * -1);
let mut plus = [0i128; N];
if j == N - 1 {
plus[i..N - 2].fill(1);
} else {
plus[i..j].fill(1);
plus[j..N - 2].fill(2);
plus[N - 2] = 1;
}
plus[N - 1] = 1;
let plus_root = Self::new(plus);
roots.push(plus_root);
roots.push(plus_root * -1);
}
}
Ok(roots)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct DualRootLatticeD<const N: usize> {
pub coeffs: [i128; N],
}
impl<const N: usize> DualRootLatticeD<N> {
#[must_use = "The coeffs are now inside the lattice point"]
pub fn new(coeffs: [i128; N]) -> Self {
Self { coeffs }
}
}
impl<const N: usize> Add for DualRootLatticeD<N> {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self::new(core::array::from_fn(|i| self.coeffs[i] + rhs.coeffs[i]))
}
}
impl<const N: usize> Sub for DualRootLatticeD<N> {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
Self::new(core::array::from_fn(|i| self.coeffs[i] - rhs.coeffs[i]))
}
}
impl<const N: usize> AddAssign for DualRootLatticeD<N> {
fn add_assign(&mut self, rhs: Self) {
for i in 0..N {
self.coeffs[i] += rhs.coeffs[i];
}
}
}
impl<const N: usize> SubAssign for DualRootLatticeD<N> {
fn sub_assign(&mut self, rhs: Self) {
for i in 0..N {
self.coeffs[i] -= rhs.coeffs[i];
}
}
}
impl<const N: usize> Mul<i128> for DualRootLatticeD<N> {
type Output = Self;
fn mul(self, rhs: i128) -> Self {
Self::new(core::array::from_fn(|i| self.coeffs[i] * rhs))
}
}
impl<const N: usize> MulAssign<i128> for DualRootLatticeD<N> {
fn mul_assign(&mut self, rhs: i128) {
for i in 0..N {
self.coeffs[i] *= rhs;
}
}
}
impl<const N: usize> Zero for DualRootLatticeD<N> {
fn zero() -> Self {
Self::new([0; N])
}
fn is_zero(&self) -> bool {
self.coeffs.iter().all(|c| *c == 0)
}
}
impl<const N: usize> Lattice<N> for DualRootLatticeD<N> {
type DualLattice = RootLatticeD<N>;
fn inner_product(&self, other: &Self) -> Ratio<i128> {
if N == 0 {
return Ratio::zero();
}
let last = N - 1;
let mut diag = 0i128;
let mut border = 0i128;
for k in 0..last {
diag += self.coeffs[k] * other.coeffs[k];
border += self.coeffs[k] * other.coeffs[last] + self.coeffs[last] * other.coeffs[k];
}
let last_term = self.coeffs[last] * other.coeffs[last];
Ratio::from_integer(diag) + Ratio::new(border, 2) + Ratio::new((N as i128) * last_term, 4)
}
fn is_integral() -> bool {
N == 0
}
fn is_even() -> bool {
N == 0
}
fn is_self_dual() -> bool {
N == 0
}
fn signature() -> (usize, usize, usize) {
(N, 0, 0)
}
fn discriminant() -> Ratio<u128> {
if N == 0 {
Ratio::from_integer(1)
} else {
Ratio::new(1, 4)
}
}
fn short_vectors() -> Result<Vec<Self>, ShortVectorError> {
Err(ShortVectorError::Unknown)
}
}
#[cfg(test)]
mod tests {
use num::Zero;
use num::rational::Ratio;
use super::{DualRootLatticeD, Lattice, RootLatticeD, ShortVectorError};
#[test]
fn d4_simple_roots_have_norm_2_and_central_node_is_alpha_2() {
let a1 = RootLatticeD::<4>::new([1, 0, 0, 0]);
let a2 = RootLatticeD::<4>::new([0, 1, 0, 0]);
let a3 = RootLatticeD::<4>::new([0, 0, 1, 0]);
let a4 = RootLatticeD::<4>::new([0, 0, 0, 1]);
for a in [a1, a2, a3, a4] {
assert_eq!(a.lattice_norm_sq(), Ratio::from_integer(2));
}
assert_eq!(a2.inner_product(&a1), Ratio::from_integer(-1));
assert_eq!(a2.inner_product(&a3), Ratio::from_integer(-1));
assert_eq!(a2.inner_product(&a4), Ratio::from_integer(-1));
assert_eq!(a1.inner_product(&a3), Ratio::from_integer(0));
assert_eq!(a1.inner_product(&a4), Ratio::from_integer(0));
assert_eq!(a3.inner_product(&a4), Ratio::from_integer(0));
}
#[test]
fn d_n_invariants() {
assert!(RootLatticeD::<5>::is_integral());
assert!(RootLatticeD::<5>::is_even());
assert!(!RootLatticeD::<5>::is_self_dual());
assert_eq!(RootLatticeD::<5>::signature(), (5, 0, 0));
assert_eq!(RootLatticeD::<5>::discriminant(), Ratio::from_integer(4));
}
#[test]
fn d_n_roots() {
for n in 2..=7 {
match n {
2 => check_d_n_roots::<2>(),
3 => check_d_n_roots::<3>(),
4 => check_d_n_roots::<4>(),
5 => check_d_n_roots::<5>(),
6 => check_d_n_roots::<6>(),
7 => check_d_n_roots::<7>(),
_ => unreachable!(),
}
}
}
fn check_d_n_roots<const N: usize>() {
let roots = RootLatticeD::<N>::short_vectors().unwrap();
assert_eq!(roots.len(), 2 * N * (N - 1));
for r in &roots {
assert_eq!(r.lattice_norm_sq(), Ratio::from_integer(2));
}
for (idx, r) in roots.iter().enumerate() {
assert!(!roots[idx + 1..].contains(r));
}
}
#[test]
fn d6_root_matches_known_coefficients() {
let roots = RootLatticeD::<6>::short_vectors().unwrap();
assert!(roots.contains(&RootLatticeD::<6>::new([1, 2, 2, 2, 1, 1])));
assert!(roots.contains(&RootLatticeD::<6>::new([-1, -2, -2, -2, -1, -1])));
assert!(roots.contains(&RootLatticeD::<6>::new([1, 0, 0, 0, 0, 0])));
}
#[test]
fn dual_d4_glue_vector_structure() {
assert!(!DualRootLatticeD::<4>::is_integral());
assert_eq!(DualRootLatticeD::<4>::signature(), (4, 0, 0));
assert_eq!(DualRootLatticeD::<4>::discriminant(), Ratio::new(1, 4));
assert_eq!(
DualRootLatticeD::<4>::short_vectors(),
Err(ShortVectorError::Unknown)
);
let e1 = DualRootLatticeD::<4>::new([1, 0, 0, 0]);
let v = DualRootLatticeD::<4>::new([0, 0, 0, 1]);
assert_eq!(e1.lattice_norm_sq(), Ratio::from_integer(1));
assert_eq!(v.lattice_norm_sq(), Ratio::from_integer(1)); assert_eq!(e1.inner_product(&v), Ratio::new(1, 2));
}
#[test]
fn group_structure() {
let mut v = RootLatticeD::<4>::new([1, 2, 3, 4]);
v += RootLatticeD::<4>::new([-1, 0, 1, 0]);
assert_eq!(v, RootLatticeD::<4>::new([0, 2, 4, 4]));
v -= RootLatticeD::<4>::new([0, 2, 4, 4]);
assert!(v.is_zero());
let w = RootLatticeD::<4>::new([1, -1, 2, 0]) * 3;
assert_eq!(w, RootLatticeD::<4>::new([3, -3, 6, 0]));
}
}