moma/lib.rs
1//! # MOMA: Moving Origin Modular Arithmetic
2//!
3//! A framework for exploring number theory concepts using a "moving origin"
4//! in modular arithmetic.
5//!
6//! The core idea of MOMA is that for a given modulus `m`, the "zero point" or "origin"
7//! for the calculation `x mod m` is not fixed. Instead, it shifts based on a
8//! contextual value, typically a prime number `p`. This allows for the study of
9//! properties of numbers (like primes) in a dynamic relational context.
10//!
11//! ## Core Concepts
12//!
13//! - **`MomaRing`**: The central object for calculations. It is defined by a `modulus` and an `OriginStrategy`.
14//! - **`OriginStrategy`**: A trait that defines *how* the origin moves. The crate provides several
15//! common strategies, and users can easily implement their own.
16//! - **Residue**: The result of a MOMA calculation, computed as `(value + origin) % modulus`.
17//!
18//! ## Example Usage
19//!
20//! ```
21//! use moma::{MomaRing, strategy};
22//!
23//! // Create a MOMA ring with modulus 37.
24//! // The origin will be the gap between a prime and the previous prime.
25//! let ring = MomaRing::new(37, strategy::PrimeGap);
26//!
27//! // Let's analyze the prime p = 29.
28//! let p = 29;
29//!
30//! // The value we want to find the residue of. In my original research,
31//! // this was the sum of a prime and its predecessor.
32//! let value_to_test = p + primes::prev_prime(p); // 29 + 23 = 52
33//!
34//! // Calculate the MOMA residue.
35//! // The origin for p=29 is (29 - 23) = 6.
36//! // The residue is (52 + 6) % 37 = 58 % 37 = 21.
37//! let residue = ring.residue(value_to_test, p);
38//!
39//! println!("For p={}, the MOMA residue of {} is {}", p, value_to_test, residue);
40//! assert_eq!(residue, 21);
41//! ```
42
43// Re-export key components for easier access.
44pub use crate::analysis::{CompositeDampener, MassField};
45pub use crate::core::{MomaRing, OriginStrategy};
46
47/// Core MOMA structures and traits.
48pub mod core {
49 use crate::primes;
50
51 /// Defines a strategy for calculating the moving origin for a given prime context.
52 ///
53 /// This trait is the cornerstone of MOMA's flexibility. By implementing this trait,
54 /// users can create custom logic for how the modular origin should be determined.
55 pub trait OriginStrategy {
56 /// Calculates the origin based on a contextual prime `p`.
57 ///
58 /// # Parameters
59 /// - `p`: The prime number providing the context for the origin calculation.
60 fn calculate_origin(&self, p: u64) -> u64;
61 }
62
63 /// The central struct for performing Moving Origin Modular Arithmetic.
64 ///
65 /// A `MomaRing` is configured with a modulus and a chosen `OriginStrategy`.
66 /// It then calculates residues by shifting the input value by the dynamically
67 /// computed origin before applying the modulus.
68 pub struct MomaRing<S: OriginStrategy> {
69 pub modulus: u64,
70 strategy: S,
71 }
72
73 impl<S: OriginStrategy> MomaRing<S> {
74 /// Creates a new `MomaRing` with a given modulus and origin strategy.
75 ///
76 /// # Parameters
77 /// - `modulus`: The modulus for the arithmetic operations.
78 /// - `strategy`: An instance of a struct that implements `OriginStrategy`.
79 pub fn new(modulus: u64, strategy: S) -> Self {
80 Self { modulus, strategy }
81 }
82
83 /// Calculates the MOMA residue for a value within a prime context.
84 ///
85 /// This is the primary operation of the `MomaRing`. It first calculates the
86 /// origin using the ring's strategy and the provided `prime_context`,
87 /// then computes `(value + origin) % modulus`.
88 ///
89 /// # Parameters
90 /// - `value`: The input value to map to the ring.
91 /// - `prime_context`: The prime number used to determine the origin shift.
92 pub fn residue(&self, value: u64, prime_context: u64) -> u64 {
93 // Ensure modulus is not zero to prevent division by zero panic.
94 if self.modulus == 0 {
95 return value;
96 }
97 let origin = self.strategy.calculate_origin(prime_context);
98 (value.wrapping_add(origin)) % self.modulus
99 }
100
101 /// A convenience method for calculating the "signature" of a prime.
102 ///
103 /// The signature is defined as the residue of the sum of a prime and its
104 /// immediate predecessor. This is a common use case in MOMA-based analysis.
105 ///
106 /// # Parameters
107 /// - `p`: The prime for which to calculate the signature.
108 pub fn signature(&self, p: u64) -> u64 {
109 if p < 3 { return 0; } // prev_prime(2) is problematic, handle edge case.
110 let input = p.wrapping_add(primes::prev_prime(p));
111 self.residue(input, p)
112 }
113 }
114}
115
116/// Implementations of various origin strategies.
117pub mod strategy {
118 use crate::core::OriginStrategy;
119 use crate::primes;
120
121 /// An origin strategy where the origin is fixed to a constant value.
122 #[derive(Debug, Clone, Copy)]
123 pub struct Fixed(pub u64);
124 impl OriginStrategy for Fixed {
125 fn calculate_origin(&self, _p: u64) -> u64 {
126 self.0
127 }
128 }
129
130 /// An origin strategy where the origin is the gap between a prime and its predecessor.
131 /// `origin(p) = p - p_prev`
132 #[derive(Debug, Clone, Copy)]
133 pub struct PrimeGap;
134 impl OriginStrategy for PrimeGap {
135 fn calculate_origin(&self, p: u64) -> u64 {
136 if p < 3 { return 0; }
137 p - primes::prev_prime(p)
138 }
139 }
140
141 /// An origin strategy where the origin is the sum of prime factors of all
142 /// composite numbers in the gap between a prime and its successor.
143 /// `origin(p) = Σ mass(c)` for `c` in `(p, p_next)`.
144 #[derive(Debug, Clone, Copy)]
145 pub struct CompositeMass;
146 impl OriginStrategy for CompositeMass {
147 fn calculate_origin(&self, p: u64) -> u64 {
148 let p_next = primes::next_prime(p);
149 (p + 1..p_next)
150 .filter(|&n| !primes::is_prime(n))
151 .map(primes::prime_factor_mass)
152 .sum()
153 }
154 }
155}
156
157/// Tools for number-theoretic analysis related to MOMA.
158pub mod analysis {
159 use crate::primes;
160
161 /// A tool to analyze the "dampening" of composite numbers within a range.
162 ///
163 /// The dampening score measures how many composites in a range [lower, upper]
164 /// are divisible by a given set of small primes. A higher score means the
165 /// composites in the range are "less random" and more likely to be multiples
166 /// of small primes.
167 pub struct CompositeDampener {
168 pub lower: u64,
169 pub upper: u64,
170 pub small_primes: Vec<u64>,
171 }
172
173 impl CompositeDampener {
174 /// Calculates the dampening score for the given range.
175 ///
176 /// The score is the ratio of composites hit by `small_primes` to the total
177 /// number of composites in the range.
178 pub fn score(&self) -> f64 {
179 let composites: Vec<u64> = (self.lower + 1..self.upper)
180 .filter(|&n| !primes::is_prime(n))
181 .collect();
182
183 if composites.is_empty() {
184 return 0.0;
185 }
186
187 let hits = composites
188 .iter()
189 .filter(|&c| self.small_primes.iter().any(|sp| c % sp == 0))
190 .count();
191
192 hits as f64 / composites.len() as f64
193 }
194 }
195
196 /// A tool to analyze the "mass" of composite numbers between consecutive primes.
197 pub struct MassField {
198 pub range_start: u64,
199 pub range_end: u64,
200 }
201
202 impl MassField {
203 /// Generates a map of `(prime, composite_mass_in_next_gap)`.
204 ///
205 /// The "mass" is the sum of the count of prime factors for each composite
206 /// number between a prime `p` and its successor `p_next`.
207 pub fn generate_mass_map(&self) -> Vec<(u64, u64)> {
208 let mut map = Vec::new();
209 let mut p = primes::next_prime(self.range_start.saturating_sub(1));
210
211 while p < self.range_end {
212 let p_next = primes::next_prime(p);
213 let mass = (p + 1..p_next)
214 .filter(|&n| !primes::is_prime(n))
215 .map(primes::prime_factor_mass)
216 .sum();
217 map.push((p, mass));
218 p = p_next;
219 }
220 map
221 }
222 }
223}
224
225/// Utility functions for prime number operations.
226///
227/// NOTE: For a high-performance production crate, consider replacing these
228/// with a dependency on a specialized library like `primal`.
229pub mod primes {
230
231 /// A basic primality test.
232 pub fn is_prime(n: u64) -> bool {
233 if n < 2 { return false; }
234 if n == 2 || n == 3 { return true; }
235 if n % 2 == 0 || n % 3 == 0 { return false; }
236 let mut i = 5;
237 while i * i <= n {
238 if n % i == 0 || n % (i + 2) == 0 {
239 return false;
240 }
241 i += 6;
242 }
243 true
244 }
245
246 /// Finds the next prime number strictly greater than `n`.
247 pub fn next_prime(n: u64) -> u64 {
248 if n < 2 { return 2; }
249 // Start with the next odd number.
250 let mut x = if n % 2 == 0 { n + 1 } else { n + 2 };
251 loop {
252 if is_prime(x) {
253 return x;
254 }
255 x += 2; // Only check odd numbers.
256 }
257 }
258
259 /// Finds the greatest prime number strictly less than `n`.
260 /// Returns 0 if no such prime exists (e.g., for n <= 2).
261 pub fn prev_prime(n: u64) -> u64 {
262 if n <= 2 { return 0; }
263 let mut x = n - 1;
264 while x >= 2 {
265 if is_prime(x) {
266 return x;
267 }
268 x -= 1;
269 }
270 0
271 }
272
273 /// Calculates the "mass" of a number, defined as the count of its prime factors
274 /// with multiplicity. For example, `prime_factor_mass(12) = mass(2*2*3) = 3`.
275 pub fn prime_factor_mass(n: u64) -> u64 {
276 if n < 2 { return 0; }
277 let mut count = 0;
278 let mut temp_n = n;
279 let mut factor = 2;
280 while factor * factor <= temp_n {
281 while temp_n % factor == 0 {
282 count += 1;
283 temp_n /= factor;
284 }
285 factor += 1;
286 }
287 if temp_n > 1 {
288 count += 1;
289 }
290 count
291 }
292}