use crate::clifford::Metric;
use crate::scalar::Scalar;
use crate::scalar::Surreal;
use std::cmp::Ordering;
#[derive(Debug, Clone, PartialEq)]
pub struct ResidueForm {
pub valuation: Surreal,
pub signature: (usize, usize),
}
#[derive(Debug, Clone, PartialEq)]
pub struct SpringerDecomp {
pub graded: Vec<ResidueForm>,
pub radical_dim: usize,
pub total_signature: (usize, usize),
}
pub fn springer_decompose(metric: &Metric<Surreal>) -> Option<SpringerDecomp> {
let metric = crate::forms::as_diagonal(metric)?;
let mut buckets: Vec<(Surreal, (usize, usize))> = Vec::new();
let mut radical_dim = 0usize;
for x in &metric.q {
if x.is_zero() {
radical_dim += 1;
continue;
}
let (exp, coeff) = x.terms().first().expect("nonzero surreal has a term");
let sign = coeff.sign();
let slot = buckets
.iter_mut()
.find(|(v, _)| v.cmp(exp) == Ordering::Equal);
let (p, q) = match slot {
Some((_, pq)) => pq,
None => {
buckets.push((exp.clone(), (0, 0)));
&mut buckets.last_mut().unwrap().1
}
};
match sign {
Ordering::Greater => *p += 1,
Ordering::Less => *q += 1,
Ordering::Equal => unreachable!("nonzero surreal has nonzero leading coeff"),
}
}
buckets.sort_by(|a, b| b.0.cmp(&a.0));
let mut total = (0usize, 0usize);
let graded: Vec<ResidueForm> = buckets
.into_iter()
.map(|(valuation, signature)| {
total.0 += signature.0;
total.1 += signature.1;
ResidueForm {
valuation,
signature,
}
})
.collect();
Some(SpringerDecomp {
graded,
radical_dim,
total_signature: total,
})
}
impl SpringerDecomp {
pub fn display(&self) -> String {
self.to_string()
}
}
impl std::fmt::Display for SpringerDecomp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let graded: Vec<(String, (usize, usize))> = self
.graded
.iter()
.map(|g| (g.valuation.to_string(), g.signature))
.collect();
write!(
f,
"SpringerDecomp(graded={graded:?}, radical_dim={}, total_signature={:?})",
self.radical_dim, self.total_signature
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::forms::classify_surreal;
use crate::scalar::Rational;
fn w(n: i128) -> Surreal {
Surreal::from_int(n)
}
#[test]
fn display_render_pin() {
let m = Metric::diagonal(vec![Surreal::omega(), Surreal::epsilon(), w(1), w(-1)]);
let d = springer_decompose(&m).unwrap();
assert_eq!(
d.to_string(),
"SpringerDecomp(graded=[(\"1\", (1, 0)), (\"0\", (1, 1)), (\"-1\", (1, 0))], radical_dim=0, total_signature=(3, 1))"
);
assert_eq!(d.display(), d.to_string());
}
#[test]
fn three_valuation_levels() {
let m = Metric::diagonal(vec![Surreal::omega(), Surreal::epsilon(), w(1), w(-1)]);
let d = springer_decompose(&m).unwrap();
assert_eq!(d.graded.len(), 3);
assert_eq!(d.graded[0].valuation, w(1));
assert_eq!(d.graded[0].signature, (1, 0));
assert_eq!(d.graded[1].valuation, w(0));
assert_eq!(d.graded[1].signature, (1, 1));
assert_eq!(d.graded[2].valuation, w(-1));
assert_eq!(d.graded[2].signature, (1, 0));
assert_eq!(d.total_signature, (3, 1));
assert_eq!(d.total_signature, classify_surreal(&m).unwrap().signature);
}
#[test]
fn single_valuation_bucket() {
let two_omega = Surreal::monomial(Surreal::one(), Rational::from_int(2));
let m = Metric::diagonal(vec![Surreal::omega(), two_omega, Surreal::omega().neg()]);
let d = springer_decompose(&m).unwrap();
assert_eq!(d.graded.len(), 1);
assert_eq!(d.graded[0].valuation, w(1));
assert_eq!(d.graded[0].signature, (2, 1));
assert_eq!(d.total_signature, (2, 1));
}
#[test]
fn reads_only_the_leading_term() {
let m = Metric::diagonal(vec![Surreal::omega().add(&w(1))]);
let d = springer_decompose(&m).unwrap();
assert_eq!(d.graded.len(), 1);
assert_eq!(d.graded[0].valuation, w(1));
assert_eq!(d.graded[0].signature, (1, 0));
}
#[test]
fn radical_is_counted_separately() {
let m = Metric::diagonal(vec![w(0), Surreal::omega()]);
let d = springer_decompose(&m).unwrap();
assert_eq!(d.radical_dim, 1);
assert_eq!(d.graded.len(), 1);
assert_eq!(d.total_signature, (1, 0));
}
#[test]
fn witt_class_is_just_the_signature() {
let m = Metric::diagonal(vec![Surreal::omega(), w(1), Surreal::epsilon().neg()]);
let d = springer_decompose(&m).unwrap();
let witt = d.total_signature.0 as i128 - d.total_signature.1 as i128;
let (p, q) = classify_surreal(&m).unwrap().signature;
assert_eq!(witt, p as i128 - q as i128);
}
#[test]
fn nonorthogonal_metric_is_diagonalized_first() {
let mut b = std::collections::BTreeMap::new();
b.insert((0usize, 1usize), Surreal::from_int(1));
let m = Metric::new(vec![w(0), w(0)], b);
let d = springer_decompose(&m).unwrap();
assert_eq!(d.total_signature, (1, 1));
assert_eq!(d.total_signature, classify_surreal(&m).unwrap().signature);
}
}