use crate::clifford::Metric;
use crate::forms::{finite_odd_witt, hasse_invariant_finite_odd, FiniteOddField, WittClassG};
use crate::scalar::Scalar;
pub fn tensor_form<S: Scalar>(a: &Metric<S>, b: &Metric<S>) -> Option<Metric<S>> {
if !a.b.is_empty() || !a.a.is_empty() || !b.b.is_empty() || !b.a.is_empty() {
return None;
}
let mut q = Vec::with_capacity(a.q.len() * b.q.len());
for ai in &a.q {
for bj in &b.q {
q.push(ai.mul(bj));
}
}
Some(Metric::diagonal(q))
}
pub fn pfister1<S: Scalar>(a: &S) -> Metric<S> {
Metric::diagonal(vec![S::one(), a.neg()])
}
pub fn pfister<S: Scalar>(scales: &[S]) -> Metric<S> {
let mut acc = Metric::diagonal(vec![S::one()]);
for a in scales {
acc = tensor_form(&acc, &pfister1(a)).expect("Pfister factors are diagonal");
}
acc
}
pub fn in_fundamental_ideal<S: Scalar>(metric: &Metric<S>) -> Option<bool> {
if !metric.b.is_empty() || !metric.a.is_empty() {
return None;
}
Some(metric.q.iter().filter(|x| !x.is_zero()).count() % 2 == 0)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EnStaircase {
pub e0: u128,
pub e1: u128,
pub e2: i128,
pub stabilizes_at: usize,
}
pub fn e_staircase_finite_odd<F: FiniteOddField>(metric: &Metric<F>) -> Option<EnStaircase> {
let (e0, e1) = match finite_odd_witt(metric)? {
WittClassG::OddChar { e0, sclass, .. } => (e0, sclass),
_ => unreachable!("finite_odd_witt returns the OddChar variant"),
};
Some(EnStaircase {
e0,
e1,
e2: hasse_invariant_finite_odd(metric)?,
stabilizes_at: 2,
})
}
pub fn e_real(signature: i128, n: usize) -> Option<u128> {
if n >= 128 {
return None;
}
let modulus = 1i128 << n;
if signature % modulus != 0 {
return None;
}
Some((signature / modulus).rem_euclid(2) as u128)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::scalar::{Fp, Fpn};
use std::collections::BTreeMap;
fn diag<const P: u128>(qs: &[u128]) -> Metric<Fp<P>> {
Metric::diagonal(qs.iter().map(|&x| Fp::<P>::from_u128(x)).collect())
}
#[test]
fn tensor_form_multiplies_entries() {
let t = tensor_form(&diag::<5>(&[1, 2]), &diag::<5>(&[1, 2])).unwrap();
assert_eq!(
t.q,
vec![
Fp::<5>::from_u128(1),
Fp::<5>::from_u128(2),
Fp::<5>::from_u128(2),
Fp::<5>::from_u128(4)
]
);
}
#[test]
fn fundamental_ideal_requires_diagonal_representative() {
assert_eq!(in_fundamental_ideal(&diag::<5>(&[1, 2])), Some(true));
assert_eq!(in_fundamental_ideal(&diag::<5>(&[1, 2, 3])), Some(false));
let mut b = BTreeMap::new();
b.insert((0, 1), Fp::<5>::from_u128(2));
let hyperbolic = Metric::new(vec![Fp::<5>::zero(), Fp::<5>::zero()], b);
assert_eq!(in_fundamental_ideal(&hyperbolic), None);
}
#[test]
fn pfister_shapes() {
let p1 = pfister(&[Fp::<7>::from_u128(3)]);
assert_eq!(p1.q, vec![Fp::<7>::one(), Fp::<7>::from_int(-3)]);
let p2 = pfister(&[Fp::<7>::from_u128(3), Fp::<7>::from_u128(5)]);
assert_eq!(
p2.q,
vec![
Fp::<7>::one(),
Fp::<7>::from_int(-5),
Fp::<7>::from_int(-3),
Fp::<7>::from_u128(15 % 7),
]
);
}
#[test]
fn two_fold_pfister_is_hyperbolic() {
for a in 1..5u128 {
for b in 1..5u128 {
let p = pfister(&[Fp::<5>::from_u128(a), Fp::<5>::from_u128(b)]);
assert_eq!(
finite_odd_witt(&p).unwrap(),
WittClassG::oddchar_zero(5, 0), "2-fold Pfister ⟨⟨{a},{b}⟩⟩ over F_5 must be hyperbolic"
);
}
}
for a in 1..3u128 {
for b in 1..3u128 {
let p = pfister(&[Fp::<3>::from_u128(a), Fp::<3>::from_u128(b)]);
assert_eq!(finite_odd_witt(&p).unwrap(), WittClassG::oddchar_zero(3, 1));
}
}
}
#[test]
fn i_squared_is_zero_over_extension_field_f9() {
let elems: Vec<Fpn<3, 2>> = (1..9u128)
.map(|code| {
let mut c = code;
let mut coeffs = [0u128; 2];
for slot in coeffs.iter_mut() {
*slot = c % 3;
c /= 3;
}
Fpn::from_coeffs(&coeffs)
})
.filter(|x| !x.is_zero())
.collect();
for &a in &elems {
for &b in &elems {
let p = pfister(&[a, b]);
let det = p.q.iter().fold(Fpn::<3, 2>::one(), |acc, x| acc.mul(x));
assert!(
det.is_square(),
"2-fold Pfister over F_9 disc must be square"
);
}
}
}
#[test]
fn e_staircase_reuses_disc_and_hasse() {
let m = diag::<5>(&[1, 2, 3]);
let s = e_staircase_finite_odd(&m).unwrap();
assert_eq!(s.e0, 1); assert_eq!(s.e2, hasse_invariant_finite_odd(&m).unwrap());
assert_eq!(s.e2, 1); assert_eq!(s.stabilizes_at, 2);
if let WittClassG::OddChar { sclass, .. } = finite_odd_witt(&m).unwrap() {
assert_eq!(s.e1, sclass);
}
}
#[test]
fn class_ring_mul_matches_concrete_tensor_form() {
fn check<const P: u128>() {
let mut forms: Vec<Metric<Fp<P>>> = Vec::new();
for e in 1..P {
forms.push(diag::<P>(&[e]));
}
for e in 1..P {
for f in 1..P {
forms.push(diag::<P>(&[e, f]));
}
}
for a in &forms {
for b in &forms {
let lhs = finite_odd_witt(&tensor_form(a, b).unwrap()).unwrap();
let rhs = finite_odd_witt(a)
.unwrap()
.try_mul(&finite_odd_witt(b).unwrap())
.expect("tensor_form check stays inside one odd-char Witt ring");
assert_eq!(lhs, rhs, "P={P}: ring law disagrees with tensor_form");
}
}
}
check::<3>(); check::<5>(); }
#[test]
fn ring_unit_is_neutral() {
let one3 = WittClassG::oddchar_one(3, 1);
let one5 = WittClassG::oddchar_one(5, 0);
for m in [diag::<3>(&[1]), diag::<3>(&[2]), diag::<3>(&[1, 2])] {
let c = finite_odd_witt(&m).unwrap();
assert_eq!(c.try_mul(&one3).expect("same F3 Witt ring"), c);
}
for m in [diag::<5>(&[1]), diag::<5>(&[2]), diag::<5>(&[1, 2])] {
let c = finite_odd_witt(&m).unwrap();
assert_eq!(c.try_mul(&one5).expect("same F5 Witt ring"), c);
}
let sig3 = WittClassG::char0(3, 0);
assert_eq!(
sig3.try_mul(&WittClassG::char0(1, 0))
.expect("same char-0 Witt ring"),
sig3
);
assert_eq!(
WittClassG::char0(2, 0)
.try_mul(&WittClassG::char0(5, 0))
.expect("same char-0 Witt ring"),
WittClassG::Char0 { signature: 10 }
);
}
#[test]
fn real_staircase_reads_the_signature_in_binary() {
assert_eq!(e_real(4, 0), Some(0)); assert_eq!(e_real(4, 1), Some(0)); assert_eq!(e_real(4, 2), Some(1)); assert_eq!(e_real(4, 3), None); assert_eq!(e_real(6, 1), Some(1)); assert_eq!(e_real(6, 2), None); assert_eq!(e_real(-8, 3), Some(1)); }
}