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, preserves algebraic-ness
435                // Otherwise, generally transcendental
436                if arg_types.len() >= 2 && arg_types[0] == Integer {
437                    arg_types[1]
438                } else {
439                    Transcendental
440                }
441            }
442
443            // Transcendental functions
444            Ln | Exp | SinPi | CosPi | TanPi | Log | LambertW | Atan2 => Transcendental,
445
446            // User-defined functions - assume transcendental (most general)
447            UserFunction0 | UserFunction1 | UserFunction2 | UserFunction3 | UserFunction4
448            | UserFunction5 | UserFunction6 | UserFunction7 | UserFunction8 | UserFunction9
449            | UserFunction10 | UserFunction11 | UserFunction12 | UserFunction13
450            | UserFunction14 | UserFunction15 => Transcendental,
451        }
452    }
453
454    /// Get the inherent numeric type of this symbol (for constants)
455    /// Returns Transcendental for operators (since they can produce any type)
456    pub const fn inherent_type(self) -> NumType {
457        use NumType::*;
458        use Symbol::*;
459
460        match self {
461            // Integer constants
462            One | Two | Three | Four | Five | Six | Seven | Eight | Nine => Integer,
463
464            // Transcendental constants
465            Pi | E => Transcendental,
466
467            // Algebraic constant
468            Phi => Algebraic,
469
470            // New constants
471            Gamma => Transcendental,
472            Plastic => Algebraic,
473            Apery => Transcendental,
474            Catalan => Transcendental,
475
476            // Variable
477            X => Transcendental,
478
479            // User constants - assume transcendental (most general)
480            UserConstant0 | UserConstant1 | UserConstant2 | UserConstant3 | UserConstant4
481            | UserConstant5 | UserConstant6 | UserConstant7 | UserConstant8 | UserConstant9
482            | UserConstant10 | UserConstant11 | UserConstant12 | UserConstant13
483            | UserConstant14 | UserConstant15 => Transcendental,
484
485            // All operators default to Transcendental (most general)
486            // The actual result type depends on operands
487            _ => Transcendental,
488        }
489    }
490
491    /// Get the infix name for display
492    pub const fn name(self) -> &'static str {
493        use Symbol::*;
494        match self {
495            One => "1",
496            Two => "2",
497            Three => "3",
498            Four => "4",
499            Five => "5",
500            Six => "6",
501            Seven => "7",
502            Eight => "8",
503            Nine => "9",
504            Pi => "pi",
505            E => "e",
506            Phi => "phi",
507            Gamma => "gamma",
508            Plastic => "plastic",
509            Apery => "apery",
510            Catalan => "catalan",
511            X => "x",
512            Neg => "-",
513            Recip => "1/",
514            Sqrt => "sqrt",
515            Square => "^2",
516            Ln => "ln",
517            Exp => "e^",
518            SinPi => "sinpi",
519            CosPi => "cospi",
520            TanPi => "tanpi",
521            LambertW => "W",
522            Add => "+",
523            Sub => "-",
524            Mul => "*",
525            Div => "/",
526            Pow => "^",
527            Root => "\"/",
528            Log => "log_",
529            Atan2 => "atan2",
530            // User constants - placeholder names (can be overridden by profile)
531            UserConstant0 => "u0",
532            UserConstant1 => "u1",
533            UserConstant2 => "u2",
534            UserConstant3 => "u3",
535            UserConstant4 => "u4",
536            UserConstant5 => "u5",
537            UserConstant6 => "u6",
538            UserConstant7 => "u7",
539            UserConstant8 => "u8",
540            UserConstant9 => "u9",
541            UserConstant10 => "u10",
542            UserConstant11 => "u11",
543            UserConstant12 => "u12",
544            UserConstant13 => "u13",
545            UserConstant14 => "u14",
546            UserConstant15 => "u15",
547            // User functions - placeholder names (can be overridden by profile)
548            UserFunction0 => "f0",
549            UserFunction1 => "f1",
550            UserFunction2 => "f2",
551            UserFunction3 => "f3",
552            UserFunction4 => "f4",
553            UserFunction5 => "f5",
554            UserFunction6 => "f6",
555            UserFunction7 => "f7",
556            UserFunction8 => "f8",
557            UserFunction9 => "f9",
558            UserFunction10 => "f10",
559            UserFunction11 => "f11",
560            UserFunction12 => "f12",
561            UserFunction13 => "f13",
562            UserFunction14 => "f14",
563            UserFunction15 => "f15",
564        }
565    }
566
567    /// Get the display name for this symbol.
568    ///
569    /// Note: For per-run name overrides, use `SymbolTable::name()` instead.
570    pub fn display_name(self) -> String {
571        self.name().to_string()
572    }
573
574    /// Parse a symbol from its byte representation
575    pub fn from_byte(b: u8) -> Option<Self> {
576        use Symbol::*;
577        Some(match b {
578            b'1' => One,
579            b'2' => Two,
580            b'3' => Three,
581            b'4' => Four,
582            b'5' => Five,
583            b'6' => Six,
584            b'7' => Seven,
585            b'8' => Eight,
586            b'9' => Nine,
587            b'p' => Pi,
588            b'e' => E,
589            b'f' => Phi,
590            b'x' => X,
591            b'g' => Gamma,
592            b'P' => Plastic,
593            b'z' => Apery,
594            b'G' => Catalan,
595            b'n' => Neg,
596            b'r' => Recip,
597            b'q' => Sqrt,
598            b's' => Square,
599            b'l' => Ln,
600            b'E' => Exp,
601            b'S' => SinPi,
602            b'C' => CosPi,
603            b'T' => TanPi,
604            b'W' => LambertW,
605            b'+' => Add,
606            b'-' => Sub,
607            b'*' => Mul,
608            b'/' => Div,
609            b'^' => Pow,
610            b'v' => Root,
611            b'L' => Log,
612            b'A' => Atan2,
613            // User constants (byte range 128-143)
614            128 => UserConstant0,
615            129 => UserConstant1,
616            130 => UserConstant2,
617            131 => UserConstant3,
618            132 => UserConstant4,
619            133 => UserConstant5,
620            134 => UserConstant6,
621            135 => UserConstant7,
622            136 => UserConstant8,
623            137 => UserConstant9,
624            138 => UserConstant10,
625            139 => UserConstant11,
626            140 => UserConstant12,
627            141 => UserConstant13,
628            142 => UserConstant14,
629            143 => UserConstant15,
630            // User functions (byte range 144-159)
631            // Also support printable aliases for CLI use:
632            // 'H'-'W' (skipping used ones) and 'Y', 'Z'
633            144 => UserFunction0,
634            145 => UserFunction1,
635            146 => UserFunction2,
636            147 => UserFunction3,
637            148 => UserFunction4,
638            149 => UserFunction5,
639            150 => UserFunction6,
640            151 => UserFunction7,
641            152 => UserFunction8,
642            153 => UserFunction9,
643            154 => UserFunction10,
644            155 => UserFunction11,
645            156 => UserFunction12,
646            157 => UserFunction13,
647            158 => UserFunction14,
648            159 => UserFunction15,
649            // Printable aliases for user functions (for CLI expression parsing)
650            // 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
651            b'H' => UserFunction0,
652            b'I' => UserFunction1,
653            b'J' => UserFunction2,
654            b'K' => UserFunction3,
655            b'M' => UserFunction4,
656            b'N' => UserFunction5,
657            b'O' => UserFunction6,
658            b'Q' => UserFunction7,
659            b'R' => UserFunction8,
660            b'U' => UserFunction9,
661            b'V' => UserFunction10,
662            b'Y' => UserFunction11,
663            b'Z' => UserFunction12,
664            b'B' => UserFunction13,
665            b'D' => UserFunction14,
666            b'F' => UserFunction15,
667            _ => return None,
668        })
669    }
670
671    /// Get user constant index (0-15) if this is a user constant symbol
672    pub fn user_constant_index(self) -> Option<u8> {
673        use Symbol::*;
674        match self {
675            UserConstant0 => Some(0),
676            UserConstant1 => Some(1),
677            UserConstant2 => Some(2),
678            UserConstant3 => Some(3),
679            UserConstant4 => Some(4),
680            UserConstant5 => Some(5),
681            UserConstant6 => Some(6),
682            UserConstant7 => Some(7),
683            UserConstant8 => Some(8),
684            UserConstant9 => Some(9),
685            UserConstant10 => Some(10),
686            UserConstant11 => Some(11),
687            UserConstant12 => Some(12),
688            UserConstant13 => Some(13),
689            UserConstant14 => Some(14),
690            UserConstant15 => Some(15),
691            _ => None,
692        }
693    }
694
695    /// Get user function index (0-15) if this is a user function symbol
696    pub fn user_function_index(self) -> Option<u8> {
697        use Symbol::*;
698        match self {
699            UserFunction0 => Some(0),
700            UserFunction1 => Some(1),
701            UserFunction2 => Some(2),
702            UserFunction3 => Some(3),
703            UserFunction4 => Some(4),
704            UserFunction5 => Some(5),
705            UserFunction6 => Some(6),
706            UserFunction7 => Some(7),
707            UserFunction8 => Some(8),
708            UserFunction9 => Some(9),
709            UserFunction10 => Some(10),
710            UserFunction11 => Some(11),
711            UserFunction12 => Some(12),
712            UserFunction13 => Some(13),
713            UserFunction14 => Some(14),
714            UserFunction15 => Some(15),
715            _ => None,
716        }
717    }
718
719    /// Get all constant symbols (Seft::A)
720    pub fn constants() -> &'static [Symbol] {
721        use Symbol::*;
722        &[
723            One, Two, Three, Four, Five, Six, Seven, Eight, Nine, Pi, E, Phi, Gamma, Plastic,
724            Apery, Catalan,
725        ]
726    }
727
728    /// Get all unary operators (Seft::B)
729    pub fn unary_ops() -> &'static [Symbol] {
730        use Symbol::*;
731        &[
732            Neg, Recip, Sqrt, Square, Ln, Exp, SinPi, CosPi, TanPi, LambertW,
733        ]
734    }
735
736    /// Get all binary operators (Seft::C)
737    pub fn binary_ops() -> &'static [Symbol] {
738        use Symbol::*;
739        &[Add, Sub, Mul, Div, Pow, Root, Log, Atan2]
740    }
741}
742
743impl fmt::Display for Symbol {
744    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
745        write!(f, "{}", self.name())
746    }
747}
748
749impl From<Symbol> for u8 {
750    fn from(s: Symbol) -> u8 {
751        s as u8
752    }
753}
754
755#[cfg(test)]
756mod tests {
757    use super::*;
758
759    /// Weights must match original RIES calibration so that the complexity
760    /// scale has the same meaning — preventing expression count explosion.
761    #[test]
762    fn test_weights_match_original_ries() {
763        // Digits: original RIES uses 10/13/15/16/17/18/18/19/19
764        assert_eq!(Symbol::One.default_weight(), 10);
765        assert_eq!(Symbol::Two.default_weight(), 13);
766        assert_eq!(Symbol::Three.default_weight(), 15);
767        assert_eq!(Symbol::Four.default_weight(), 16);
768        assert_eq!(Symbol::Five.default_weight(), 17);
769        assert_eq!(Symbol::Six.default_weight(), 18);
770        assert_eq!(Symbol::Seven.default_weight(), 18);
771        assert_eq!(Symbol::Eight.default_weight(), 19);
772        assert_eq!(Symbol::Nine.default_weight(), 19);
773
774        // Named constants: original RIES uses pi=14, e=16, phi=18, x=15
775        assert_eq!(Symbol::Pi.default_weight(), 14);
776        assert_eq!(Symbol::E.default_weight(), 16);
777        assert_eq!(Symbol::Phi.default_weight(), 18);
778        assert_eq!(Symbol::X.default_weight(), 15);
779
780        // Binary operators: original RIES uses +=4, *=4, -=5, /=5, ^=6, root=7, atan2=9, log=9
781        assert_eq!(Symbol::Add.default_weight(), 4);
782        assert_eq!(Symbol::Mul.default_weight(), 4);
783        assert_eq!(Symbol::Sub.default_weight(), 5);
784        assert_eq!(Symbol::Div.default_weight(), 5);
785        assert_eq!(Symbol::Pow.default_weight(), 6);
786        assert_eq!(Symbol::Root.default_weight(), 7);
787        assert_eq!(Symbol::Atan2.default_weight(), 9);
788        assert_eq!(Symbol::Log.default_weight(), 9);
789
790        // Unary operators: original RIES uses neg=7, recip=7, sqrt=9, sq=9, ln=13, exp=13, sinpi=13, cospi=13, tanpi=16
791        assert_eq!(Symbol::Neg.default_weight(), 7);
792        assert_eq!(Symbol::Recip.default_weight(), 7);
793        assert_eq!(Symbol::Sqrt.default_weight(), 9);
794        assert_eq!(Symbol::Square.default_weight(), 9);
795        assert_eq!(Symbol::Ln.default_weight(), 13);
796        assert_eq!(Symbol::Exp.default_weight(), 13);
797        assert_eq!(Symbol::SinPi.default_weight(), 13);
798        assert_eq!(Symbol::CosPi.default_weight(), 13);
799        assert_eq!(Symbol::TanPi.default_weight(), 16);
800    }
801
802    #[test]
803    fn test_symbol_roundtrip() {
804        for &sym in Symbol::constants()
805            .iter()
806            .chain(Symbol::unary_ops())
807            .chain(Symbol::binary_ops())
808        {
809            let byte = sym as u8;
810            let parsed = Symbol::from_byte(byte).unwrap();
811            assert_eq!(sym, parsed);
812        }
813    }
814
815    #[test]
816    fn test_num_type_ordering() {
817        assert!(NumType::Integer > NumType::Rational);
818        assert!(NumType::Rational > NumType::Algebraic);
819        assert!(NumType::Algebraic > NumType::Transcendental);
820    }
821
822    #[test]
823    fn test_seft() {
824        assert_eq!(Symbol::Pi.seft(), Seft::A);
825        assert_eq!(Symbol::Sqrt.seft(), Seft::B);
826        assert_eq!(Symbol::Add.seft(), Seft::C);
827    }
828}