1#![allow(clippy::needless_range_loop)]
13#![allow(dead_code)]
14use crate::thresholds::EXACT_MATCH_TOLERANCE;
19use std::f64::consts::PI;
20
21const MAX_ITERATIONS: usize = 10000;
23
24pub const DEFAULT_PSLQ_PRECISION: usize = 50;
26
27#[derive(Debug, Clone)]
29pub struct IntegerRelation {
30 pub coefficients: Vec<i64>,
32 pub basis_names: Vec<String>,
34 pub residual: f64,
36 pub is_exact: bool,
38}
39
40impl IntegerRelation {
41 pub fn format(&self) -> String {
43 let terms: Vec<String> = self
44 .coefficients
45 .iter()
46 .zip(self.basis_names.iter())
47 .filter(|(c, _)| **c != 0)
48 .map(|(c, name)| {
49 if *c == 1 {
50 name.clone()
51 } else if *c == -1 {
52 format!("-{}", name)
53 } else {
54 format!("{}*{}", c, name)
55 }
56 })
57 .collect();
58
59 if terms.is_empty() {
60 "0".to_string()
61 } else {
62 terms.join(" + ").replace("+ -", "- ")
63 }
64 }
65}
66
67pub fn standard_constants() -> Vec<(String, f64)> {
69 vec![
70 ("1".to_string(), 1.0),
71 ("π".to_string(), PI),
72 ("π²".to_string(), PI * PI),
73 ("π³".to_string(), PI * PI * PI),
74 ("e".to_string(), std::f64::consts::E),
75 ("e²".to_string(), std::f64::consts::E * std::f64::consts::E),
76 ("e^π".to_string(), std::f64::consts::E.powf(PI)),
77 ("ln(2)".to_string(), (2.0f64).ln()),
78 ("ln(π)".to_string(), PI.ln()),
79 ("√2".to_string(), std::f64::consts::SQRT_2),
80 ("√π".to_string(), PI.sqrt()),
81 ("φ".to_string(), (1.0 + 5.0f64.sqrt()) / 2.0), ("γ".to_string(), 0.5772156649015329), ("ζ(2)".to_string(), PI * PI / 6.0), ("ζ(3)".to_string(), 1.202056903159594), ("G".to_string(), 0.915965594177219), ]
87}
88
89pub fn extended_constants() -> Vec<(String, f64)> {
91 let mut constants = standard_constants();
92 constants.extend(vec![
93 ("√3".to_string(), 3.0f64.sqrt()),
94 ("√5".to_string(), 5.0f64.sqrt()),
95 ("√7".to_string(), 7.0f64.sqrt()),
96 ("ln(3)".to_string(), (3.0f64).ln()),
97 ("ln(5)".to_string(), (5.0f64).ln()),
98 ("ln(7)".to_string(), (7.0f64).ln()),
99 ("π*√2".to_string(), PI * std::f64::consts::SQRT_2),
100 ("e+π".to_string(), std::f64::consts::E + PI),
101 ("e*π".to_string(), std::f64::consts::E * PI),
102 ("2^π".to_string(), 2.0f64.powf(PI)),
103 ("π^e".to_string(), PI.powf(std::f64::consts::E)),
104 ]);
105 constants
106}
107
108#[derive(Debug, Clone)]
110pub struct PslqConfig {
111 pub max_coefficient: i64,
113 pub max_iterations: usize,
115 pub tolerance: f64,
117 pub extended_constants: bool,
119}
120
121impl Default for PslqConfig {
122 fn default() -> Self {
123 Self {
124 max_coefficient: 1000,
125 max_iterations: MAX_ITERATIONS,
126 tolerance: EXACT_MATCH_TOLERANCE,
127 extended_constants: false,
128 }
129 }
130}
131
132pub fn find_integer_relation(target: f64, config: &PslqConfig) -> Option<IntegerRelation> {
146 let constants = if config.extended_constants {
148 extended_constants()
149 } else {
150 standard_constants()
151 };
152
153 let _n = constants.len() + 1;
156 let mut x: Vec<f64> = vec![target];
157 for (_, val) in &constants {
158 x.push(*val);
159 }
160
161 let coefficients = pslq(&x, config)?;
163
164 if coefficients[0] == 0 {
166 return None;
167 }
168
169 let mut residual = 0.0;
171 for (i, c) in coefficients.iter().enumerate() {
172 residual += (*c as f64) * x[i];
173 }
174 residual = residual.abs();
175
176 if residual > config.tolerance * target.abs().max(1.0) {
178 return None;
179 }
180
181 let mut basis_names = vec!["x".to_string()];
183 for (name, _) in &constants {
184 basis_names.push(name.clone());
185 }
186
187 Some(IntegerRelation {
188 coefficients,
189 basis_names,
190 residual,
191 is_exact: residual < EXACT_MATCH_TOLERANCE,
192 })
193}
194
195fn pslq(x: &[f64], config: &PslqConfig) -> Option<Vec<i64>> {
200 let n = x.len();
201 if n < 2 {
202 return None;
203 }
204
205 let _gamma = (4.0 / 3.0_f64).sqrt(); let mut s: Vec<f64> = vec![0.0; n];
210 s[n - 1] = x[n - 1].abs();
211 for i in (0..n - 1).rev() {
212 s[i] = (s[i + 1].powi(2) + x[i].powi(2)).sqrt();
213 }
214
215 let scale = s[0];
216 if scale == 0.0 {
217 return None;
218 }
219
220 let mut y: Vec<f64> = x.iter().map(|xi| xi / scale).collect();
222 for i in 0..n {
223 s[i] /= scale;
224 }
225
226 let mut h: Vec<Vec<f64>> = vec![vec![0.0; n]; n - 1];
228 for i in 0..n - 1 {
229 for j in 0..=i.min(n - 2) {
230 if i == j {
231 h[i][j] = s[j + 1] / s[j];
232 } else {
233 h[i][j] = -y[j] * y[i + 1] / (s[i] * s[i + 1]);
234 }
235 }
236 }
237
238 let mut a: Vec<Vec<i64>> = vec![vec![0; n]; n];
240 let mut b: Vec<Vec<i64>> = vec![vec![0; n]; n];
241 for i in 0..n {
242 a[i][i] = 1;
243 b[i][i] = 1;
244 }
245
246 for _iteration in 0..config.max_iterations {
248 let mut max_val = 0.0;
250 let mut max_idx = 0;
251 for i in 0..n - 1 {
252 let val = h[i][i].abs();
253 if val > max_val {
254 max_val = val;
255 max_idx = i;
256 }
257 }
258
259 if max_idx < n - 1 {
261 y.swap(max_idx, max_idx + 1);
262 a.swap(max_idx, max_idx + 1);
263 b.swap(max_idx, max_idx + 1);
264 for j in 0..n - 1 {
265 let tmp = h[max_idx][j];
266 h[max_idx][j] = h[max_idx + 1][j];
267 h[max_idx + 1][j] = tmp;
268 }
269 }
270
271 for i in (1..n).rev() {
273 if max_idx < n - 1 && h[max_idx][max_idx].abs() > 1e-50 {
274 let t = (h[i - 1][max_idx] / h[max_idx][max_idx]).round();
275 y[i - 1] -= t * y[max_idx];
276 for j in 0..n - 1 {
277 h[i - 1][j] -= t * h[max_idx][j];
278 }
279 for j in 0..n {
280 a[i - 1][j] -= (t as i64) * a[max_idx][j];
281 b[j][i - 1] -= (t as i64) * b[j][max_idx];
282 }
283 }
284 }
285
286 for i in 0..n {
288 if y[i].abs() < config.tolerance {
289 let coeffs: Vec<i64> = (0..n).map(|j| b[j][i]).collect();
291
292 if coeffs.iter().all(|&c| c.abs() <= config.max_coefficient) {
294 return Some(coeffs);
295 }
296 }
297 }
298
299 let mut min_diag = f64::MAX;
301 for i in 0..n - 1 {
302 if h[i][i].abs() < min_diag {
303 min_diag = h[i][i].abs();
304 }
305 }
306
307 if min_diag < config.tolerance {
308 let coeffs: Vec<i64> = (0..n).map(|j| b[j][0]).collect();
310 if coeffs.iter().all(|&c| c.abs() <= config.max_coefficient) {
311 return Some(coeffs);
312 }
313 }
314 }
315
316 None
317}
318
319pub fn find_rational_approximation(x: f64, max_denominator: i64) -> Option<(i64, i64)> {
323 let a0 = x.floor() as i64;
324 let mut remainder = x - a0 as f64;
325
326 if remainder.abs() < EXACT_MATCH_TOLERANCE {
327 return Some((a0, 1));
328 }
329
330 let mut h_prev = 1i64;
332 let mut h_curr = a0;
333 let mut k_prev = 0i64;
334 let mut k_curr = 1i64;
335
336 for _ in 0..100 {
337 if remainder.abs() < EXACT_MATCH_TOLERANCE {
338 break;
339 }
340
341 let reciprocal = 1.0 / remainder;
342 let a = reciprocal.floor() as i64;
343 remainder = reciprocal - a as f64;
344
345 let h_next = a * h_curr + h_prev;
346 let k_next = a * k_curr + k_prev;
347
348 if k_next > max_denominator {
350 break;
351 }
352
353 h_prev = h_curr;
354 h_curr = h_next;
355 k_prev = k_curr;
356 k_curr = k_next;
357
358 let approx = h_curr as f64 / k_curr as f64;
360 if (approx - x).abs() < EXACT_MATCH_TOLERANCE {
361 return Some((h_curr, k_curr));
362 }
363 }
364
365 if k_curr > 0 && k_curr <= max_denominator {
367 let approx = h_curr as f64 / k_curr as f64;
368 if (approx - x).abs() < x.abs() * 0.01 {
369 return Some((h_curr, k_curr));
370 }
371 }
372
373 None
374}
375
376pub fn find_minimal_polynomial(x: f64, max_degree: usize, max_coeff: i64) -> Option<Vec<i64>> {
381 for degree in 1..=max_degree {
383 if let Some(coeffs) = try_polynomial_degree(x, degree, max_coeff) {
388 return Some(coeffs);
389 }
390 }
391 None
392}
393
394fn try_polynomial_degree(x: f64, degree: usize, max_coeff: i64) -> Option<Vec<i64>> {
396 if degree == 0 {
397 return None;
398 }
399
400 let mut powers = vec![1.0; degree + 1];
402 for i in 1..=degree {
403 powers[i] = powers[i - 1] * x;
404 }
405
406 let mut best_coeffs: Option<Vec<i64>> = None;
412 let mut best_error = f64::MAX;
413
414 let search_range = (-(max_coeff / 10).max(1)..=(max_coeff / 10).max(1)).collect::<Vec<_>>();
416
417 if degree <= 2 {
419 for coeffs in coefficients_product(&search_range, degree + 1) {
420 let mut value = 0.0;
421 for (i, c) in coeffs.iter().enumerate() {
422 value += (*c as f64) * powers[i];
423 }
424
425 let error = value.abs();
426 if error < best_error && error < EXACT_MATCH_TOLERANCE * 100.0 {
427 best_error = error;
428 best_coeffs = Some(coeffs);
429 }
430 }
431 }
432
433 best_coeffs
434}
435
436fn coefficients_product(ranges: &[i64], count: usize) -> Vec<Vec<i64>> {
438 if count == 0 {
439 return vec![vec![]];
440 }
441
442 let mut result = Vec::new();
443 let rest = coefficients_product(ranges, count - 1);
444 for r in rest {
445 for &val in ranges {
446 let mut combo = r.clone();
447 combo.push(val);
448 result.push(combo);
449 }
450 }
451 result
452}
453
454#[cfg(test)]
455mod tests {
456 use super::*;
457
458 #[test]
459 fn test_rational_approximation_pi() {
460 let result = find_rational_approximation(PI, 1000);
462 assert!(result.is_some());
463 let (num, den) = result.unwrap();
464 assert_eq!(num, 355);
465 assert_eq!(den, 113);
466 }
467
468 #[test]
469 fn test_rational_approximation_sqrt2() {
470 let result = find_rational_approximation(std::f64::consts::SQRT_2, 200);
472 assert!(result.is_some());
473 let (num, den) = result.unwrap();
474 let approx = num as f64 / den as f64;
475 assert!((approx - std::f64::consts::SQRT_2).abs() < 0.001);
476 }
477
478 #[test]
479 fn test_integer_relation_simple() {
480 let config = PslqConfig::default();
482 let result = find_integer_relation(2.0 * PI, &config);
483 if let Some(rel) = result {
485 assert!(rel.residual < 0.01);
486 }
487 }
488
489 #[test]
490 fn test_minimal_polynomial_sqrt2() {
491 let result = find_minimal_polynomial(std::f64::consts::SQRT_2, 4, 100);
493 if let Some(coeffs) = result {
494 let value: f64 = coeffs
496 .iter()
497 .enumerate()
498 .map(|(i, c)| *c as f64 * std::f64::consts::SQRT_2.powi(i as i32))
499 .sum();
500 assert!(value.abs() < 0.01);
501 }
502 }
503}