use crate::clifford::Metric;
use crate::forms::arf_invariant;
use crate::scalar::{nim_degree, Nimber};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WittClassError {
GeneralBilinearMetric,
Singular {
radical_dim: usize,
radical_anisotropic: bool,
},
}
impl std::fmt::Display for WittClassError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
WittClassError::GeneralBilinearMetric => {
f.write_str("general-bilinear metric: classifier requires a pure (q, b) metric")
}
WittClassError::Singular {
radical_dim,
radical_anisotropic,
} => write!(
f,
"singular form: radical_dim={radical_dim}, radical_anisotropic={radical_anisotropic}"
),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum WittClassGError {
DifferentFields,
DifferentCharacteristics,
Char2NotARing,
}
impl std::fmt::Display for WittClassGError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
WittClassGError::DifferentFields => {
f.write_str("Witt classes are from different finite fields")
}
WittClassGError::DifferentCharacteristics => {
f.write_str("cannot combine Witt classes across different characteristics")
}
WittClassGError::Char2NotARing => f.write_str(
"char-2 quadratic Witt classes form a module, not a ring; ring multiplication is undefined",
),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WittClass {
field_degree: u128,
arf: u128,
}
impl WittClass {
pub fn new(field_degree: u128, arf: u128) -> Option<Self> {
if field_degree == 0 || arf > 1 {
return None;
}
Some(WittClass { field_degree, arf })
}
pub fn field_degree(&self) -> u128 {
self.field_degree
}
pub fn arf(&self) -> u128 {
self.arf
}
pub fn zero_f2() -> Self {
WittClass {
field_degree: 1,
arf: 0,
}
}
#[deprecated(since = "0.0.0", note = "use zero_f2() for clarity")]
pub fn zero() -> Self {
Self::zero_f2()
}
pub fn zero_over(field_degree: u128) -> Self {
assert!(field_degree > 0, "char-2 field degree must be positive");
WittClass {
field_degree,
arf: 0,
}
}
pub fn try_from_metric(metric: &Metric<Nimber>) -> Result<Self, WittClassError> {
let arf = arf_invariant(metric).ok_or(WittClassError::GeneralBilinearMetric)?;
if arf.radical_dim != 0 {
return Err(WittClassError::Singular {
radical_dim: arf.radical_dim,
radical_anisotropic: arf.radical_anisotropic,
});
}
Ok(WittClass {
field_degree: nimber_metric_field_degree(metric),
arf: arf.arf,
})
}
pub fn try_add(&self, other: &WittClass) -> Result<WittClass, WittClassGError> {
if self.field_degree != other.field_degree {
return Err(WittClassGError::DifferentFields);
}
Ok(WittClass {
field_degree: self.field_degree,
arf: self.arf ^ other.arf,
})
}
pub fn neg(&self) -> WittClass {
*self
}
pub fn is_hyperbolic(&self) -> bool {
self.arf == 0
}
pub fn anisotropic_dim(&self) -> usize {
if self.arf == 0 {
0
} else {
2
}
}
pub fn display(&self) -> String {
self.to_string()
}
}
impl std::fmt::Display for WittClass {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let field = format!("F_2^{}", self.field_degree);
if self.arf == 0 {
write!(f, "0 (hyperbolic class over {field})")
} else {
write!(f, "[anisotropic plane] (Arf 1 over {field})")
}
}
}
fn nimber_metric_field_degree(metric: &Metric<Nimber>) -> u128 {
metric
.q
.iter()
.map(|x| nim_degree(x.0))
.chain(metric.b.values().map(|x| nim_degree(x.0)))
.max()
.unwrap_or(1)
}
impl std::ops::Neg for WittClass {
type Output = WittClass;
fn neg(self) -> WittClass {
WittClass::neg(&self)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WittClassG {
Char0 {
signature: i128,
},
OddChar {
field_order: u128,
kappa: u128,
e0: u128,
sclass: u128,
},
Char2 {
field_degree: u128,
arf: u128,
},
}
impl WittClassG {
pub fn char0(p: usize, q: usize) -> Self {
WittClassG::Char0 {
signature: p as i128 - q as i128,
}
}
pub fn try_char2_from_metric(metric: &Metric<Nimber>) -> Result<Self, WittClassError> {
let class = WittClass::try_from_metric(metric)?;
Ok(WittClassG::Char2 {
field_degree: class.field_degree,
arf: class.arf,
})
}
pub fn oddchar_zero(field_order: u128, kappa: u128) -> Self {
WittClassG::OddChar {
field_order,
kappa,
e0: 0,
sclass: 0,
}
}
pub fn try_add(&self, other: &WittClassG) -> Result<WittClassG, WittClassGError> {
match (*self, *other) {
(WittClassG::Char0 { signature: a }, WittClassG::Char0 { signature: b }) => {
Ok(WittClassG::Char0 { signature: a + b })
}
(
WittClassG::Char2 {
field_degree: ma,
arf: a,
},
WittClassG::Char2 {
field_degree: mb,
arf: b,
},
) => {
if ma != mb {
return Err(WittClassGError::DifferentFields);
}
Ok(WittClassG::Char2 {
field_degree: ma,
arf: a ^ b,
})
}
(
WittClassG::OddChar {
field_order: qa,
kappa: ka,
e0: e0a,
sclass: sa,
},
WittClassG::OddChar {
field_order: qb,
kappa: kb,
e0: e0b,
sclass: sb,
},
) => {
if qa != qb || ka != kb {
return Err(WittClassGError::DifferentFields);
}
let twist = if e0a & e0b == 1 { ka } else { 0 };
Ok(WittClassG::OddChar {
field_order: qa,
kappa: ka,
e0: e0a ^ e0b,
sclass: sa ^ sb ^ twist,
})
}
_ => Err(WittClassGError::DifferentCharacteristics),
}
}
pub fn try_mul(&self, other: &WittClassG) -> Result<WittClassG, WittClassGError> {
match (*self, *other) {
(WittClassG::Char0 { signature: a }, WittClassG::Char0 { signature: b }) => {
Ok(WittClassG::Char0 { signature: a * b })
}
(
WittClassG::OddChar {
field_order: qa,
kappa: ka,
e0: e0a,
sclass: sa,
},
WittClassG::OddChar {
field_order: qb,
kappa: kb,
e0: e0b,
sclass: sb,
},
) => {
if qa != qb || ka != kb {
return Err(WittClassGError::DifferentFields);
}
if ka == 1 {
let za = (e0a + 2 * sa) as i128;
let zb = (e0b + 2 * sb) as i128;
let z = (za * zb).rem_euclid(4);
Ok(WittClassG::OddChar {
field_order: qa,
kappa: 1,
e0: (z & 1) as u128,
sclass: ((z >> 1) & 1) as u128,
})
} else {
let (a1, b1) = (e0a ^ sa, sa);
let (a2, b2) = (e0b ^ sb, sb);
let ar = (a1 & a2) ^ (b1 & b2);
let br = (a1 & b2) ^ (a2 & b1);
Ok(WittClassG::OddChar {
field_order: qa,
kappa: 0,
e0: ar ^ br,
sclass: br,
})
}
}
(WittClassG::Char2 { .. }, WittClassG::Char2 { .. }) => {
Err(WittClassGError::Char2NotARing)
}
_ => Err(WittClassGError::DifferentCharacteristics),
}
}
pub fn oddchar_one(field_order: u128, kappa: u128) -> Self {
WittClassG::OddChar {
field_order,
kappa,
e0: 1,
sclass: 0,
}
}
pub fn display(&self) -> String {
self.to_string()
}
}
impl std::fmt::Display for WittClassG {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
WittClassG::Char0 { signature } => write!(f, "W(R): signature {signature}"),
WittClassG::OddChar {
field_order,
kappa,
e0,
sclass,
} => write!(
f,
"W(F_{field_order}): e0 {e0} sclass {sclass} (kappa {kappa})"
),
WittClassG::Char2 { field_degree, arf } => {
write!(f, "W_q(F_2^{field_degree}): arf {arf}")
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::BTreeMap;
fn metric(qs: &[u128], bs: &[((usize, usize), u128)]) -> Metric<Nimber> {
let q = qs.iter().map(|&x| Nimber(x)).collect();
let mut b = BTreeMap::new();
for &((i, j), v) in bs {
b.insert((i, j), Nimber(v));
}
Metric::new(q, b)
}
#[test]
fn hyperbolic_is_identity_anisotropic_is_order_two() {
let h = WittClass::try_from_metric(&metric(&[0, 0], &[((0, 1), 1)]))
.expect("hyperbolic plane is nonsingular"); let a = WittClass::try_from_metric(&metric(&[1, 1], &[((0, 1), 1)]))
.expect("anisotropic plane is nonsingular"); assert!(h.is_hyperbolic());
assert!(!a.is_hyperbolic());
assert_eq!(h, WittClass::zero_f2());
assert_eq!(a.anisotropic_dim(), 2);
assert_eq!(a.try_add(&a), Ok(WittClass::zero_f2()));
assert_eq!(a.try_add(&h), Ok(a)); }
#[test]
fn group_law_is_xor_of_arf() {
let h = WittClass::zero_f2();
let a = WittClass {
field_degree: 1,
arf: 1,
};
assert_eq!(a.try_add(&a), Ok(h));
assert_eq!(a.try_add(&h), Ok(a));
assert_eq!(h.try_add(&h), Ok(h));
assert_eq!(-a, a);
let am = metric(&[1, 1], &[((0, 1), 1)]);
let combined = WittClass::try_from_metric(&am.direct_sum(&am))
.expect("orthogonal sum of nonsingular planes is nonsingular");
assert_eq!(combined, a.try_add(&a).unwrap()); }
#[test]
fn witt_class_over_f4() {
let aniso = WittClass::try_from_metric(&metric(&[2, 2], &[((0, 1), 1)]))
.expect("F4 anisotropic plane is nonsingular");
let split = WittClass::try_from_metric(&metric(&[2, 3], &[((0, 1), 1)]))
.expect("F4 split plane is nonsingular");
assert_eq!(aniso.field_degree, 2);
assert_eq!(split.field_degree, 2);
assert_eq!(aniso.arf, 1);
assert_eq!(split.arf, 0);
assert!(split.is_hyperbolic());
assert_eq!(aniso.try_add(&split), Ok(aniso));
}
#[test]
fn cross_field_char2_witt_addition_is_rejected() {
let f2_aniso_metric = metric(&[1, 1], &[((0, 1), 1)]);
let f4_aniso_metric = metric(&[2, 2], &[((0, 1), 1)]);
let f2_aniso = WittClass::try_from_metric(&f2_aniso_metric)
.expect("F2 anisotropic plane is nonsingular");
let f4_aniso = WittClass::try_from_metric(&f4_aniso_metric)
.expect("F4 anisotropic plane is nonsingular");
assert_eq!(
(f2_aniso.field_degree, f2_aniso.arf),
(1, 1),
"the F2 plane has Arf 1 over F2"
);
assert_eq!(
(f4_aniso.field_degree, f4_aniso.arf),
(2, 1),
"the F4 plane has Arf 1 over F4"
);
assert!(f2_aniso.try_add(&f4_aniso).is_err());
assert!(WittClassG::try_char2_from_metric(&f2_aniso_metric)
.unwrap()
.try_add(&WittClassG::try_char2_from_metric(&f4_aniso_metric).unwrap())
.is_err());
let summed = f2_aniso_metric.direct_sum(&f4_aniso_metric);
let re_evaluated = arf_invariant(&summed).expect("direct sum is a nimber metric");
assert_eq!(
re_evaluated.arf, 1,
"bare XOR would predict 0, but the sum re-evaluated over F4 has Arf 1"
);
}
#[test]
fn singular_forms_are_not_silently_projected_to_witt_classes() {
let defective = metric(&[0, 0, 1], &[((0, 1), 1)]);
assert_eq!(
WittClass::try_from_metric(&defective),
Err(WittClassError::Singular {
radical_dim: 1,
radical_anisotropic: true,
})
);
assert!(WittClassG::try_char2_from_metric(&defective).is_err());
}
}