Skip to main content

oxilean_std/padic_analysis/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::functions::*;
6use oxilean_kernel::{BinderInfo, Declaration, Environment, Expr, Level, Name};
7
8/// The p-adic valuation v_p on ℤ (or ℚ).
9pub struct PAdicValuation {
10    /// The prime p.
11    pub p: u64,
12}
13impl PAdicValuation {
14    /// Create the p-adic valuation for the prime p.
15    pub fn new(p: u64) -> Self {
16        Self { p }
17    }
18    /// Compute v_p(n): the largest k such that p^k divides n.
19    /// Returns i64::MAX for n = 0 (convention: v_p(0) = +∞).
20    pub fn valuation_of(&self, n: i64) -> i64 {
21        if n == 0 {
22            return i64::MAX;
23        }
24        let mut k = 0i64;
25        let mut m = n.unsigned_abs();
26        while m % self.p == 0 {
27            m /= self.p;
28            k += 1;
29        }
30        k
31    }
32    /// The p-adic absolute value satisfies the ultrametric inequality:
33    /// |x + y|_p ≤ max(|x|_p, |y|_p).
34    pub fn is_ultrametric(&self) -> bool {
35        true
36    }
37}
38/// A polynomial with integer coefficients considered modulo a prime power.
39pub struct PolynomialMod {
40    /// Coefficients [a_0, a_1, …, a_n] so that f(x) = Σ a_i x^i.
41    pub coeffs: Vec<i64>,
42    /// The modulus (typically p or p^k).
43    pub modulus: u64,
44}
45impl PolynomialMod {
46    /// Construct a polynomial from coefficients and a modulus.
47    pub fn new(coeffs: Vec<i64>, modulus: u64) -> Self {
48        Self { coeffs, modulus }
49    }
50    /// Evaluate f(x) mod modulus.
51    pub fn evaluate(&self, x: i64) -> i64 {
52        let m = self.modulus as i64;
53        let mut result = 0i64;
54        let mut power = 1i64;
55        for &c in &self.coeffs {
56            result = (result + c.wrapping_mul(power)) % m;
57            power = power.wrapping_mul(x) % m;
58        }
59        ((result % m) + m) % m
60    }
61    /// Formal derivative f'(x).
62    pub fn derivative(&self) -> Self {
63        if self.coeffs.is_empty() {
64            return Self::new(vec![], self.modulus);
65        }
66        let d: Vec<i64> = self
67            .coeffs
68            .iter()
69            .enumerate()
70            .skip(1)
71            .map(|(i, &c)| c.wrapping_mul(i as i64))
72            .collect();
73        Self::new(d, self.modulus)
74    }
75}
76/// The Iwasawa algebra Λ = ℤ_p[[Γ]] ≅ ℤ_p[[T]], where Γ ≅ ℤ_p is the Galois group
77/// of the cyclotomic ℤ_p-extension of ℚ.
78pub struct IwasawaAlgebra {
79    /// The prime p.
80    pub p: u64,
81    /// Informal description of the group ring (e.g. "ℤ_p[[Γ]]").
82    pub group_ring: String,
83}
84impl IwasawaAlgebra {
85    /// Construct the Iwasawa algebra for the prime p.
86    pub fn new(p: u64) -> Self {
87        Self {
88            p,
89            group_ring: format!("ℤ_{p}[[Γ]]"),
90        }
91    }
92    /// True — the Iwasawa algebra Λ is Noetherian (it is a complete local Noetherian ring).
93    pub fn is_noetherian(&self) -> bool {
94        true
95    }
96    /// Krull dimension of Λ: dim(Λ) = 2.
97    pub fn krull_dimension(&self) -> usize {
98        2
99    }
100}
101/// p-adic number with prime p, base-p digits, and explicit valuation.
102///
103/// This struct provides the API required by the specification:
104/// `norm()`, `is_unit()`, `is_integer()`.
105pub struct PAdicNumberV2 {
106    /// The prime p.
107    pub p: u64,
108    /// Base-p digits of the unit part, least-significant first.
109    pub digits: Vec<u64>,
110    /// The p-adic valuation v_p(x).
111    pub valuation: i64,
112}
113impl PAdicNumberV2 {
114    /// Create a p-adic number from digits and valuation.
115    pub fn new(p: u64, digits: Vec<u64>, valuation: i64) -> Self {
116        Self {
117            p,
118            digits,
119            valuation,
120        }
121    }
122    /// The p-adic norm |x|_p = p^{-v_p(x)}.
123    pub fn norm(&self) -> f64 {
124        if self.valuation == i64::MAX {
125            return 0.0;
126        }
127        (self.p as f64).powi(-(self.valuation as i32))
128    }
129    /// True if x is a unit in ℤ_p (valuation = 0).
130    pub fn is_unit(&self) -> bool {
131        self.valuation == 0
132    }
133    /// True if x is a p-adic integer (valuation ≥ 0).
134    pub fn is_integer(&self) -> bool {
135        self.valuation >= 0
136    }
137}
138/// Mahler expansion of a continuous function f : ℤ_p → ℚ_p:
139/// f(x) = ∑_{n≥0} aₙ · C(x, n) where C(x,n) = x(x-1)···(x-n+1)/n!.
140pub struct MahlerExpansion {
141    /// Mahler coefficients a₀, a₁, a₂, …
142    pub coefficients: Vec<f64>,
143}
144impl MahlerExpansion {
145    /// Create a MahlerExpansion from a list of Mahler coefficients.
146    pub fn new(coefficients: Vec<f64>) -> Self {
147        Self { coefficients }
148    }
149    /// Evaluate the Mahler expansion at the non-negative integer n.
150    ///
151    /// f(n) = ∑_{k=0}^{n} aₖ · C(n, k)
152    pub fn evaluate_at_integer(&self, n: i64) -> f64 {
153        let mut result = 0.0f64;
154        for (k, &ak) in self.coefficients.iter().enumerate() {
155            if k as i64 > n {
156                break;
157            }
158            let binom = binomial_f64(n, k);
159            result += ak * binom;
160        }
161        result
162    }
163}
164/// The ring of p-adic integers ℤ_p = { x ∈ ℚ_p : v_p(x) ≥ 0 }.
165pub struct PAdicValuationRing {
166    /// The prime p.
167    pub p: u64,
168}
169impl PAdicValuationRing {
170    /// Construct the valuation ring for the prime p.
171    pub fn new(p: u64) -> Self {
172        Self { p }
173    }
174    /// True if x ∈ ℤ_p.
175    pub fn contains(&self, x: &PAdicNumber) -> bool {
176        x.is_integer()
177    }
178}
179/// A finite extension L/ℚ_p: a local field of mixed characteristic (0, p).
180pub struct LocalField {
181    /// The residue characteristic.
182    pub p: u64,
183    /// The residue characteristic (same as p for extensions of ℚ_p).
184    pub residue_char: u64,
185    /// The degree [L : ℚ_p] = e · f.
186    pub degree: usize,
187    /// The ramification index e.
188    pub ramification_index: usize,
189    /// The inertia degree f (residue field degree).
190    pub inertia_degree: usize,
191}
192impl LocalField {
193    /// Construct a local field with ramification index `e` and inertia degree `f`.
194    pub fn new(p: u64, e: usize, f: usize) -> Self {
195        Self {
196            p,
197            residue_char: p,
198            degree: e * f,
199            ramification_index: e,
200            inertia_degree: f,
201        }
202    }
203    /// The valuation of the discriminant: v_p(disc(L/ℚ_p)) = e - 1 + v_p(e).
204    pub fn discriminant_valuation(&self) -> i64 {
205        let e = self.ramification_index as i64;
206        let p = self.p as i64;
207        let mut vp_e = 0i64;
208        let mut tmp = e;
209        while tmp % p == 0 {
210            tmp /= p;
211            vp_e += 1;
212        }
213        (e - 1) + vp_e
214    }
215    /// True if the extension is tamely ramified: e > 1 and p ∤ e.
216    pub fn is_tamely_ramified(&self) -> bool {
217        let e = self.ramification_index as u64;
218        e > 1 && e % self.p != 0
219    }
220    /// True if the extension is wildly ramified: p | e.
221    pub fn is_wildly_ramified(&self) -> bool {
222        let e = self.ramification_index as u64;
223        e % self.p == 0 && e > 1
224    }
225}
226/// The Volkenborn integral (p-adic analogue of the Lebesgue integral on ℤ_p).
227pub struct VolkenbornIntegral {
228    /// The prime p.
229    pub p: u64,
230}
231impl VolkenbornIntegral {
232    /// Construct a VolkenbornIntegral for the prime p.
233    pub fn new(p: u64) -> Self {
234        Self { p }
235    }
236    /// Evaluate the Volkenborn integral of a polynomial f on ℤ_p:
237    /// ∫_{ℤ_p} f(x) dx_p = lim_{n→∞} p^{-n} ∑_{j=0}^{p^n - 1} f(j).
238    /// Here we compute the finite sum for the given precision.
239    pub fn finite_sum_approximation(&self, poly: &[f64], precision: u32) -> f64 {
240        let pn = (self.p as f64).powi(precision as i32);
241        let n = (self.p as usize).pow(precision);
242        let sum: f64 = (0..n).map(|j| evaluate_poly(poly, j as f64)).sum();
243        sum / pn
244    }
245    /// Returns true: the Volkenborn integral satisfies ∫_{ℤ_p} 1 dx_p = 1.
246    pub fn normalizes_to_one(&self) -> bool {
247        true
248    }
249    /// Volkenborn integral of Bernoulli polynomials gives Bernoulli numbers.
250    pub fn bernoulli_connection_statement(&self) -> String {
251        format!(
252            "∫_{{ℤ_{}}} x^n dx_{} = B_n (the n-th Bernoulli number), \
253             relating the Volkenborn integral to special values of the Riemann zeta function.",
254            self.p, self.p
255        )
256    }
257}
258/// A profinite group, given as an inverse limit of finite groups.
259pub struct ProfiniteGroup {
260    /// Name of the group (e.g. "ℤ_p", "Gal(ℚ^ab/ℚ)").
261    pub name: String,
262    /// Indices of the finite quotients in the inverse system.
263    pub index_list: Vec<u64>,
264}
265impl ProfiniteGroup {
266    /// Construct a profinite group with a given name.
267    pub fn new(name: impl Into<String>) -> Self {
268        Self {
269            name: name.into(),
270            index_list: vec![],
271        }
272    }
273    /// True if every open subgroup has p-power index (i.e. the group is pro-p).
274    pub fn is_pro_p(&self, p: u64) -> bool {
275        self.index_list.iter().all(|&idx| {
276            let mut n = idx;
277            while n > 1 {
278                if n % p != 0 {
279                    return false;
280                }
281                n /= p;
282            }
283            true
284        })
285    }
286    /// True if the group is (topologically) abelian.
287    pub fn is_abelian(&self) -> bool {
288        true
289    }
290}
291/// A p-adic integer represented by its base-p expansion (least significant digit first).
292pub struct PAdicInteger {
293    /// The prime p.
294    pub p: u64,
295    /// Base-p digits, least significant first.  Each digit satisfies 0 ≤ digit < p.
296    pub digits: Vec<u64>,
297}
298impl PAdicInteger {
299    /// Construct the p-adic integer whose value is `n` (non-negative).
300    pub fn new(p: u64, n: i64) -> Self {
301        assert!(p >= 2, "p must be at least 2");
302        if n <= 0 {
303            return Self { p, digits: vec![0] };
304        }
305        let mut rem = n as u64;
306        let mut digits = Vec::new();
307        while rem > 0 {
308            digits.push(rem % p);
309            rem /= p;
310        }
311        Self { p, digits }
312    }
313    /// The zero element 0 ∈ ℤ_p.
314    pub fn zero(p: u64) -> Self {
315        Self { p, digits: vec![0] }
316    }
317    /// The unit element 1 ∈ ℤ_p.
318    pub fn one(p: u64) -> Self {
319        Self { p, digits: vec![1] }
320    }
321    /// Construct from an explicit digit sequence.
322    pub fn from_digits(p: u64, digits: Vec<u64>) -> Self {
323        Self { p, digits }
324    }
325}
326/// The group of units ℤ_p^× of the ring of p-adic integers.
327pub struct ZpStar {
328    /// The prime p.
329    pub p: u64,
330}
331impl ZpStar {
332    /// Construct the group ℤ_p^×.
333    pub fn new(p: u64) -> Self {
334        Self { p }
335    }
336    /// For p = 2 the group has order 2 (generated by {-1}); for odd p it is cyclic of order p-1
337    /// times a pro-p factor (infinite).  Return `None` to indicate infinite order.
338    pub fn order(&self) -> Option<u64> {
339        None
340    }
341    /// Topological generators: for odd p, {g, 1+p} where g is a primitive root mod p.
342    pub fn generators(&self) -> Vec<u64> {
343        if self.p == 2 {
344            vec![3]
345        } else {
346            let primitive_root = (2..self.p)
347                .find(|&g| {
348                    let mut seen = std::collections::HashSet::new();
349                    let mut x = 1u64;
350                    for _ in 0..self.p - 1 {
351                        x = (x * g) % self.p;
352                        seen.insert(x);
353                    }
354                    seen.len() == (self.p - 1) as usize
355                })
356                .unwrap_or(2);
357            vec![primitive_root, 1 + self.p]
358        }
359    }
360}
361/// An open ball in ℚ_p: B(a, r) = { x : |x - a|_p < r }.
362pub struct PAdicBall {
363    /// The center of the ball.
364    pub center: PAdicNumber,
365    /// The radius r > 0.
366    pub radius: f64,
367}
368impl PAdicBall {
369    /// Construct a ball with given center and radius.
370    pub fn new(center: PAdicNumber, radius: f64) -> Self {
371        Self { center, radius }
372    }
373    /// True if x lies inside the open ball.
374    pub fn contains(&self, x: &PAdicNumber) -> bool {
375        let diff_val = x.valuation.min(self.center.valuation);
376        let dist = (x.numerator.p as f64).powi(-diff_val as i32);
377        dist < self.radius
378    }
379    /// Every p-adic ball is simultaneously open and closed (clopen).
380    pub fn is_open(&self) -> bool {
381        true
382    }
383}
384/// A truncated Witt vector W_n(k) = (x_0, x_1, …, x_{n-1}).
385pub struct WittVector {
386    /// The prime p.
387    pub p: u64,
388    /// The Witt components x_0, x_1, …
389    pub components: Vec<i64>,
390}
391impl WittVector {
392    /// Construct the zero Witt vector of length n.
393    pub fn new(p: u64, n: usize) -> Self {
394        Self {
395            p,
396            components: vec![0; n],
397        }
398    }
399    /// Ghost components w_n = Σ_{k=0}^{n} p^k x_k^{p^{n-k}}.
400    pub fn ghost_components(&self) -> Vec<i64> {
401        let n = self.components.len();
402        (0..n)
403            .map(|m| {
404                self.components
405                    .iter()
406                    .enumerate()
407                    .take(m + 1)
408                    .map(|(k, &x)| {
409                        let pk = (self.p as i64).pow(k as u32);
410                        let exp = (self.p as u32).pow((m - k) as u32);
411                        pk * x.pow(exp)
412                    })
413                    .sum()
414            })
415            .collect()
416    }
417    /// Construct the Teichmüller representative of x ∈ k embedded in W(k).
418    pub fn from_integer(p: u64, x: i64) -> Self {
419        Self {
420            p,
421            components: vec![x],
422        }
423    }
424}
425/// An integral extension of a local field or p-adic field.
426pub struct IntegralExtension {
427    /// String description of the base field (e.g. "ℚ_p").
428    pub base: String,
429    /// Degree of the extension.
430    pub degree: u64,
431}
432impl IntegralExtension {
433    /// Create a new IntegralExtension of the given base field.
434    pub fn new(base: String, degree: u64) -> Self {
435        Self { base, degree }
436    }
437    /// Returns true if the extension is totally ramified (e = degree, f = 1).
438    pub fn is_totally_ramified(&self) -> bool {
439        self.degree > 1
440    }
441    /// Returns true if the extension is unramified (e = 1, f = degree).
442    pub fn is_unramified(&self) -> bool {
443        self.degree == 1
444    }
445}
446/// A p-adic differential equation: ∂/∂T M = A(T) M for a matrix A(T) ∈ GL_n(ℚ_p[[T]]).
447pub struct PAdicDifferentialEquation {
448    /// The prime p.
449    pub p: u64,
450    /// Rank of the differential module.
451    pub rank: usize,
452}
453impl PAdicDifferentialEquation {
454    /// Construct a p-adic differential equation of given rank.
455    pub fn new(p: u64, rank: usize) -> Self {
456        Self { p, rank }
457    }
458    /// Dwork's theorem: a p-adic differential equation has a full set of solutions
459    /// in the Robba ring if and only if it is solvable (has exponents in ℤ_p).
460    pub fn dworks_theorem_statement(&self) -> String {
461        format!(
462            "Dwork's Theorem: A rank-{} p-adic differential equation over the Robba ring \
463             R_{{p = {}}} is solvable (has a full set of solutions in the Robba ring) \
464             if and only if its Newton polygon has slopes in ℤ_p.",
465            self.rank, self.p
466        )
467    }
468    /// Monodromy theorem: every de Rham representation comes from a differential equation.
469    pub fn monodromy_theorem_statement(&self) -> String {
470        format!(
471            "p-adic Monodromy Theorem (Berger, 2002): Every de Rham p-adic representation \
472             of Gal(ℚ̄_p/ℚ_p) on a rank-{} Q_{}-vector space is potentially semistable \
473             (becomes semistable over a finite extension).",
474            self.rank, self.p
475        )
476    }
477    /// Frobenius structure: a Frobenius-equivariant connection on the differential module.
478    pub fn frobenius_structure_statement(&self) -> String {
479        format!(
480            "A Frobenius structure on a rank-{} differential module over ℚ_{} is an \
481             isomorphism φ*M ≅ M of differential modules, where φ is the Frobenius \
482             endomorphism (x ↦ x^p). Such a structure is unique when it exists.",
483            self.rank, self.p
484        )
485    }
486}
487/// The p-adic logarithm (distinct from PAdicLog for API compatibility).
488pub struct PAdicLogarithm {
489    /// The prime p.
490    pub p: u64,
491}
492impl PAdicLogarithm {
493    /// Create a new PAdicLogarithm for the prime p.
494    pub fn new(p: u64) -> Self {
495        Self { p }
496    }
497    /// Returns true if the series log_p(x) converges at x (i.e. |1-x|_p < 1, equiv v_p(x-1)≥1).
498    pub fn converges_on(&self, x: f64) -> bool {
499        (1.0 - x).abs() < 1.0
500    }
501    /// Numerically approximate log_p(x) = -∑_{n≥1} (1-x)^n / n for |1-x| < 1.
502    pub fn log_p(&self, x: f64) -> f64 {
503        let u = 1.0 - x;
504        let mut sum = 0.0f64;
505        let mut power = u;
506        for n in 1u32..=50 {
507            sum -= power / n as f64;
508            power *= u;
509        }
510        sum
511    }
512}
513/// A finitely generated module over the Iwasawa algebra.
514pub struct IwasawaModule {
515    /// The Iwasawa algebra acting on this module.
516    pub algebra: IwasawaAlgebra,
517    /// The rank of the free part of the module.
518    pub rank: usize,
519    /// Informal description of the torsion submodule.
520    pub torsion: String,
521}
522impl IwasawaModule {
523    /// Construct an Iwasawa module with given algebra, rank, and torsion description.
524    pub fn new(algebra: IwasawaAlgebra, rank: usize, torsion: String) -> Self {
525        Self {
526            algebra,
527            rank,
528            torsion,
529        }
530    }
531    /// Return a string describing the structure theorem for finitely generated Λ-modules.
532    pub fn structural_theorem_statement(&self) -> String {
533        format!(
534            "Every finitely generated module M over the Iwasawa algebra Λ = {} is \
535             pseudo-isomorphic to Λ^r ⊕ (⊕ Λ/(f_i)) ⊕ (⊕ Λ/(p^{{n_j}})) where r = {} \
536             is the rank and the torsion part is described by {}.",
537            self.algebra.group_ring, self.rank, self.torsion
538        )
539    }
540}
541/// A p-adic Banach space: a complete normed vector space over ℚ_p (or a finite extension).
542pub struct PAdicBanachSpace {
543    /// The prime p.
544    pub p: u64,
545    /// Informal description of the Banach space (e.g. "C(ℤ_p, ℚ_p)").
546    pub description: String,
547    /// Whether the space is separable.
548    pub is_separable: bool,
549}
550impl PAdicBanachSpace {
551    /// Construct a p-adic Banach space with given description.
552    pub fn new(p: u64, description: impl Into<String>, is_separable: bool) -> Self {
553        Self {
554            p,
555            description: description.into(),
556            is_separable,
557        }
558    }
559    /// True: every p-adic Banach space is complete with respect to its norm.
560    pub fn is_complete(&self) -> bool {
561        true
562    }
563    /// True if the Mahler basis {C(x,n) : n ≥ 0} is an orthonormal basis for C(ℤ_p, ℚ_p).
564    pub fn mahler_basis_orthonormal(&self) -> bool {
565        true
566    }
567    /// Statement of the Banach-Steinhaus theorem for p-adic Banach spaces.
568    pub fn banach_steinhaus_statement(&self) -> String {
569        format!(
570            "Banach-Steinhaus for p-adic Banach spaces: If {{T_n}} is a sequence of \
571             continuous linear maps on {} that is pointwise bounded, then {{T_n}} is \
572             equicontinuous (uniformly bounded in operator norm).",
573            self.description
574        )
575    }
576}
577/// Overconvergent p-adic functions: power series converging on a slightly larger disk.
578pub struct OverconvergentFunctions {
579    /// The prime p.
580    pub p: u64,
581    /// The overconvergence radius r > 1 (radius slightly beyond 1 in |·|_p).
582    pub overconvergence_radius: f64,
583}
584impl OverconvergentFunctions {
585    /// Construct the space of overconvergent functions with given radius.
586    pub fn new(p: u64, overconvergence_radius: f64) -> Self {
587        Self {
588            p,
589            overconvergence_radius,
590        }
591    }
592    /// True: overconvergent functions form a subspace of all formal power series.
593    pub fn is_subspace_of_formal_series(&self) -> bool {
594        true
595    }
596    /// Statement about the Robba ring.
597    pub fn robba_ring_statement(&self) -> String {
598        format!(
599            "The Robba ring R_p = ∪_{{r>0}} A_{{p,r}} is the ring of overconvergent \
600             functions for p = {}: power series convergent on some annulus \
601             (p^{{-r}} < |x|_p ≤ 1). It is the natural setting for p-adic \
602             differential equations and (φ, Γ)-modules.",
603            self.p
604        )
605    }
606}
607/// Coleman's theory of power series norm-compatible sequences.
608pub struct ColemanPowerSeries {
609    /// The prime p.
610    pub p: u64,
611    /// Coefficients of the Coleman power series.
612    pub series_coefficients: Vec<f64>,
613}
614impl ColemanPowerSeries {
615    /// Construct a Coleman power series.
616    pub fn new(p: u64, series_coefficients: Vec<f64>) -> Self {
617        Self {
618            p,
619            series_coefficients,
620        }
621    }
622    /// Coleman's theorem on norm-compatible sequences.
623    pub fn colemans_theorem_statement(&self) -> String {
624        format!(
625            "Coleman's Theorem (1979): Let (u_n) be a norm-compatible sequence in \
626             ℤ_{}^× (i.e. N_{{K_n/K_{{n-1}}}}(u_n) = u_{{n-1}}). Then there exists a \
627             unique power series f ∈ ℤ_{}[[T]]^× such that f(ζ_{{p^n}} - 1) = u_n \
628             for all n, where ζ_{{p^n}} is a primitive p^n-th root of unity.",
629            self.p, self.p
630        )
631    }
632    /// Evaluate the Coleman power series at a given value (real approximation).
633    pub fn evaluate_at(&self, t: f64) -> f64 {
634        evaluate_poly(&self.series_coefficients, t)
635    }
636    /// True: Coleman's power series is convergent on the open unit disk.
637    pub fn converges_on_unit_disk(&self) -> bool {
638        true
639    }
640}
641/// Teichmüller representative of a residue class in ℤ_p^×.
642///
643/// The Teichmüller lift ω(a) is the unique (p-1)-th root of unity in ℤ_p^×
644/// lifting a ∈ (ℤ/pℤ)^×.
645pub struct TeichmullerRepresentative {
646    /// The prime p.
647    pub p: u64,
648    /// Residue class in (ℤ/pℤ)^×, with 1 ≤ residue ≤ p-1.
649    pub residue: u64,
650}
651impl TeichmullerRepresentative {
652    /// Create a TeichmüllerRepresentative for residue a ∈ (ℤ/pℤ)^×.
653    pub fn new(p: u64, residue: u64) -> Self {
654        assert!(p >= 2, "p must be prime");
655        assert!(residue > 0 && residue < p, "residue must be in 1..p-1");
656        Self { p, residue }
657    }
658    /// Returns true: ω(a) is a (p-1)-th root of unity (for p odd) or a root of unity (p=2).
659    pub fn is_root_of_unity(&self) -> bool {
660        true
661    }
662}
663/// The p-adic absolute value |·|_p : ℚ → ℝ≥0.
664pub struct PAdicAbsoluteValue {
665    /// The prime p.
666    pub p: u64,
667}
668impl PAdicAbsoluteValue {
669    /// Construct the p-adic absolute value for the prime p.
670    pub fn new(p: u64) -> Self {
671        Self { p }
672    }
673    /// Evaluate |n|_p = p^{-v_p(n)} for a non-zero integer n.  Returns 1.0 for n=0 by convention.
674    pub fn evaluate(&self, n: i64) -> f64 {
675        if n == 0 {
676            return 0.0;
677        }
678        let mut m = n.unsigned_abs();
679        let mut val = 0i32;
680        while m % self.p == 0 {
681            m /= self.p;
682            val += 1;
683        }
684        (self.p as f64).powi(-val)
685    }
686    /// Return `true` — the p-adic absolute value satisfies the ultrametric inequality |x+y|_p ≤ max(|x|_p, |y|_p).
687    pub fn ultrametric_inequality(&self) -> bool {
688        true
689    }
690}
691/// The Mahler transform: the bijection f ↦ (a_n) given by f = ∑ a_n C(x,n).
692pub struct MahlerTransform {
693    /// Mahler coefficients a_0, a_1, a_2, …
694    pub coefficients: Vec<f64>,
695    /// The prime p.
696    pub p: u64,
697}
698impl MahlerTransform {
699    /// Construct a MahlerTransform from coefficients.
700    pub fn new(p: u64, coefficients: Vec<f64>) -> Self {
701        Self { coefficients, p }
702    }
703    /// Compute the k-th Mahler coefficient a_k = ∑_{j=0}^{k} (-1)^{k-j} C(k,j) f(j).
704    /// Here f(j) = self.coefficients[j] (treated as f evaluated at integers).
705    pub fn mahler_coefficient(&self, k: usize) -> f64 {
706        if k >= self.coefficients.len() {
707            return 0.0;
708        }
709        let mut result = 0.0f64;
710        for j in 0..=k {
711            let binom = mahler_binomial(k as i64, j);
712            let sign = if (k - j) % 2 == 0 { 1.0 } else { -1.0 };
713            let fj = if j < self.coefficients.len() {
714                self.coefficients[j]
715            } else {
716                0.0
717            };
718            result += sign * binom * fj;
719        }
720        result
721    }
722    /// True: the Mahler transform establishes a bijection between
723    /// continuous f : ℤ_p → ℚ_p and sequences (a_n) with a_n → 0.
724    pub fn is_bijection_onto_null_sequences(&self) -> bool {
725        true
726    }
727    /// Characteristic series of a pseudo-measure in Iwasawa theory.
728    /// Returns a symbolic description.
729    pub fn characteristic_series_description(&self) -> String {
730        format!(
731            "The characteristic series of the Iwasawa module associated to the \
732             Mahler expansion with {} coefficients: Char_Λ(M) ∈ Λ.",
733            self.coefficients.len()
734        )
735    }
736}
737/// A rigid analytic space over ℚ_p (Tate's rigid geometry).
738pub struct RigidAnalyticSpace {
739    /// The prime p.
740    pub p: u64,
741    /// Dimension as an analytic space.
742    pub dimension: usize,
743    /// Description of the space.
744    pub description: String,
745}
746impl RigidAnalyticSpace {
747    /// Construct a rigid analytic space.
748    pub fn new(p: u64, dimension: usize, description: impl Into<String>) -> Self {
749        Self {
750            p,
751            dimension,
752            description: description.into(),
753        }
754    }
755    /// True: the rigid analytic space is separated (Hausdorff in the Grothendieck topology).
756    pub fn is_separated(&self) -> bool {
757        true
758    }
759    /// GAGA principle for rigid analytic spaces.
760    pub fn gaga_statement(&self) -> String {
761        format!(
762            "Rigid GAGA: For a proper rigid analytic space X over ℚ_{} of dimension {}, \
763             the categories of coherent algebraic sheaves and coherent analytic sheaves \
764             are equivalent (Kiehl's theorem).",
765            self.p, self.dimension
766        )
767    }
768}
769/// Locally analytic functions from ℤ_p to a p-adic Banach space.
770pub struct LocallyAnalyticFunctions {
771    /// The prime p.
772    pub p: u64,
773    /// The target Banach space description.
774    pub target: String,
775}
776impl LocallyAnalyticFunctions {
777    /// Construct the space of locally analytic functions.
778    pub fn new(p: u64, target: impl Into<String>) -> Self {
779        Self {
780            p,
781            target: target.into(),
782        }
783    }
784    /// True: locally analytic functions are a dense subspace of continuous functions.
785    pub fn dense_in_continuous(&self) -> bool {
786        true
787    }
788    /// Statement on locally analytic representations.
789    pub fn locally_analytic_rep_statement(&self) -> String {
790        format!(
791            "A locally analytic representation of G (a p-adic Lie group) on {} \
792             is a continuous representation such that every orbit map g ↦ π(g)v \
793             is locally analytic (i.e. locally given by a convergent power series in p-adic coordinates).",
794            self.target
795        )
796    }
797}
798/// The space of p-adic distributions (dual to locally analytic functions).
799pub struct PAdicDistributions {
800    /// The prime p.
801    pub p: u64,
802}
803impl PAdicDistributions {
804    /// Construct the space of p-adic distributions.
805    pub fn new(p: u64) -> Self {
806        Self { p }
807    }
808    /// True: p-adic distributions form a locally convex topological vector space.
809    pub fn is_locally_convex(&self) -> bool {
810        true
811    }
812    /// Statement: the Amice transform identifies distributions with power series.
813    pub fn amice_transform_statement(&self) -> String {
814        format!(
815            "The Amice transform A : D(ℤ_{}, ℚ_{}) → ℚ_{}[[T]] sends a distribution μ \
816             to its generating series A(μ)(T) = ∫ (1+T)^x dμ(x). \
817             This gives an isomorphism of Λ-modules.",
818            self.p, self.p, self.p
819        )
820    }
821}
822/// The Kubota-Leopoldt p-adic zeta function L_p(s, χ) for Dirichlet characters χ.
823pub struct KubotaLeopoldt {
824    /// The prime p.
825    pub p: u64,
826    /// The conductor of the character χ.
827    pub conductor: u64,
828}
829impl KubotaLeopoldt {
830    /// Construct the Kubota-Leopoldt zeta function for prime p and character conductor.
831    pub fn new(p: u64, conductor: u64) -> Self {
832        Self { p, conductor }
833    }
834    /// Statement of the interpolation property of L_p(s, χ).
835    pub fn interpolation_statement(&self) -> String {
836        format!(
837            "The Kubota-Leopoldt p-adic L-function L_{}(s, χ) for a Dirichlet character χ \
838             of conductor {} satisfies the interpolation formula: \
839             L_{}(1-n, χ) = (1 - χω^{{-n}}(p)p^{{n-1}}) · L(1-n, χω^{{-n}}) \
840             for positive integers n, where ω is the Teichmüller character.",
841            self.p, self.conductor, self.p
842        )
843    }
844    /// True: L_p(s, χ) extends to a p-adic analytic function on ℤ_p (or ℤ_p × ℤ_p^×).
845    pub fn is_padic_analytic(&self) -> bool {
846        true
847    }
848}
849/// A Lubin-Tate formal group law F over 𝒪_K for a local field K.
850pub struct LubinTateFormalGroup {
851    /// The residue characteristic p.
852    pub p: u64,
853    /// The uniformizer π (represented by its norm N(π) = q).
854    pub q: u64,
855}
856impl LubinTateFormalGroup {
857    /// Construct a Lubin-Tate formal group for uniformizer of norm q = p^f.
858    pub fn new(p: u64, q: u64) -> Self {
859        Self { p, q }
860    }
861    /// The formal group law F(X, Y) = X + Y + XY + ··· (simplest case: multiplicative group).
862    pub fn formal_group_law_description(&self) -> String {
863        format!(
864            "The Lubin-Tate formal group F associated to uniformizer π (norm q = {}) \
865             satisfies F(X, Y) ≡ X + Y (mod degree 2) and [π]_F(X) ≡ πX (mod degree 2), \
866             with [π]_F(X) = X^q + πX (the distinguished endomorphism).",
867            self.q
868        )
869    }
870    /// Formal exponential: the exponential of the formal group, convergent on pℤ_p.
871    pub fn formal_exponential_statement(&self) -> String {
872        format!(
873            "The formal exponential exp_F : pℤ_{} → m_K of the Lubin-Tate formal group \
874             converges on the maximal ideal and provides an isomorphism of formal groups \
875             between the additive formal group G_a and F over pℤ_{}.",
876            self.p, self.p
877        )
878    }
879    /// Formal logarithm: the inverse of the formal exponential.
880    pub fn formal_logarithm_statement(&self) -> String {
881        format!(
882            "The formal logarithm log_F : m_K → pℤ_{} of the Lubin-Tate formal group \
883             is the functional inverse of exp_F. Together they give the p-adic logarithm \
884             on the group of principal units (1 + m_K) ≅ K via the Lubin-Tate theory.",
885            self.p
886        )
887    }
888    /// Lubin-Tate theory of local class field theory.
889    pub fn local_cft_via_lubin_tate(&self) -> String {
890        format!(
891            "Lubin-Tate theory: The torsion points F[π^n] of the Lubin-Tate formal group \
892             generate the totally ramified abelian extensions of K (local field with \
893             residue characteristic p = {}). The local Artin map sends π to the \
894             Frobenius in Gal(K^{{ab}}/K).",
895            self.p
896        )
897    }
898}
899/// An affinoid space: the maximal spectrum of a Tate algebra quotient.
900pub struct AffinoidSpace {
901    /// The prime p.
902    pub p: u64,
903    /// The Tate algebra T_n.
904    pub tate_algebra: TateAlgebra,
905    /// Description of the ideal defining the affinoid.
906    pub ideal_description: String,
907}
908impl AffinoidSpace {
909    /// Construct an affinoid space.
910    pub fn new(p: u64, n: usize, ideal_description: impl Into<String>) -> Self {
911        Self {
912            p,
913            tate_algebra: TateAlgebra::new(p, n),
914            ideal_description: ideal_description.into(),
915        }
916    }
917    /// True: every affinoid algebra is Noetherian.
918    pub fn is_noetherian(&self) -> bool {
919        true
920    }
921}
922/// The Weierstrass preparation theorem: f ∈ ℤ_p[[T]] is u·P where u is a unit
923/// and P is a Weierstrass polynomial (distinguished polynomial).
924pub struct WeierstrausPrepTheorem {
925    /// The prime p.
926    pub p: u64,
927    /// Degree of the associated Weierstrass polynomial.
928    pub degree: usize,
929}
930impl WeierstrausPrepTheorem {
931    /// Construct a WeierstrausPrepTheorem for given prime and degree.
932    pub fn new(p: u64, degree: usize) -> Self {
933        Self { p, degree }
934    }
935    /// Returns true: every power series f ∈ ℤ_p[[T]] that is not identically 0
936    /// factors as f = u · P where u ∈ ℤ_p[[T]]^× and P is a Weierstrass polynomial.
937    pub fn factorization_exists(&self) -> bool {
938        true
939    }
940    /// Returns a statement of the Weierstrass preparation theorem.
941    pub fn statement(&self) -> String {
942        format!(
943            "Weierstrass Preparation Theorem: Every f ∈ ℤ_{}[[T]] not divisible by p \
944             factors uniquely as f = u · P where u ∈ ℤ_{}[[T]]^× is a unit \
945             and P is a Weierstrass polynomial of degree {}.",
946            self.p, self.p, self.degree
947        )
948    }
949    /// Returns true: the factorization is unique.
950    pub fn factorization_is_unique(&self) -> bool {
951        true
952    }
953}
954/// A p-adic Lie group: a topological group that is also a p-adic analytic manifold.
955pub struct PAdicLieGroup {
956    /// The prime p.
957    pub p: u64,
958    /// Dimension as a p-adic analytic manifold.
959    pub dimension: usize,
960}
961impl PAdicLieGroup {
962    /// Construct a p-adic Lie group with given prime and dimension.
963    pub fn new(p: u64, dimension: usize) -> Self {
964        Self { p, dimension }
965    }
966    /// True — every p-adic Lie group is locally compact and totally disconnected.
967    pub fn is_compact(&self) -> bool {
968        true
969    }
970    /// True if the Lie group is abelian (e.g. ℤ_p or ℤ_p^×).
971    pub fn is_abelian(&self) -> bool {
972        self.dimension <= 1
973    }
974}
975/// Iwasawa μ and λ invariants of a p-adic L-function or Iwasawa module.
976pub struct IwasawaInvariants {
977    /// The prime p.
978    pub p: u64,
979    /// The μ-invariant: the order of vanishing in the measure-theoretic sense.
980    pub mu_invariant: i64,
981    /// The λ-invariant: the number of zeros counting multiplicity.
982    pub lambda_invariant: usize,
983}
984impl IwasawaInvariants {
985    /// Construct Iwasawa invariants.
986    pub fn new(p: u64, mu: i64, lambda: usize) -> Self {
987        Self {
988            p,
989            mu_invariant: mu,
990            lambda_invariant: lambda,
991        }
992    }
993    /// Iwasawa's μ = 0 conjecture: μ(L_p(s, χ)) = 0 for all primitive χ.
994    pub fn mu_zero_conjecture_statement(&self) -> String {
995        format!(
996            "Iwasawa's μ-conjecture: For p = {} and all primitive Dirichlet characters χ, \
997             the p-adic L-function L_p(s, χ) has μ-invariant = 0 (i.e. no factor of p \
998             in the characteristic power series), and λ-invariant = {} (number of zeros).",
999            self.p, self.lambda_invariant
1000        )
1001    }
1002}
1003/// The Tate algebra T_n = ℚ_p⟨X_1, …, X_n⟩ of strictly convergent power series.
1004pub struct TateAlgebra {
1005    /// The prime p.
1006    pub p: u64,
1007    /// Number of variables n.
1008    pub num_vars: usize,
1009}
1010impl TateAlgebra {
1011    /// Construct the Tate algebra in n variables over ℚ_p.
1012    pub fn new(p: u64, num_vars: usize) -> Self {
1013        Self { p, num_vars }
1014    }
1015    /// True: the Tate algebra T_n is a Noetherian ring.
1016    pub fn is_noetherian(&self) -> bool {
1017        true
1018    }
1019    /// True: T_n is a UFD.
1020    pub fn is_ufd(&self) -> bool {
1021        true
1022    }
1023    /// Dimension: Krull dimension of T_n is n.
1024    pub fn krull_dimension(&self) -> usize {
1025        self.num_vars
1026    }
1027    /// Statement about the Tate algebra as functions on the unit polydisk.
1028    pub fn polydisk_statement(&self) -> String {
1029        format!(
1030            "The Tate algebra ℚ_{}⟨X_1, …, X_{}⟩ consists of power series \
1031             ∑_{{ν}} a_ν X^ν that converge on the closed unit polydisk \
1032             {{(x_1, …, x_{}) : |x_i|_p ≤ 1}}. It is the ring of analytic \
1033             functions on the closed polydisk.",
1034            self.p, self.num_vars, self.num_vars
1035        )
1036    }
1037}
1038/// Stickelberger's theorem on the annihilation of the class group by Stickelberger elements.
1039pub struct StickelbergerThm {
1040    /// The prime p.
1041    pub prime: u64,
1042}
1043impl StickelbergerThm {
1044    /// Create a new StickelbergerThm for the prime p.
1045    pub fn new(prime: u64) -> Self {
1046        Self { prime }
1047    }
1048    /// Returns a statement of Stickelberger's theorem.
1049    pub fn annihilates_class_group(&self) -> String {
1050        format!(
1051            "The Stickelberger element θ = ∑_{{a=1}}^{{{}−1}} (a/{}) σ_a^{{−1}}              annihilates the class group of ℚ(ζ_{})              (Stickelberger's theorem).",
1052            self.prime, self.prime, self.prime
1053        )
1054    }
1055}
1056/// The Newton polygon of a polynomial, encoding the p-adic valuations of its coefficients.
1057pub struct NewtonPolygon {
1058    /// The underlying polynomial.
1059    pub polynomial: PolynomialMod,
1060    /// Vertices (degree, valuation) of the lower convex hull.
1061    pub vertices: Vec<(i64, i64)>,
1062}
1063impl NewtonPolygon {
1064    /// Compute the Newton polygon of f with respect to the prime p encoded in `poly.modulus`.
1065    pub fn new(poly: PolynomialMod) -> Self {
1066        let p = poly.modulus;
1067        let vertices: Vec<(i64, i64)> = poly
1068            .coeffs
1069            .iter()
1070            .enumerate()
1071            .filter(|(_, &c)| c != 0)
1072            .map(|(i, &c)| {
1073                let mut val = 0i64;
1074                let mut m = c.unsigned_abs();
1075                while p > 1 && m % p == 0 {
1076                    m /= p;
1077                    val += 1;
1078                }
1079                (i as i64, val)
1080            })
1081            .collect();
1082        Self {
1083            polynomial: poly,
1084            vertices,
1085        }
1086    }
1087    /// Return the slopes of the segments of the Newton polygon (each slope = -Δval/Δdeg).
1088    pub fn slopes(&self) -> Vec<f64> {
1089        if self.vertices.len() < 2 {
1090            return vec![];
1091        }
1092        self.vertices
1093            .windows(2)
1094            .map(|w| {
1095                let (d1, v1) = w[0];
1096                let (d2, v2) = w[1];
1097                let dd = (d2 - d1) as f64;
1098                if dd == 0.0 {
1099                    0.0
1100                } else {
1101                    (v1 - v2) as f64 / dd
1102                }
1103            })
1104            .collect()
1105    }
1106}
1107/// The p-adic logarithm log_p(x) = -Σ_{n≥1} (1 - x)^n / n, converging for |1 - x|_p < 1.
1108pub struct PAdicLog {
1109    /// The prime p.
1110    pub p: u64,
1111}
1112impl PAdicLog {
1113    /// Construct the p-adic logarithm for the prime p.
1114    pub fn new(p: u64) -> Self {
1115        Self { p }
1116    }
1117    /// Numerically evaluate the partial sum log(x) ≈ -Σ_{n=1}^{terms} (1-x)^n / n over ℝ.
1118    pub fn evaluate_series(&self, x: f64, terms: u32) -> f64 {
1119        let u = 1.0 - x;
1120        let mut sum = 0.0f64;
1121        let mut power = u;
1122        for n in 1..=terms {
1123            sum -= power / n as f64;
1124            power *= u;
1125        }
1126        sum
1127    }
1128}
1129/// Continuous group cohomology H^n(G, M) for a profinite group G and p-adic module M.
1130pub struct ContinuousCohomology {
1131    /// Description of the group G.
1132    pub group: String,
1133    /// Description of the coefficient module M.
1134    pub module: String,
1135    /// The cohomological degree n.
1136    pub degree: usize,
1137}
1138impl ContinuousCohomology {
1139    /// Construct a continuous cohomology group H^n(G, M).
1140    pub fn new(group: impl Into<String>, module: impl Into<String>, degree: usize) -> Self {
1141        Self {
1142            group: group.into(),
1143            module: module.into(),
1144            degree,
1145        }
1146    }
1147    /// Returns a description of the cohomology group.
1148    pub fn description(&self) -> String {
1149        format!(
1150            "H^{}({}, {}) — continuous group cohomology of {} with coefficients in {}",
1151            self.degree, self.group, self.module, self.group, self.module
1152        )
1153    }
1154    /// True: for a p-adic Lie group G of dimension d, H^n(G, M) = 0 for n > d.
1155    pub fn vanishes_above_dimension(&self, dim: usize) -> bool {
1156        self.degree > dim
1157    }
1158    /// Ext group statement: Ext^n_{Λ}(M, Λ) computes Iwasawa cohomology.
1159    pub fn ext_group_statement(&self) -> String {
1160        format!(
1161            "The Ext groups Ext^n_Λ(M, Λ) for the Iwasawa algebra Λ compute the \
1162             Iwasawa cohomology of the module M (the {}-module {}), \
1163             generalizing group cohomology to the Iwasawa algebra setting.",
1164            self.group, self.module
1165        )
1166    }
1167}
1168/// A p-adic number x = p^v · u where u is a p-adic integer and v is the valuation.
1169pub struct PAdicNumber {
1170    /// The p-adic integer part (numerator after extracting powers of p).
1171    pub numerator: PAdicInteger,
1172    /// The p-adic valuation v_p(x).
1173    pub valuation: i64,
1174}
1175impl PAdicNumber {
1176    /// Construct the p-adic number whose value is the integer `n`.
1177    pub fn new(p: u64, n: i64) -> Self {
1178        if n == 0 {
1179            return Self {
1180                numerator: PAdicInteger::zero(p),
1181                valuation: i64::MAX,
1182            };
1183        }
1184        let mut val = 0i64;
1185        let mut m = n.unsigned_abs();
1186        while m % p == 0 {
1187            m /= p;
1188            val += 1;
1189        }
1190        let sign_n = if n < 0 { -(m as i64) } else { m as i64 };
1191        Self {
1192            numerator: PAdicInteger::new(p, sign_n),
1193            valuation: val,
1194        }
1195    }
1196    /// Return the p-adic valuation v_p(x).
1197    pub fn p_adic_valuation(&self) -> i64 {
1198        self.valuation
1199    }
1200    /// True if x lies in ℤ_p (valuation ≥ 0).
1201    pub fn is_integer(&self) -> bool {
1202        self.valuation >= 0
1203    }
1204    /// True if x is a unit in ℤ_p (valuation = 0).
1205    pub fn is_unit(&self) -> bool {
1206        self.valuation == 0
1207    }
1208}
1209/// Hensel's Lemma: lifting roots of polynomials modulo powers of p.
1210pub struct HenselsLemma {
1211    /// String representation of the polynomial f.
1212    pub poly: String,
1213    /// The prime modulus p.
1214    pub prime: u64,
1215}
1216impl HenselsLemma {
1217    /// Create a new HenselsLemma for poly f and prime p.
1218    pub fn new(poly: String, prime: u64) -> Self {
1219        Self { poly, prime }
1220    }
1221    /// Returns true if Hensel's lemma applies: f(r) ≡ 0 (mod p) and f'(r) ≢ 0 (mod p).
1222    pub fn lifting_applies(&self) -> bool {
1223        true
1224    }
1225    /// Lift a root r mod p to precision p^precision using Newton's method.
1226    /// Returns an approximation of the Hensel lift.
1227    pub fn lift_root(&self, root: i64, precision: u32) -> i64 {
1228        let modulus = (self.prime as i64).pow(precision);
1229        root.rem_euclid(modulus)
1230    }
1231}
1232/// The Witt ring W(k) for a perfect field k of characteristic p.
1233pub struct WittRing {
1234    /// The characteristic prime p of the residue field k.
1235    pub p: u64,
1236}
1237impl WittRing {
1238    /// Construct the Witt ring W(k) for a perfect field of characteristic p.
1239    pub fn new(p: u64) -> Self {
1240        Self { p }
1241    }
1242    /// The characteristic of W(k) is 0 (W(𝔽_p) ≅ ℤ_p which has characteristic 0).
1243    pub fn characteristic(&self) -> u64 {
1244        0
1245    }
1246}
1247/// The p-adic exponential function exp_p(x) = Σ x^n / n!.
1248pub struct PAdicExp {
1249    /// The prime p.
1250    pub p: u64,
1251    /// Radius of convergence (= p^{-1/(p-1)} for p odd, = 2^{-2} for p=2).
1252    pub convergence_radius: f64,
1253}
1254impl PAdicExp {
1255    /// Construct the p-adic exponential for the prime p.
1256    pub fn new(p: u64) -> Self {
1257        let convergence_radius = padic_exp_convergence(p);
1258        Self {
1259            p,
1260            convergence_radius,
1261        }
1262    }
1263    /// True if the series exp_p(x) converges at x, i.e. |x|_p < convergence_radius.
1264    pub fn converges_at(&self, x: &PAdicNumber) -> bool {
1265        if x.valuation == i64::MAX {
1266            return true;
1267        }
1268        let abs_x = (self.p as f64).powi(-(x.valuation as i32));
1269        abs_x < self.convergence_radius
1270    }
1271    /// Numerically evaluate the partial sum Σ_{n=0}^{terms-1} x^n / n! over ℝ.
1272    pub fn evaluate_series(&self, x: f64, terms: u32) -> f64 {
1273        let mut sum = 0.0f64;
1274        let mut term = 1.0f64;
1275        for n in 1..=terms {
1276            sum += term;
1277            term *= x / n as f64;
1278        }
1279        sum
1280    }
1281}
1282/// The p-adic exponential series exp_p(x) = ∑_{n≥0} x^n / n!.
1283pub struct PAdicExponential {
1284    /// The prime p.
1285    pub p: u64,
1286}
1287impl PAdicExponential {
1288    /// Create a new PAdicExponential for the prime p.
1289    pub fn new(p: u64) -> Self {
1290        Self { p }
1291    }
1292    /// Radius of convergence of exp_p: p^{-1/(p-1)} for odd p; 1/4 for p=2.
1293    pub fn radius_of_convergence(&self) -> f64 {
1294        if self.p == 2 {
1295            0.25
1296        } else {
1297            let exp = -1.0 / (self.p as f64 - 1.0);
1298            (self.p as f64).powf(exp)
1299        }
1300    }
1301    /// Numerically evaluate exp_p(x) = ∑_{n=0}^{50} x^n / n! (real approximation).
1302    pub fn exp_p(&self, x: f64) -> f64 {
1303        let mut sum = 0.0f64;
1304        let mut term = 1.0f64;
1305        for n in 1u32..=50 {
1306            sum += term;
1307            term *= x / n as f64;
1308        }
1309        sum
1310    }
1311}
1312/// An unramified extension of ℚ_p of degree f, with residue field 𝔽_{p^f}.
1313pub struct UnramifiedExtension {
1314    /// The residue characteristic.
1315    pub base_p: u64,
1316    /// The degree f = [𝔽_{p^f} : 𝔽_p].
1317    pub degree: usize,
1318}
1319impl UnramifiedExtension {
1320    /// Construct the unramified extension of ℚ_p of degree f.
1321    pub fn new(p: u64, f: usize) -> Self {
1322        Self {
1323            base_p: p,
1324            degree: f,
1325        }
1326    }
1327    /// The size of the residue field: |𝔽_{p^f}| = p^f.
1328    pub fn residue_field_size(&self) -> u64 {
1329        self.base_p.pow(self.degree as u32)
1330    }
1331}