use crate::clifford::Metric;
use crate::scalar::Scalar;
pub fn gram<S: Scalar>(m: &Metric<S>) -> Option<Vec<Vec<S>>> {
let two = S::one().add(&S::one());
let half = two.inv()?; let n = m.q.len();
let mut g = vec![vec![S::zero(); n]; n];
for (i, qi) in m.q.iter().enumerate() {
g[i][i] = qi.clone();
}
for (&(i, j), bij) in &m.b {
let off = bij.mul(&half);
g[j][i] = off.clone();
g[i][j] = off;
}
Some(g)
}
fn swap_sym<S: Scalar>(g: &mut [Vec<S>], k: usize, m: usize) {
g.swap(k, m);
for row in g.iter_mut() {
row.swap(k, m);
}
}
fn add_sym<S: Scalar>(g: &mut [Vec<S>], i: usize, j: usize) {
let n = g.len();
for t in 0..n {
g[i][t] = g[i][t].add(&g[j][t].clone());
}
for t in 0..n {
g[t][i] = g[t][i].add(&g[t][j].clone());
}
}
fn ensure_pivot<S: Scalar>(g: &mut [Vec<S>], k: usize) -> bool {
let n = g.len();
if !g[k][k].is_zero() {
return true;
}
for m in (k + 1)..n {
if !g[m][m].is_zero() {
swap_sym(g, k, m);
return true;
}
}
for i in k..n {
for j in (i + 1)..n {
if !g[i][j].is_zero() {
add_sym(g, i, j); if i != k {
swap_sym(g, k, i);
}
return true;
}
}
}
false }
pub fn diagonalize<S: Scalar>(m: &Metric<S>) -> Option<Metric<S>> {
let mut g = gram(m)?;
let n = g.len();
for k in 0..n {
if !ensure_pivot(&mut g, k) {
break; }
let pivot_inv = g[k][k].inv()?;
for r in (k + 1)..n {
if g[r][k].is_zero() {
continue;
}
let factor = g[r][k].mul(&pivot_inv);
let row_k: Vec<S> = g[k].clone();
for t in 0..n {
let sub = factor.mul(&row_k[t]);
g[r][t] = g[r][t].sub(&sub);
}
let col_k: Vec<S> = (0..n).map(|t| g[t][k].clone()).collect();
for t in 0..n {
let sub = factor.mul(&col_k[t]);
g[t][r] = g[t][r].sub(&sub);
}
}
}
let diag: Vec<S> = (0..n).map(|i| g[i][i].clone()).collect();
Some(Metric::diagonal(diag))
}
pub fn as_diagonal<S: Scalar>(m: &Metric<S>) -> Option<Metric<S>> {
if m.b.is_empty() && m.a.is_empty() {
Some(m.clone())
} else {
diagonalize(m)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::forms::{classify_finite_odd, classify_rational, classify_surreal};
use crate::scalar::{Fp, Rational, Surreal, Zp};
use std::collections::BTreeMap;
fn rat(n: i128) -> Rational {
Rational::from_int(n)
}
#[test]
fn hyperbolic_plane_diagonalizes_to_disc_minus_one() {
let mut b = BTreeMap::new();
b.insert((0, 1), rat(2));
let m = Metric::new(vec![rat(0), rat(0)], b);
let d = diagonalize(&m).unwrap();
let det = d.q.iter().fold(rat(1), |acc, x| acc.mul(x));
assert_eq!(det.sign(), std::cmp::Ordering::Less);
assert_eq!(
classify_rational(&m).unwrap(),
classify_rational(&Metric::diagonal(vec![rat(1), rat(-1)])).unwrap()
);
}
#[test]
fn off_diagonal_real_form_keeps_its_signature() {
let mut b = BTreeMap::new();
b.insert((0, 1), rat(2));
let m = Metric::new(vec![rat(1), rat(2)], b);
let d = diagonalize(&m).unwrap();
assert!(d.q.iter().all(|x| x.sign() == std::cmp::Ordering::Greater));
assert_eq!(
classify_surreal(&Metric::diagonal(
d.q.iter()
.map(|x| Surreal::from_rational(x.clone()))
.collect()
)),
classify_surreal(&Metric::diagonal(vec![
Surreal::from_int(1),
Surreal::from_int(1)
]))
);
}
#[test]
fn classifiers_now_accept_nondiagonal_metrics() {
const P: u128 = 5;
let mut b = BTreeMap::new();
b.insert((0, 1), Fp::<P>::from_int(2));
let m = Metric::new(vec![Fp::<P>::from_int(0), Fp::<P>::from_int(0)], b);
let got = classify_finite_odd(&m).unwrap();
let want = classify_finite_odd(&Metric::diagonal(vec![
Fp::<P>::from_int(1),
Fp::<P>::from_int(-1),
]))
.unwrap();
assert_eq!(got.dim, want.dim);
assert_eq!(got.disc_is_square, want.disc_is_square);
}
#[test]
fn characteristic_two_is_not_diagonalizable() {
use crate::scalar::Nimber;
let mut b = BTreeMap::new();
b.insert((0, 1), Nimber(1));
let m = Metric::new(vec![Nimber(1), Nimber(1)], b);
assert!(diagonalize(&m).is_none());
assert!(as_diagonal(&m).is_none());
}
#[test]
fn nonfield_nonunit_pivot_returns_none() {
let m = Metric::diagonal(vec![Zp::<3, 2>::from_int(3)]);
assert!(diagonalize(&m).is_none());
}
}