1use 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#[derive(Debug, Clone)]
17pub struct VerificationResult {
18 pub original: Match,
20 pub highprec_error: Option<f64>,
22 pub is_verified: bool,
24 pub error_ratio: Option<f64>,
27}
28
29impl VerificationResult {
30 pub fn f64_result(m: Match) -> Self {
32 Self {
33 original: m,
34 highprec_error: None,
35 is_verified: true, error_ratio: None,
37 }
38 }
39}
40
41#[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#[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 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 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 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 let diff = (lhs.clone() - rhs.clone()).abs();
104
105 if diff.clone() < tolerance_hp.clone() {
106 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 VerificationResult {
123 original: m,
124 highprec_error: Some(f64::INFINITY),
125 is_verified: false,
126 error_ratio: None,
127 }
128 }
129 }
130 _ => {
131 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#[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#[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#[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 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 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 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 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 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 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 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 _ => return None,
326 }
327 }
328
329 stack.pop()
330}
331
332pub fn format_verification_report(results: &[VerificationResult], max_display: usize) -> String {
334 let mut output = String::new();
335
336 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}