use super::Ordinal;
use crate::scalar::{is_prime_u128, nim_mul};
use std::collections::{BTreeMap, BTreeSet};
type GenKey = BTreeMap<u128, Vec<u128>>;
pub(super) fn place_prime(m: u128) -> u128 {
let mut count = 0u128;
let mut n = 2u128; loop {
n += 1;
if is_prime_u128(n) {
count += 1;
if count == m + 1 {
return n;
}
}
}
}
pub(super) fn alpha_ordinal(u: u128) -> Option<Ordinal> {
let f = multiplicative_order_two_mod_prime(u)?;
let mut val = chi_sum(&q_set(f)?)?;
val = val.nim_add(&Ordinal::from_u128(finite_excess(u)?));
Some(val)
}
fn finite_excess(u: u128) -> Option<u128> {
const EXCESS: [u128; 126] = [
0, 0, 1, 1, 0, 0, 4, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 4, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, ];
EXCESS.get(odd_prime_place(u)? as usize).copied()
}
fn multiplicative_order_two_mod_prime(u: u128) -> Option<u128> {
if u <= 2 || !is_prime_u128(u) {
return None;
}
let mut x = 2 % u;
let mut k = 1u128;
while x != 1 {
x = x.checked_mul(2)? % u;
k = k.checked_add(1)?;
if k > u - 1 {
return None;
}
}
Some(k)
}
fn q_set(h: u128) -> Option<Vec<u128>> {
if h == 1 {
return Some(Vec::new());
}
let (r, g) = smallest_prime_power_factor(h)?;
let mut qs = q_set(g)?;
let chi_g = chi_sum(&qs)?;
let degree = chi_g.finite_subfield_degree()?;
if !degree.is_multiple_of(r) {
qs.push(r);
qs.sort_unstable();
}
Some(qs)
}
fn smallest_prime_power_factor(h: u128) -> Option<(u128, u128)> {
if h <= 1 {
return None;
}
let u = smallest_prime_factor(h)?;
let mut r = 1u128;
let mut g = h;
while g.is_multiple_of(u) {
r = r.checked_mul(u)?;
g /= u;
}
Some((r, g))
}
fn smallest_prime_factor(n: u128) -> Option<u128> {
if n < 2 {
return None;
}
if n.is_multiple_of(2) {
return Some(2);
}
let mut d = 3u128;
while d <= n / d {
if n.is_multiple_of(d) {
return Some(d);
}
d += 2;
}
Some(n)
}
fn chi_sum(qs: &[u128]) -> Option<Ordinal> {
qs.iter()
.try_fold(Ordinal::zero(), |acc, &q| Some(acc.nim_add(&chi(q)?)))
}
fn chi(q: u128) -> Option<Ordinal> {
let (p, n) = prime_power(q)?;
if p == 2 {
let shift = q / 2;
if shift >= u128::BITS as u128 {
return None;
}
return Some(Ordinal::from_u128(1u128 << shift));
}
let place = odd_prime_place(p)?;
let coeff = checked_pow(p, n - 1)?;
let exp = Ordinal::monomial(Ordinal::from_u128(place), coeff);
Some(Ordinal::omega_pow(exp))
}
fn prime_power(q: u128) -> Option<(u128, u128)> {
if q < 2 {
return None;
}
let p = smallest_prime_factor(q)?;
let mut n = 0u128;
let mut rest = q;
while rest.is_multiple_of(p) {
n += 1;
rest /= p;
}
(rest == 1).then_some((p, n))
}
fn odd_prime_place(p: u128) -> Option<u128> {
if p == 2 || !is_prime_u128(p) {
return None;
}
let mut place = 0u128;
loop {
let q = place_prime(place);
if q == p {
return Some(place);
}
if q > p {
return None;
}
place += 1;
}
}
pub(super) fn checked_pow(base: u128, exp: u128) -> Option<u128> {
let mut acc = 1u128;
for _ in 0..exp {
acc = acc.checked_mul(base)?;
}
Some(acc)
}
pub(super) fn base_digits(mut v: u128, base: u128) -> Vec<u128> {
let mut d = Vec::new();
while v > 0 {
d.push(v % base);
v /= base;
}
d
}
fn decompose_exp(e: &Ordinal) -> Option<GenKey> {
let mut key = GenKey::new();
for (exp, c) in e.terms() {
let m = exp.as_finite()?; key.insert(m, base_digits(*c, place_prime(m)));
}
Some(key)
}
fn recompose_exp(key: &GenKey) -> Option<Ordinal> {
key.iter().try_fold(Ordinal::zero(), |acc, (&m, digits)| {
let u = place_prime(m);
let mut v: u128 = 0;
let mut pw: u128 = 1;
for &d in digits {
let term = d.checked_mul(pw)?;
v = v.checked_add(term)?;
pw = pw.checked_mul(u)?;
}
Some(if v == 0 {
acc
} else {
acc.nim_add(&Ordinal::monomial(Ordinal::from_u128(m), v))
})
})
}
fn reduce_place(raw: &[u128], u: u128) -> Option<(Vec<u128>, u128)> {
let mut d = raw.to_vec();
for k in (0..d.len()).rev() {
let carry = d[k] / u;
d[k] %= u;
if carry == 0 {
continue;
}
if k == 0 {
while d.last() == Some(&0) {
d.pop();
}
return Some((d, carry));
}
d[k - 1] = d[k - 1].checked_add(carry)?;
}
let mut digits: Vec<u128> = d;
while digits.last() == Some(&0) {
digits.pop();
}
Some((digits, 0))
}
fn reduce_keys(a: &GenKey, b: &GenKey) -> Option<(GenKey, BTreeMap<u128, u128>)> {
let mut base = GenKey::new();
let mut overflow: BTreeMap<u128, u128> = BTreeMap::new();
let places: BTreeSet<u128> = a.keys().chain(b.keys()).copied().collect();
for m in places {
let da = a.get(&m).map(Vec::as_slice).unwrap_or(&[]);
let db = b.get(&m).map(Vec::as_slice).unwrap_or(&[]);
let len = da.len().max(db.len());
let raw: Vec<u128> = (0..len)
.map(|i| {
da.get(i)
.copied()
.unwrap_or(0)
.checked_add(db.get(i).copied().unwrap_or(0))
})
.collect::<Option<_>>()?;
let (red, q) = reduce_place(&raw, place_prime(m))?;
if q > 0 {
overflow.insert(m, q);
}
if !red.is_empty() {
base.insert(m, red);
}
}
Some((base, overflow))
}
fn mul_mono(ka: &GenKey, ca: u128, kb: &GenKey, cb: u128) -> Option<Ordinal> {
let (base_key, overflow) = reduce_keys(ka, kb)?;
let coeff = nim_mul(ca, cb);
let base = if base_key.is_empty() {
Ordinal::from_u128(coeff)
} else {
Ordinal::monomial(recompose_exp(&base_key)?, coeff)
};
if overflow.is_empty() {
return Some(base);
}
let mut excess = Ordinal::from_u128(1);
for (&m, &q) in &overflow {
let alpha = alpha_ordinal(place_prime(m))?;
for _ in 0..q {
excess = mul(&excess, &alpha)?;
}
}
mul(&base, &excess)
}
pub(super) fn mul(a: &Ordinal, b: &Ordinal) -> Option<Ordinal> {
let mut acc = Ordinal::zero();
for (ea, ca) in a.terms() {
let ka = decompose_exp(ea)?;
for (eb, cb) in b.terms() {
let kb = decompose_exp(eb)?;
let term = mul_mono(&ka, *ca, &kb, *cb)?;
acc = acc.nim_add(&term);
}
}
Some(acc)
}
#[cfg(test)]
mod tests {
use super::*;
fn fin(n: u128) -> Ordinal {
Ordinal::from_u128(n)
}
fn w() -> Ordinal {
Ordinal::omega()
}
fn ww() -> Ordinal {
Ordinal::omega_pow(Ordinal::omega()) }
fn chi7() -> Ordinal {
Ordinal::omega_pow(Ordinal::omega_pow(fin(2))) }
fn chi7_pow(n: u128) -> Ordinal {
let mut p = fin(1);
for _ in 0..n {
p = mul(&p, &chi7()).unwrap();
}
p
}
fn expected_alpha(u: u128) -> Ordinal {
match u {
3 => fin(2),
5 => fin(4),
7 => w().nim_add(&fin(1)),
11 => Ordinal::omega_pow(w()).nim_add(&fin(1)),
13 => w().nim_add(&fin(4)),
17 => fin(16),
19 => Ordinal::omega_pow(fin(3)).nim_add(&fin(4)),
23 => Ordinal::omega_pow(Ordinal::omega_pow(fin(3))).nim_add(&fin(1)),
29 => Ordinal::omega_pow(Ordinal::omega_pow(fin(2))).nim_add(&fin(4)),
31 => Ordinal::omega_pow(w()).nim_add(&fin(1)),
37 => Ordinal::omega_pow(fin(3)).nim_add(&fin(4)),
41 => Ordinal::omega_pow(w()).nim_add(&fin(1)),
43 => Ordinal::omega_pow(Ordinal::omega_pow(fin(2))).nim_add(&fin(1)),
47 => Ordinal::omega_pow(Ordinal::omega_pow(fin(7))).nim_add(&fin(1)),
_ => panic!("unexpected test row"),
}
}
#[test]
fn place_primes_are_the_odd_primes() {
for (m, p) in [
(0, 3),
(1, 5),
(2, 7),
(3, 11),
(4, 13),
(5, 17),
(6, 19),
(7, 23),
(8, 29),
(9, 31),
(10, 37),
(11, 41),
(12, 43),
(13, 47),
(14, 53),
] {
assert_eq!(place_prime(m), p);
}
}
#[test]
fn dimuro_rows_are_assembled_from_order_qset_and_finite_excess() {
for (u, f, qs, m) in [
(3, 2, &[2][..], 0),
(5, 4, &[4][..], 0),
(7, 3, &[3][..], 1),
(11, 10, &[5][..], 1),
(13, 12, &[3, 4][..], 0),
(17, 8, &[8][..], 0),
(19, 18, &[9][..], 4),
(23, 11, &[11][..], 1),
(29, 28, &[4, 7][..], 0),
(31, 5, &[5][..], 1),
(37, 36, &[4, 9][..], 0),
(41, 20, &[5][..], 1),
(43, 14, &[7][..], 1),
(47, 23, &[23][..], 1),
] {
assert_eq!(multiplicative_order_two_mod_prime(u), Some(f));
assert_eq!(q_set(f).as_deref(), Some(qs));
assert_eq!(finite_excess(u), Some(m));
assert_eq!(alpha_ordinal(u), Some(expected_alpha(u)));
}
}
#[test]
fn beyond_dimuro_table_rows_are_assembled_from_order_qset_and_finite_excess() {
for (u, f, qs, m, expected) in [
(
73,
9,
&[9][..],
1,
Ordinal::omega_pow(fin(3)).nim_add(&fin(1)),
),
(
89,
11,
&[11][..],
1,
Ordinal::omega_pow(Ordinal::omega_pow(fin(3))).nim_add(&fin(1)),
),
] {
assert_eq!(multiplicative_order_two_mod_prime(u), Some(f), "f({u})");
assert_eq!(q_set(f).as_deref(), Some(qs), "q_set(f({u}))");
assert_eq!(finite_excess(u), Some(m), "m_{u}");
assert_eq!(alpha_ordinal(u), Some(expected), "alpha_{u}");
}
}
#[test]
fn alpha_excesses_descend_in_place() {
for m in 0..=13u128 {
let u = place_prime(m);
let alpha = alpha_ordinal(u).unwrap();
let mut hi: Option<u128> = None;
for (exp, _) in alpha.terms() {
if let Some(sub) = decompose_exp(exp) {
if let Some(&mx) = sub.keys().last() {
hi = Some(hi.map_or(mx, |h| h.max(mx)));
}
}
}
if let Some(h) = hi {
assert!(h < m, "α_{u} reaches place {h} ≥ its own place {m}");
}
}
}
#[test]
fn reproduces_cube_tower_below_omega_omega() {
let wsq = mul(&w(), &w()).unwrap();
assert_eq!(wsq, Ordinal::omega_pow(fin(2)));
assert_eq!(mul(&wsq, &w()).unwrap(), fin(2)); assert_eq!(mul(&wsq, &wsq).unwrap(), Ordinal::monomial(fin(1), 2)); }
#[test]
fn quintic_landmarks_from_dimuro() {
let w2 = mul(&ww(), &ww()).unwrap();
assert_eq!(w2, Ordinal::omega_pow(Ordinal::monomial(fin(1), 2))); let w3 = mul(&w2, &ww()).unwrap();
assert_eq!(w3, Ordinal::omega_pow(Ordinal::monomial(fin(1), 3))); let w4 = mul(&w3, &ww()).unwrap();
assert_eq!(w4, Ordinal::omega_pow(Ordinal::monomial(fin(1), 4))); let w5 = mul(&w4, &ww()).unwrap();
assert_eq!(w5, fin(4)); }
#[test]
fn cross_place_products() {
assert_eq!(
mul(&ww(), &w()).unwrap(),
Ordinal::omega_pow(Ordinal::omega().nim_add(&fin(1)))
);
assert_eq!(
mul(&ww(), &fin(2)).unwrap(),
Ordinal::monomial(Ordinal::omega(), 2)
);
}
#[test]
fn septic_kummer_landmark() {
assert_eq!(chi7_pow(7), w().nim_add(&fin(1)));
let e_w2_1 = Ordinal::omega_pow(fin(2)).nim_add(&fin(1)); let w2 = Ordinal::omega_pow(fin(2)); assert_eq!(
chi7_pow(8),
Ordinal::omega_pow(e_w2_1).nim_add(&Ordinal::omega_pow(w2))
);
let w2_2 = Ordinal::monomial(fin(2), 2); let w2_2_1 = w2_2.nim_add(&fin(1)); assert_eq!(
chi7_pow(9),
Ordinal::omega_pow(w2_2_1).nim_add(&Ordinal::omega_pow(w2_2))
);
assert_eq!(chi7_pow(9), mul(&chi7_pow(8), &chi7()).unwrap());
}
#[test]
fn locally_verified_alpha_47_landmark() {
let chi47 = Ordinal::omega_pow(Ordinal::omega_pow(fin(13)));
let mut pow = fin(1);
for _ in 0..47 {
pow = mul(&pow, &chi47).unwrap();
}
assert_eq!(
pow,
Ordinal::omega_pow(Ordinal::omega_pow(fin(7))).nim_add(&fin(1))
);
}
#[test]
fn quintic_stage_field_axioms() {
let w = Ordinal::omega();
let wn = |n| Ordinal::monomial(fin(1), n); let elems: Vec<Ordinal> = vec![
fin(1),
fin(2),
fin(3), w.clone(), Ordinal::omega_pow(fin(2)), Ordinal::omega_pow(fin(3)), ww(), Ordinal::omega_pow(wn(2)), Ordinal::omega_pow(wn(5)), Ordinal::omega_pow(w.nim_add(&fin(1))), ww().nim_add(&w).nim_add(&fin(1)), wn(3).nim_add(&fin(2)), Ordinal::omega_pow(wn(2)).nim_add(&Ordinal::omega_pow(fin(3))), ];
check_field_axioms(&elems);
}
#[test]
fn septic_stage_field_axioms() {
let mut elems: Vec<Ordinal> = vec![fin(1), fin(2), fin(3), w(), w().nim_add(&fin(1))];
for n in 1..=6u128 {
elems.push(chi7_pow(n));
}
elems.push(chi7().nim_add(&w())); elems.push(Ordinal::monomial(Ordinal::omega_pow(fin(2)), 2).nim_add(&fin(1))); elems.push(chi7_pow(3).nim_add(&w()).nim_add(&fin(1))); check_field_axioms(&elems);
}
fn check_field_axioms(elems: &[Ordinal]) {
let one = fin(1);
for a in elems {
for b in elems {
let ab = a.nim_mul(b).expect("sample is closed under ⊗");
assert_eq!(ab, b.nim_mul(a).unwrap(), "non-commutative");
assert_eq!(a.nim_mul(&one).unwrap(), *a, "identity");
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 excess_table_matches_oeis_a380496() {
for (u, m) in [
(3, 0),
(5, 0),
(7, 1),
(11, 1),
(13, 0),
(17, 0),
(19, 4),
(23, 1),
(29, 0),
(31, 1),
(37, 0),
(41, 1),
(43, 1),
(47, 1),
(53, 1),
(61, 0),
(73, 1),
(109, 0),
(163, 4),
(263, 1),
(521, 0),
(659, 0),
(701, 0),
(709, 1),
] {
assert_eq!(finite_excess(u), Some(m), "m_{u}");
}
for place in 0..126u128 {
let u = place_prime(place);
let m = finite_excess(u).expect("all 126 b-file rows are defined");
assert!(
m == 0 || m == 1 || ((u == 19 || u == 163) && m == 4),
"m_{u} = {m} is outside the A380496 value set"
);
}
}
#[test]
fn excess_table_matches_vendored_b380496_in_full() {
let b_file = include_str!("b380496.txt");
let mut rows = 0u128;
for line in b_file.lines() {
let line = line.trim();
if line.is_empty() {
continue;
}
let mut parts = line.split_whitespace();
let n: u128 = parts
.next()
.expect("row has an index")
.parse()
.expect("n is u128");
let a_n: u128 = parts
.next()
.expect("row has a value")
.parse()
.expect("a(n) is u128");
assert!(parts.next().is_none(), "unexpected extra column in row {n}");
let place = n - 1;
let u = place_prime(place);
assert_eq!(finite_excess(u), Some(a_n), "OEIS A380496 a({n}), u={u}");
rows += 1;
}
assert_eq!(rows, 126, "expected all 126 known A380496 b-file rows");
}
#[test]
fn boundary_returns_none_past_prime_709() {
let at53 = Ordinal::omega_pow(Ordinal::monomial(fin(14), 50)); assert!(mul(&at53, &at53).is_some());
assert_eq!(place_prime(125), 709);
assert_eq!(place_prime(126), 719);
assert_eq!(finite_excess(709), Some(1)); assert_eq!(finite_excess(719), None);
let at719 = Ordinal::omega_pow(Ordinal::monomial(fin(126), 360));
assert_eq!(mul(&at719, &at719), None);
let w_ww = Ordinal::omega_pow(ww()); assert_eq!(mul(&w_ww, &w()), None);
}
#[test]
fn identity_preserves_large_valid_prime_digits() {
assert_eq!(place_prime(53), 257);
let exp = Ordinal::monomial(fin(53), 256);
let x = Ordinal::omega_pow(exp);
assert_eq!(mul(&x, &fin(1)), Some(x.clone()));
assert_eq!(mul(&fin(1), &x), Some(x));
}
}