use crate::clifford::{Metric, MAX_BASIS_DIM};
use crate::forms::ArfInvariants;
use crate::scalar::{
nim_square, nim_trace, CyclicGaloisExtension, FieldExtension, Fp, Nimber, Scalar,
};
use std::collections::BTreeMap;
fn assemble_twisted_form<E: Scalar, T: Scalar>(
basis: &[E],
twist: impl Fn(&E) -> E,
trace: impl Fn(&E) -> T,
) -> Metric<T> {
let n = basis.len();
let tw: Vec<E> = basis.iter().map(&twist).collect();
let q: Vec<T> = basis
.iter()
.zip(&tw)
.map(|(e, te)| trace(&e.mul(te)))
.collect();
let mut b = BTreeMap::new();
for i in 0..n {
for j in (i + 1)..n {
let t = trace(&basis[i].mul(&tw[j]).add(&basis[j].mul(&tw[i])));
if !t.is_zero() {
b.insert((i, j), t);
}
}
}
Metric::general(q, b, BTreeMap::new())
}
fn insert_metric_block<S: Scalar>(
q: &mut [S],
b: &mut BTreeMap<(usize, usize), S>,
offset: usize,
block: Metric<S>,
) {
let (bq, bb, ba) = block.into_parts();
debug_assert!(ba.is_empty());
for (i, qi) in bq.into_iter().enumerate() {
q[offset + i] = qi;
}
for ((i, j), v) in bb {
b.insert((offset + i, offset + j), v);
}
}
pub fn trace_twisted_form<E>(k: usize) -> Metric<E::Base>
where
E: CyclicGaloisExtension,
{
assemble_twisted_form(&E::basis(), |e| e.sigma_power(k), |z| z.trace())
}
pub fn cyclic_algebra_trace_form<E>(a: &E::Base) -> Metric<E::Base>
where
E: CyclicGaloisExtension,
{
let basis = E::basis();
let n = basis.len();
let dim = n
.checked_mul(n)
.expect("cyclic algebra trace-form dimension overflowed");
assert!(
dim <= MAX_BASIS_DIM,
"cyclic_algebra_trace_form has dimension [E:F]^2={dim}, exceeding {MAX_BASIS_DIM}"
);
let mut q = vec![E::Base::zero(); dim];
let mut b = BTreeMap::new();
let line0 = assemble_twisted_form(&basis, |x| x.clone(), |z| z.trace());
insert_metric_block(&mut q, &mut b, 0, line0);
if n % 2 == 0 {
let mid = n / 2;
let middle = assemble_twisted_form(&basis, |x| x.sigma_power(mid), |z| a.mul(&z.trace()));
insert_metric_block(&mut q, &mut b, mid * n, middle);
}
for i in 1..n {
let j = n - i;
if i >= j {
continue;
}
for r in 0..n {
for s in 0..n {
let term = basis[r]
.mul(&basis[s].sigma_power(i))
.add(&basis[s].mul(&basis[r].sigma_power(j)));
let value = a.mul(&term.trace());
if !value.is_zero() {
b.insert((i * n + r, j * n + s), value);
}
}
}
}
Metric::general(q, b, BTreeMap::new())
}
pub fn transfer_diagonal<E>(entries: &[E]) -> Metric<E::Base>
where
E: CyclicGaloisExtension,
{
let basis = E::basis();
let mut result = Metric::diagonal(Vec::new());
for lambda in entries {
let block = assemble_twisted_form(&basis, |x| lambda.mul(x), |z| z.trace());
result = result.direct_sum(&block);
}
result
}
pub fn trace_form_arf<E>(k: usize) -> Option<ArfInvariants>
where
E: CyclicGaloisExtension + FieldExtension<Base = Fp<2>>,
{
trace_twisted_form::<E>(k)
.map(|x| Nimber(x.value()))
.classify()
.ok()
}
pub fn gold_form(m: usize, a: usize) -> Metric<Nimber> {
assert!(
m.is_power_of_two() && m <= 128,
"the nimbers < 2^m form a subfield only for m a power of two ≤ 128"
);
let basis: Vec<Nimber> = (0..m).map(|i| Nimber(1u128 << i)).collect();
assemble_twisted_form(
&basis,
|x| {
let mut t = x.0;
for _ in 0..a {
t = nim_square(t);
}
Nimber(t)
},
|x| Nimber(nim_trace(x.0, m as u128)),
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::scalar::{Fp, Fpn, Qq, Rational, Surcomplex};
fn r(n: i128) -> Rational {
Rational::from_int(n)
}
fn gcd(a: usize, b: usize) -> usize {
if b == 0 {
a
} else {
gcd(b, a % b)
}
}
fn eval_rational_metric(m: &Metric<Rational>, coords: &[i128]) -> Rational {
assert_eq!(m.dim(), coords.len());
let mut total = Rational::zero();
for (i, &ci) in coords.iter().enumerate() {
let x = r(ci);
total = total.add(&m.q[i].mul(&x).mul(&x));
}
for (&(i, j), bij) in &m.b {
total = total.add(&bij.mul(&r(coords[i])).mul(&r(coords[j])));
}
total
}
#[test]
fn surcomplex_twist_is_the_norm_form() {
let m = trace_twisted_form::<Surcomplex<Rational>>(1);
assert_eq!(m.q, vec![Rational::from_int(2), Rational::from_int(2)]);
assert!(m.b.is_empty());
}
#[test]
fn cyclic_trace_form_degree_two_is_literal_trd_square() {
for a in [-3i128, -1, 2, 5] {
let m = cyclic_algebra_trace_form::<Surcomplex<Rational>>(&r(a));
assert_eq!(m.q, vec![r(2), r(-2), r(2 * a), r(2 * a)]);
assert!(m.b.is_empty());
}
}
#[test]
fn cyclic_trace_form_degree_two_satisfies_cayley_hamilton_relation() {
for a in [-3i128, 2, 5] {
let m = cyclic_algebra_trace_form::<Surcomplex<Rational>>(&r(a));
for p in -1..=1 {
for q in -1..=1 {
for u in -1..=1 {
for v in -1..=1 {
let lhs = eval_rational_metric(&m, &[p, q, u, v]);
let trd = 2 * p;
let nrd = p * p + q * q - a * u * u - a * v * v;
let rhs = r(trd * trd - 2 * nrd);
assert_eq!(lhs, rhs, "a={a}, coords={:?}", [p, q, u, v]);
}
}
}
}
}
}
#[test]
fn qq_twist_uses_the_unramified_galois_basis() {
type Q9 = Qq<3, 3, 2>;
let m = trace_twisted_form::<Q9>(1);
assert_eq!(m.q.len(), 2);
assert!(m.q.iter().all(|x| !x.is_zero()));
assert!(m.q.iter().all(|x| x.valuation().is_some()));
}
#[test]
fn cyclic_trace_form_degree_three_has_hyperbolic_cross_pair() {
let t = cyclic_algebra_trace_form::<Fpn<3, 3>>(&Fp::<3>::one());
let line0 = trace_twisted_form::<Fpn<3, 3>>(0);
assert_eq!(t.dim(), 9);
assert_eq!(&t.q[..3], line0.q());
assert!(t.q[3..].iter().all(|x| x.is_zero()));
for (&(i, j), v) in line0.b() {
assert_eq!(t.b.get(&(i, j)), Some(v));
}
let t_dec = t.witt_decompose().expect("F_3 trace form decomposition");
let line_dec = line0
.witt_decompose()
.expect("F_3 line trace form decomposition");
assert_eq!(t_dec.anisotropic_dim, line_dec.anisotropic_dim);
}
#[test]
fn gold_form_over_small_fpn_matches_rank_formula() {
let f4 = trace_form_arf::<Fpn<2, 2>>(1).unwrap();
assert_eq!((f4.rank, f4.radical_dim), (0, 2));
let f8 = trace_form_arf::<Fpn<2, 3>>(1).unwrap();
assert_eq!((f8.rank, f8.radical_dim), (2, 1));
}
#[test]
fn gold_form_over_nim_subfields_matches_rank_formula() {
for m in [2usize, 4, 8] {
let a = 1usize;
let arf = gold_form(m, a).classify().unwrap();
let g = gcd(2 * a, m);
assert_eq!(
(arf.rank, arf.radical_dim),
(m - g, g),
"Gold form over F_2^{m} (a={a})"
);
}
let arf = gold_form(8, 3).classify().unwrap();
assert_eq!((arf.rank, arf.radical_dim), (6, 2));
}
#[test]
fn transfer_of_unit_form_is_the_k0_twisted_form() {
let s = transfer_diagonal::<Fpn<3, 2>>(&[Fpn::<3, 2>::one()]);
let t0 = trace_twisted_form::<Fpn<3, 2>>(0);
assert_eq!(s.q, t0.q);
assert_eq!(s.b, t0.b);
}
#[test]
fn transfer_of_a_hyperbolic_form_is_split() {
let one = Fpn::<3, 2>::one();
let hyp = transfer_diagonal::<Fpn<3, 2>>(&[one, one.neg()]);
let dec = hyp.witt_decompose().expect("Fp<3> Witt decomposition");
assert_eq!(
dec.anisotropic_dim, 0,
"transfer of a hyperbolic form splits"
);
}
#[test]
fn frobenius_reciprocity_projection_formula() {
let c = Fp::<3>::from_int(2); let lam = Fpn::<3, 2>::from_coeffs(&[1, 1]); let lhs = transfer_diagonal::<Fpn<3, 2>>(&[Fpn::<3, 2>::embed(&c).mul(&lam)]);
let base = transfer_diagonal::<Fpn<3, 2>>(&[lam]);
let scaled_q: Vec<Fp<3>> = base.q.iter().map(|x| c.mul(x)).collect();
let scaled_b: BTreeMap<(usize, usize), Fp<3>> =
base.b.iter().map(|(k, v)| (*k, c.mul(v))).collect();
assert_eq!(lhs.q, scaled_q);
assert_eq!(lhs.b, scaled_b);
}
#[test]
fn springer_odd_degree_restriction_is_injective() {
let aniso = Metric::<Fp<3>>::diagonal(vec![Fp::<3>::one(), Fp::<3>::one()]);
let base_dec = aniso.witt_decompose().expect("Fp<3> Witt decomposition");
assert_eq!(base_dec.anisotropic_dim, 2, "⟨1,1⟩ anisotropic over F_3");
let restricted =
Metric::<Fpn<3, 3>>::diagonal(vec![Fpn::<3, 3>::one(), Fpn::<3, 3>::one()]);
match restricted
.witt_decompose()
.expect("F_27 Witt decomposition")
{
crate::forms::FiniteFieldWittDecomp::Odd(d) => {
assert_eq!(
d.anisotropic_dim, 2,
"still anisotropic over F_27 ⇒ injective"
);
}
other => panic!("expected odd-characteristic decomposition, got {other:?}"),
}
}
#[test]
fn metric_map_lifts_fp2_to_nimber() {
let over_f2 = trace_twisted_form::<Fpn<2, 3>>(1);
let lifted = over_f2.map(|x| Nimber(x.value()));
assert_eq!(lifted.q.len(), over_f2.q.len());
for (i, qi) in over_f2.q.iter().enumerate() {
assert_eq!(lifted.q[i].0, qi.value());
}
assert_eq!(lifted.b.len(), over_f2.b.len());
}
}