mod cantor;
mod nim;
mod subfield;
mod tower;
use crate::scalar::{nim_inv, Scalar};
use std::cmp::Ordering;
use std::fmt;
#[derive(Clone, PartialEq, Eq)]
pub struct Ordinal {
terms: Vec<(Ordinal, u128)>,
}
impl Ordinal {
pub fn zero() -> Self {
Ordinal { terms: Vec::new() }
}
pub fn from_u128(n: u128) -> Self {
if n == 0 {
Ordinal::zero()
} else {
Ordinal {
terms: vec![(Ordinal::zero(), n)],
}
}
}
pub fn monomial(exp: Ordinal, coeff: u128) -> Self {
if coeff == 0 {
Ordinal::zero()
} else {
Ordinal {
terms: vec![(exp, coeff)],
}
}
}
pub fn omega_pow(exp: Ordinal) -> Self {
Ordinal::monomial(exp, 1)
}
pub fn omega() -> Self {
Ordinal::omega_pow(Ordinal::from_u128(1))
}
pub fn is_zero(&self) -> bool {
self.terms.is_empty()
}
pub fn terms(&self) -> &[(Ordinal, u128)] {
&self.terms
}
pub fn fuzzy(&self, other: &Self) -> bool {
self != other
}
#[allow(clippy::should_implement_trait)]
pub fn cmp(&self, other: &Ordinal) -> Ordering {
for ((e1, c1), (e2, c2)) in self.terms.iter().zip(other.terms.iter()) {
match e1.cmp(e2) {
Ordering::Equal => {}
ord => return ord,
}
match c1.cmp(c2) {
Ordering::Equal => {}
ord => return ord,
}
}
self.terms.len().cmp(&other.terms.len())
}
pub fn as_finite(&self) -> Option<u128> {
match self.terms.as_slice() {
[] => Some(0),
[(exp, c)] if exp.is_zero() => Some(*c),
_ => None,
}
}
pub fn nim_pow(&self, mut k: u128) -> Option<Ordinal> {
if k == 0 {
return Some(Ordinal::from_u128(1));
}
let mut acc = Ordinal::from_u128(1);
let mut base = self.clone();
loop {
if k & 1 == 1 {
acc = acc.nim_mul(&base)?;
}
k >>= 1;
if k == 0 {
break;
}
base = base.nim_mul(&base)?;
}
Some(acc)
}
pub fn checked_inv(&self) -> Option<Ordinal> {
if self.is_zero() {
return None;
}
if let Some(x) = self.as_finite() {
return nim_inv(x).map(Ordinal::from_u128);
}
let degree = self.finite_subfield_degree()?;
let one = Ordinal::from_u128(1);
let mut acc = one.clone();
let mut power = self.clone();
for _ in 1..degree {
power = power.nim_mul(&power)?;
acc = acc.nim_mul(&power)?;
}
(self.nim_mul(&acc).as_ref() == Some(&one)).then_some(acc)
}
}
pub use subfield::{ordinal_common_finite_subfield_degree, ordinal_finite_subfield_degree};
impl Scalar for Ordinal {
fn zero() -> Self {
Ordinal::zero()
}
fn one() -> Self {
Ordinal::from_u128(1)
}
fn add(&self, rhs: &Self) -> Self {
self.nim_add(rhs)
}
fn neg(&self) -> Self {
self.clone()
}
fn mul(&self, rhs: &Self) -> Self {
self.nim_mul(rhs).unwrap_or_else(|| {
panic!(
"Ordinal::mul escaped the source-verified nim-product tower: left={self:?}, right={rhs:?}"
)
})
}
fn characteristic() -> u128 {
2
}
fn inv(&self) -> Option<Self> {
self.checked_inv()
}
}
fn fmt_exp(e: &Ordinal) -> String {
if e.is_zero() {
String::new()
} else if *e == Ordinal::from_u128(1) {
"ω".to_string()
} else if e.terms.len() == 1 && e.terms[0].0.is_zero() {
format!("ω↑{}", e.terms[0].1) } else {
format!("ω↑({})", fmt_cnf(e)) }
}
fn fmt_cnf(x: &Ordinal) -> String {
let parts: Vec<String> = x
.terms
.iter()
.map(|(e, c)| {
let base = fmt_exp(e);
if base.is_empty() {
format!("{c}") } else if *c == 1 {
base
} else {
format!("{base}⋅{c}")
}
})
.collect();
parts.join(" + ")
}
impl fmt::Display for Ordinal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.terms.is_empty() {
return write!(f, "*0"); }
let bare =
(self.terms.len() == 1 && self.terms[0].0.is_zero()) || *self == Ordinal::omega();
if bare {
write!(f, "*{}", fmt_cnf(self))
} else {
write!(f, "*({})", fmt_cnf(self))
}
}
}
impl fmt::Debug for Ordinal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn fin(n: u128) -> Ordinal {
Ordinal::from_u128(n)
}
#[test]
fn cantor_normal_form_ordering() {
let one = fin(1);
let omega = Ordinal::omega(); let omega_times_2 = Ordinal::monomial(one.clone(), 2); let omega_sq = Ordinal::omega_pow(fin(2)); let omega_omega = Ordinal::omega_pow(Ordinal::omega()); assert_eq!(one.cmp(&omega), Ordering::Less);
assert_eq!(omega.cmp(&omega_times_2), Ordering::Less);
assert_eq!(omega_times_2.cmp(&omega_sq), Ordering::Less);
assert_eq!(omega_sq.cmp(&omega_omega), Ordering::Less);
assert_eq!(
omega_omega.cmp(&Ordinal::omega_pow(fin(100))),
Ordering::Greater
);
}
#[test]
fn fuzzy_is_distinctness_not_cnf_order() {
assert!(!Ordinal::omega().fuzzy(&Ordinal::omega()));
assert!(Ordinal::omega().fuzzy(&fin(7)));
}
#[test]
fn display_reads_as_cnf() {
assert_eq!(format!("{:?}", Ordinal::omega()), "*ω");
assert_eq!(format!("{:?}", Ordinal::monomial(fin(1), 3)), "*(ω⋅3)");
assert_eq!(format!("{:?}", Ordinal::omega_pow(fin(2))), "*(ω↑2)");
assert_eq!(
format!("{:?}", Ordinal::omega().nim_add(&fin(1))),
"*(ω + 1)"
);
assert_eq!(format!("{:?}", fin(5)), "*5");
assert_eq!(format!("{:?}", Ordinal::zero()), "*0");
assert_eq!(
format!("{:?}", Ordinal::omega_pow(Ordinal::omega())),
"*(ω↑(ω))"
);
}
#[test]
fn scalar_impl_matches_checked_nim_arithmetic() {
let w = Ordinal::omega();
let one = Ordinal::one();
assert_eq!(w.add(&one), w.nim_add(&one));
assert_eq!(w.neg(), w);
assert_eq!(w.mul(&w).mul(&w), fin(2)); assert_eq!(Ordinal::characteristic(), 2);
}
#[test]
fn checked_inverse_covers_finite_and_f64_subfield() {
let three = fin(3);
assert_eq!(three.mul(&three.inv().unwrap()), Ordinal::one());
let w_plus_1 = Ordinal::omega().nim_add(&fin(1));
let inv = w_plus_1.inv().expect("ω+1 lies in the enumerated F_64");
assert_eq!(w_plus_1.mul(&inv), Ordinal::one());
}
#[test]
#[should_panic(expected = "Ordinal::mul escaped the source-verified nim-product tower")]
fn scalar_mul_panics_past_verified_tower() {
let out_of_range = Ordinal::omega_pow(Ordinal::omega_pow(Ordinal::omega()));
let _ = out_of_range.mul(&Ordinal::omega());
}
#[test]
fn nim_pow_zero_is_one() {
assert_eq!(Ordinal::omega().nim_pow(0), Some(fin(1)));
assert_eq!(fin(0).nim_pow(0), Some(fin(1)));
assert_eq!(fin(5).nim_pow(0), Some(fin(1)));
}
#[test]
fn nim_pow_omega_cubed_is_two() {
let omega = Ordinal::omega();
assert_eq!(omega.nim_pow(3), Some(fin(2)));
}
#[test]
fn nim_pow_propagates_none_on_escape() {
let out_of_range = Ordinal::omega_pow(Ordinal::omega_pow(Ordinal::omega()));
assert_eq!(out_of_range.nim_pow(2), None);
}
}