use super::Ordinal;
use crate::scalar::nim_mul;
fn canonicalize(raw: Vec<(Ordinal, u128)>) -> Vec<(Ordinal, u128)> {
super::super::cnf::merge_descending(raw, |a, b| a.cmp(b), |x, y| x ^ y, |c| *c == 0)
}
impl Ordinal {
pub fn nim_add(&self, other: &Ordinal) -> Ordinal {
let mut raw = self.terms.clone();
raw.extend(other.terms.iter().cloned());
Ordinal {
terms: canonicalize(raw),
}
}
pub fn as_below_omega3(&self) -> Option<[u128; 3]> {
let mut coeffs = [0u128; 3];
for (exp, c) in &self.terms {
let e = exp.as_finite()?;
if e >= 3 {
return None;
}
coeffs[e as usize] = *c;
}
Some(coeffs)
}
pub fn from_omega3_coeffs(c: [u128; 3]) -> Self {
let mut raw = Vec::new();
for (i, &v) in c.iter().enumerate() {
if v != 0 {
raw.push((Ordinal::from_u128(i as u128), v));
}
}
Ordinal {
terms: canonicalize(raw),
}
}
pub fn nim_mul(&self, other: &Ordinal) -> Option<Ordinal> {
if self.is_zero() || other.is_zero() {
return Some(Ordinal::zero());
}
if let (Some(a), Some(b)) = (self.as_finite(), other.as_finite()) {
return Some(Ordinal::from_u128(nim_mul(a, b)));
}
super::tower::mul(self, other)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn fin(n: u128) -> Ordinal {
Ordinal::from_u128(n)
}
#[test]
fn nim_add_is_xor_below_omega() {
for a in 0..16u128 {
for b in 0..16u128 {
assert_eq!(fin(a).nim_add(&fin(b)), fin(a ^ b));
}
}
}
#[test]
fn self_inverse_and_cancellation() {
let omega = Ordinal::omega();
assert!(omega.nim_add(&omega).is_zero());
let w3 = Ordinal::monomial(fin(1), 3);
assert!(w3.nim_add(&w3).is_zero());
let w_plus_1 = omega.nim_add(&fin(1));
assert_eq!(w_plus_1.nim_add(&fin(1)), omega);
let w2 = Ordinal::monomial(fin(1), 2);
assert_eq!(w2.nim_add(&omega), Ordinal::monomial(fin(1), 3));
}
#[test]
fn additive_group_axioms_with_infinite_terms() {
let a = Ordinal::omega().nim_add(&fin(2)); let b = Ordinal::omega_pow(fin(2)).nim_add(&fin(1)); let c = Ordinal::monomial(fin(1), 5); assert_eq!(a.nim_add(&b).nim_add(&c), a.nim_add(&b.nim_add(&c)));
assert_eq!(a.nim_add(&b), b.nim_add(&a));
assert_eq!(a.nim_add(&Ordinal::zero()), a);
assert!(a.nim_add(&a).is_zero());
}
#[test]
fn finite_nim_mul_agrees_with_nimber() {
for a in 0..16u128 {
for b in 0..16u128 {
assert_eq!(fin(a).nim_mul(&fin(b)), Some(fin(nim_mul(a, b))));
}
}
}
#[test]
fn omega_squared_is_omega_squared() {
let omega = Ordinal::omega();
assert_eq!(omega.nim_mul(&omega).unwrap(), Ordinal::omega_pow(fin(2)));
}
#[test]
fn omega_cubed_is_two() {
let omega = Ordinal::omega();
let omega_sq = omega.nim_mul(&omega).unwrap();
let omega_cubed = omega_sq.nim_mul(&omega).unwrap();
assert_eq!(omega_cubed, fin(2));
assert_eq!(
omega_sq.nim_mul(&omega_sq).unwrap(),
Ordinal::monomial(fin(1), 2)
);
}
#[test]
fn omega_plus_one_squared_and_cubed_by_hand() {
let w_plus_1 = Ordinal::omega().nim_add(&fin(1));
let sq = w_plus_1.nim_mul(&w_plus_1).unwrap();
assert_eq!(sq, Ordinal::omega_pow(fin(2)).nim_add(&fin(1)));
let cubed = sq.nim_mul(&w_plus_1).unwrap();
let expected = Ordinal::from_omega3_coeffs([3, 1, 1]); assert_eq!(cubed, expected);
}
#[test]
fn f4_adjoin_omega_is_a_field() {
let elems: Vec<Ordinal> = (0..64u128)
.map(|i| Ordinal::from_omega3_coeffs([i & 3, (i >> 2) & 3, (i >> 4) & 3]))
.collect();
let zero = Ordinal::zero();
let one = fin(1);
for a in &elems {
for b in &elems {
let ab = a.nim_mul(b).expect("F_4(ω) is closed");
assert!(elems.iter().any(|e| e == &ab), "product escaped F_4(ω)");
assert_eq!(ab, b.nim_mul(a).unwrap(), "non-commutative");
}
}
let witnesses = [
Ordinal::zero(),
fin(1),
fin(2),
fin(3),
Ordinal::omega(),
Ordinal::omega_pow(fin(2)),
Ordinal::omega().nim_add(&fin(1)),
Ordinal::omega_pow(fin(2)).nim_add(&Ordinal::omega()),
Ordinal::from_omega3_coeffs([3, 2, 1]),
Ordinal::from_omega3_coeffs([1, 3, 2]),
];
for a in &witnesses {
for b in &witnesses {
let ab = a.nim_mul(b).unwrap();
for c in &witnesses {
let lhs = ab.nim_mul(c).unwrap();
let rhs = a.nim_mul(&b.nim_mul(c).unwrap()).unwrap();
assert_eq!(lhs, rhs, "× not associative");
let lhs = a.nim_mul(&b.nim_add(c)).unwrap();
let rhs = ab.nim_add(&a.nim_mul(c).unwrap());
assert_eq!(lhs, rhs, "× not distributive over ⊕");
}
}
}
for a in elems.iter().filter(|e| !e.is_zero()) {
let inv = elems
.iter()
.find(|b| a.nim_mul(b).unwrap() == one)
.unwrap_or_else(|| panic!("no inverse for {a:?}"));
assert_eq!(a.nim_mul(inv).unwrap(), one);
}
for a in &elems {
assert_eq!(zero.nim_mul(a).unwrap(), zero);
}
}
#[test]
fn cube_root_tower_relations() {
let omega = Ordinal::omega(); let w3 = Ordinal::omega_pow(fin(3)); let w9 = Ordinal::omega_pow(fin(9)); assert_eq!(w3.nim_mul(&w3).unwrap(), Ordinal::omega_pow(fin(6)));
assert_eq!(w3.nim_mul(&omega).unwrap(), Ordinal::omega_pow(fin(4)));
let w3_cubed = w3.nim_mul(&w3).unwrap().nim_mul(&w3).unwrap();
assert_eq!(w3_cubed, omega);
let w9_cubed = w9.nim_mul(&w9).unwrap().nim_mul(&w9).unwrap();
assert_eq!(w9_cubed, w3);
}
#[test]
fn consistency_with_below_omega3_path() {
let elems: Vec<Ordinal> = (0..64u128)
.map(|i| Ordinal::from_omega3_coeffs([i & 3, (i >> 2) & 3, (i >> 4) & 3]))
.collect();
for a in &elems {
for b in &elems {
let (ca, cb) = (a.as_below_omega3().unwrap(), b.as_below_omega3().unwrap());
let mut p = [0u128; 5];
for (i, &ai) in ca.iter().enumerate() {
for (j, &bj) in cb.iter().enumerate() {
p[i + j] ^= nim_mul(ai, bj);
}
}
let old = Ordinal::from_omega3_coeffs([
p[0] ^ nim_mul(2, p[3]),
p[1] ^ nim_mul(2, p[4]),
p[2],
]);
assert_eq!(a.nim_mul(b).unwrap(), old, "tower path disagrees with old");
}
}
}
#[test]
fn tower_multiplication_ring_axioms() {
let mut elems: Vec<Ordinal> = Vec::new();
for &e in &[0u128, 1, 2, 3, 4, 5, 6, 8, 9, 10, 18, 27] {
for c in 1..=3u128 {
elems.push(Ordinal::monomial(fin(e), c));
}
}
elems.push(Ordinal::omega().nim_add(&fin(1))); elems.push(
Ordinal::omega_pow(fin(3))
.nim_add(&Ordinal::omega())
.nim_add(&fin(2)),
); elems.push(Ordinal::omega_pow(fin(9)).nim_add(&Ordinal::omega_pow(fin(3))));
for a in &elems {
for b in &elems {
let ab = a.nim_mul(b).expect("< ω^ω is closed under ⊗");
assert_eq!(ab, b.nim_mul(a).unwrap(), "non-commutative");
for c in &elems {
let l = ab.nim_mul(c).unwrap();
let r = a.nim_mul(&b.nim_mul(c).unwrap()).unwrap();
assert_eq!(l, r, "× not associative");
let l = a.nim_mul(&b.nim_add(c)).unwrap();
let r = ab.nim_add(&a.nim_mul(c).unwrap());
assert_eq!(l, r, "× not distributive over ⊕");
}
}
}
}
#[test]
fn multiplication_reaches_past_omega_omega() {
let omega = Ordinal::omega();
let ww = Ordinal::omega_pow(omega.clone()); assert_eq!(
ww.nim_mul(&omega).unwrap(),
Ordinal::omega_pow(omega.nim_add(&fin(1)))
);
let mut p = ww.clone();
for _ in 0..4 {
p = p.nim_mul(&ww).unwrap();
}
assert_eq!(p, fin(4));
let chi7 = Ordinal::omega_pow(Ordinal::omega_pow(fin(2))); let mut q = fin(1);
for _ in 0..7 {
q = q.nim_mul(&chi7).unwrap();
}
assert_eq!(q, omega.nim_add(&fin(1))); assert_eq!(Ordinal::omega_pow(ww.clone()).nim_mul(&omega), None); }
}