use super::*;
use crate::clifford::Metric;
use crate::forms::WittClassG;
use crate::scalar::{Fp, Fpn, Scalar};
fn diag<const P: u128>(qs: &[u128]) -> Metric<Fp<P>> {
Metric::diagonal(qs.iter().map(|&x| Fp::<P>::from_u128(x)).collect())
}
#[test]
fn euler_criterion_matches_brute_force() {
for p_elt in 0..7u128 {
let x = Fp::<7>::from_u128(p_elt);
let brute = (0..7).any(|y| Fp::<7>::from_u128(y).mul(&Fp::<7>::from_u128(y)) == x);
assert_eq!(is_square::<7>(x), brute, "x = {p_elt} mod 7");
}
for p_elt in 0..5u128 {
let x = Fp::<5>::from_u128(p_elt);
let brute = (0..5).any(|y| Fp::<5>::from_u128(y).mul(&Fp::<5>::from_u128(y)) == x);
assert_eq!(is_square::<5>(x), brute, "x = {p_elt} mod 5");
}
}
#[test]
fn discriminant_distinguishes_planes_over_f3() {
assert!(
classify_finite_odd(&diag::<3>(&[1, 1]))
.unwrap()
.disc_is_square
);
assert!(
!classify_finite_odd(&diag::<3>(&[1, 2]))
.unwrap()
.disc_is_square
);
}
#[test]
fn invalid_moduli_are_rejected() {
assert!(classify_finite_odd(&diag::<2>(&[1, 1])).is_none());
assert!(std::panic::catch_unwind(|| diag::<9>(&[1, 1])).is_err());
assert!(std::panic::catch_unwind(|| is_square::<2>(Fp::<2>::from_u128(1))).is_err());
assert!(std::panic::catch_unwind(|| is_square::<9>(Fp::<9>::from_u128(1))).is_err());
}
#[test]
fn hasse_is_trivial_over_finite_fields() {
for a in 1..5u128 {
for b in 1..5u128 {
assert_eq!(
hilbert_symbol::<5>(Fp::<5>::from_u128(a), Fp::<5>::from_u128(b)),
1
);
}
}
for a in 1..7u128 {
for b in 1..7u128 {
assert_eq!(
hilbert_symbol::<7>(Fp::<7>::from_u128(a), Fp::<7>::from_u128(b)),
1
);
}
}
assert_eq!(
hasse_invariant_finite_odd(&diag::<5>(&[1, 2, 3, 4])).unwrap(),
1
);
assert_eq!(
hasse_invariant_finite_odd(&diag::<7>(&[1, 3, 5])).unwrap(),
1
);
}
fn det_small<const P: u128>(m: &[Vec<Fp<P>>]) -> Fp<P> {
match m.len() {
1 => m[0][0],
2 => m[0][0].mul(&m[1][1]).sub(&m[0][1].mul(&m[1][0])),
_ => unreachable!("only n ≤ 2 in tests"),
}
}
fn is_isometric<const P: u128>(d1: &[Fp<P>], d2: &[Fp<P>]) -> bool {
let n = d1.len();
assert_eq!(n, d2.len());
let mut total = 1u128;
for _ in 0..(n * n) {
total *= P;
}
for code in 0..total {
let mut m = vec![vec![Fp::<P>::from_u128(0); n]; n];
let mut c = code;
for row in m.iter_mut() {
for entry in row.iter_mut() {
*entry = Fp::<P>::from_u128(c % P);
c /= P;
}
}
if det_small(&m).is_zero() {
continue;
}
let mut ok = true;
'pair: for i in 0..n {
for j in 0..n {
let mut c_ij = Fp::<P>::from_u128(0);
for k in 0..n {
c_ij = c_ij.add(&m[k][i].mul(&d1[k]).mul(&m[k][j]));
}
let want = if i == j { d2[i] } else { Fp::<P>::from_u128(0) };
if c_ij != want {
ok = false;
break 'pair;
}
}
}
if ok {
return true;
}
}
false
}
#[test]
fn dim_plus_disc_is_complete_over_finite_fields() {
fn check<const P: u128>(dim: usize) {
let mut forms: Vec<Vec<Fp<P>>> = vec![vec![]];
for _ in 0..dim {
let mut next = vec![];
for f in &forms {
for e in 1..P {
let mut g = f.clone();
g.push(Fp::<P>::from_u128(e));
next.push(g);
}
}
forms = next;
}
for a in &forms {
for b in &forms {
let disc_a = a.iter().fold(Fp::<P>::one(), |acc, x| acc.mul(x));
let disc_b = b.iter().fold(Fp::<P>::one(), |acc, x| acc.mul(x));
let same_class = is_square::<P>(disc_a) == is_square::<P>(disc_b);
assert_eq!(is_isometric::<P>(a, b), same_class, "P={P} a={a:?} b={b:?}");
}
}
}
check::<3>(1);
check::<3>(2);
check::<5>(1);
check::<5>(2);
}
#[test]
fn oddchar_witt_is_a_homomorphism() {
let forms = [
diag::<3>(&[1]),
diag::<3>(&[2]),
diag::<3>(&[1, 1]),
diag::<3>(&[1, 2]),
];
for a in &forms {
for b in &forms {
let sum = a.direct_sum(b);
assert_eq!(
finite_odd_witt(&sum).unwrap(),
finite_odd_witt(a)
.unwrap()
.try_add(&finite_odd_witt(b).unwrap())
.expect("same finite-field Witt group"),
"homomorphism failed"
);
}
}
}
#[test]
fn witt_group_is_z4_when_minus_one_nonsquare() {
let g = finite_odd_witt(&diag::<3>(&[1])).unwrap();
let id = WittClassG::oddchar_zero(3, 1);
let g2 = g.try_add(&g).expect("same F3 Witt group");
let g3 = g2.try_add(&g).expect("same F3 Witt group");
let g4 = g3.try_add(&g).expect("same F3 Witt group");
assert_ne!(g, id);
assert_ne!(g2, id); assert_ne!(g3, id);
assert_eq!(g4, id); }
#[test]
fn witt_group_is_z2xz2_when_minus_one_square() {
let id = WittClassG::oddchar_zero(5, 0);
let g = finite_odd_witt(&diag::<5>(&[1])).unwrap(); let h = finite_odd_witt(&diag::<5>(&[2])).unwrap(); assert_eq!(g.try_add(&g).expect("same F5 Witt group"), id);
assert_eq!(h.try_add(&h).expect("same F5 Witt group"), id);
let gh = g.try_add(&h).expect("same F5 Witt group");
assert_eq!(gh.try_add(&gh).expect("same F5 Witt group"), id);
let elems = [id, g, h, gh];
for i in 0..4 {
for j in (i + 1)..4 {
assert_ne!(elems[i], elems[j], "elements {i},{j} coincide");
}
}
}
#[test]
fn extension_fields_use_the_same_trait_path() {
let f9 = Metric::diagonal(vec![Fpn::<3, 2>::constant(1), Fpn::<3, 2>::generator()]);
let class = classify_finite_odd(&f9).unwrap();
assert_eq!(class.field_order, 9);
assert_eq!(
finite_odd_witt(&f9)
.unwrap()
.try_add(&WittClassG::oddchar_zero(9, 0))
.expect("same F9 Witt group"),
finite_odd_witt(&f9).unwrap()
);
}