Skip to main content

ries_rs/
highprec_verify.rs

1//! High-precision verification for match refinement
2//!
3//! After f64 search finds candidate matches, this module verifies them
4//! at higher precision to distinguish true formulas from numerical coincidences.
5
6use crate::search::Match;
7#[cfg(feature = "highprec")]
8use crate::symbol::Symbol;
9#[cfg(feature = "highprec")]
10use crate::thresholds::EXACT_MATCH_TOLERANCE;
11
12#[cfg(feature = "highprec")]
13use crate::precision::{HighPrec, RiesFloat};
14
15/// Result of high-precision verification
16#[derive(Debug, Clone)]
17pub struct VerificationResult {
18    /// Original match (f64 precision)
19    pub original: Match,
20    /// Error at high precision (None if f64 mode)
21    pub highprec_error: Option<f64>,
22    /// Whether the match is verified at high precision
23    pub is_verified: bool,
24    /// Relative change in error (highprec_error / original.error)
25    /// Values >> 1 indicate the f64 match was an impostor
26    pub error_ratio: Option<f64>,
27}
28
29impl VerificationResult {
30    /// Create a verification result for f64 mode (no high precision)
31    pub fn f64_result(m: Match) -> Self {
32        Self {
33            original: m,
34            highprec_error: None,
35            is_verified: true, // Assume verified in f64 mode
36            error_ratio: None,
37        }
38    }
39}
40
41/// Verify matches at high precision
42///
43/// When the `highprec` feature is enabled, this re-evaluates matches
44/// using arbitrary precision to verify they are true formulas.
45#[cfg(feature = "highprec")]
46pub fn verify_matches_highprec(
47    matches: Vec<Match>,
48    target: f64,
49    precision_bits: u32,
50    user_constants: &[crate::profile::UserConstant],
51) -> Vec<VerificationResult> {
52    verify_matches_highprec_with_trig_scale(
53        matches,
54        target,
55        precision_bits,
56        user_constants,
57        crate::eval::DEFAULT_TRIG_ARGUMENT_SCALE,
58    )
59}
60
61/// Verify matches at high precision using an explicit trig argument scale.
62#[cfg(feature = "highprec")]
63pub fn verify_matches_highprec_with_trig_scale(
64    matches: Vec<Match>,
65    target: f64,
66    precision_bits: u32,
67    user_constants: &[crate::profile::UserConstant],
68    trig_argument_scale: f64,
69) -> Vec<VerificationResult> {
70    let target_hp = HighPrec::from_f64_with_prec(target, precision_bits);
71    let tolerance_hp = HighPrec::from_f64_with_prec(EXACT_MATCH_TOLERANCE, precision_bits);
72
73    matches
74        .into_iter()
75        .map(|m| {
76            // Get user constant values at high precision
77            let hp_constants: Vec<HighPrec> = user_constants
78                .iter()
79                .map(|uc| HighPrec::from_f64_with_prec(uc.value, precision_bits))
80                .collect();
81
82            // Evaluate LHS(x) at high precision
83            let lhs_val = evaluate_highprec(
84                &m.lhs.expr,
85                m.x_value,
86                precision_bits,
87                &hp_constants,
88                trig_argument_scale,
89            );
90
91            // Evaluate RHS at high precision
92            let rhs_val = evaluate_highprec(
93                &m.rhs.expr,
94                m.x_value,
95                precision_bits,
96                &hp_constants,
97                trig_argument_scale,
98            );
99
100            match (lhs_val, rhs_val) {
101                (Some(lhs), Some(rhs)) => {
102                    // Check if LHS ≈ RHS at high precision
103                    let diff = (lhs.clone() - rhs.clone()).abs();
104
105                    if diff.clone() < tolerance_hp.clone() {
106                        // Equation holds at high precision - verified!
107                        let hp_error = (lhs - target_hp.clone()).abs().to_f64();
108                        let error_ratio = if m.error.abs() > 1e-20 {
109                            Some(hp_error / m.error.abs())
110                        } else {
111                            None
112                        };
113
114                        VerificationResult {
115                            original: m,
116                            highprec_error: Some(hp_error),
117                            is_verified: true,
118                            error_ratio,
119                        }
120                    } else {
121                        // Equation doesn't hold at high precision - impostor
122                        VerificationResult {
123                            original: m,
124                            highprec_error: Some(f64::INFINITY),
125                            is_verified: false,
126                            error_ratio: None,
127                        }
128                    }
129                }
130                _ => {
131                    // Evaluation failed at high precision
132                    VerificationResult {
133                        original: m,
134                        highprec_error: Some(f64::INFINITY),
135                        is_verified: false,
136                        error_ratio: None,
137                    }
138                }
139            }
140        })
141        .collect()
142}
143
144/// Fallback for non-highprec builds
145#[cfg(not(feature = "highprec"))]
146pub fn verify_matches_highprec(
147    matches: Vec<Match>,
148    _target: f64,
149    _precision_bits: u32,
150    _user_constants: &[crate::profile::UserConstant],
151) -> Vec<VerificationResult> {
152    verify_matches_highprec_with_trig_scale(
153        matches,
154        _target,
155        _precision_bits,
156        _user_constants,
157        crate::eval::DEFAULT_TRIG_ARGUMENT_SCALE,
158    )
159}
160
161/// Fallback for non-highprec builds with explicit trig scaling.
162#[cfg(not(feature = "highprec"))]
163pub fn verify_matches_highprec_with_trig_scale(
164    matches: Vec<Match>,
165    _target: f64,
166    _precision_bits: u32,
167    _user_constants: &[crate::profile::UserConstant],
168    _trig_argument_scale: f64,
169) -> Vec<VerificationResult> {
170    matches
171        .into_iter()
172        .map(VerificationResult::f64_result)
173        .collect()
174}
175
176/// Evaluate an expression at high precision
177#[cfg(feature = "highprec")]
178fn evaluate_highprec(
179    expr: &crate::expr::Expression,
180    x: f64,
181    precision_bits: u32,
182    user_constants: &[HighPrec],
183    trig_argument_scale: f64,
184) -> Option<HighPrec> {
185    // Use precision-aware constructors for full accuracy beyond f64 limits
186    let zero = HighPrec::zero_with_prec(precision_bits);
187    let one = HighPrec::one_with_prec(precision_bits);
188    let trig_scale = HighPrec::from_f64_with_prec(trig_argument_scale, precision_bits);
189    let x_hp = HighPrec::from_f64_with_prec(x, precision_bits);
190
191    let mut stack: Vec<HighPrec> = Vec::with_capacity(32);
192
193    for sym in expr.symbols() {
194        match sym {
195            // Numbers - use string-based construction for consistency
196            Symbol::One => stack.push(one.clone()),
197            Symbol::Two => stack.push(HighPrec::from_str_with_prec("2", precision_bits)),
198            Symbol::Three => stack.push(HighPrec::from_str_with_prec("3", precision_bits)),
199            Symbol::Four => stack.push(HighPrec::from_str_with_prec("4", precision_bits)),
200            Symbol::Five => stack.push(HighPrec::from_str_with_prec("5", precision_bits)),
201            Symbol::Six => stack.push(HighPrec::from_str_with_prec("6", precision_bits)),
202            Symbol::Seven => stack.push(HighPrec::from_str_with_prec("7", precision_bits)),
203            Symbol::Eight => stack.push(HighPrec::from_str_with_prec("8", precision_bits)),
204            Symbol::Nine => stack.push(HighPrec::from_str_with_prec("9", precision_bits)),
205
206            // Variables and constants - use precision-aware constructors
207            Symbol::X => stack.push(x_hp.clone()),
208            Symbol::Pi => stack.push(HighPrec::pi_with_prec(precision_bits)),
209            Symbol::E => stack.push(HighPrec::e_with_prec(precision_bits)),
210            Symbol::Phi => stack.push(HighPrec::phi_with_prec(precision_bits)),
211            Symbol::Gamma => stack.push(HighPrec::gamma_with_prec(precision_bits)),
212            Symbol::Apery => stack.push(HighPrec::apery_with_prec(precision_bits)),
213            Symbol::Catalan => stack.push(HighPrec::catalan_with_prec(precision_bits)),
214            Symbol::Plastic => stack.push(HighPrec::plastic_with_prec(precision_bits)),
215
216            // Binary operators
217            Symbol::Add => {
218                let b = stack.pop()?;
219                let a = stack.pop()?;
220                stack.push(a + b);
221            }
222            Symbol::Sub => {
223                let b = stack.pop()?;
224                let a = stack.pop()?;
225                stack.push(a - b);
226            }
227            Symbol::Mul => {
228                let b = stack.pop()?;
229                let a = stack.pop()?;
230                stack.push(a * b);
231            }
232            Symbol::Div => {
233                let b = stack.pop()?;
234                let a = stack.pop()?;
235                if b.clone() == zero {
236                    return None;
237                }
238                stack.push(a / b);
239            }
240            Symbol::Pow => {
241                let exp = stack.pop()?;
242                let base = stack.pop()?;
243                stack.push(base.pow(exp));
244            }
245
246            // Unary operators
247            Symbol::Neg => {
248                let a = stack.pop()?;
249                stack.push(-a);
250            }
251            Symbol::Recip => {
252                let a = stack.pop()?;
253                if a.clone() == zero {
254                    return None;
255                }
256                stack.push(one.clone() / a);
257            }
258            Symbol::Sqrt => {
259                let a = stack.pop()?;
260                if a.clone() < zero {
261                    return None;
262                }
263                stack.push(a.sqrt());
264            }
265            Symbol::Square => {
266                let a = stack.pop()?;
267                stack.push(a.clone() * a);
268            }
269            Symbol::Ln => {
270                let a = stack.pop()?;
271                if a.clone() <= zero {
272                    return None;
273                }
274                stack.push(a.ln());
275            }
276            Symbol::Exp => {
277                let a = stack.pop()?;
278                stack.push(a.exp());
279            }
280
281            // Trig functions
282            Symbol::SinPi => {
283                let a = stack.pop()?;
284                stack.push((trig_scale.clone() * a).sin());
285            }
286            Symbol::CosPi => {
287                let a = stack.pop()?;
288                stack.push((trig_scale.clone() * a).cos());
289            }
290            Symbol::TanPi => {
291                let a = stack.pop()?;
292                stack.push((trig_scale.clone() * a).tan());
293            }
294
295            // User constants
296            sym if matches!(
297                sym,
298                Symbol::UserConstant0
299                    | Symbol::UserConstant1
300                    | Symbol::UserConstant2
301                    | Symbol::UserConstant3
302                    | Symbol::UserConstant4
303                    | Symbol::UserConstant5
304                    | Symbol::UserConstant6
305                    | Symbol::UserConstant7
306                    | Symbol::UserConstant8
307                    | Symbol::UserConstant9
308                    | Symbol::UserConstant10
309                    | Symbol::UserConstant11
310                    | Symbol::UserConstant12
311                    | Symbol::UserConstant13
312                    | Symbol::UserConstant14
313                    | Symbol::UserConstant15
314            ) =>
315            {
316                let idx = sym.user_constant_index()? as usize;
317                if idx < user_constants.len() {
318                    stack.push(user_constants[idx].clone());
319                } else {
320                    return None;
321                }
322            }
323
324            // Skip other symbols for now
325            _ => return None,
326        }
327    }
328
329    stack.pop()
330}
331
332/// Format verification results for display
333pub fn format_verification_report(results: &[VerificationResult], max_display: usize) -> String {
334    let mut output = String::new();
335
336    // Separate verified and unverified
337    let verified: Vec<_> = results
338        .iter()
339        .filter(|r| r.is_verified)
340        .take(max_display)
341        .collect();
342
343    let unverified: Vec<_> = results
344        .iter()
345        .filter(|r| !r.is_verified)
346        .take(max_display)
347        .collect();
348
349    if !verified.is_empty() {
350        output.push_str("\n  -- Verified at high precision --\n\n");
351        for r in &verified {
352            let hp_str = match r.highprec_error {
353                Some(e) if e.is_finite() => format!(" [hp error: {:.2e}]", e),
354                Some(_) => " [hp error: inf]".to_string(),
355                None => String::new(),
356            };
357            let ratio_str = match r.error_ratio {
358                Some(ratio) if ratio > 1.1 => {
359                    format!(" [error ratio: {:.1}x - likely impostor]", ratio)
360                }
361                Some(ratio) if ratio > 1.01 => format!(" [error ratio: {:.2}x]", ratio),
362                _ => String::new(),
363            };
364            output.push_str(&format!(
365                "  {:<24} = {:<24}  {{{}}}{}{}\n",
366                r.original.lhs.expr.to_infix(),
367                r.original.rhs.expr.to_infix(),
368                r.original.complexity,
369                hp_str,
370                ratio_str
371            ));
372        }
373    }
374
375    if !unverified.is_empty() {
376        output.push_str("\n  -- Failed high-precision verification (impostors) --\n\n");
377        for r in &unverified {
378            output.push_str(&format!(
379                "  {:<24} = {:<24}  {{{}}}\n",
380                r.original.lhs.expr.to_infix(),
381                r.original.rhs.expr.to_infix(),
382                r.original.complexity
383            ));
384        }
385    }
386
387    output
388}
389
390#[cfg(test)]
391mod tests {
392    use super::*;
393
394    #[test]
395    fn test_f64_result() {
396        let m = crate::search::Match {
397            lhs: crate::expr::EvaluatedExpr::new(
398                crate::expr::Expression::parse("x").unwrap(),
399                2.5,
400                1.0,
401                crate::symbol::NumType::Integer,
402            ),
403            rhs: crate::expr::EvaluatedExpr::new(
404                crate::expr::Expression::parse("5").unwrap(),
405                5.0,
406                0.0,
407                crate::symbol::NumType::Integer,
408            ),
409            x_value: 2.5,
410            error: 0.0,
411            complexity: 14,
412        };
413
414        let result = VerificationResult::f64_result(m);
415        assert!(result.is_verified);
416        assert!(result.highprec_error.is_none());
417    }
418
419    #[test]
420    #[allow(clippy::approx_constant)]
421    fn test_format_verification_report() {
422        let m = crate::search::Match {
423            lhs: crate::expr::EvaluatedExpr::new(
424                crate::expr::Expression::parse("x").unwrap(),
425                3.14159,
426                1.0,
427                crate::symbol::NumType::Integer,
428            ),
429            rhs: crate::expr::EvaluatedExpr::new(
430                crate::expr::Expression::parse("p").unwrap(),
431                std::f64::consts::PI,
432                0.0,
433                crate::symbol::NumType::Integer,
434            ),
435            x_value: std::f64::consts::PI,
436            error: 1e-10,
437            complexity: 14,
438        };
439
440        let verified = VerificationResult {
441            original: m,
442            highprec_error: Some(1e-12),
443            is_verified: true,
444            error_ratio: Some(0.01),
445        };
446
447        let report = format_verification_report(&[verified], 10);
448        assert!(report.contains("Verified at high precision"));
449    }
450
451    #[cfg(feature = "highprec")]
452    #[test]
453    fn test_evaluate_highprec_reads_user_constant_slots() {
454        let expr = crate::expr::Expression::from_symbols(&[crate::symbol::Symbol::UserConstant0]);
455        let constants = vec![HighPrec::from_f64_with_prec(1.234567890123456, 256)];
456
457        let evaluated = evaluate_highprec(
458            &expr,
459            0.0,
460            256,
461            &constants,
462            crate::eval::DEFAULT_TRIG_ARGUMENT_SCALE,
463        )
464        .expect("expected user constant slot to resolve");
465        assert!((evaluated.to_f64() - 1.234567890123456).abs() < 1e-15);
466    }
467
468    #[cfg(feature = "highprec")]
469    #[test]
470    fn test_evaluate_highprec_fails_for_missing_user_constant_slot() {
471        let expr = crate::expr::Expression::from_symbols(&[crate::symbol::Symbol::UserConstant1]);
472        let constants = vec![HighPrec::from_f64_with_prec(1.0, 256)];
473
474        let evaluated = evaluate_highprec(
475            &expr,
476            0.0,
477            256,
478            &constants,
479            crate::eval::DEFAULT_TRIG_ARGUMENT_SCALE,
480        );
481        assert!(
482            evaluated.is_none(),
483            "missing user constant slot should fail verification evaluation"
484        );
485    }
486}