use crate::clifford::Metric;
use crate::forms::FiniteChar2Field;
use crate::scalar::{
nim_add, nim_inv, nim_mul, nim_trace, ordinal_common_finite_subfield_degree, Fpn, Nimber,
Ordinal, Scalar,
};
use std::collections::BTreeMap;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OrthogonalType {
OPlus,
OMinus,
}
impl fmt::Display for OrthogonalType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
OrthogonalType::OPlus => f.write_str("O+"),
OrthogonalType::OMinus => f.write_str("O-"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ArfInvariants {
pub arf: u128,
pub rank: usize,
pub radical_dim: usize,
pub radical_anisotropic: bool,
}
impl ArfInvariants {
pub fn o_type(&self) -> OrthogonalType {
if self.arf == 0 {
OrthogonalType::OPlus
} else {
OrthogonalType::OMinus
}
}
pub fn display(&self) -> String {
self.to_string()
}
}
impl fmt::Display for ArfInvariants {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"ArfInvariants(arf={}, {}, rank={}, radical_dim={}, radical_anisotropic={})",
self.arf,
self.o_type(),
self.rank,
self.radical_dim,
self.radical_anisotropic,
)
}
}
fn above(i: usize) -> u128 {
if i >= 127 {
0
} else {
(!0u128) << (i + 1)
}
}
fn q_of(v: u128, qd: &[bool], bmat: &[u128]) -> bool {
let mut acc = false;
let mut vv = v;
while vv != 0 {
let i = vv.trailing_zeros() as usize;
vv &= vv - 1;
if qd[i] {
acc ^= true;
}
let inter = bmat[i] & v & above(i);
if inter.count_ones() & 1 == 1 {
acc ^= true;
}
}
acc
}
fn b_of(u: u128, v: u128, bmat: &[u128]) -> bool {
let mut acc = false;
let mut uu = u;
while uu != 0 {
let i = uu.trailing_zeros() as usize;
uu &= uu - 1;
if (bmat[i] & v).count_ones() & 1 == 1 {
acc ^= true;
}
}
acc
}
pub fn arf_f2(n: usize, qd: &[bool], bmat: &[u128]) -> ArfInvariants {
assert!(
n <= 128,
"arf_f2 uses u128 bitmasks, so n must be at most 128"
);
assert!(
qd.len() >= n && bmat.len() >= n,
"arf_f2 needs qd and bmat entries for every basis vector"
);
let mut vectors: Vec<u128> = (0..n).map(|i| 1u128 << i).collect();
let mut arf = false;
let mut pairs = 0usize;
let mut radical: Vec<u128> = Vec::new();
while let Some(a) = vectors.pop() {
if let Some(pos) = vectors.iter().position(|&w| b_of(a, w, bmat)) {
let bb = vectors.swap_remove(pos);
for w in vectors.iter_mut() {
let mut nw = *w;
if b_of(*w, bb, bmat) {
nw ^= a;
}
if b_of(*w, a, bmat) {
nw ^= bb;
}
*w = nw;
}
if q_of(a, qd, bmat) && q_of(bb, qd, bmat) {
arf ^= true;
}
pairs += 1;
} else {
radical.push(a); }
}
let radical_anisotropic = radical.iter().any(|&v| q_of(v, qd, bmat));
ArfInvariants {
arf: arf as u128,
rank: 2 * pairs,
radical_dim: radical.len(),
radical_anisotropic,
}
}
pub(crate) fn min_field_degree(max_val: u128) -> u128 {
let mut m = 1u128; loop {
if m >= 128 {
return 128;
}
if max_val < (1u128 << m) {
return m;
}
m <<= 1;
}
}
fn vscale(c: u128, v: &[u128]) -> Vec<u128> {
v.iter().map(|&x| nim_mul(c, x)).collect()
}
fn vadd(u: &[u128], v: &[u128]) -> Vec<u128> {
u.iter().zip(v).map(|(&a, &b)| nim_add(a, b)).collect()
}
fn vscale_field<F: Scalar>(c: &F, v: &[F]) -> Vec<F> {
v.iter().map(|x| c.mul(x)).collect()
}
fn vadd_field<F: Scalar>(u: &[F], v: &[F]) -> Vec<F> {
u.iter().zip(v).map(|(a, b)| a.add(b)).collect()
}
fn qf(v: &[u128], q: &[u128], bmat: &[Vec<u128>]) -> u128 {
let n = v.len();
let mut acc = 0u128;
for i in 0..n {
acc ^= nim_mul(nim_mul(v[i], v[i]), q[i]);
for j in (i + 1)..n {
acc ^= nim_mul(nim_mul(v[i], v[j]), bmat[i][j]);
}
}
acc
}
fn bf(u: &[u128], v: &[u128], bmat: &[Vec<u128>]) -> u128 {
let n = u.len();
let mut acc = 0u128;
for i in 0..n {
for j in (i + 1)..n {
let cross = nim_add(nim_mul(u[i], v[j]), nim_mul(u[j], v[i]));
acc ^= nim_mul(cross, bmat[i][j]);
}
}
acc
}
fn qf_field<F: Scalar>(v: &[F], q: &[F], bmat: &[Vec<F>]) -> F {
let n = v.len();
let mut acc = F::zero();
for i in 0..n {
acc = acc.add(&v[i].mul(&v[i]).mul(&q[i]));
for j in (i + 1)..n {
acc = acc.add(&v[i].mul(&v[j]).mul(&bmat[i][j]));
}
}
acc
}
fn bf_field<F: Scalar>(u: &[F], v: &[F], bmat: &[Vec<F>]) -> F {
let n = u.len();
let mut acc = F::zero();
for i in 0..n {
for j in (i + 1)..n {
let cross = u[i].mul(&v[j]).add(&u[j].mul(&v[i]));
acc = acc.add(&cross.mul(&bmat[i][j]));
}
}
acc
}
fn arf_char2_core<F>(
metric: &Metric<F>,
trace_to_f2: impl Fn(&F) -> Option<u128>,
) -> Option<ArfInvariants>
where
F: Scalar,
{
if !metric.a.is_empty() {
return None;
}
let n = metric.q.len();
let q = metric.q.clone();
let mut bmat = vec![vec![F::zero(); n]; n];
for (&(i, j), v) in &metric.b {
bmat[i][j] = v.clone();
bmat[j][i] = v.clone();
}
let mut vectors: Vec<Vec<F>> = (0..n)
.map(|i| {
let mut e = vec![F::zero(); n];
e[i] = F::one();
e
})
.collect();
let mut s = F::zero();
let mut pairs = 0usize;
let mut radical_dim = 0usize;
let mut radical_anisotropic = false;
while let Some(a) = vectors.pop() {
if let Some(pos) = vectors
.iter()
.position(|w| !bf_field(&a, w, &bmat).is_zero())
{
let braw = vectors.swap_remove(pos);
let c = bf_field(&a, &braw, &bmat);
let c_inv = c.inv()?;
let b = vscale_field(&c_inv, &braw); for w in vectors.iter_mut() {
let wb = bf_field(w, &b, &bmat);
let wa = bf_field(w, &a, &bmat);
let mut nw = w.clone();
if !wb.is_zero() {
nw = vadd_field(&nw, &vscale_field(&wb, &a));
}
if !wa.is_zero() {
nw = vadd_field(&nw, &vscale_field(&wa, &b));
}
*w = nw;
}
let qa = qf_field(&a, &q, &bmat);
let qb = qf_field(&b, &q, &bmat);
s = s.add(&qa.mul(&qb));
pairs += 1;
} else {
radical_dim += 1;
if !qf_field(&a, &q, &bmat).is_zero() {
radical_anisotropic = true;
}
}
}
let arf = trace_to_f2(&s)?;
Some(ArfInvariants {
arf,
rank: 2 * pairs,
radical_dim,
radical_anisotropic,
})
}
pub(crate) fn nimber_metric_max_val(metric: &Metric<Nimber>) -> u128 {
let mut maxv = metric.q.iter().map(|x| x.0).max().unwrap_or(0);
for v in metric.b.values() {
maxv = maxv.max(v.0);
}
maxv
}
pub fn arf_nimber(metric: &Metric<Nimber>) -> Option<ArfInvariants> {
let maxv = nimber_metric_max_val(metric);
arf_nimber_at_degree(metric, min_field_degree(maxv))
}
pub(crate) fn arf_nimber_at_degree(metric: &Metric<Nimber>, m: u128) -> Option<ArfInvariants> {
if !metric.a.is_empty() {
return None;
}
let n = metric.q.len();
let q: Vec<u128> = metric.q.iter().map(|x| x.0).collect();
let mut bmat = vec![vec![0u128; n]; n];
for (&(i, j), v) in &metric.b {
bmat[i][j] = v.0;
bmat[j][i] = v.0;
}
let mut vectors: Vec<Vec<u128>> = (0..n)
.map(|i| {
let mut e = vec![0u128; n];
e[i] = 1;
e
})
.collect();
let mut s = 0u128; let mut pairs = 0usize;
let mut radical_dim = 0usize;
let mut radical_anisotropic = false;
while let Some(a) = vectors.pop() {
if let Some(pos) = vectors.iter().position(|w| bf(&a, w, &bmat) != 0) {
let braw = vectors.swap_remove(pos);
let c = bf(&a, &braw, &bmat);
let b = vscale(nim_inv(c)?, &braw); for w in vectors.iter_mut() {
let wb = bf(w, &b, &bmat);
let wa = bf(w, &a, &bmat);
let mut nw = w.clone();
if wb != 0 {
nw = vadd(&nw, &vscale(wb, &a));
}
if wa != 0 {
nw = vadd(&nw, &vscale(wa, &b));
}
*w = nw;
}
s ^= nim_mul(qf(&a, &q, &bmat), qf(&b, &q, &bmat));
pairs += 1;
} else {
radical_dim += 1;
if qf(&a, &q, &bmat) != 0 {
radical_anisotropic = true;
}
}
}
let arf = nim_trace(s, m);
Some(ArfInvariants {
arf,
rank: 2 * pairs,
radical_dim,
radical_anisotropic,
})
}
pub fn arf_char2<F: FiniteChar2Field>(metric: &Metric<F>) -> Option<ArfInvariants> {
F::ensure_supported()?;
arf_char2_core(metric, |x| Some(F::artin_schreier_class(*x)))
}
pub fn arf_fpn_char2<const P: u128, const N: usize>(
metric: &Metric<Fpn<P, N>>,
) -> Option<ArfInvariants> {
if P != 2 || !Fpn::<P, N>::is_supported_field() {
return None;
}
use crate::scalar::FieldExtension;
arf_char2_core(metric, |x| Some(x.trace().value()))
}
pub fn arf_invariant(metric: &Metric<Nimber>) -> Option<ArfInvariants> {
arf_nimber(metric)
}
fn ordinal_trace_to_f2_at_degree(x: &Ordinal, degree: u128) -> Option<u128> {
let mut acc = Ordinal::zero();
let mut y = x.clone();
for i in 0..degree {
acc = acc.add(&y);
if i + 1 != degree {
y = y.nim_mul(&y)?;
}
}
match acc.as_finite()? {
0 => Some(0),
1 => Some(1),
_ => None,
}
}
pub(crate) fn ordinal_to_nimber_metric(metric: &Metric<Ordinal>) -> Option<Metric<Nimber>> {
if !metric.a.is_empty() {
return None;
}
let q = metric
.q
.iter()
.map(|x| x.as_finite().map(Nimber))
.collect::<Option<Vec<_>>>()?;
let b = metric
.b
.iter()
.map(|(&(i, j), x)| x.as_finite().map(|v| ((i, j), Nimber(v))))
.collect::<Option<BTreeMap<_, _>>>()?;
Some(Metric::new(q, b))
}
pub fn ordinal_metric_finite_subfield_degree(metric: &Metric<Ordinal>) -> Option<u128> {
if !metric.a.is_empty() {
return None;
}
ordinal_common_finite_subfield_degree(metric.q.iter().chain(metric.b.values()))
}
pub(crate) fn arf_ordinal_at_degree(
metric: &Metric<Ordinal>,
degree: u128,
) -> Option<ArfInvariants> {
if !metric.a.is_empty() {
return None;
}
let metric_degree = ordinal_metric_finite_subfield_degree(metric)?;
if !degree.is_multiple_of(metric_degree) {
return None;
}
arf_char2_core(metric, |x| ordinal_trace_to_f2_at_degree(x, degree))
}
pub fn arf_ordinal_finite(metric: &Metric<Ordinal>) -> Option<ArfInvariants> {
if !metric.a.is_empty() {
return None;
}
if let Some(nim) = ordinal_to_nimber_metric(metric) {
return arf_nimber(&nim);
}
let degree = ordinal_metric_finite_subfield_degree(metric)?;
arf_ordinal_at_degree(metric, degree)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::scalar::{Fp, Fpn, Ordinal};
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)
}
fn b1(pairs: &[(usize, usize)]) -> Vec<((usize, usize), u128)> {
pairs.iter().map(|&p| (p, 1)).collect()
}
fn metric_field<F: Scalar>(qs: &[F], bs: &[((usize, usize), F)]) -> Metric<F> {
let mut b = BTreeMap::new();
for ((i, j), v) in bs {
b.insert((*i, *j), v.clone());
}
Metric::new(qs.to_vec(), b)
}
#[test]
fn hyperbolic_plane_is_o_plus() {
let r = arf_invariant(&metric(&[0, 0], &b1(&[(0, 1)]))).unwrap();
assert_eq!(
(r.arf, r.rank, r.radical_dim, r.o_type()),
(0, 2, 0, OrthogonalType::OPlus)
);
}
#[test]
fn anisotropic_plane_is_o_minus() {
let r = arf_invariant(&metric(&[1, 1], &b1(&[(0, 1)]))).unwrap();
assert_eq!((r.arf, r.rank, r.o_type()), (1, 2, OrthogonalType::OMinus));
}
#[test]
fn the_two_planes_are_distinguished() {
let h = arf_invariant(&metric(&[0, 0], &b1(&[(0, 1)]))).unwrap();
let a = arf_invariant(&metric(&[1, 1], &b1(&[(0, 1)]))).unwrap();
assert_ne!(h.arf, a.arf); }
#[test]
fn arf_is_additive_over_orthogonal_sum() {
let hh = arf_invariant(&metric(&[0, 0, 0, 0], &b1(&[(0, 1), (2, 3)]))).unwrap();
let ha = arf_invariant(&metric(&[0, 0, 1, 1], &b1(&[(0, 1), (2, 3)]))).unwrap();
let aa = arf_invariant(&metric(&[1, 1, 1, 1], &b1(&[(0, 1), (2, 3)]))).unwrap();
assert_eq!((hh.arf, hh.rank), (0, 4));
assert_eq!((ha.arf, ha.rank), (1, 4));
assert_eq!((aa.arf, aa.rank), (0, 4)); }
#[test]
fn arf_additive_over_graded_tensor() {
let a = metric(&[1, 1], &b1(&[(0, 1)])); let h = metric(&[0, 0], &b1(&[(0, 1)])); let aa = arf_invariant(&a.direct_sum(&a)).unwrap();
let hh = arf_invariant(&h.direct_sum(&h)).unwrap();
let ah = arf_invariant(&a.direct_sum(&h)).unwrap();
assert_eq!(aa.arf, 0); assert_eq!(hh.arf, 0); assert_eq!(ah.arf, 1); assert_eq!((aa.rank, hh.rank, ah.rank), (4, 4, 4));
}
#[test]
fn radical_is_detected() {
let r = arf_invariant(&metric(&[0, 0, 1], &b1(&[(0, 1)]))).unwrap();
assert_eq!(
(r.rank, r.radical_dim, r.radical_anisotropic, r.arf),
(2, 1, true, 0)
);
}
#[test]
fn f4_forms_via_trace() {
let r1 = arf_invariant(&metric(&[2, 3], &b1(&[(0, 1)]))).unwrap();
assert_eq!(
(r1.arf, r1.o_type(), r1.rank),
(0, OrthogonalType::OPlus, 2)
);
let r2 = arf_invariant(&metric(&[2, 2], &b1(&[(0, 1)]))).unwrap();
assert_eq!(
(r2.arf, r2.o_type(), r2.rank),
(1, OrthogonalType::OMinus, 2)
);
}
#[test]
#[allow(clippy::type_complexity)] fn generic_char2_agrees_with_f2_bitmask() {
let cases: &[(&[u128], &[(usize, usize)])] = &[
(&[0, 0], &[(0, 1)]),
(&[1, 1], &[(0, 1)]),
(&[0, 0, 1], &[(0, 1)]),
(&[1, 0, 1, 1], &[(0, 1), (2, 3)]),
];
for (qs, ps) in cases {
let qf: Vec<Fp<2>> = qs.iter().map(|&x| Fp::<2>::from_u128(x)).collect();
let bf: Vec<((usize, usize), Fp<2>)> =
ps.iter().map(|&p| (p, Fp::<2>::one())).collect();
let general = arf_char2(&metric_field(&qf, &bf)).unwrap();
let n = qs.len();
let qd: Vec<bool> = qs.iter().map(|&x| x == 1).collect();
let mut bmat = vec![0u128; n];
for &(i, j) in *ps {
bmat[i] |= 1 << j;
bmat[j] |= 1 << i;
}
assert_eq!(general, arf_f2(n, &qd, &bmat), "mismatch on q={qs:?}");
}
}
#[test]
fn f8_forms_use_the_absolute_trace() {
type F8 = Fpn<2, 3>;
let a = F8::generator();
let one = F8::one();
let m = metric_field(&[a, a], &[((0, 1), one)]);
let r = arf_char2(&m).unwrap();
assert_eq!(r.rank, 2);
assert_eq!(r.radical_dim, 0);
assert_eq!(r.arf, F8::artin_schreier_class(a.mul(&a)));
let doubled = m.direct_sum(&m);
assert_eq!(arf_char2(&doubled).unwrap().arf, 0);
}
#[test]
fn f8_zero_count_matches_arf_for_planes() {
type F8 = Fpn<2, 3>;
let elems: Vec<F8> = (0..F8::field_order()).map(F8::from_index).collect();
let planes = [
metric_field(&[F8::zero(), F8::zero()], &[((0, 1), F8::one())]),
metric_field(&[F8::generator(), F8::generator()], &[((0, 1), F8::one())]),
];
for m in planes {
let r = arf_char2(&m).unwrap();
let q0 = m.q[0];
let q1 = m.q[1];
let b01 = m.b[&(0, 1)];
let zeros = elems
.iter()
.flat_map(|&x| elems.iter().map(move |&y| (x, y)))
.filter(|&(x, y)| {
x.mul(&x)
.mul(&q0)
.add(&y.mul(&y).mul(&q1))
.add(&x.mul(&y).mul(&b01))
.is_zero()
})
.count() as i128;
let q = F8::field_order() as i128;
let expected = if r.arf == 0 { q + (q - 1) } else { q - (q - 1) };
assert_eq!(zeros, expected, "wrong zero count for {r:?}");
}
}
#[test]
fn ordinal_f64_forms_use_the_absolute_trace() {
let w = Ordinal::omega();
let one = Ordinal::one();
let m = metric_field(&[w.clone(), w.clone()], &[((0, 1), one)]);
let r = arf_ordinal_finite(&m).unwrap();
assert_eq!(r.rank, 2);
assert_eq!(r.radical_dim, 0);
assert_eq!(ordinal_metric_finite_subfield_degree(&m), Some(6));
assert_eq!(r.arf, ordinal_trace_to_f2_at_degree(&w.mul(&w), 6).unwrap());
let higher = Metric::diagonal(vec![Ordinal::omega_pow(Ordinal::omega())]);
assert_eq!(ordinal_metric_finite_subfield_degree(&higher), Some(20));
assert!(arf_ordinal_finite(&higher).is_some());
}
#[test]
fn ordinal_detector_extends_past_f64_window() {
let chi5 = Ordinal::omega_pow(Ordinal::omega());
let mut b = BTreeMap::new();
b.insert((0usize, 1usize), chi5.clone());
let m = Metric::new(vec![Ordinal::zero(), Ordinal::zero()], b);
let r = arf_ordinal_finite(&m).unwrap();
assert_eq!(ordinal_metric_finite_subfield_degree(&m), Some(20));
assert_eq!((r.arf, r.rank, r.radical_dim), (0, 2, 0));
}
#[test]
fn ordinal_detector_rejects_past_the_staged_segment() {
let outside = Metric::diagonal(vec![Ordinal::omega_pow(Ordinal::omega_pow(
Ordinal::omega(),
))]);
assert_eq!(ordinal_metric_finite_subfield_degree(&outside), None);
assert_eq!(arf_ordinal_finite(&outside), None);
}
#[test]
fn arf_rejects_general_bilinear_metrics() {
let mut a = BTreeMap::new();
a.insert((0, 1), Nimber(1));
let m = Metric::general(vec![Nimber(1), Nimber(1)], BTreeMap::new(), a);
assert_eq!(arf_invariant(&m), None);
}
#[test]
#[allow(clippy::type_complexity)] fn general_agrees_with_f2_bitmask() {
let cases: &[(&[u128], &[(usize, usize)])] = &[
(&[0, 0], &[(0, 1)]),
(&[1, 1], &[(0, 1)]),
(&[0, 0, 1], &[(0, 1)]),
(&[1, 0, 1, 1], &[(0, 1), (2, 3)]),
(&[1, 1, 1, 1, 0], &[(0, 1), (2, 3)]),
];
for (qs, ps) in cases {
let general = arf_nimber(&metric(qs, &b1(ps))).unwrap();
let n = qs.len();
let qd: Vec<bool> = qs.iter().map(|&x| x == 1).collect();
let mut bmat = vec![0u128; n];
for &(i, j) in *ps {
bmat[i] |= 1 << j;
bmat[j] |= 1 << i;
}
assert_eq!(general, arf_f2(n, &qd, &bmat), "mismatch on q={:?}", qs);
}
}
#[test]
#[should_panic(expected = "at most 128")]
fn arf_f2_rejects_dimension_past_u128_bitmask() {
arf_f2(129, &[false; 129], &[0u128; 129]);
}
#[test]
#[should_panic(expected = "entries for every basis vector")]
fn arf_f2_rejects_mismatched_lengths() {
arf_f2(3, &[false, false], &[0u128, 0u128, 0u128]);
}
}