use crate::clifford::{Metric, MAX_BASIS_DIM};
use crate::forms::{relevant_primes, try_disc_class, try_hasse_at_place, try_square_free, Place};
use crate::scalar::Surcomplex;
use crate::scalar::Surreal;
use crate::scalar::{ExactRoots, Rational, Scalar};
use std::cmp::Ordering;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BaseField {
R,
C,
H,
}
impl BaseField {
fn symbol(self) -> &'static str {
match self {
BaseField::R => "R",
BaseField::C => "C",
BaseField::H => "H",
}
}
fn real_dimension_log2(self) -> usize {
match self {
BaseField::R => 0,
BaseField::C => 1,
BaseField::H => 2,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CliffordInvariants {
pub base: BaseField,
pub matrix_dim: u128,
pub doubled: bool,
pub radical_dim: usize,
pub ground: BaseField,
pub signature: (usize, usize),
}
impl CliffordInvariants {
pub fn display(&self) -> String {
self.to_string()
}
}
impl std::fmt::Display for CliffordInvariants {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let unit = if self.matrix_dim == 1 {
self.base.symbol().to_string()
} else {
format!("M_{}({})", self.matrix_dim, self.base.symbol())
};
let core = if self.doubled {
format!("{unit} ⊕ {unit}")
} else {
unit
};
if self.radical_dim > 0 {
write!(
f,
"{core} ⊗̂ Λ({}^{})",
self.ground.symbol(),
self.radical_dim
)
} else {
f.write_str(&core)
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RationalPlaceInvariant {
pub place: Place,
pub hasse: i128,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RationalCliffordInvariants {
pub dim: usize,
pub radical_dim: usize,
pub discriminant: i128,
pub signature: (usize, usize),
pub local_hasse: Vec<RationalPlaceInvariant>,
pub real_closure: CliffordInvariants,
}
impl RationalCliffordInvariants {
pub fn display(&self) -> String {
self.to_string()
}
}
impl std::fmt::Display for RationalCliffordInvariants {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let locals = self
.local_hasse
.iter()
.map(|h| match h.place {
Place::Real => format!("R:{:+}", h.hasse),
Place::Prime(p) => format!("Q_{}:{:+}", p, h.hasse),
})
.collect::<Vec<_>>()
.join(", ");
let rad = if self.radical_dim > 0 {
format!(" radical {}", self.radical_dim)
} else {
String::new()
};
write!(
f,
"Q: dim {} disc {} sig ({},{}) hasse [{}]{}; over R: {}",
self.dim,
self.discriminant,
self.signature.0,
self.signature.1,
locals,
rad,
self.real_closure
)
}
}
fn p2(k: usize) -> u128 {
1u128
.checked_shl(k.try_into().expect("matrix exponent fits u32"))
.expect("matrix dimension exceeds u128")
}
fn real_core(p: usize, q: usize) -> (BaseField, u128, bool) {
let n = p + q;
let s = (q as i128 - p as i128).rem_euclid(8) as usize;
let base = match s {
0 | 6 | 7 => BaseField::R,
1 | 5 => BaseField::C,
2..=4 => BaseField::H,
_ => unreachable!(),
};
let doubled = s % 4 == 3;
let matrix_exp = (n - base.real_dimension_log2() - usize::from(doubled)) / 2;
(base, p2(matrix_exp), doubled)
}
pub fn classify_real(p: usize, q: usize, r: usize) -> CliffordInvariants {
assert!(
p + q <= MAX_BASIS_DIM,
"classify_real: signature dimension p+q={} exceeds MAX_BASIS_DIM={MAX_BASIS_DIM}",
p + q
);
let (base, matrix_dim, doubled) = real_core(p, q);
CliffordInvariants {
base,
matrix_dim,
doubled,
radical_dim: r,
ground: BaseField::R,
signature: (p, q),
}
}
pub fn classify_complex(n: usize, r: usize) -> CliffordInvariants {
assert!(
n <= MAX_BASIS_DIM,
"classify_complex: dimension n={n} exceeds MAX_BASIS_DIM={MAX_BASIS_DIM}"
);
let doubled = !n.is_multiple_of(2);
let matrix_dim = p2((n - usize::from(doubled)) / 2);
CliffordInvariants {
base: BaseField::C,
matrix_dim,
doubled,
radical_dim: r,
ground: BaseField::C,
signature: (n, 0),
}
}
pub(crate) fn surreal_signature(metric: &Metric<Surreal>) -> Option<(usize, usize, usize)> {
let diag = crate::forms::as_diagonal(metric)?;
let (mut p, mut q, mut r) = (0, 0, 0);
for x in &diag.q {
match x.sign() {
Ordering::Greater => {
x.sqrt()?; p += 1;
}
Ordering::Less => {
x.neg().sqrt()?;
q += 1;
}
Ordering::Equal => r += 1,
}
}
Some((p, q, r))
}
pub(crate) fn surcomplex_rank(metric: &Metric<Surcomplex<Surreal>>) -> Option<(usize, usize)> {
let diag = crate::forms::as_diagonal(metric)?;
let mut nonzero = 0usize;
let mut radical = 0usize;
for z in &diag.q {
if z.is_zero() {
radical += 1;
} else {
z.sqrt()?;
nonzero += 1;
}
}
Some((nonzero, radical))
}
fn rational_square_class(x: &Rational) -> Option<i128> {
try_square_free(x.numer().checked_mul(x.denom())?)
}
pub fn classify_rational(metric: &Metric<Rational>) -> Option<RationalCliffordInvariants> {
let diag = crate::forms::as_diagonal(metric)?;
let mut entries = Vec::new();
let mut radical_dim = 0usize;
let mut signature = (0usize, 0usize);
for x in &diag.q {
if x.is_zero() {
radical_dim += 1;
continue;
}
match x.sign() {
Ordering::Greater => signature.0 += 1,
Ordering::Less => signature.1 += 1,
Ordering::Equal => unreachable!("zero handled above"),
}
entries.push(rational_square_class(x)?);
}
let discriminant = if entries.is_empty() {
1
} else {
try_disc_class(&entries)?
};
let mut local_hasse = vec![RationalPlaceInvariant {
place: Place::Real,
hasse: try_hasse_at_place(&entries, Place::Real)?,
}];
for p in relevant_primes(&entries) {
local_hasse.push(RationalPlaceInvariant {
place: Place::Prime(p),
hasse: try_hasse_at_place(&entries, Place::Prime(p))?,
});
}
Some(RationalCliffordInvariants {
dim: entries.len(),
radical_dim,
discriminant,
signature,
local_hasse,
real_closure: classify_real(signature.0, signature.1, radical_dim),
})
}
pub fn classify_surreal(metric: &Metric<Surreal>) -> Option<CliffordInvariants> {
let (p, q, r) = surreal_signature(metric)?;
Some(classify_real(p, q, r))
}
pub fn classify_surcomplex(metric: &Metric<Surcomplex<Surreal>>) -> Option<CliffordInvariants> {
let (nonzero, r) = surcomplex_rank(metric)?;
Some(classify_complex(nonzero, r))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::clifford::{CliffordAlgebra, Metric};
use crate::scalar::Scalar;
fn rat(n: i128) -> Rational {
Rational::from_int(n)
}
fn surreal_diag(qs: &[i128]) -> Metric<Surreal> {
Metric::diagonal(qs.iter().map(|&x| Surreal::from_int(x)).collect())
}
fn cl_real(qs: &[i128]) -> Option<CliffordInvariants> {
classify_surreal(&surreal_diag(qs))
}
fn name(qs: &[i128]) -> String {
cl_real(qs).unwrap().display()
}
#[test]
fn low_dimensional_real_clifford_table() {
assert_eq!(name(&[]), "R"); assert_eq!(name(&[1]), "R ⊕ R"); assert_eq!(name(&[-1]), "C"); assert_eq!(name(&[1, 1]), "M_2(R)"); assert_eq!(name(&[1, -1]), "M_2(R)"); assert_eq!(name(&[-1, -1]), "H"); assert_eq!(name(&[1, 1, 1]), "M_2(C)"); assert_eq!(name(&[-1, -1, -1]), "H ⊕ H"); assert_eq!(name(&[-1, -1, -1, -1]), "M_2(H)"); }
#[test]
fn physics_signatures() {
assert_eq!(name(&[1, -1, -1, -1]), "M_2(H)"); assert_eq!(name(&[1, 1, 1, -1]), "M_4(R)"); assert_eq!(name(&[1, 1, 1, 1, -1]), "M_4(C)"); }
#[test]
fn dimension_is_consistent() {
for p in 0..=5usize {
for q in 0..=5usize {
let t = classify_real(p, q, 0);
let unit = match t.base {
BaseField::R => 1u128,
BaseField::C => 2u128,
BaseField::H => 4u128,
};
let copies = if t.doubled { 2u128 } else { 1u128 };
let real_dim = copies * unit * t.matrix_dim * t.matrix_dim;
assert_eq!(real_dim, 1u128 << (p + q), "Cl({p},{q})");
}
}
}
#[test]
fn radical_gives_exterior_factor() {
assert_eq!(name(&[-1, 0, 0]), "C ⊗̂ Λ(R^2)");
assert_eq!(name(&[0, 0, 0]), "R ⊗̂ Λ(R^3)");
}
#[test]
fn matrix_dimension_reaches_dim_128_boundary() {
assert_eq!(classify_real(128, 0, 0).matrix_dim, 1u128 << 64);
assert_eq!(classify_complex(128, 0).matrix_dim, 1u128 << 64);
}
#[test]
#[should_panic(expected = "MAX_BASIS_DIM")]
fn classify_real_rejects_dimension_past_max_basis_dim() {
classify_real(129, 0, 0);
}
#[test]
#[should_panic(expected = "MAX_BASIS_DIM")]
fn classify_complex_rejects_dimension_past_max_basis_dim() {
classify_complex(129, 0);
}
#[test]
fn rational_classification_keeps_square_classes_and_local_hasse_data() {
let one = classify_rational(&Metric::diagonal(vec![rat(1)])).unwrap();
let two = classify_rational(&Metric::diagonal(vec![rat(2)])).unwrap();
assert_eq!(one.signature, two.signature);
assert_ne!(one.discriminant, two.discriminant);
let h = classify_rational(&Metric::diagonal(vec![rat(-1), rat(-1)])).unwrap();
assert_eq!(h.discriminant, 1);
assert_eq!(h.signature, (0, 2));
assert!(h
.local_hasse
.iter()
.any(|x| x.place == Place::Real && x.hasse == -1));
assert!(h
.local_hasse
.iter()
.any(|x| x.place == Place::Prime(2) && x.hasse == -1));
}
#[test]
fn surreal_accepts_represented_exact_square_classes() {
let m = Metric::diagonal(vec![Surreal::omega(), Surreal::epsilon().neg()]);
assert_eq!(classify_surreal(&m).unwrap().display(), "M_2(R)");
assert_eq!(
classify_surreal(&surreal_diag(&[4])).unwrap().display(),
"R ⊕ R"
);
}
#[test]
fn surreal_declines_unrepresented_square_classes() {
assert_eq!(classify_surreal(&surreal_diag(&[2])), None);
}
#[test]
fn surcomplex_is_two_fold_on_exact_square_subdomain() {
let even =
Metric::<Surcomplex<Surreal>>::diagonal(vec![Surcomplex::one(), Surcomplex::one()]);
assert_eq!(classify_surcomplex(&even).unwrap().display(), "M_2(C)"); let odd = Metric::<Surcomplex<Surreal>>::diagonal(vec![Surcomplex::one()]);
assert_eq!(classify_surcomplex(&odd).unwrap().display(), "C ⊕ C"); let minus_one = Metric::<Surcomplex<Surreal>>::diagonal(vec![Surcomplex::new(
Surreal::from_int(-1),
Surreal::zero(),
)]);
assert_eq!(classify_surcomplex(&minus_one).unwrap().display(), "C ⊕ C");
let square_of_two_plus_i = Metric::<Surcomplex<Surreal>>::diagonal(vec![Surcomplex::new(
Surreal::from_int(3),
Surreal::from_int(4),
)]);
assert_eq!(
classify_surcomplex(&square_of_two_plus_i)
.unwrap()
.display(),
"C ⊕ C"
);
}
#[test]
fn surcomplex_declines_unrepresented_square_classes() {
let two = Metric::<Surcomplex<Surreal>>::diagonal(vec![Surcomplex::new(
Surreal::from_int(2),
Surreal::zero(),
)]);
assert_eq!(classify_surcomplex(&two), None);
}
#[test]
fn even_subalgebra_classification_drops_one_dimension() {
let alg = CliffordAlgebra::new(3, Metric::diagonal(vec![rat(1), rat(1), rat(1)]));
let even = alg.even_subalgebra().unwrap();
assert_eq!(
classify_rational(even.metric())
.unwrap()
.real_closure
.display(),
"H"
);
let st = CliffordAlgebra::new(4, Metric::diagonal(vec![rat(1), rat(-1), rat(-1), rat(-1)]));
let st_even = st.even_subalgebra().unwrap();
assert_eq!(
classify_rational(st_even.metric())
.unwrap()
.real_closure
.display(),
classify_real(1, 2, 0).display()
);
}
}