use std::ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign};
use num::Zero;
use num::rational::Ratio;
use super::{Lattice, ShortVectorError};
#[allow(clippy::many_single_char_names)]
fn dual_cartan_entry(n: usize, i: usize, j: usize) -> Ratio<i128> {
let a = (i + 1) as i128;
let b = (j + 1) as i128;
let n_plus_one = (n + 1) as i128;
Ratio::new(a.min(b) * (n_plus_one - a.max(b)), n_plus_one)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct RootLatticeA<const N: usize> {
pub coeffs: [i128; N],
}
impl<const N: usize> RootLatticeA<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 RootLatticeA<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 RootLatticeA<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 RootLatticeA<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 RootLatticeA<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 RootLatticeA<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 RootLatticeA<N> {
fn mul_assign(&mut self, rhs: i128) {
for i in 0..N {
self.coeffs[i] *= rhs;
}
}
}
impl<const N: usize> Zero for RootLatticeA<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 RootLatticeA<N> {
type DualLattice = DualRootLatticeA<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 i in 0..N.saturating_sub(1) {
total -= self.coeffs[i] * other.coeffs[i + 1] + self.coeffs[i + 1] * other.coeffs[i];
}
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> {
Ratio::from_integer(N as u128 + 1)
}
fn short_vectors() -> Result<Vec<Self>, ShortVectorError> {
let mut roots = Vec::with_capacity(N * (N + 1));
for i in 0..N {
for j in i..N {
let mut coeffs = [0i128; N];
coeffs[i..=j].fill(1);
let root = Self::new(coeffs);
roots.push(root);
roots.push(root * -1);
}
}
Ok(roots)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct DualRootLatticeA<const N: usize> {
pub coeffs: [i128; N],
}
impl<const N: usize> DualRootLatticeA<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 DualRootLatticeA<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 DualRootLatticeA<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 DualRootLatticeA<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 DualRootLatticeA<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 DualRootLatticeA<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 DualRootLatticeA<N> {
fn mul_assign(&mut self, rhs: i128) {
for i in 0..N {
self.coeffs[i] *= rhs;
}
}
}
impl<const N: usize> Zero for DualRootLatticeA<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 DualRootLatticeA<N> {
type DualLattice = RootLatticeA<N>;
fn inner_product(&self, other: &Self) -> Ratio<i128> {
let mut total = Ratio::from_integer(0);
for i in 0..N {
for j in 0..N {
total += dual_cartan_entry(N, i, j)
* Ratio::from_integer(self.coeffs[i] * other.coeffs[j]);
}
}
total
}
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> {
Ratio::new(1, N as u128 + 1)
}
fn short_vectors() -> Result<Vec<Self>, ShortVectorError> {
if N == 0 {
return Ok(Vec::new());
}
let mut vectors: Vec<Self> = Vec::with_capacity(2 * (N + 1));
for i in 1..=(N + 1) {
let mut coeffs = [0i128; N];
if i <= N {
coeffs[i - 1] += 1;
}
if i >= 2 {
coeffs[i - 2] -= 1;
}
let v = Self::new(coeffs);
if !vectors.contains(&v) {
vectors.push(v);
}
let neg = v * -1;
if !vectors.contains(&neg) {
vectors.push(neg);
}
}
Ok(vectors)
}
}
#[cfg(test)]
mod tests {
use num::Zero;
use num::rational::Ratio;
use super::{DualRootLatticeA, Lattice, RootLatticeA};
#[test]
fn a2_simple_roots_have_norm_2_and_pair_to_minus_1() {
let a1 = RootLatticeA::<2>::new([1, 0]);
let a2 = RootLatticeA::<2>::new([0, 1]);
assert_eq!(a1.lattice_norm_sq(), Ratio::from_integer(2));
assert_eq!(a2.lattice_norm_sq(), Ratio::from_integer(2));
assert_eq!(a1.inner_product(&a2), Ratio::from_integer(-1));
}
#[test]
fn a_n_invariants() {
assert!(RootLatticeA::<3>::is_integral());
assert!(RootLatticeA::<3>::is_even());
assert!(!RootLatticeA::<3>::is_self_dual());
assert_eq!(RootLatticeA::<3>::signature(), (3, 0, 0));
assert_eq!(RootLatticeA::<3>::discriminant(), Ratio::from_integer(4));
}
#[test]
fn dual_a2_has_reciprocal_discriminant() {
assert!(!DualRootLatticeA::<2>::is_integral());
assert_eq!(DualRootLatticeA::<2>::signature(), (2, 0, 0));
assert_eq!(DualRootLatticeA::<2>::discriminant(), Ratio::new(1, 3));
let dual_e1 = DualRootLatticeA::<2>::new([1, 0]);
assert_eq!(dual_e1.lattice_norm_sq(), Ratio::new(2, 3));
}
#[test]
fn a2_discriminant_group_quadratic_matches_dual_norm() {
let dual_e1 = DualRootLatticeA::<2>::new([1, 0]);
assert_eq!(
RootLatticeA::<2>::discriminant_group_quadratic(dual_e1),
Ratio::new(2, 3)
);
let alpha_1_in_dual_coords = DualRootLatticeA::<2>::new([2, -1]);
let shifted = dual_e1 + alpha_1_in_dual_coords;
assert_eq!(shifted.lattice_norm_sq(), Ratio::new(14, 3));
assert_eq!(
RootLatticeA::<2>::discriminant_group_quadratic(shifted),
RootLatticeA::<2>::discriminant_group_quadratic(dual_e1)
);
}
#[test]
fn a_n_roots() {
let roots = RootLatticeA::<3>::short_vectors().unwrap();
assert_eq!(roots.len(), 3 * 4);
for r in &roots {
assert_eq!(r.lattice_norm_sq(), Ratio::from_integer(2));
}
assert!(roots.contains(&RootLatticeA::<3>::new([1, 0, 0])));
assert!(roots.contains(&RootLatticeA::<3>::new([0, -1, -1])));
}
#[test]
fn dual_a_n_minimal_vectors() {
for n in 1..=4 {
match n {
1 => check_dual_minimal_vectors::<1>(),
2 => check_dual_minimal_vectors::<2>(),
3 => check_dual_minimal_vectors::<3>(),
4 => check_dual_minimal_vectors::<4>(),
_ => unreachable!(),
}
}
}
fn check_dual_minimal_vectors<const N: usize>() {
let vectors = DualRootLatticeA::<N>::short_vectors().unwrap();
let expected_len = if N == 1 { 2 } else { 2 * (N + 1) };
assert_eq!(vectors.len(), expected_len);
let expected_norm = Ratio::new(N as i128, N as i128 + 1);
for v in &vectors {
assert_eq!(v.lattice_norm_sq(), expected_norm);
}
}
#[test]
fn group_structure() {
let mut v = RootLatticeA::<3>::new([1, 2, 3]);
v += RootLatticeA::<3>::new([-1, 0, 1]);
assert_eq!(v, RootLatticeA::<3>::new([0, 2, 4]));
v -= RootLatticeA::<3>::new([0, 2, 4]);
assert!(v.is_zero());
let w = RootLatticeA::<3>::new([1, -1, 2]) * 3;
assert_eq!(w, RootLatticeA::<3>::new([3, -3, 6]));
}
}