Skip to main content

proof_engine/number_theory/
padic.rs

1//! p-adic numbers: representations, norms, arithmetic, and fractal rendering.
2
3use glam::{Vec2, Vec3, Vec4};
4
5/// A p-adic number represented by its prime, digit expansion, and valuation.
6/// The value is: sum_{i=0}^{n-1} digits[i] * p^(valuation + i).
7#[derive(Debug, Clone, PartialEq)]
8pub struct PAdic {
9    pub prime: u64,
10    pub digits: Vec<u64>,
11    pub valuation: i64,
12}
13
14impl PAdic {
15    /// Create a p-adic representation of an integer with given precision (number of digits).
16    pub fn from_integer(n: i64, prime: u64, precision: usize) -> Self {
17        assert!(prime >= 2, "prime must be >= 2");
18        if n == 0 {
19            return PAdic {
20                prime,
21                digits: vec![0; precision],
22                valuation: 0,
23            };
24        }
25
26        let p = prime as i64;
27        // For negative numbers, we use p-adic representation (complement)
28        // -1 in p-adic = (p-1)(p-1)(p-1)...
29        let mut digits = Vec::with_capacity(precision);
30        let mut val = n;
31
32        if val > 0 {
33            // Find valuation
34            let mut v = 0i64;
35            while val % p == 0 && val != 0 {
36                val /= p;
37                v += 1;
38            }
39            for _ in 0..precision {
40                digits.push((val.rem_euclid(p)) as u64);
41                val = val.div_euclid(p);
42            }
43            PAdic { prime, digits, valuation: v }
44        } else {
45            // Negative: compute as p^precision - |n| in base p
46            // This gives the p-adic complement
47            let mut v = 0i64;
48            let mut abs_val = -val;
49            while abs_val % p == 0 && abs_val != 0 {
50                abs_val /= p;
51                v += 1;
52            }
53            // Represent -abs_val in p-adic: use the fact that
54            // -x = (p^N - x) in p-adic for sufficiently large N
55            let mut complement = Vec::with_capacity(precision);
56            let mut carry = 0i64;
57            let mut first = true;
58            let mut remaining = abs_val;
59            for _ in 0..precision {
60                let d = remaining.rem_euclid(p);
61                remaining = remaining.div_euclid(p);
62                let comp_digit = if first && d == 0 {
63                    first = false;
64                    0
65                } else if first {
66                    first = false;
67                    (p - d) as u64
68                } else {
69                    (p - 1 - d - carry) as u64
70                };
71                // Actually, simpler: treat as unsigned and negate
72                complement.push(0);
73            }
74            // Simpler approach: use modular arithmetic
75            // -n mod p^k for large k
76            digits.clear();
77            let mut rem = n;
78            for _ in 0..precision {
79                let d = rem.rem_euclid(p) as u64;
80                digits.push(d);
81                rem = rem.div_euclid(p);
82            }
83            PAdic { prime, digits, valuation: v }
84        }
85    }
86
87    /// Display the p-adic expansion as a string: ...d_2 d_1 d_0 . d_{-1} ...
88    pub fn to_string_expansion(&self) -> String {
89        let mut s = String::from("...");
90        for d in self.digits.iter().rev() {
91            s.push_str(&d.to_string());
92        }
93        s
94    }
95
96    /// Evaluate to f64 (truncated, only meaningful for analysis).
97    pub fn to_f64_approx(&self) -> f64 {
98        let p = self.prime as f64;
99        let mut result = 0.0f64;
100        for (i, &d) in self.digits.iter().enumerate() {
101            result += d as f64 * p.powi((self.valuation + i as i64) as i32);
102        }
103        result
104    }
105
106    /// Number of digits in the expansion.
107    pub fn precision(&self) -> usize {
108        self.digits.len()
109    }
110}
111
112/// p-adic valuation of n with respect to prime p.
113pub fn padic_valuation(mut n: i64, p: u64) -> i64 {
114    if n == 0 {
115        return i64::MAX; // conventionally infinite
116    }
117    let p = p as i64;
118    n = n.abs();
119    let mut v = 0i64;
120    while n % p == 0 {
121        n /= p;
122        v += 1;
123    }
124    v
125}
126
127/// p-adic norm |n|_p = p^{-v_p(n)}.
128pub fn padic_norm(n: i64, p: u64) -> f64 {
129    if n == 0 {
130        return 0.0;
131    }
132    let v = padic_valuation(n, p);
133    (p as f64).powi(-v as i32)
134}
135
136/// p-adic distance |a - b|_p.
137pub fn padic_distance(a: i64, b: i64, p: u64) -> f64 {
138    padic_norm(a - b, p)
139}
140
141/// Add two p-adic numbers (must have same prime and precision is min of both).
142pub fn add(a: &PAdic, b: &PAdic) -> PAdic {
143    assert_eq!(a.prime, b.prime, "cannot add p-adics with different primes");
144    let p = a.prime;
145    let prec = a.digits.len().min(b.digits.len());
146
147    // Align valuations
148    let min_val = a.valuation.min(b.valuation);
149    let a_shift = (a.valuation - min_val) as usize;
150    let b_shift = (b.valuation - min_val) as usize;
151
152    let total_len = prec + a_shift.max(b_shift);
153    let mut digits = vec![0u64; total_len];
154    let mut carry = 0u64;
155
156    for i in 0..total_len {
157        let da = if i >= a_shift && i - a_shift < a.digits.len() {
158            a.digits[i - a_shift]
159        } else {
160            0
161        };
162        let db = if i >= b_shift && i - b_shift < b.digits.len() {
163            b.digits[i - b_shift]
164        } else {
165            0
166        };
167        let sum = da + db + carry;
168        digits[i] = sum % p;
169        carry = sum / p;
170    }
171
172    // Trim to precision
173    digits.truncate(prec);
174
175    // Find valuation (leading zeros)
176    let mut val = min_val;
177    while !digits.is_empty() && digits[0] == 0 {
178        digits.remove(0);
179        val += 1;
180    }
181    if digits.is_empty() {
182        digits.push(0);
183        val = 0;
184    }
185
186    PAdic { prime: p, digits, valuation: val }
187}
188
189/// Multiply two p-adic numbers.
190pub fn multiply(a: &PAdic, b: &PAdic) -> PAdic {
191    assert_eq!(a.prime, b.prime);
192    let p = a.prime;
193    let prec = a.digits.len().min(b.digits.len());
194    let val = a.valuation + b.valuation;
195
196    let mut digits = vec![0u64; prec];
197    for i in 0..a.digits.len().min(prec) {
198        let mut carry = 0u64;
199        for j in 0..b.digits.len().min(prec) {
200            if i + j >= prec {
201                break;
202            }
203            let prod = a.digits[i] * b.digits[j] + digits[i + j] + carry;
204            digits[i + j] = prod % p;
205            carry = prod / p;
206        }
207    }
208
209    PAdic { prime: p, digits, valuation: val }
210}
211
212// ─── Renderer ───────────────────────────────────────────────────────────────
213
214/// Renders p-adic expansions as a tree / fractal-like pattern.
215/// Each level of the tree corresponds to a digit position,
216/// branching into p children (one per possible digit).
217pub struct PadicRenderer {
218    pub prime: u64,
219    pub depth: usize,
220    pub origin: Vec3,
221    pub scale: f32,
222}
223
224pub struct PadicNode {
225    pub digit: u64,
226    pub level: usize,
227    pub position: Vec3,
228    pub color: Vec4,
229    pub character: char,
230}
231
232impl PadicRenderer {
233    pub fn new(prime: u64, depth: usize, origin: Vec3, scale: f32) -> Self {
234        Self { prime, depth, origin, scale }
235    }
236
237    /// Generate a tree of all p-adic digit sequences up to `depth` levels.
238    pub fn render_tree(&self) -> Vec<PadicNode> {
239        let mut nodes = Vec::new();
240        self.build_tree(&mut nodes, 0, 0, 0.0, self.scale);
241        nodes
242    }
243
244    fn build_tree(
245        &self,
246        nodes: &mut Vec<PadicNode>,
247        level: usize,
248        _path_value: u64,
249        x_center: f32,
250        width: f32,
251    ) {
252        if level >= self.depth {
253            return;
254        }
255        let p = self.prime;
256        let child_width = width / p as f32;
257        for d in 0..p {
258            let x = x_center + (d as f32 - (p - 1) as f32 / 2.0) * child_width;
259            let y = -(level as f32) * self.scale * 0.5;
260            let hue = d as f32 / p as f32;
261            nodes.push(PadicNode {
262                digit: d,
263                level,
264                position: self.origin + Vec3::new(x, y, 0.0),
265                color: Vec4::new(hue, 1.0 - hue, 0.5, 1.0),
266                character: std::char::from_digit(d as u32, 36).unwrap_or('?'),
267            });
268            self.build_tree(
269                nodes,
270                level + 1,
271                _path_value * p + d,
272                x,
273                child_width,
274            );
275        }
276    }
277
278    /// Highlight the path corresponding to a specific integer in the tree.
279    pub fn highlight_path(&self, n: i64) -> Vec<(usize, u64)> {
280        let p = self.prime as i64;
281        let mut path = Vec::new();
282        let mut val = n;
283        for level in 0..self.depth {
284            let d = val.rem_euclid(p) as u64;
285            path.push((level, d));
286            val = val.div_euclid(p);
287        }
288        path
289    }
290}
291
292// ─── Tests ──────────────────────────────────────────────────────────────────
293
294#[cfg(test)]
295mod tests {
296    use super::*;
297
298    #[test]
299    fn from_integer_basic() {
300        let pa = PAdic::from_integer(25, 5, 5);
301        assert_eq!(pa.prime, 5);
302        assert_eq!(pa.valuation, 2);
303        // 25 = 5^2, so digits should be [1, 0, 0, ...]
304        assert_eq!(pa.digits[0], 1);
305    }
306
307    #[test]
308    fn from_integer_zero() {
309        let pa = PAdic::from_integer(0, 3, 5);
310        assert_eq!(pa.digits, vec![0, 0, 0, 0, 0]);
311    }
312
313    #[test]
314    fn padic_norm_values() {
315        // |25|_5 = 5^{-2} = 0.04
316        assert!((padic_norm(25, 5) - 0.04).abs() < 1e-12);
317        // |12|_3 = 3^{-1} = 1/3
318        assert!((padic_norm(12, 3) - 1.0 / 3.0).abs() < 1e-12);
319        // |7|_5 = 1 (7 not divisible by 5)
320        assert!((padic_norm(7, 5) - 1.0).abs() < 1e-12);
321        // |0|_p = 0
322        assert_eq!(padic_norm(0, 7), 0.0);
323    }
324
325    #[test]
326    fn padic_distance_values() {
327        // |1 - 26|_5 = |25|_5 = 1/25
328        assert!((padic_distance(1, 26, 5) - 0.04).abs() < 1e-12);
329    }
330
331    #[test]
332    fn padic_ultrametric() {
333        // |a - c|_p <= max(|a-b|_p, |b-c|_p)  (strong triangle inequality)
334        let a = 3;
335        let b = 8;
336        let c = 18;
337        let p = 5u64;
338        let d_ac = padic_distance(a, c, p);
339        let d_ab = padic_distance(a, b, p);
340        let d_bc = padic_distance(b, c, p);
341        assert!(d_ac <= d_ab.max(d_bc) + 1e-12);
342    }
343
344    #[test]
345    fn add_padics() {
346        // In 5-adics: 3 + 7 = 10
347        let a = PAdic::from_integer(3, 5, 5);
348        let b = PAdic::from_integer(7, 5, 5);
349        let c = add(&a, &b);
350        let val = c.to_f64_approx();
351        assert!((val - 10.0).abs() < 1e-6, "expected 10, got {}", val);
352    }
353
354    #[test]
355    fn multiply_padics() {
356        // In 5-adics: 3 * 4 = 12
357        let a = PAdic::from_integer(3, 5, 5);
358        let b = PAdic::from_integer(4, 5, 5);
359        let c = multiply(&a, &b);
360        let val = c.to_f64_approx();
361        assert!((val - 12.0).abs() < 1e-6, "expected 12, got {}", val);
362    }
363
364    #[test]
365    fn negative_integer() {
366        // -1 in 2-adics should be ...1111
367        let pa = PAdic::from_integer(-1, 2, 8);
368        for &d in &pa.digits {
369            assert_eq!(d, 1, "all digits of -1 in 2-adic should be 1");
370        }
371    }
372
373    #[test]
374    fn renderer_tree_size() {
375        let r = PadicRenderer::new(3, 3, Vec3::ZERO, 10.0);
376        let nodes = r.render_tree();
377        // 3 + 9 + 27 = 39
378        assert_eq!(nodes.len(), 3 + 9 + 27);
379    }
380
381    #[test]
382    fn highlight_path() {
383        let r = PadicRenderer::new(5, 4, Vec3::ZERO, 1.0);
384        let path = r.highlight_path(37);
385        // 37 in base 5: 37 = 1*25 + 2*5 + 2 => digits [2, 2, 1, 0]
386        assert_eq!(path[0], (0, 2));
387        assert_eq!(path[1], (1, 2));
388        assert_eq!(path[2], (2, 1));
389        assert_eq!(path[3], (3, 0));
390    }
391}