Skip to main content

ries_rs/
presets.rs

1//! Built-in domain presets for common mathematical domains
2//!
3//! These presets configure symbol sets, weights, and user constants
4//! appropriate for different areas of mathematics.
5
6use crate::profile::{Profile, UserConstant};
7use crate::symbol::{NumType, Symbol};
8
9/// Available built-in presets
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub enum Preset {
12    /// Analytic number theory: ζ values, Γ, log, π powers
13    AnalyticNT,
14    /// Elliptic/modular: K(k), E(k), Γ(1/4), q-series
15    Elliptic,
16    /// Combinatorics: Catalan, Apéry, polylog patterns
17    Combinatorics,
18    /// Physics: π, log, γ, ζ, Clausen-type constants
19    Physics,
20    /// Number theory: rational/algebraic focus
21    NumberTheory,
22    /// Calculus: standard functions, no exotic constants
23    Calculus,
24}
25
26impl Preset {
27    /// Get preset name for CLI
28    pub fn name(&self) -> &'static str {
29        match self {
30            Preset::AnalyticNT => "analytic-nt",
31            Preset::Elliptic => "elliptic",
32            Preset::Combinatorics => "combinatorics",
33            Preset::Physics => "physics",
34            Preset::NumberTheory => "number-theory",
35            Preset::Calculus => "calculus",
36        }
37    }
38
39    /// Get preset description
40    pub fn description(&self) -> &'static str {
41        match self {
42            Preset::AnalyticNT => "Analytic number theory: ζ values, Γ, log, π powers",
43            Preset::Elliptic => "Elliptic/modular: K(k), E(k), Γ(1/4), q-series constants",
44            Preset::Combinatorics => "Combinatorics: Catalan, Apéry, polylog patterns",
45            Preset::Physics => "Physics: π, log, γ, ζ, Clausen-type constants",
46            Preset::NumberTheory => "Number theory: rational/algebraic focus",
47            Preset::Calculus => "Calculus: standard functions, no exotic constants",
48        }
49    }
50
51    /// Parse from string (for CLI)
52    #[allow(clippy::should_implement_trait)]
53    pub fn from_str(s: &str) -> Option<Self> {
54        match s.to_lowercase().as_str() {
55            "analytic-nt" | "ant" | "analytic" => Some(Preset::AnalyticNT),
56            "elliptic" | "modular" => Some(Preset::Elliptic),
57            "combinatorics" | "combo" | "catalan" => Some(Preset::Combinatorics),
58            "physics" | "phys" => Some(Preset::Physics),
59            "number-theory" | "nt" | "algebraic" => Some(Preset::NumberTheory),
60            "calculus" | "calc" => Some(Preset::Calculus),
61            _ => None,
62        }
63    }
64
65    /// Generate the profile for this preset
66    #[allow(clippy::wrong_self_convention)]
67    pub fn to_profile(&self) -> Profile {
68        let mut profile = Profile::new();
69
70        match self {
71            Preset::AnalyticNT => {
72                // Analytic number theory uses zeta values, gamma, log, powers of pi
73                // Lower weights for: pi, e, log, exp
74                // Higher weights for: trig (less common)
75                // Add zeta(2) = π²/6, zeta(3), zeta(4) as constants
76
77                profile.symbol_weights = vec![
78                    (Symbol::Pi, 6),        // Lower weight - very common
79                    (Symbol::Ln, 6),        // Lower weight - very common
80                    (Symbol::Exp, 6),       // Lower weight - very common
81                    (Symbol::SinPi, 12),    // Higher weight - less common
82                    (Symbol::CosPi, 12),    // Higher weight - less common
83                    (Symbol::TanPi, 14),    // Higher weight - less common
84                    (Symbol::LambertW, 16), // Rarely used
85                ]
86                .into_iter()
87                .collect();
88
89                // Add zeta(2) = π²/6 ≈ 1.644934
90                profile.constants.push(UserConstant {
91                    weight: 10,
92                    name: "z2".to_string(),
93                    description: "ζ(2) = π²/6 (Basel problem)".to_string(),
94                    value: std::f64::consts::PI * std::f64::consts::PI / 6.0,
95                    num_type: NumType::Transcendental,
96                });
97
98                // zeta(3) ≈ 1.202057 (Apéry's constant) - already built-in as 'z'
99                // But let's ensure it has a good weight
100                profile.symbol_weights.insert(Symbol::Apery, 8);
101
102                // Euler-Mascheroni gamma - already built-in as 'g'
103                profile.symbol_weights.insert(Symbol::Gamma, 8);
104            }
105
106            Preset::Elliptic => {
107                // Elliptic integrals, modular forms
108                // Focus on: sqrt, powers, log
109                // Avoid: trig (elliptic integrals replace them)
110
111                profile.symbol_weights = vec![
112                    (Symbol::Sqrt, 4),   // Lower - very common
113                    (Symbol::Square, 4), // Lower - very common
114                    (Symbol::Ln, 6),     // Common
115                    (Symbol::Pi, 6),     // Common
116                    (Symbol::SinPi, 16), // Avoid - elliptic replaces
117                    (Symbol::CosPi, 16), // Avoid - elliptic replaces
118                    (Symbol::TanPi, 18), // Avoid - elliptic replaces
119                ]
120                .into_iter()
121                .collect();
122
123                // Γ(1/4) ≈ 3.6256 - important for elliptic integrals
124                profile.constants.push(UserConstant {
125                    weight: 12,
126                    name: "g14".to_string(),
127                    description: "Γ(1/4) - elliptic integral constant".to_string(),
128                    value: 3.625609908221908,
129                    num_type: NumType::Transcendental,
130                });
131
132                // K(1/√2) = Γ²(1/4)/(4√π) ≈ 1.85407
133                profile.constants.push(UserConstant {
134                    weight: 14,
135                    name: "K1".to_string(),
136                    description: "K(1/√2) - complete elliptic integral".to_string(),
137                    value: 1.854074677301372,
138                    num_type: NumType::Transcendental,
139                });
140
141                // Golden ratio - already built-in, ensure good weight
142                profile.symbol_weights.insert(Symbol::Phi, 8);
143            }
144
145            Preset::Combinatorics => {
146                // Combinatorics: Catalan, Apéry, polylog patterns
147                // Focus on: integers, rationals, simple algebraic
148
149                profile.symbol_weights = vec![
150                    (Symbol::One, 2),   // Very common
151                    (Symbol::Two, 2),   // Very common
152                    (Symbol::Three, 2), // Very common
153                    (Symbol::Four, 3),  // Common
154                    (Symbol::Five, 3),  // Common
155                    (Symbol::Pi, 12),   // Less common
156                    (Symbol::E, 12),    // Less common
157                    (Symbol::Ln, 14),   // Rare
158                    (Symbol::Exp, 14),  // Rare
159                ]
160                .into_iter()
161                .collect();
162
163                // Catalan's constant G ≈ 0.915966 - already built-in
164                profile.symbol_weights.insert(Symbol::Catalan, 8);
165
166                // Apéry's constant ζ(3) ≈ 1.202057 - already built-in
167                profile.symbol_weights.insert(Symbol::Apery, 8);
168            }
169
170            Preset::Physics => {
171                // Physics constants: π, log, γ, ζ, combinations
172                // Similar to analytic-nt but more focused on practical values
173
174                profile.symbol_weights = vec![
175                    (Symbol::Pi, 5),     // Very common
176                    (Symbol::E, 6),      // Very common
177                    (Symbol::Ln, 6),     // Common
178                    (Symbol::Exp, 6),    // Common
179                    (Symbol::Sqrt, 5),   // Common
180                    (Symbol::SinPi, 10), // Moderate
181                    (Symbol::CosPi, 10), // Moderate
182                ]
183                .into_iter()
184                .collect();
185
186                // Euler-Mascheroni gamma
187                profile.symbol_weights.insert(Symbol::Gamma, 8);
188
189                // Fine structure constant α ≈ 1/137.036 (inverse)
190                // Not adding as it's very specific, but γ is important
191            }
192
193            Preset::NumberTheory => {
194                // Number theory: focus on rationals, algebraic numbers
195                // Avoid transcendentals, focus on integers
196
197                profile.symbol_weights = vec![
198                    (Symbol::One, 2),
199                    (Symbol::Two, 2),
200                    (Symbol::Three, 2),
201                    (Symbol::Four, 3),
202                    (Symbol::Five, 3),
203                    (Symbol::Six, 3),
204                    (Symbol::Seven, 3),
205                    (Symbol::Eight, 4),
206                    (Symbol::Nine, 4),
207                    (Symbol::Sqrt, 6),   // Algebraic
208                    (Symbol::Square, 6), // Algebraic
209                    (Symbol::Pi, 16),    // Avoid transcendental
210                    (Symbol::E, 16),     // Avoid transcendental
211                    (Symbol::Ln, 18),    // Avoid
212                    (Symbol::Exp, 18),   // Avoid
213                ]
214                .into_iter()
215                .collect();
216
217                // Golden ratio - algebraic
218                profile.symbol_weights.insert(Symbol::Phi, 8);
219
220                // Plastic constant - algebraic
221                profile.symbol_weights.insert(Symbol::Plastic, 10);
222            }
223
224            Preset::Calculus => {
225                // Standard calculus: all functions available, no exotic constants
226                // Balanced weights for general use
227
228                profile.symbol_weights = vec![
229                    (Symbol::Pi, 7),
230                    (Symbol::E, 7),
231                    (Symbol::Ln, 7),
232                    (Symbol::Exp, 7),
233                    (Symbol::Sqrt, 5),
234                    (Symbol::Square, 5),
235                    (Symbol::SinPi, 8),
236                    (Symbol::CosPi, 8),
237                    (Symbol::TanPi, 9),
238                ]
239                .into_iter()
240                .collect();
241            }
242        }
243
244        profile
245    }
246
247    /// List all available presets
248    pub fn all() -> &'static [Preset] {
249        &[
250            Preset::AnalyticNT,
251            Preset::Elliptic,
252            Preset::Combinatorics,
253            Preset::Physics,
254            Preset::NumberTheory,
255            Preset::Calculus,
256        ]
257    }
258}
259
260/// Print available presets (for --list-presets)
261#[allow(clippy::print_literal)]
262pub fn print_presets() {
263    println!("Available domain presets:");
264    println!();
265    println!("  {:<15} {}", "PRESET", "DESCRIPTION");
266    println!("  {}", "-".repeat(70));
267    for preset in Preset::all() {
268        println!("  {:<15} {}", preset.name(), preset.description());
269    }
270    println!();
271    println!("Usage: ries-rs --preset <name> <target>");
272    println!("Example: ries-rs --preset physics 6.67430e-11");
273}
274
275#[cfg(test)]
276mod tests {
277    use super::*;
278
279    #[test]
280    fn test_preset_parse() {
281        assert_eq!(Preset::from_str("analytic-nt"), Some(Preset::AnalyticNT));
282        assert_eq!(Preset::from_str("ANT"), Some(Preset::AnalyticNT));
283        assert_eq!(Preset::from_str("physics"), Some(Preset::Physics));
284        assert_eq!(Preset::from_str("PHYS"), Some(Preset::Physics));
285        assert_eq!(Preset::from_str("invalid"), None);
286    }
287
288    #[test]
289    fn test_preset_profile_non_empty() {
290        for preset in Preset::all() {
291            let profile = preset.to_profile();
292            // Each preset should modify at least something
293            assert!(
294                !profile.symbol_weights.is_empty() || !profile.constants.is_empty(),
295                "Preset {:?} should have some configuration",
296                preset
297            );
298        }
299    }
300
301    #[test]
302    fn test_analytic_nt_has_gamma() {
303        let profile = Preset::AnalyticNT.to_profile();
304        assert!(profile.symbol_weights.contains_key(&Symbol::Gamma));
305    }
306
307    #[test]
308    fn test_number_theory_avoids_transcendentals() {
309        let profile = Preset::NumberTheory.to_profile();
310        // Pi and E should have high weights (discouraged)
311        assert!(profile.symbol_weights.get(&Symbol::Pi) > Some(&10));
312        assert!(profile.symbol_weights.get(&Symbol::E) > Some(&10));
313    }
314}