Skip to main content

ries_rs/
symbol.rs

1//! Symbol definitions for RIES expressions
2//!
3//! Symbols represent constants, variables, and operators in postfix notation.
4
5use std::fmt;
6
7/// Stack effect type - how many values a symbol pops and pushes
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9pub enum Seft {
10    /// Constants and variables: push 1 value (pop 0)
11    A,
12    /// Unary operators: pop 1, push 1
13    B,
14    /// Binary operators: pop 2, push 1
15    C,
16}
17
18/// Number type classification for algebraic properties
19#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
20#[repr(u8)]
21pub enum NumType {
22    /// Transcendental (e.g., e^π)
23    Transcendental = 0,
24    /// Liouvillian (closed under exp, ln, and algebraic operations)
25    Liouvillian = 1,
26    /// Elementary (between algebraic and Liouvillian)
27    Elementary = 2,
28    /// Algebraic (roots of polynomials with rational coefficients)
29    Algebraic = 3,
30    /// Constructible (compass and straightedge)
31    Constructible = 4,
32    /// Rational
33    Rational = 5,
34    /// Integer
35    Integer = 6,
36}
37
38impl NumType {
39    /// Combine two types - result is the "weaker" (more general) type
40    #[inline]
41    pub fn combine(self, other: Self) -> Self {
42        std::cmp::min(self, other)
43    }
44
45    /// Check if this type is at least as strong as the given type
46    #[inline]
47    pub fn is_at_least(self, required: Self) -> bool {
48        self >= required
49    }
50}
51
52/// A symbol in a RIES expression
53#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
54#[repr(u8)]
55pub enum Symbol {
56    // === Constants (Seft::A) ===
57    One = b'1',
58    Two = b'2',
59    Three = b'3',
60    Four = b'4',
61    Five = b'5',
62    Six = b'6',
63    Seven = b'7',
64    Eight = b'8',
65    Nine = b'9',
66    Pi = b'p',
67    E = b'e',
68    Phi = b'f',
69    /// Euler-Mascheroni constant γ ≈ 0.5772156649
70    Gamma = b'g',
71    /// Plastic constant ρ ≈ 1.3247179572
72    Plastic = b'P',
73    /// Apéry's constant ζ(3) ≈ 1.2020569032
74    Apery = b'z',
75    /// Catalan's constant G ≈ 0.9159655942
76    Catalan = b'G',
77    X = b'x',
78
79    // === User-defined constant slots (reserved byte range 128-143) ===
80    // These are accessed via UserConstant0, UserConstant1, etc.
81    // The actual values are stored in the profile/symbol table
82    UserConstant0 = 128,
83    UserConstant1 = 129,
84    UserConstant2 = 130,
85    UserConstant3 = 131,
86    UserConstant4 = 132,
87    UserConstant5 = 133,
88    UserConstant6 = 134,
89    UserConstant7 = 135,
90    UserConstant8 = 136,
91    UserConstant9 = 137,
92    UserConstant10 = 138,
93    UserConstant11 = 139,
94    UserConstant12 = 140,
95    UserConstant13 = 141,
96    UserConstant14 = 142,
97    UserConstant15 = 143,
98
99    // === User-defined function slots (reserved byte range 144-159) ===
100    // These act as unary operators that expand to their defined body
101    UserFunction0 = 144,
102    UserFunction1 = 145,
103    UserFunction2 = 146,
104    UserFunction3 = 147,
105    UserFunction4 = 148,
106    UserFunction5 = 149,
107    UserFunction6 = 150,
108    UserFunction7 = 151,
109    UserFunction8 = 152,
110    UserFunction9 = 153,
111    UserFunction10 = 154,
112    UserFunction11 = 155,
113    UserFunction12 = 156,
114    UserFunction13 = 157,
115    UserFunction14 = 158,
116    UserFunction15 = 159,
117
118    // === Unary operators (Seft::B) ===
119    Neg = b'n',
120    Recip = b'r',
121    Sqrt = b'q',
122    Square = b's',
123    Ln = b'l',
124    Exp = b'E',
125    SinPi = b'S',
126    CosPi = b'C',
127    TanPi = b'T',
128    LambertW = b'W',
129
130    // === Binary operators (Seft::C) ===
131    Add = b'+',
132    Sub = b'-',
133    Mul = b'*',
134    Div = b'/',
135    Pow = b'^',
136    Root = b'v', // a-th root of b
137    Log = b'L',  // log base a of b
138    Atan2 = b'A',
139}
140
141impl Symbol {
142    /// Get the stack effect type of this symbol
143    #[inline]
144    pub const fn seft(self) -> Seft {
145        use Symbol::*;
146        match self {
147            One | Two | Three | Four | Five | Six | Seven | Eight | Nine | Pi | E | Phi | Gamma
148            | Plastic | Apery | Catalan | X | UserConstant0 | UserConstant1 | UserConstant2
149            | UserConstant3 | UserConstant4 | UserConstant5 | UserConstant6 | UserConstant7
150            | UserConstant8 | UserConstant9 | UserConstant10 | UserConstant11 | UserConstant12
151            | UserConstant13 | UserConstant14 | UserConstant15 => Seft::A,
152
153            Neg | Recip | Sqrt | Square | Ln | Exp | SinPi | CosPi | TanPi | LambertW
154            | UserFunction0 | UserFunction1 | UserFunction2 | UserFunction3 | UserFunction4
155            | UserFunction5 | UserFunction6 | UserFunction7 | UserFunction8 | UserFunction9
156            | UserFunction10 | UserFunction11 | UserFunction12 | UserFunction13
157            | UserFunction14 | UserFunction15 => Seft::B,
158
159            Add | Sub | Mul | Div | Pow | Root | Log | Atan2 => Seft::C,
160        }
161    }
162
163    /// Get the default complexity weight of this symbol
164    ///
165    /// Complexity weights determine how "simple" an expression is, affecting
166    /// which equations RIES presents first. Lower complexity = simpler expression.
167    ///
168    /// # Calibration Methodology
169    ///
170    /// Weights are calibrated to match original RIES behavior while ensuring
171    /// intuitive simplicity ordering:
172    ///
173    /// ## Constants
174    /// - **Small integers (1-9)**: Range from 3-6, with smaller digits cheaper
175    ///   - Rationale: Single digits are fundamental building blocks
176    ///   - `1` and `2` are cheapest (3) as they appear in most simple equations
177    ///   - Larger digits cost more as they're less "fundamental"
178    ///
179    /// - **Transcendental constants (π, e)**: Weight 8
180    ///   - Higher than integers as they require special notation
181    ///   - Same weight as they're equally "fundamental" in mathematics
182    ///
183    /// - **Algebraic constants (φ, ρ)**: Weight 10
184    ///   - Higher than π/e as they're less commonly used
185    ///   - Plastic constant (ρ) is algebraic (root of x³ = x + 1)
186    ///
187    /// - **Special constants (γ, ζ(3), G)**: Weight 10-12
188    ///   - Euler-Mascheroni γ and Catalan's G: 10
189    ///   - Apéry's constant ζ(3): 12 (higher due to obscurity)
190    ///
191    /// ## Unary Operators
192    /// - **Negation (-)**: Weight 4 - simplest unary operation
193    /// - **Reciprocal (1/x)**: Weight 5 - slightly more complex
194    /// - **Square (x²)**: Weight 5 - very common, moderate cost
195    /// - **Square root (√)**: Weight 6 - inverse of square
196    /// - **Logarithm (ln)**: Weight 8 - transcendental operation
197    /// - **Exponential (e^x)**: Weight 8 - inverse of ln, transcendental
198    /// - **Trigonometric (sin(πx), cos(πx))**: Weight 9-10 - periodic complexity
199    /// - **Lambert W**: Weight 12 - most complex, rarely used
200    ///
201    /// ## Binary Operators
202    /// - **Addition/Subtraction (+, -)**: Weight 3 - simplest operations
203    /// - **Multiplication (*)**: Weight 3 - fundamental arithmetic
204    /// - **Division (/)**: Weight 4 - slightly more complex than multiply
205    /// - **Power (^)**: Weight 5 - exponentiation
206    /// - **Root (ᵃ√b)**: Weight 6 - inverse of power, more notation
207    /// - **Logarithm base (log_a b)**: Weight 7 - two transcendental ops
208    /// - **Atan2**: Weight 7 - two-argument inverse trig
209    ///
210    /// # Example Weight Calculations
211    ///
212    /// ```text
213    /// Expression    Postfix    Weight Calculation          Total
214    /// x = 2         x2=        6(x) + 3(2)                 9
215    /// x² = 4        xs4=       6(x) + 5(s) + 4(4)          15
216    /// 2x = 5        2x*5=      3(2) + 6(x) + 3(*) + 5(5)   17
217    /// e^x = π       xEep       6(x) + 8(E) + 8(p)          22
218    /// x^x = π²      xx^ps      6+6+5+8+5                   30
219    /// ```
220    ///
221    /// # Design Philosophy
222    ///
223    /// The weight system follows these principles:
224    ///
225    /// 1. **Pedagogical value**: Simpler concepts have lower weights
226    /// 2. **Historical consistency**: Weights approximate original RIES behavior
227    /// 3. **Practical usage**: Commonly-used operations are cheaper
228    /// 4. **Composability**: Complex expressions = sum of symbol weights
229    ///
230    /// # See Also
231    ///
232    /// For a detailed explanation of the calibration process and rationale,
233    /// see `docs/COMPLEXITY.md` in the source repository.
234    #[inline]
235    pub fn weight(self) -> u32 {
236        self.default_weight()
237    }
238
239    /// Get the default complexity weight (without overrides)
240    ///
241    /// This is used by SymbolTable to build per-run weight configurations.
242    #[inline]
243    pub const fn default_weight(self) -> u32 {
244        use Symbol::*;
245        match self {
246            // Digits: original RIES calibration (1=10, 2=13, ..., 9=19)
247            // These weights reflect the "specificity" each digit contributes —
248            // a digit encodes more information than a structural operator.
249            One => 10,
250            Two => 13,
251            Three => 15,
252            Four => 16,
253            Five => 17,
254            Six => 18,
255            Seven => 18,
256            Eight => 19,
257            Nine => 19,
258
259            // Named constants: original RIES calibration
260            Pi => 14,
261            E => 16,
262            Phi => 18,
263
264            // Extensions not in original RIES — weighted by tier:
265            // similar obscurity/frequency to phi (weight 18) or slightly above
266            Gamma => 20,   // Euler-Mascheroni γ — less common than phi
267            Plastic => 20, // Plastic constant (algebraic, obscure)
268            Apery => 22,   // Apéry's constant ζ(3) — rarely appears in closed forms
269            Catalan => 20, // Catalan's constant
270
271            // Variable
272            X => 15,
273
274            // User constants: similar to named constants
275            UserConstant0 | UserConstant1 | UserConstant2 | UserConstant3 | UserConstant4
276            | UserConstant5 | UserConstant6 | UserConstant7 | UserConstant8 | UserConstant9
277            | UserConstant10 | UserConstant11 | UserConstant12 | UserConstant13
278            | UserConstant14 | UserConstant15 => 16,
279
280            // Unary operators: original RIES calibration
281            Neg => 7,
282            Recip => 7,
283            Sqrt => 9,
284            Square => 9,
285            Ln => 13,
286            Exp => 13,
287            SinPi => 13,
288            CosPi => 13,
289            TanPi => 16,
290            LambertW => 20, // Not in original RIES; heavier than tanpi
291
292            // User-defined functions
293            UserFunction0 | UserFunction1 | UserFunction2 | UserFunction3 | UserFunction4
294            | UserFunction5 | UserFunction6 | UserFunction7 | UserFunction8 | UserFunction9
295            | UserFunction10 | UserFunction11 | UserFunction12 | UserFunction13
296            | UserFunction14 | UserFunction15 => 16,
297
298            // Binary operators: original RIES calibration
299            Add => 4,
300            Sub => 5,
301            Mul => 4,
302            Div => 5,
303            Pow => 6,
304            Root => 7,
305            Log => 9,
306            Atan2 => 9,
307        }
308    }
309
310    /// Legacy (original RIES) per-symbol weight delta used for parity ranking.
311    ///
312    /// Original RIES uses a base symbol cost plus a signed per-symbol delta where
313    /// many binary operators have negative deltas. This function exposes the
314    /// signed delta component for output-ranking parity mode.
315    #[inline]
316    pub fn legacy_parity_weight(self) -> i32 {
317        use Symbol::*;
318        match self {
319            // Constants (original C deltas)
320            One => 0,
321            Two => 3,
322            Three => 5,
323            Four => 6,
324            Five => 7,
325            Six => 8,
326            Seven => 8,
327            Eight => 9,
328            Nine => 9,
329            Pi => 4,
330            E => 6,
331            Phi => 8,
332            X => 5,
333
334            // Unary operators
335            Neg => -3,
336            Recip => -3,
337            Square => -1,
338            Sqrt => -1,
339            Ln => 3,
340            Exp => 3,
341            SinPi => 3,
342            CosPi => 3,
343            TanPi => 6,
344            LambertW => 5,
345
346            // Binary operators
347            Add => -6,
348            Sub => -5,
349            Mul => -6,
350            Div => -5,
351            Pow => -4,
352            Root => -3,
353            Log => -1,
354            Atan2 => -1,
355
356            // Symbols absent from original baseline: use configured weight.
357            _ => self.weight() as i32,
358        }
359    }
360
361    /// Get the result type when this operation is applied
362    pub fn result_type(self, arg_types: &[NumType]) -> NumType {
363        use NumType::*;
364        use Symbol::*;
365
366        match self {
367            // Integer constants
368            One | Two | Three | Four | Five | Six | Seven | Eight | Nine => Integer,
369
370            // Transcendental constants
371            Pi | E => Transcendental,
372
373            // Algebraic constant
374            Phi => Algebraic,
375
376            // New constants
377            // Euler-Mascheroni γ is believed to be transcendental
378            Gamma => Transcendental,
379            // Plastic constant is algebraic (root of x³ = x + 1)
380            Plastic => Algebraic,
381            // Apéry's constant ζ(3) is irrational but type unknown
382            Apery => Transcendental,
383            // Catalan's constant is believed to be transcendental
384            Catalan => Transcendental,
385
386            // Variable inherits from context
387            X => Transcendental,
388
389            // User constants - assume transcendental (most general)
390            UserConstant0 | UserConstant1 | UserConstant2 | UserConstant3 | UserConstant4
391            | UserConstant5 | UserConstant6 | UserConstant7 | UserConstant8 | UserConstant9
392            | UserConstant10 | UserConstant11 | UserConstant12 | UserConstant13
393            | UserConstant14 | UserConstant15 => Transcendental,
394
395            // Operations that preserve integer-ness
396            Neg | Add | Sub | Mul => {
397                if arg_types.iter().all(|t| *t == Integer) {
398                    Integer
399                } else {
400                    arg_types.iter().copied().fold(Integer, NumType::combine)
401                }
402            }
403
404            // Division: integer -> rational
405            Div | Recip => {
406                let base = arg_types.iter().copied().fold(Integer, NumType::combine);
407                if base == Integer {
408                    Rational
409                } else {
410                    base
411                }
412            }
413
414            // Square root: rational -> constructible (or algebraic)
415            Sqrt => {
416                let base = arg_types.iter().copied().fold(Integer, NumType::combine);
417                if base.is_at_least(Constructible) {
418                    Constructible
419                } else if base.is_at_least(Algebraic) {
420                    Algebraic
421                } else {
422                    base
423                }
424            }
425
426            // Square preserves type
427            Square => arg_types.iter().copied().fold(Integer, NumType::combine),
428
429            // Nth root: generally algebraic
430            Root => Algebraic,
431
432            // Power: depends on exponent
433            Pow => {
434                // If exponent is integer, result has the same type as the base.
435                // Otherwise (fractional/algebraic/transcendental exponent), generally transcendental.
436                // arg_types = [base_type, exponent_type] in postfix stack order.
437                if arg_types.len() >= 2 && arg_types[1] == Integer {
438                    arg_types[0]
439                } else {
440                    Transcendental
441                }
442            }
443
444            // Transcendental functions
445            Ln | Exp | SinPi | CosPi | TanPi | Log | LambertW | Atan2 => Transcendental,
446
447            // User-defined functions - assume transcendental (most general)
448            UserFunction0 | UserFunction1 | UserFunction2 | UserFunction3 | UserFunction4
449            | UserFunction5 | UserFunction6 | UserFunction7 | UserFunction8 | UserFunction9
450            | UserFunction10 | UserFunction11 | UserFunction12 | UserFunction13
451            | UserFunction14 | UserFunction15 => Transcendental,
452        }
453    }
454
455    /// Get the inherent numeric type of this symbol (for constants)
456    /// Returns Transcendental for operators (since they can produce any type)
457    pub const fn inherent_type(self) -> NumType {
458        use NumType::*;
459        use Symbol::*;
460
461        match self {
462            // Integer constants
463            One | Two | Three | Four | Five | Six | Seven | Eight | Nine => Integer,
464
465            // Transcendental constants
466            Pi | E => Transcendental,
467
468            // Algebraic constant
469            Phi => Algebraic,
470
471            // New constants
472            Gamma => Transcendental,
473            Plastic => Algebraic,
474            Apery => Transcendental,
475            Catalan => Transcendental,
476
477            // Variable
478            X => Transcendental,
479
480            // User constants - assume transcendental (most general)
481            UserConstant0 | UserConstant1 | UserConstant2 | UserConstant3 | UserConstant4
482            | UserConstant5 | UserConstant6 | UserConstant7 | UserConstant8 | UserConstant9
483            | UserConstant10 | UserConstant11 | UserConstant12 | UserConstant13
484            | UserConstant14 | UserConstant15 => Transcendental,
485
486            // All operators default to Transcendental (most general)
487            // The actual result type depends on operands
488            _ => Transcendental,
489        }
490    }
491
492    /// Get the infix name for display
493    pub const fn name(self) -> &'static str {
494        use Symbol::*;
495        match self {
496            One => "1",
497            Two => "2",
498            Three => "3",
499            Four => "4",
500            Five => "5",
501            Six => "6",
502            Seven => "7",
503            Eight => "8",
504            Nine => "9",
505            Pi => "pi",
506            E => "e",
507            Phi => "phi",
508            Gamma => "gamma",
509            Plastic => "plastic",
510            Apery => "apery",
511            Catalan => "catalan",
512            X => "x",
513            Neg => "-",
514            Recip => "1/",
515            Sqrt => "sqrt",
516            Square => "^2",
517            Ln => "ln",
518            Exp => "e^",
519            SinPi => "sinpi",
520            CosPi => "cospi",
521            TanPi => "tanpi",
522            LambertW => "W",
523            Add => "+",
524            Sub => "-",
525            Mul => "*",
526            Div => "/",
527            Pow => "^",
528            Root => "\"/",
529            Log => "log_",
530            Atan2 => "atan2",
531            // User constants - placeholder names (can be overridden by profile)
532            UserConstant0 => "u0",
533            UserConstant1 => "u1",
534            UserConstant2 => "u2",
535            UserConstant3 => "u3",
536            UserConstant4 => "u4",
537            UserConstant5 => "u5",
538            UserConstant6 => "u6",
539            UserConstant7 => "u7",
540            UserConstant8 => "u8",
541            UserConstant9 => "u9",
542            UserConstant10 => "u10",
543            UserConstant11 => "u11",
544            UserConstant12 => "u12",
545            UserConstant13 => "u13",
546            UserConstant14 => "u14",
547            UserConstant15 => "u15",
548            // User functions - placeholder names (can be overridden by profile)
549            UserFunction0 => "f0",
550            UserFunction1 => "f1",
551            UserFunction2 => "f2",
552            UserFunction3 => "f3",
553            UserFunction4 => "f4",
554            UserFunction5 => "f5",
555            UserFunction6 => "f6",
556            UserFunction7 => "f7",
557            UserFunction8 => "f8",
558            UserFunction9 => "f9",
559            UserFunction10 => "f10",
560            UserFunction11 => "f11",
561            UserFunction12 => "f12",
562            UserFunction13 => "f13",
563            UserFunction14 => "f14",
564            UserFunction15 => "f15",
565        }
566    }
567
568    /// Get the display name for this symbol.
569    ///
570    /// Note: For per-run name overrides, use `SymbolTable::name()` instead.
571    pub fn display_name(self) -> String {
572        self.name().to_string()
573    }
574
575    /// Parse a symbol from its byte representation
576    pub fn from_byte(b: u8) -> Option<Self> {
577        use Symbol::*;
578        Some(match b {
579            b'1' => One,
580            b'2' => Two,
581            b'3' => Three,
582            b'4' => Four,
583            b'5' => Five,
584            b'6' => Six,
585            b'7' => Seven,
586            b'8' => Eight,
587            b'9' => Nine,
588            b'p' => Pi,
589            b'e' => E,
590            b'f' => Phi,
591            b'x' => X,
592            b'g' => Gamma,
593            b'P' => Plastic,
594            b'z' => Apery,
595            b'G' => Catalan,
596            b'n' => Neg,
597            b'r' => Recip,
598            b'q' => Sqrt,
599            b's' => Square,
600            b'l' => Ln,
601            b'E' => Exp,
602            b'S' => SinPi,
603            b'C' => CosPi,
604            b'T' => TanPi,
605            b'W' => LambertW,
606            b'+' => Add,
607            b'-' => Sub,
608            b'*' => Mul,
609            b'/' => Div,
610            b'^' => Pow,
611            b'v' => Root,
612            b'L' => Log,
613            b'A' => Atan2,
614            // User constants (byte range 128-143)
615            128 => UserConstant0,
616            129 => UserConstant1,
617            130 => UserConstant2,
618            131 => UserConstant3,
619            132 => UserConstant4,
620            133 => UserConstant5,
621            134 => UserConstant6,
622            135 => UserConstant7,
623            136 => UserConstant8,
624            137 => UserConstant9,
625            138 => UserConstant10,
626            139 => UserConstant11,
627            140 => UserConstant12,
628            141 => UserConstant13,
629            142 => UserConstant14,
630            143 => UserConstant15,
631            // User functions (byte range 144-159)
632            // Also support printable aliases for CLI use:
633            // 'H'-'W' (skipping used ones) and 'Y', 'Z'
634            144 => UserFunction0,
635            145 => UserFunction1,
636            146 => UserFunction2,
637            147 => UserFunction3,
638            148 => UserFunction4,
639            149 => UserFunction5,
640            150 => UserFunction6,
641            151 => UserFunction7,
642            152 => UserFunction8,
643            153 => UserFunction9,
644            154 => UserFunction10,
645            155 => UserFunction11,
646            156 => UserFunction12,
647            157 => UserFunction13,
648            158 => UserFunction14,
649            159 => UserFunction15,
650            // Printable aliases for user functions (for CLI expression parsing)
651            // H=0, I=1, J=2, K=3, M=4, N=5, O=6, Q=7, R=8, U=9, V=10, Y=11, Z=12, B=13, D=14, F=15
652            b'H' => UserFunction0,
653            b'I' => UserFunction1,
654            b'J' => UserFunction2,
655            b'K' => UserFunction3,
656            b'M' => UserFunction4,
657            b'N' => UserFunction5,
658            b'O' => UserFunction6,
659            b'Q' => UserFunction7,
660            b'R' => UserFunction8,
661            b'U' => UserFunction9,
662            b'V' => UserFunction10,
663            b'Y' => UserFunction11,
664            b'Z' => UserFunction12,
665            b'B' => UserFunction13,
666            b'D' => UserFunction14,
667            b'F' => UserFunction15,
668            _ => return None,
669        })
670    }
671
672    /// Get user constant index (0-15) if this is a user constant symbol
673    pub fn user_constant_index(self) -> Option<u8> {
674        use Symbol::*;
675        match self {
676            UserConstant0 => Some(0),
677            UserConstant1 => Some(1),
678            UserConstant2 => Some(2),
679            UserConstant3 => Some(3),
680            UserConstant4 => Some(4),
681            UserConstant5 => Some(5),
682            UserConstant6 => Some(6),
683            UserConstant7 => Some(7),
684            UserConstant8 => Some(8),
685            UserConstant9 => Some(9),
686            UserConstant10 => Some(10),
687            UserConstant11 => Some(11),
688            UserConstant12 => Some(12),
689            UserConstant13 => Some(13),
690            UserConstant14 => Some(14),
691            UserConstant15 => Some(15),
692            _ => None,
693        }
694    }
695
696    /// Get user function index (0-15) if this is a user function symbol
697    pub fn user_function_index(self) -> Option<u8> {
698        use Symbol::*;
699        match self {
700            UserFunction0 => Some(0),
701            UserFunction1 => Some(1),
702            UserFunction2 => Some(2),
703            UserFunction3 => Some(3),
704            UserFunction4 => Some(4),
705            UserFunction5 => Some(5),
706            UserFunction6 => Some(6),
707            UserFunction7 => Some(7),
708            UserFunction8 => Some(8),
709            UserFunction9 => Some(9),
710            UserFunction10 => Some(10),
711            UserFunction11 => Some(11),
712            UserFunction12 => Some(12),
713            UserFunction13 => Some(13),
714            UserFunction14 => Some(14),
715            UserFunction15 => Some(15),
716            _ => None,
717        }
718    }
719
720    /// Get all constant symbols (Seft::A)
721    pub fn constants() -> &'static [Symbol] {
722        use Symbol::*;
723        &[
724            One, Two, Three, Four, Five, Six, Seven, Eight, Nine, Pi, E, Phi, Gamma, Plastic,
725            Apery, Catalan,
726        ]
727    }
728
729    /// Get all unary operators (Seft::B)
730    pub fn unary_ops() -> &'static [Symbol] {
731        use Symbol::*;
732        &[
733            Neg, Recip, Sqrt, Square, Ln, Exp, SinPi, CosPi, TanPi, LambertW,
734        ]
735    }
736
737    /// Get all binary operators (Seft::C)
738    pub fn binary_ops() -> &'static [Symbol] {
739        use Symbol::*;
740        &[Add, Sub, Mul, Div, Pow, Root, Log, Atan2]
741    }
742}
743
744impl fmt::Display for Symbol {
745    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
746        write!(f, "{}", self.name())
747    }
748}
749
750impl From<Symbol> for u8 {
751    fn from(s: Symbol) -> u8 {
752        s as u8
753    }
754}
755
756#[cfg(test)]
757mod tests {
758    use super::*;
759
760    /// Weights must match original RIES calibration so that the complexity
761    /// scale has the same meaning — preventing expression count explosion.
762    #[test]
763    fn test_weights_match_original_ries() {
764        // Digits: original RIES uses 10/13/15/16/17/18/18/19/19
765        assert_eq!(Symbol::One.default_weight(), 10);
766        assert_eq!(Symbol::Two.default_weight(), 13);
767        assert_eq!(Symbol::Three.default_weight(), 15);
768        assert_eq!(Symbol::Four.default_weight(), 16);
769        assert_eq!(Symbol::Five.default_weight(), 17);
770        assert_eq!(Symbol::Six.default_weight(), 18);
771        assert_eq!(Symbol::Seven.default_weight(), 18);
772        assert_eq!(Symbol::Eight.default_weight(), 19);
773        assert_eq!(Symbol::Nine.default_weight(), 19);
774
775        // Named constants: original RIES uses pi=14, e=16, phi=18, x=15
776        assert_eq!(Symbol::Pi.default_weight(), 14);
777        assert_eq!(Symbol::E.default_weight(), 16);
778        assert_eq!(Symbol::Phi.default_weight(), 18);
779        assert_eq!(Symbol::X.default_weight(), 15);
780
781        // Binary operators: original RIES uses +=4, *=4, -=5, /=5, ^=6, root=7, atan2=9, log=9
782        assert_eq!(Symbol::Add.default_weight(), 4);
783        assert_eq!(Symbol::Mul.default_weight(), 4);
784        assert_eq!(Symbol::Sub.default_weight(), 5);
785        assert_eq!(Symbol::Div.default_weight(), 5);
786        assert_eq!(Symbol::Pow.default_weight(), 6);
787        assert_eq!(Symbol::Root.default_weight(), 7);
788        assert_eq!(Symbol::Atan2.default_weight(), 9);
789        assert_eq!(Symbol::Log.default_weight(), 9);
790
791        // Unary operators: original RIES uses neg=7, recip=7, sqrt=9, sq=9, ln=13, exp=13, sinpi=13, cospi=13, tanpi=16
792        assert_eq!(Symbol::Neg.default_weight(), 7);
793        assert_eq!(Symbol::Recip.default_weight(), 7);
794        assert_eq!(Symbol::Sqrt.default_weight(), 9);
795        assert_eq!(Symbol::Square.default_weight(), 9);
796        assert_eq!(Symbol::Ln.default_weight(), 13);
797        assert_eq!(Symbol::Exp.default_weight(), 13);
798        assert_eq!(Symbol::SinPi.default_weight(), 13);
799        assert_eq!(Symbol::CosPi.default_weight(), 13);
800        assert_eq!(Symbol::TanPi.default_weight(), 16);
801    }
802
803    #[test]
804    fn test_symbol_roundtrip() {
805        for &sym in Symbol::constants()
806            .iter()
807            .chain(Symbol::unary_ops())
808            .chain(Symbol::binary_ops())
809        {
810            let byte = sym as u8;
811            let parsed = Symbol::from_byte(byte).unwrap();
812            assert_eq!(sym, parsed);
813        }
814    }
815
816    #[test]
817    fn test_num_type_ordering() {
818        assert!(NumType::Integer > NumType::Rational);
819        assert!(NumType::Rational > NumType::Algebraic);
820        assert!(NumType::Algebraic > NumType::Transcendental);
821    }
822
823    #[test]
824    fn test_seft() {
825        assert_eq!(Symbol::Pi.seft(), Seft::A);
826        assert_eq!(Symbol::Sqrt.seft(), Seft::B);
827        assert_eq!(Symbol::Add.seft(), Seft::C);
828    }
829
830    // result_type arg order: for postfix `a b Pow`, arg_types = [base_type, exponent_type]
831    // Rule: integer exponent preserves the base's type; non-integer exponent → Transcendental.
832
833    #[test]
834    fn test_pow_result_type_algebraic_base_integer_exponent() {
835        // sqrt(2)^2 → Algebraic^Integer → should be Algebraic (an algebraic number raised
836        // to an integer power is still algebraic)
837        let result = Symbol::Pow.result_type(&[NumType::Algebraic, NumType::Integer]);
838        assert_eq!(
839            result,
840            NumType::Algebraic,
841            "Algebraic^Integer should be Algebraic"
842        );
843    }
844
845    #[test]
846    fn test_pow_result_type_integer_base_algebraic_exponent() {
847        // 2^phi → Integer^Algebraic → should be Transcendental
848        // (non-integer exponent makes the result transcendental by Gelfond-Schneider)
849        let result = Symbol::Pow.result_type(&[NumType::Integer, NumType::Algebraic]);
850        assert_eq!(
851            result,
852            NumType::Transcendental,
853            "Integer^Algebraic should be Transcendental"
854        );
855    }
856
857    #[test]
858    fn test_pow_result_type_integer_base_integer_exponent() {
859        // 2^3 = 8 → Integer
860        let result = Symbol::Pow.result_type(&[NumType::Integer, NumType::Integer]);
861        assert_eq!(
862            result,
863            NumType::Integer,
864            "Integer^Integer should be Integer"
865        );
866    }
867}