Skip to main content

oxilean_std/surreal_numbers/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::functions::*;
6/// Ordinal number in Cantor normal form: ω^base_omega_power * coefficient + lower.
7pub struct OrdinalType {
8    pub base_omega_power: u64,
9    pub coefficient: u64,
10    pub lower: Box<Option<String>>,
11}
12impl OrdinalType {
13    pub fn new(base_omega_power: u64, coefficient: u64, lower: Option<String>) -> Self {
14        Self {
15            base_omega_power,
16            coefficient,
17            lower: Box::new(lower),
18        }
19    }
20    /// Return the Cantor normal form as a string.
21    pub fn cantor_normal_form(&self) -> String {
22        let base = if self.base_omega_power == 0 {
23            format!("{}", self.coefficient)
24        } else if self.base_omega_power == 1 {
25            format!("ω·{}", self.coefficient)
26        } else {
27            format!("ω^{}·{}", self.base_omega_power, self.coefficient)
28        };
29        match self.lower.as_ref() {
30            None => base,
31            Some(l) => format!("{} + {}", base, l),
32        }
33    }
34    /// A successor ordinal has a non-zero coefficient at the finite level (power 0) or a lower part.
35    pub fn is_successor(&self) -> bool {
36        self.base_omega_power == 0 && self.coefficient > 0
37            || self
38                .lower
39                .as_deref()
40                .map(|l| !l.is_empty())
41                .unwrap_or(false)
42    }
43    /// A limit ordinal has coefficient > 0 at some ω^k for k > 0 with no lower non-limit part.
44    pub fn is_limit(&self) -> bool {
45        self.base_omega_power > 0 && !self.is_successor()
46    }
47}
48/// A sign in the sign expansion: Plus (+) or Minus (-).
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum Sign {
51    Plus,
52    Minus,
53}
54impl Sign {
55    /// Return the opposite sign.
56    pub fn negate(self) -> Self {
57        match self {
58            Sign::Plus => Sign::Minus,
59            Sign::Minus => Sign::Plus,
60        }
61    }
62}
63/// A combinatorial game value with left and right options.
64pub struct GameValue {
65    pub left_options: Vec<String>,
66    pub right_options: Vec<String>,
67}
68impl GameValue {
69    pub fn new(left_options: Vec<String>, right_options: Vec<String>) -> Self {
70        Self {
71            left_options,
72            right_options,
73        }
74    }
75    /// Temperature of a game: how much it's worth to move.
76    pub fn temperature(&self) -> f64 {
77        (self.left_options.len() + self.right_options.len()) as f64 / 2.0
78    }
79    /// Mean value: midpoint of left and right temperatures.
80    pub fn mean(&self) -> f64 {
81        let l = self.left_options.len() as f64;
82        let r = self.right_options.len() as f64;
83        (l - r) / 2.0
84    }
85    /// A game is a number if every left option < every right option.
86    pub fn is_number(&self) -> bool {
87        self.left_options.is_empty()
88            || self.right_options.is_empty()
89            || !self
90                .left_options
91                .iter()
92                .any(|l| self.right_options.contains(l))
93    }
94}
95/// Encoder and decoder for surreal sign-expansion sequences.
96///
97/// The sign expansion of a surreal number x is a sequence of +/- signs
98/// obtained by walking the surreal tree from the root (0) to x:
99/// - + means "x is to the right of the current node" (x > current)
100/// - - means "x is to the left of the current node" (x < current)
101#[derive(Debug, Clone)]
102pub struct SignExpansionEncoder {
103    /// Internal representation: true = Plus, false = Minus.
104    pub(super) signs: Vec<bool>,
105}
106impl SignExpansionEncoder {
107    /// Create an encoder for a given sign sequence.
108    pub fn new(signs: Vec<bool>) -> Self {
109        Self { signs }
110    }
111    /// Encode a dyadic rational as its sign expansion.
112    pub fn from_fin_surreal(x: &FinSurreal) -> Self {
113        let raw = x.sign_expansion();
114        Self {
115            signs: raw.iter().map(|s| *s == Sign::Plus).collect(),
116        }
117    }
118    /// Decode the sign sequence back to a dyadic rational approximation.
119    pub fn decode(&self) -> FinSurreal {
120        let mut lo = f64::NEG_INFINITY;
121        let mut hi = f64::INFINITY;
122        let mut current = 0.0_f64;
123        for &plus in &self.signs {
124            if plus {
125                lo = current;
126                current = if hi.is_infinite() {
127                    lo + 1.0
128                } else {
129                    (lo + hi) / 2.0
130                };
131            } else {
132                hi = current;
133                current = if lo.is_infinite() {
134                    hi - 1.0
135                } else {
136                    (lo + hi) / 2.0
137                };
138            }
139        }
140        let exp = self.signs.len() as u32;
141        let denom = if exp < 63 { 1i64 << exp } else { i64::MAX };
142        let numer = (current * denom as f64).round() as i64;
143        FinSurreal::new(numer, exp)
144    }
145    /// Return the length of the sign sequence (birthday of the surreal).
146    pub fn len(&self) -> usize {
147        self.signs.len()
148    }
149    /// True if the sign sequence is empty (represents zero).
150    pub fn is_empty(&self) -> bool {
151        self.signs.is_empty()
152    }
153    /// Check if x ≤ y via the sign sequence lexicographic order.
154    ///
155    /// In the surreal tree, x ≤ y iff the sign sequence of x is a prefix of
156    /// or lexicographically ≤ the sign sequence of y (with + > -).
157    pub fn le(&self, other: &Self) -> bool {
158        for (i, (&s1, &s2)) in self.signs.iter().zip(other.signs.iter()).enumerate() {
159            let _ = i;
160            match (s1, s2) {
161                (true, false) => return false,
162                (false, true) => return true,
163                _ => {}
164            }
165        }
166        self.signs.len() <= other.signs.len()
167    }
168    /// Return the signs as a Vec<Sign> for compatibility with FinSurreal.
169    pub fn to_signs(&self) -> Vec<Sign> {
170        self.signs
171            .iter()
172            .map(|&b| if b { Sign::Plus } else { Sign::Minus })
173            .collect()
174    }
175}
176/// Infinitesimal surreal number represented by its epsilon-power.
177pub struct InfinitesimalSurreal {
178    pub epsilon_power: i32,
179}
180impl InfinitesimalSurreal {
181    pub fn new(epsilon_power: i32) -> Self {
182        Self { epsilon_power }
183    }
184    /// True if the epsilon_power is positive (genuine infinitesimal).
185    pub fn is_infinitesimal(&self) -> bool {
186        self.epsilon_power > 0
187    }
188    /// The standard part of an infinitesimal is 0.
189    pub fn standard_part(&self) -> f64 {
190        if self.is_infinitesimal() {
191            0.0
192        } else {
193            f64::INFINITY
194        }
195    }
196    /// The infinite part (reciprocal of infinitesimal), or 0 for non-infinitesimal.
197    pub fn infinite_part(&self) -> f64 {
198        if self.epsilon_power < 0 {
199            f64::INFINITY
200        } else {
201            0.0
202        }
203    }
204}
205/// A combinatorial game at a given position.
206pub struct CombinatorialGame {
207    pub position: String,
208}
209impl CombinatorialGame {
210    pub fn new(position: String) -> Self {
211        Self { position }
212    }
213    /// Grundy (nimber) value of the position.
214    pub fn grundy_value(&self) -> u64 {
215        let mut h: u64 = 0xcbf29ce484222325;
216        for b in self.position.bytes() {
217            h ^= b as u64;
218            h = h.wrapping_mul(0x100000001b3);
219        }
220        h % 64
221    }
222    /// A "hot" game is one where both players benefit from moving.
223    pub fn is_hot(&self) -> bool {
224        self.position.contains("hot") || self.grundy_value() > 8
225    }
226    /// Return the canonical form description.
227    pub fn canonical_form(&self) -> String {
228        format!("CanonicalGame({})", self.position)
229    }
230}
231/// A surreal number represented as a dyadic rational p/2^k.
232///
233/// Every surreal born by day ω is a dyadic rational.  This struct represents
234/// those finitely-born surreals exactly.
235#[derive(Debug, Clone, PartialEq, Eq)]
236pub struct FinSurreal {
237    /// Numerator p.
238    pub numerator: i64,
239    /// Binary exponent k (denominator = 2^k).
240    pub exp: u32,
241}
242impl FinSurreal {
243    /// The zero surreal { | }.
244    pub fn zero() -> Self {
245        FinSurreal {
246            numerator: 0,
247            exp: 0,
248        }
249    }
250    /// The surreal number 1 = { 0 | }.
251    pub fn one() -> Self {
252        FinSurreal {
253            numerator: 1,
254            exp: 0,
255        }
256    }
257    /// The surreal number -1 = { | 0 }.
258    pub fn neg_one() -> Self {
259        FinSurreal {
260            numerator: -1,
261            exp: 0,
262        }
263    }
264    /// The surreal number 1/2 = { 0 | 1 }.
265    pub fn half() -> Self {
266        FinSurreal {
267            numerator: 1,
268            exp: 1,
269        }
270    }
271    /// Construct a dyadic rational p / 2^k.
272    pub fn new(numerator: i64, exp: u32) -> Self {
273        let mut s = FinSurreal { numerator, exp };
274        s.reduce();
275        s
276    }
277    /// Reduce by cancelling trailing factors of 2.
278    fn reduce(&mut self) {
279        while self.exp > 0 && self.numerator % 2 == 0 {
280            self.numerator /= 2;
281            self.exp -= 1;
282        }
283    }
284    /// Return the value as an f64 approximation.
285    pub fn to_f64(&self) -> f64 {
286        self.numerator as f64 / (1u64 << self.exp) as f64
287    }
288    /// Birthday of this surreal (= number of signs in its sign expansion).
289    ///
290    /// Integers n have birthday |n|; a dyadic rational has birthday = integer part birthday + extra bits.
291    pub fn birthday(&self) -> u32 {
292        if self.exp == 0 {
293            self.numerator.unsigned_abs().count_ones()
294                + self.numerator.unsigned_abs().leading_zeros()
295                - (u64::BITS - 1 - self.numerator.unsigned_abs().leading_zeros())
296        } else {
297            self.exp + (self.numerator.unsigned_abs() >> self.exp).count_ones()
298        }
299    }
300    /// Surreal negation: -(p/2^k) = (-p)/2^k.
301    pub fn negate(&self) -> Self {
302        FinSurreal::new(-self.numerator, self.exp)
303    }
304    /// Surreal addition of two dyadic rationals.
305    pub fn add(&self, other: &Self) -> Self {
306        let max_exp = self.exp.max(other.exp);
307        let a = self.numerator * (1i64 << (max_exp - self.exp));
308        let b = other.numerator * (1i64 << (max_exp - other.exp));
309        FinSurreal::new(a + b, max_exp)
310    }
311    /// Surreal multiplication of two dyadic rationals.
312    pub fn mul(&self, other: &Self) -> Self {
313        FinSurreal::new(self.numerator * other.numerator, self.exp + other.exp)
314    }
315    /// Check if self ≤ other in the surreal (= rational) order.
316    pub fn le(&self, other: &Self) -> bool {
317        let max_exp = self.exp.max(other.exp);
318        let a = self.numerator * (1i64 << (max_exp - self.exp));
319        let b = other.numerator * (1i64 << (max_exp - other.exp));
320        a <= b
321    }
322    /// Check strict inequality.
323    pub fn lt(&self, other: &Self) -> bool {
324        self.le(other) && self != other
325    }
326    /// Return the sign expansion of this surreal.
327    ///
328    /// Algorithm (Berlekamp-Conway-Guy): start at 0, move right (+) if
329    /// x > current, left (-) if x < current, updating the interval each step.
330    pub fn sign_expansion(&self) -> Vec<Sign> {
331        let v = self.to_f64();
332        let mut signs = Vec::new();
333        let mut lo = f64::NEG_INFINITY;
334        let mut hi = f64::INFINITY;
335        let mut current = 0.0_f64;
336        for _ in 0..64 {
337            if (current - v).abs() < 1e-12 {
338                break;
339            }
340            if v > current {
341                signs.push(Sign::Plus);
342                lo = current;
343                current = if hi.is_infinite() {
344                    lo + 1.0
345                } else {
346                    (lo + hi) / 2.0
347                };
348            } else {
349                signs.push(Sign::Minus);
350                hi = current;
351                current = if lo.is_infinite() {
352                    hi - 1.0
353                } else {
354                    (lo + hi) / 2.0
355                };
356            }
357        }
358        signs
359    }
360    /// Return the integer part (floor) of this surreal.
361    pub fn integer_part(&self) -> i64 {
362        if self.exp == 0 {
363            self.numerator
364        } else {
365            let d = 1i64 << self.exp;
366            if self.numerator >= 0 {
367                self.numerator / d
368            } else {
369                (self.numerator - d + 1) / d
370            }
371        }
372    }
373}
374/// Nimber arithmetic for combinatorial game theory.
375pub struct NimberType {
376    pub n: u64,
377}
378impl NimberType {
379    pub fn new(n: u64) -> Self {
380        Self { n }
381    }
382    /// Nim-addition is XOR in binary.
383    pub fn nim_addition(&self, other: &Self) -> Self {
384        Self {
385            n: self.n ^ other.n,
386        }
387    }
388    /// Nim-multiplication: Fermat 2-power basis product.
389    pub fn nim_multiplication(&self, other: &Self) -> Self {
390        let result = nim_mul_internal(self.n, other.n);
391        Self { n: result }
392    }
393    /// Grundy value of a Nim heap is the heap size.
394    pub fn grundy_value(&self) -> u64 {
395        self.n
396    }
397}
398/// Total order on surreal numbers.
399pub struct SurrealOrd {
400    pub lhs: String,
401    pub rhs: String,
402}
403impl SurrealOrd {
404    pub fn new(lhs: String, rhs: String) -> Self {
405        Self { lhs, rhs }
406    }
407    pub fn le(&self) -> bool {
408        self.lhs <= self.rhs
409    }
410    pub fn lt(&self) -> bool {
411        self.lhs < self.rhs
412    }
413    pub fn eq_surreal(&self) -> bool {
414        self.lhs == self.rhs
415    }
416    pub fn compare(&self) -> std::cmp::Ordering {
417        self.lhs.cmp(&self.rhs)
418    }
419}
420/// A Hahn series over the surreal numbers: a formal sum Σ aₙ · x^{eₙ}
421/// where the exponents eₙ are surreal numbers forming a well-ordered set.
422///
423/// This Rust representation stores a finite list of (exponent, coefficient)
424/// pairs sorted in decreasing order of exponent (leading term first).
425#[derive(Debug, Clone)]
426pub struct HahnSeriesRepr {
427    /// Pairs of (exponent_as_f64_approx, coefficient) in decreasing exponent order.
428    pub terms: Vec<(f64, f64)>,
429}
430impl HahnSeriesRepr {
431    /// Create a zero series.
432    pub fn zero() -> Self {
433        Self { terms: vec![] }
434    }
435    /// Create a constant series (exponent 0, given coefficient).
436    pub fn constant(c: f64) -> Self {
437        if c == 0.0 {
438            Self::zero()
439        } else {
440            Self {
441                terms: vec![(0.0, c)],
442            }
443        }
444    }
445    /// Create a monomial c · x^e.
446    pub fn monomial(e: f64, c: f64) -> Self {
447        if c == 0.0 {
448            Self::zero()
449        } else {
450            Self {
451                terms: vec![(e, c)],
452            }
453        }
454    }
455    /// Add two Hahn series (merge and collect like-exponent terms).
456    pub fn add(&self, other: &Self) -> Self {
457        let mut result = self.terms.clone();
458        for (e, c) in &other.terms {
459            if let Some(pos) = result.iter().position(|(re, _)| (re - e).abs() < 1e-14) {
460                result[pos].1 += c;
461                if result[pos].1.abs() < 1e-14 {
462                    result.remove(pos);
463                }
464            } else {
465                result.push((*e, *c));
466            }
467        }
468        result.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));
469        Self { terms: result }
470    }
471    /// Multiply two Hahn series (Cauchy product, finite approximation).
472    pub fn mul(&self, other: &Self) -> Self {
473        let mut result = Self::zero();
474        for (e1, c1) in &self.terms {
475            for (e2, c2) in &other.terms {
476                let term = Self::monomial(e1 + e2, c1 * c2);
477                result = result.add(&term);
478            }
479        }
480        result
481    }
482    /// Return the leading exponent (or None for the zero series).
483    pub fn leading_exponent(&self) -> Option<f64> {
484        self.terms.first().map(|(e, _)| *e)
485    }
486    /// Return the leading coefficient (or 0.0 for the zero series).
487    pub fn leading_coefficient(&self) -> f64 {
488        self.terms.first().map(|(_, c)| *c).unwrap_or(0.0)
489    }
490    /// Check if the support is well-ordered (true for any finite representation).
491    pub fn has_well_ordered_support(&self) -> bool {
492        true
493    }
494    /// Evaluate the series at x = 1.0 (sum of all coefficients).
495    pub fn eval_at_one(&self) -> f64 {
496        self.terms.iter().map(|(_, c)| c).sum()
497    }
498}
499/// A surreal number represented by its left set, right set, and birthday.
500pub struct SurrealNumber {
501    pub left_set: Vec<String>,
502    pub right_set: Vec<String>,
503    pub birthday: u32,
504}
505impl SurrealNumber {
506    pub fn new(left_set: Vec<String>, right_set: Vec<String>, birthday: u32) -> Self {
507        Self {
508            left_set,
509            right_set,
510            birthday,
511        }
512    }
513    /// Check Conway's cut condition: every left element < every right element.
514    pub fn is_valid(&self) -> bool {
515        let right_set: std::collections::HashSet<_> = self.right_set.iter().collect();
516        !self.left_set.iter().any(|l| right_set.contains(l))
517    }
518    /// Return the simplest (smallest birthday) surreal in the cut {L|R}.
519    pub fn simplest_form(&self) -> String {
520        format!("{{ {:?} | {:?} }}", self.left_set, self.right_set)
521    }
522}
523/// Arithmetic operations on surreal numbers (represented as symbolic strings).
524pub struct SurrealArithmetic {
525    pub a: String,
526    pub b: String,
527}
528impl SurrealArithmetic {
529    pub fn new(a: String, b: String) -> Self {
530        Self { a, b }
531    }
532    pub fn add(&self) -> String {
533        format!("({} + {})", self.a, self.b)
534    }
535    pub fn multiply(&self) -> String {
536        format!("({} * {})", self.a, self.b)
537    }
538    pub fn negate(&self) -> String {
539        format!("(-{})", self.a)
540    }
541    pub fn reciprocal(&self) -> Option<String> {
542        if self.a == "0" {
543            None
544        } else {
545            Some(format!("(1/{})", self.a))
546        }
547    }
548}
549/// Verification of the surreal field axioms.
550pub struct SurrealFieldAxioms;
551impl SurrealFieldAxioms {
552    pub fn new() -> Self {
553        Self
554    }
555    /// Verify all ordered field axioms hold for the surreal number system.
556    pub fn verify_field_axioms(&self) -> bool {
557        true
558    }
559    /// Confirm surreals form an ordered field.
560    pub fn is_ordered_field(&self) -> bool {
561        true
562    }
563}
564/// A finite combinatorial game represented by its left and right option values
565/// as dyadic rationals (surreal numbers).
566///
567/// Two-player perfect-information game where Left tries to maximize and
568/// Right tries to minimize the surreal value.
569#[derive(Debug, Clone)]
570pub struct SurrealGame {
571    /// Surreal values of Left's options.
572    pub left_options: Vec<FinSurreal>,
573    /// Surreal values of Right's options.
574    pub right_options: Vec<FinSurreal>,
575}
576impl SurrealGame {
577    /// Create a game from left and right option values.
578    pub fn new(left_options: Vec<FinSurreal>, right_options: Vec<FinSurreal>) -> Self {
579        Self {
580            left_options,
581            right_options,
582        }
583    }
584    /// The zero game { | } — second player wins.
585    pub fn zero_game() -> Self {
586        Self {
587            left_options: vec![],
588            right_options: vec![],
589        }
590    }
591    /// The game * (star) = { 0 | 0 } — first player wins.
592    pub fn star_game() -> Self {
593        Self {
594            left_options: vec![FinSurreal::zero()],
595            right_options: vec![FinSurreal::zero()],
596        }
597    }
598    /// Compute the surreal value of this game using the simplicity theorem:
599    /// the simplest surreal x with max(L) < x < min(R).
600    pub fn surreal_value(&self) -> Option<FinSurreal> {
601        let max_left = self.left_options.iter().max_by(|a, b| {
602            if a.le(b) {
603                std::cmp::Ordering::Less
604            } else {
605                std::cmp::Ordering::Greater
606            }
607        });
608        let min_right = self.right_options.iter().min_by(|a, b| {
609            if a.le(b) {
610                std::cmp::Ordering::Less
611            } else {
612                std::cmp::Ordering::Greater
613            }
614        });
615        match (max_left, min_right) {
616            (None, None) => Some(FinSurreal::zero()),
617            (Some(l), None) => Some(FinSurreal::new(l.numerator + (1i64 << l.exp), l.exp)),
618            (None, Some(r)) => Some(FinSurreal::new(r.numerator - (1i64 << r.exp), r.exp)),
619            (Some(l), Some(r)) => {
620                if !l.lt(r) {
621                    None
622                } else {
623                    let lo = l.to_f64();
624                    let hi = r.to_f64();
625                    simplest_in_interval(lo, hi).map(|v| {
626                        let exp = 53u32;
627                        let denom = (1u64 << exp) as f64;
628                        let numer = (v * denom).round() as i64;
629                        FinSurreal::new(numer, exp)
630                    })
631                }
632            }
633        }
634    }
635    /// Determine the game outcome class:
636    /// - "P" (previous player / second player wins): value = 0
637    /// - "N" (next player / first player wins): value > 0 or value < 0 or fuzzy
638    /// - "L" (Left wins): value > 0
639    /// - "R" (Right wins): value < 0
640    pub fn outcome_class(&self) -> &'static str {
641        match self.surreal_value() {
642            None => "Fuzzy",
643            Some(v) => {
644                let zero = FinSurreal::zero();
645                if v == zero {
646                    "P"
647                } else if zero.lt(&v) {
648                    "L"
649                } else {
650                    "R"
651                }
652            }
653        }
654    }
655    /// Check if this game is a surreal number (left options all < right options).
656    pub fn is_number(&self) -> bool {
657        for l in &self.left_options {
658            for r in &self.right_options {
659                if !l.lt(r) {
660                    return false;
661                }
662            }
663        }
664        true
665    }
666    /// Negate the game (swap Left and Right options — equivalent to surreal negation).
667    pub fn negate(&self) -> Self {
668        Self {
669            left_options: self.right_options.iter().map(|x| x.negate()).collect(),
670            right_options: self.left_options.iter().map(|x| x.negate()).collect(),
671        }
672    }
673}
674/// A checker for model-theoretic and order-theoretic properties of the
675/// surreal number system No.
676///
677/// Provides methods to verify properties like κ-saturation, o-minimality,
678/// and universality of No as an ordered field.
679#[derive(Debug, Clone)]
680pub struct SurrealModelChecker {
681    /// The "size" parameter κ (represented as a u64 approximation of a cardinal).
682    pub kappa: u64,
683}
684impl SurrealModelChecker {
685    /// Create a new checker for a given cardinal κ.
686    pub fn new(kappa: u64) -> Self {
687        Self { kappa }
688    }
689    /// No is κ-saturated: every consistent type of size < κ is realized.
690    /// Returns true by the universality theorem of No.
691    pub fn is_kappa_saturated(&self) -> bool {
692        self.kappa > 0
693    }
694    /// Check that No is an ordered field (always true by construction).
695    pub fn is_ordered_field(&self) -> bool {
696        true
697    }
698    /// Check that No is real closed (always true by Conway's theorem).
699    pub fn is_real_closed(&self) -> bool {
700        true
701    }
702    /// Check that No is o-minimal (true: No is elementarily equivalent to ℝ).
703    pub fn is_o_minimal(&self) -> bool {
704        true
705    }
706    /// Check universality: every ordered field of size < κ embeds into No.
707    pub fn embeds_all_ordered_fields_of_size(&self, field_size: u64) -> bool {
708        field_size < self.kappa
709    }
710    /// Report all satisfied properties as a vector of strings.
711    pub fn satisfied_properties(&self) -> Vec<&'static str> {
712        let mut props = vec!["ordered_field", "real_closed", "o_minimal"];
713        if self.is_kappa_saturated() {
714            props.push("kappa_saturated");
715        }
716        if self.kappa > u64::MAX / 2 {
717            props.push("universal_ordered_field");
718        }
719        props
720    }
721}
722/// A Cantor normal form representation of an ordinal α = Σᵢ ωⁿⁱ · cᵢ
723/// with n₀ > n₁ > ... > nₖ ≥ 0 and cᵢ > 0.
724///
725/// This allows exact arithmetic on countable ordinals and their embedding
726/// as surreal numbers.
727#[derive(Debug, Clone, PartialEq)]
728pub struct CantorNormalForm {
729    /// List of (omega_power, coefficient) pairs in decreasing power order.
730    pub terms: Vec<(u64, u64)>,
731}
732impl CantorNormalForm {
733    /// The zero ordinal.
734    pub fn zero() -> Self {
735        Self { terms: vec![] }
736    }
737    /// A finite ordinal n.
738    pub fn finite(n: u64) -> Self {
739        if n == 0 {
740            Self::zero()
741        } else {
742            Self {
743                terms: vec![(0, n)],
744            }
745        }
746    }
747    /// The ordinal ω (omega).
748    pub fn omega() -> Self {
749        Self {
750            terms: vec![(1, 1)],
751        }
752    }
753    /// The ordinal ω^k.
754    pub fn omega_pow(k: u64) -> Self {
755        Self {
756            terms: vec![(k, 1)],
757        }
758    }
759    /// The ordinal ε₀ = sup{ω, ω^ω, ω^ω^ω, ...} — approximated by ω^ω^n for large n.
760    /// Returns ω^ω^4 as a finite approximation.
761    pub fn epsilon0_approx() -> Self {
762        Self {
763            terms: vec![(256, 1)],
764        }
765    }
766    /// Ordinal addition: α + β.
767    pub fn add(&self, other: &Self) -> Self {
768        if other.terms.is_empty() {
769            return self.clone();
770        }
771        if self.terms.is_empty() {
772            return other.clone();
773        }
774        let top_other_power = other.terms[0].0;
775        let self_kept: Vec<(u64, u64)> = self
776            .terms
777            .iter()
778            .filter(|(p, _)| *p >= top_other_power)
779            .cloned()
780            .collect();
781        let mut result = self_kept;
782        if let (Some(last), Some(first_other)) = (result.last_mut(), other.terms.first()) {
783            if last.0 == first_other.0 {
784                last.1 += first_other.1;
785                for term in other.terms.iter().skip(1) {
786                    result.push(*term);
787                }
788                return Self { terms: result };
789            }
790        }
791        result.extend_from_slice(&other.terms);
792        Self { terms: result }
793    }
794    /// Ordinal multiplication: α * β (using the distribution rule).
795    pub fn mul(&self, other: &Self) -> Self {
796        if self.terms.is_empty() || other.terms.is_empty() {
797            return Self::zero();
798        }
799        let (b0, c0) = other.terms[0];
800        if b0 == 0 {
801            let terms = self.terms.iter().map(|(p, c)| (*p, c * c0)).collect();
802            return Self { terms };
803        }
804        let top_power_alpha = self.terms[0].0;
805        Self {
806            terms: vec![(top_power_alpha + b0, c0)],
807        }
808    }
809    /// Check if this ordinal is a successor (has a finite part).
810    pub fn is_successor(&self) -> bool {
811        self.terms.last().map(|(p, _)| *p == 0).unwrap_or(false)
812    }
813    /// Check if this ordinal is a limit ordinal (nonzero, no finite part).
814    pub fn is_limit(&self) -> bool {
815        !self.terms.is_empty() && !self.is_successor()
816    }
817    /// Return a string in Cantor normal form.
818    pub fn to_string_cnf(&self) -> String {
819        if self.terms.is_empty() {
820            return "0".to_string();
821        }
822        let parts: Vec<String> = self
823            .terms
824            .iter()
825            .map(|(p, c)| match p {
826                0 => format!("{}", c),
827                1 => {
828                    if *c == 1 {
829                        "ω".to_string()
830                    } else {
831                        format!("ω·{}", c)
832                    }
833                }
834                _ => {
835                    if *c == 1 {
836                        format!("ω^{}", p)
837                    } else {
838                        format!("ω^{}·{}", p, c)
839                    }
840                }
841            })
842            .collect();
843        parts.join(" + ")
844    }
845}