use crate::clifford::Metric;
use crate::forms::char2::{
arf_nimber_at_degree, arf_ordinal_at_degree, min_field_degree, nimber_metric_max_val,
};
use crate::forms::{
arf_char2, arf_fpn_char2, as_diagonal, classify_finite_odd,
ordinal_metric_finite_subfield_degree,
};
use crate::forms::{FiniteChar2Field, FiniteOddField};
use crate::scalar::{Fpn, Nimber, Ordinal, Rational, Surcomplex, Surreal};
pub fn isometric_real(m1: &Metric<Surreal>, m2: &Metric<Surreal>) -> Option<bool> {
let s1 = crate::forms::char0::surreal_signature(m1)?;
let s2 = crate::forms::char0::surreal_signature(m2)?;
Some(s1 == s2)
}
pub fn isometric_rational(m1: &Metric<Rational>, m2: &Metric<Rational>) -> Option<bool> {
Some(crate::forms::classify_rational(m1)? == crate::forms::classify_rational(m2)?)
}
pub fn isometric_surcomplex(
m1: &Metric<Surcomplex<Surreal>>,
m2: &Metric<Surcomplex<Surreal>>,
) -> Option<bool> {
Some(crate::forms::char0::surcomplex_rank(m1)? == crate::forms::char0::surcomplex_rank(m2)?)
}
pub fn isometric_finite_odd<F: FiniteOddField>(m1: &Metric<F>, m2: &Metric<F>) -> Option<bool> {
Some(classify_finite_odd(m1)? == classify_finite_odd(m2)?)
}
pub fn isometric_nimber(m1: &Metric<Nimber>, m2: &Metric<Nimber>) -> Option<bool> {
let maxv = nimber_metric_max_val(m1).max(nimber_metric_max_val(m2));
let m = min_field_degree(maxv);
let a1 = arf_nimber_at_degree(m1, m)?;
let a2 = arf_nimber_at_degree(m2, m)?;
Some(same_char2_isometry_invariant(&a1, &a2))
}
pub fn isometric_finite_char2<F: FiniteChar2Field>(m1: &Metric<F>, m2: &Metric<F>) -> Option<bool> {
let a1 = arf_char2(m1)?;
let a2 = arf_char2(m2)?;
Some(same_char2_isometry_invariant(&a1, &a2))
}
pub fn isometric_fpn_char2<const P: u128, const N: usize>(
m1: &Metric<Fpn<P, N>>,
m2: &Metric<Fpn<P, N>>,
) -> Option<bool> {
let a1 = arf_fpn_char2(m1)?;
let a2 = arf_fpn_char2(m2)?;
Some(same_char2_isometry_invariant(&a1, &a2))
}
pub fn isometric_ordinal_finite(m1: &Metric<Ordinal>, m2: &Metric<Ordinal>) -> Option<bool> {
let d1 = ordinal_metric_finite_subfield_degree(m1)?;
let d2 = ordinal_metric_finite_subfield_degree(m2)?;
let common = lcm(d1, d2)?;
let a1 = arf_ordinal_at_degree(m1, common)?;
let a2 = arf_ordinal_at_degree(m2, common)?;
Some(same_char2_isometry_invariant(&a1, &a2))
}
fn lcm(a: u128, b: u128) -> Option<u128> {
(a / crate::linalg::integer::gcd_u128(a, b)).checked_mul(b)
}
fn same_char2_isometry_invariant(
a1: &crate::forms::ArfInvariants,
a2: &crate::forms::ArfInvariants,
) -> bool {
a1.rank == a2.rank
&& a1.radical_dim == a2.radical_dim
&& a1.radical_anisotropic == a2.radical_anisotropic
&& (a1.radical_anisotropic || a1.arf == a2.arf)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RealWittDecomp {
pub witt_index: usize,
pub anisotropic_pos: usize,
pub anisotropic_neg: usize,
pub radical_dim: usize,
}
impl RealWittDecomp {
pub fn display(&self) -> String {
self.to_string()
}
}
impl std::fmt::Display for RealWittDecomp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"RealWittDecomp(witt_index={}, anisotropic_pos={}, anisotropic_neg={}, radical_dim={})",
self.witt_index, self.anisotropic_pos, self.anisotropic_neg, self.radical_dim,
)
}
}
pub fn witt_decompose_real(m: &Metric<Surreal>) -> Option<RealWittDecomp> {
let (p, q, r) = crate::forms::char0::surreal_signature(m)?;
let k = p.min(q);
Some(RealWittDecomp {
witt_index: k,
anisotropic_pos: p - k,
anisotropic_neg: q - k,
radical_dim: r,
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct OddWittDecomp {
pub p: u128,
pub field_order: u128,
pub witt_index: usize,
pub anisotropic_dim: usize,
pub anisotropic_disc_is_square: bool,
pub radical_dim: usize,
}
impl OddWittDecomp {
pub fn display(&self) -> String {
self.to_string()
}
}
impl std::fmt::Display for OddWittDecomp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"OddWittDecomp(p={}, field_order={}, witt_index={}, anisotropic_dim={}, anisotropic_disc_is_square={}, radical_dim={})",
self.p,
self.field_order,
self.witt_index,
self.anisotropic_dim,
self.anisotropic_disc_is_square,
self.radical_dim,
)
}
}
pub fn witt_decompose_finite_odd<F: FiniteOddField>(m: &Metric<F>) -> Option<OddWittDecomp> {
F::ensure_supported()?;
let d = as_diagonal(m)?;
let nonzero: Vec<F> = d.q.into_iter().filter(|x| !x.is_zero()).collect();
let dim = nonzero.len();
let radical_dim = m.q.len() - dim;
let det = nonzero.iter().fold(F::one(), |acc, x| acc.mul(x));
let anisotropic_dim = if dim % 2 == 1 {
1
} else {
let k = dim / 2;
let sign = if k % 2 == 1 {
F::from_int(-1)
} else {
F::one()
};
if F::is_square_value(sign.mul(&det)) {
0
} else {
2
}
};
let witt_index = (dim - anisotropic_dim) / 2;
let twist = if witt_index % 2 == 1 {
F::from_int(-1)
} else {
F::one()
};
Some(OddWittDecomp {
p: F::characteristic_prime(),
field_order: F::field_order(),
witt_index,
anisotropic_dim,
anisotropic_disc_is_square: F::is_square_value(det.mul(&twist)),
radical_dim,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::forms::arf_invariant;
use crate::scalar::Fp;
use crate::scalar::Scalar;
use std::collections::BTreeMap;
fn rsur(xs: &[i128]) -> Metric<Surreal> {
Metric::diagonal(xs.iter().map(|&x| Surreal::from_int(x)).collect())
}
fn ofp<const P: u128>(xs: &[i128]) -> Metric<Fp<P>> {
Metric::diagonal(xs.iter().map(|&x| Fp::<P>::from_int(x)).collect())
}
#[test]
fn real_isometry_is_signature_equality() {
assert_eq!(isometric_real(&rsur(&[1, -1]), &rsur(&[-1, 1])), Some(true));
assert_eq!(isometric_real(&rsur(&[1, -1]), &rsur(&[1, 1])), Some(false));
assert_eq!(isometric_real(&rsur(&[1]), &rsur(&[2])), None);
let mut b = BTreeMap::new();
b.insert((0, 1), Surreal::from_int(1));
let h = Metric::new(vec![Surreal::from_int(0), Surreal::from_int(0)], b);
assert_eq!(isometric_real(&h, &rsur(&[1, -1])), Some(true));
}
#[test]
fn rational_isometry_sees_square_classes() {
let q1 = Metric::diagonal(vec![Rational::from_int(1)]);
let q2 = Metric::diagonal(vec![Rational::from_int(2)]);
assert_eq!(isometric_rational(&q1, &q1), Some(true));
assert_eq!(isometric_rational(&q1, &q2), Some(false));
}
#[test]
fn real_witt_decomposition_splits_hyperbolics() {
let d = witt_decompose_real(&rsur(&[1, 1, 1, -1, -1])).unwrap();
assert_eq!(
d,
RealWittDecomp {
witt_index: 2,
anisotropic_pos: 1,
anisotropic_neg: 0,
radical_dim: 0,
}
);
let d = witt_decompose_real(&rsur(&[1, -1, 0])).unwrap();
assert_eq!(d.witt_index, 1);
assert_eq!((d.anisotropic_pos, d.anisotropic_neg), (0, 0));
assert_eq!(d.radical_dim, 1);
}
#[test]
fn real_witt_decomp_display_matches_bound_python_repr() {
let d = RealWittDecomp {
witt_index: 2,
anisotropic_pos: 1,
anisotropic_neg: 0,
radical_dim: 0,
};
assert_eq!(
d.to_string(),
"RealWittDecomp(witt_index=2, anisotropic_pos=1, anisotropic_neg=0, radical_dim=0)"
);
assert_eq!(d.display(), d.to_string());
}
#[test]
fn oddchar_isometry_and_witt() {
const P: u128 = 5;
let d = witt_decompose_finite_odd(&ofp::<P>(&[1, 1])).unwrap();
assert_eq!(d.anisotropic_dim, 0);
assert_eq!(d.witt_index, 1);
let d = witt_decompose_finite_odd(&ofp::<P>(&[1, 1, 1])).unwrap();
assert_eq!(d.anisotropic_dim, 1);
assert_eq!(d.witt_index, 1);
assert_eq!(
isometric_finite_odd(&ofp::<P>(&[1, 1]), &ofp::<P>(&[2, 3])),
Some(true)
);
assert_eq!(
isometric_finite_odd(&ofp::<P>(&[1, 1]), &ofp::<P>(&[1, 2])),
Some(false)
);
}
#[test]
fn oddchar_anisotropic_plane_over_f3() {
const P: u128 = 3;
let d = witt_decompose_finite_odd(&ofp::<P>(&[1, 1])).unwrap();
assert_eq!(d.anisotropic_dim, 2);
assert_eq!(d.witt_index, 0);
}
#[test]
fn odd_witt_decomp_display_matches_bound_python_repr() {
let d = OddWittDecomp {
p: 5,
field_order: 25,
witt_index: 1,
anisotropic_dim: 2,
anisotropic_disc_is_square: false,
radical_dim: 0,
};
assert_eq!(
d.to_string(),
"OddWittDecomp(p=5, field_order=25, witt_index=1, anisotropic_dim=2, anisotropic_disc_is_square=false, radical_dim=0)"
);
assert_eq!(d.display(), d.to_string());
}
#[test]
fn nimber_isometry_by_arf() {
let plane = |q0, q1| {
let mut b = BTreeMap::new();
b.insert((0, 1), Nimber(1));
Metric::new(vec![Nimber(q0), Nimber(q1)], b)
};
assert_eq!(isometric_nimber(&plane(1, 1), &plane(1, 1)), Some(true));
assert_eq!(isometric_nimber(&plane(1, 1), &plane(0, 0)), Some(false));
}
#[test]
fn nimber_cross_subfield_isometry_witness() {
use crate::scalar::nim_mul;
let plane_f4 = {
let mut b = BTreeMap::new();
b.insert((0usize, 1usize), Nimber(1));
Metric::new(vec![Nimber(2), Nimber(2)], b)
};
let alpha: u128 = 4;
let alpha_sq = nim_mul(alpha, alpha); let q_b0 = nim_mul(alpha_sq, 2); let b_b01 = alpha;
assert!(q_b0 >= 4 || b_b01 >= 4, "expected F_16 entries");
let plane_f16 = {
let mut b = BTreeMap::new();
b.insert((0usize, 1usize), Nimber(b_b01));
Metric::new(vec![Nimber(q_b0), Nimber(2)], b)
};
let a_f4_standalone = arf_invariant(&plane_f4).unwrap();
let a_f16_standalone = arf_invariant(&plane_f16).unwrap();
let _ = (a_f4_standalone.arf, a_f16_standalone.arf);
assert_eq!(
isometric_nimber(&plane_f4, &plane_f16),
Some(true),
"isometric forms (related by a basis change) must compare equal"
);
let aniso_f2 = {
let mut b = BTreeMap::new();
b.insert((0usize, 1usize), Nimber(1));
Metric::new(vec![Nimber(1), Nimber(1)], b)
};
let hyp_f2 = {
let mut b = BTreeMap::new();
b.insert((0usize, 1usize), Nimber(1));
Metric::new(vec![Nimber(0), Nimber(0)], b)
};
assert_eq!(
isometric_nimber(&aniso_f2, &hyp_f2),
Some(false),
"same-field anisotropic vs hyperbolic must remain distinguished"
);
assert_eq!(
isometric_nimber(&plane_f4, &hyp_f2),
Some(false),
"F_4 anisotropic plane must not be isometric to F_2 hyperbolic (joint m=2)"
);
let hyp_f16 = {
let mut b = BTreeMap::new();
b.insert((0usize, 1usize), Nimber(b_b01)); Metric::new(vec![Nimber(0), Nimber(0)], b)
};
assert_eq!(
isometric_nimber(&plane_f4, &hyp_f16),
Some(true),
"F_4 anisotropic plane is isometric to the F_16 hyperbolic plane (joint m=4 \
makes the obstruction vanish)"
);
}
#[test]
fn ordinal_isometry_uses_common_finite_subfield_degree() {
use crate::scalar::nim_mul;
let plane_f4 = {
let mut b = BTreeMap::new();
b.insert((0usize, 1usize), Ordinal::from_u128(1));
Metric::new(vec![Ordinal::from_u128(2), Ordinal::from_u128(2)], b)
};
let alpha: u128 = 4;
let q_b0 = nim_mul(nim_mul(alpha, alpha), 2);
let plane_f16 = {
let mut b = BTreeMap::new();
b.insert((0usize, 1usize), Ordinal::from_u128(alpha));
Metric::new(vec![Ordinal::from_u128(q_b0), Ordinal::from_u128(2)], b)
};
assert_eq!(
isometric_ordinal_finite(&plane_f4, &plane_f16),
Some(true),
"finite ordinal entries need the same joint trace degree as the nimber path"
);
}
#[test]
fn defective_radical_ignores_complement_arf() {
let mut b = BTreeMap::new();
b.insert((0, 1), Nimber(1));
let split_complement = Metric::new(vec![Nimber(0), Nimber(0), Nimber(1)], b.clone());
let anisotropic_complement = Metric::new(vec![Nimber(1), Nimber(1), Nimber(1)], b);
let a1 = arf_invariant(&split_complement).unwrap();
let a2 = arf_invariant(&anisotropic_complement).unwrap();
assert_ne!(a1.arf, a2.arf);
assert!(a1.radical_anisotropic && a2.radical_anisotropic);
assert_eq!(
isometric_nimber(&split_complement, &anisotropic_complement),
Some(true)
);
}
}