tx2_iff/
prime.rs

1//! 360 Prime Pattern and PPF-based quantization
2//!
3//! This module implements the 360 Prime Pattern for optimal quantization
4//! of wavelet coefficients. The pattern is based on the mathematical discovery
5//! that every prime can be located within distance ≤180 from either:
6//! 1. A factor of m×360, OR
7//! 2. A term in the recursive sequence N_i
8//!
9//! ## Mathematical Foundation
10//!
11//! The number 360 = 2³ × 3² × 5 has special properties:
12//! - φ(360) = 96 residue classes coprime to 360
13//! - 48 residue classes are always factor-covered
14//! - 46 residue classes are always sequence-covered
15//! - 2 residue classes (261, 269) are scale-dependent
16//!
17//! ## Convergence Properties
18//!
19//! The 360-prime pattern exhibits remarkable convergence:
20//! - Precision gain: 2.56 decimal digits per iteration
21//! - d_n ≈ -log₁₀(K) + n × log₁₀(360)
22//! - After 22 iterations: 64 correct digits of π
23
24use serde::{Deserialize, Serialize};
25
26/// Number of residue classes modulo 360 that can contain primes
27pub const RESIDUE_CLASSES: usize = 96;
28
29/// The 96 residue classes modulo 360 that are coprime to 360
30/// These are the only residue classes that can contain primes > 5
31pub const PRIME_RESIDUE_CLASSES: [u16; RESIDUE_CLASSES] = [
32    1, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 49, 53, 59,
33    61, 67, 71, 73, 77, 79, 83, 89, 91, 97, 101, 103, 107, 109, 113, 119,
34    121, 127, 131, 133, 137, 139, 143, 149, 151, 157, 161, 163, 167, 169, 173, 179,
35    181, 187, 191, 193, 197, 199, 203, 209, 211, 217, 221, 223, 227, 229, 233, 239,
36    241, 247, 251, 253, 257, 259, 263, 269, 271, 277, 281, 283, 287, 289, 293, 299,
37    301, 307, 311, 313, 317, 319, 323, 329, 331, 337, 341, 343, 347, 349, 353, 359,
38];
39
40/// Category 1: Residue classes that are always factor-covered (48 total)
41pub const FACTOR_COVERED: [u16; 48] = [
42    1, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 49, 53, 59,
43    61, 67, 71, 73, 77, 79, 83, 89, 271, 277, 281, 283, 287, 289, 293, 299,
44    301, 307, 311, 313, 317, 319, 323, 329, 331, 337, 341, 343, 347, 349, 353, 359,
45];
46
47/// Category 2: Residue classes that are always sequence-covered (46 total)
48pub const SEQUENCE_COVERED: [u16; 46] = [
49    91, 97, 101, 103, 107, 109, 113, 119, 121, 127, 131, 133, 137, 139, 143, 149,
50    151, 157, 161, 163, 167, 169, 173, 179, 181, 187, 191, 193, 197, 199, 203, 209,
51    211, 217, 221, 223, 227, 229, 233, 239, 241, 247, 251, 253, 257, 259,
52];
53
54/// Category 3: Residue classes with scale-dependent coverage (2 total)
55pub const SCALE_DEPENDENT: [u16; 2] = [261, 269];
56
57/// Extended prime set including -1 as the Sign Prime
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
59pub enum ExtendedPrime {
60    /// The Sign Prime: -1 (unique negative prime)
61    SignPrime,
62    /// Magnitude primes: 2, 3, 5, 7, 11, ...
63    MagnitudePrime(u32),
64}
65
66impl ExtendedPrime {
67    /// Check if a number is prime in the PPF framework
68    pub fn is_prime(n: i32) -> bool {
69        if n == -1 {
70            return true; // Sign prime
71        }
72        if n < 2 {
73            return false;
74        }
75        if n == 2 || n == 3 {
76            return true;
77        }
78        if n % 2 == 0 || n % 3 == 0 {
79            return false;
80        }
81
82        let mut i = 5;
83        while i * i <= n {
84            if n % i == 0 || n % (i + 2) == 0 {
85                return false;
86            }
87            i += 6;
88        }
89        true
90    }
91
92    /// Get the value as i32
93    pub fn value(&self) -> i32 {
94        match self {
95            ExtendedPrime::SignPrime => -1,
96            ExtendedPrime::MagnitudePrime(p) => *p as i32,
97        }
98    }
99}
100
101/// PPF quantization table based on 360-prime pattern
102#[derive(Debug, Clone)]
103pub struct QuantizationTable {
104    /// Base quantization value
105    pub base_q: u16,
106    /// Per-residue class weights (96 entries)
107    pub weights: [f32; RESIDUE_CLASSES],
108}
109
110impl QuantizationTable {
111    /// Create a new quantization table with given base Q
112    pub fn new(base_q: u16) -> Self {
113        let mut weights = [1.0f32; RESIDUE_CLASSES];
114
115        // Apply weighting based on category
116        // Factor-covered classes get slightly higher weight (sharper quantization)
117        for &residue in &FACTOR_COVERED {
118            if let Some(idx) = PRIME_RESIDUE_CLASSES.iter().position(|&r| r == residue) {
119                weights[idx] = 0.9; // 10% finer quantization
120            }
121        }
122
123        // Sequence-covered classes get standard weight
124        for &residue in &SEQUENCE_COVERED {
125            if let Some(idx) = PRIME_RESIDUE_CLASSES.iter().position(|&r| r == residue) {
126                weights[idx] = 1.0;
127            }
128        }
129
130        // Scale-dependent classes get adaptive weight
131        for &residue in &SCALE_DEPENDENT {
132            if let Some(idx) = PRIME_RESIDUE_CLASSES.iter().position(|&r| r == residue) {
133                weights[idx] = 1.1; // 10% coarser quantization
134            }
135        }
136
137        QuantizationTable { base_q, weights }
138    }
139
140    /// Get quantization step for a given position
141    pub fn get_step(&self, x: usize, y: usize) -> u16 {
142        // Map position to residue class using 360-pattern
143        let residue = ((x + y * 360) % 360) as u16;
144
145        // Find the closest prime residue class
146        let idx = self.find_closest_residue(residue);
147
148        // Apply weight
149        let weighted_q = self.base_q as f32 * self.weights[idx];
150        weighted_q.round() as u16
151    }
152
153    /// Find the index of the closest prime residue class
154    fn find_closest_residue(&self, residue: u16) -> usize {
155        let mut min_dist = u16::MAX;
156        let mut min_idx = 0;
157
158        for (idx, &prime_residue) in PRIME_RESIDUE_CLASSES.iter().enumerate() {
159            let dist = if residue > prime_residue {
160                residue - prime_residue
161            } else {
162                prime_residue - residue
163            };
164
165            // Also consider wraparound distance
166            let wraparound_dist = 360 - dist;
167            let effective_dist = dist.min(wraparound_dist);
168
169            if effective_dist < min_dist {
170                min_dist = effective_dist;
171                min_idx = idx;
172            }
173        }
174
175        min_idx
176    }
177}
178
179/// PPF-based hash function using 360-prime pattern
180pub struct PpfHash {
181    /// Seed value
182    seed: u32,
183}
184
185impl PpfHash {
186    /// Create a new PPF hash with given seed
187    pub fn new(seed: u32) -> Self {
188        PpfHash { seed }
189    }
190
191    /// Hash a coordinate pair to a value in [0, 1)
192    pub fn hash(&self, x: u32, y: u32) -> f32 {
193        // Combine coordinates with seed using prime factorization properties
194        let combined = self.combine_coords(x, y);
195
196        // Apply Fibonacci hashing (golden ratio constant)
197        let hash = combined.wrapping_mul(2654435761u32);
198
199        // Map to 360-prime residue
200        let residue = (hash % 360) as usize;
201
202        // Find closest prime residue class
203        let idx = self.find_prime_residue(residue);
204
205        // Normalize using the prime residue value
206        PRIME_RESIDUE_CLASSES[idx] as f32 / 360.0
207    }
208
209    /// Combine coordinates using PPF properties
210    fn combine_coords(&self, x: u32, y: u32) -> u32 {
211        // Use the fact that (-1) × (-1) = (+1) in PPF
212        // Combine x and y with seed through XOR (preserves prime structure)
213        let x_prime = self.prime_factorize_approx(x);
214        let y_prime = self.prime_factorize_approx(y);
215        x_prime ^ y_prime ^ self.seed
216    }
217
218    /// Approximate prime factorization hash
219    /// Maps input to a value that preserves residue class properties
220    fn prime_factorize_approx(&self, n: u32) -> u32 {
221        if n == 0 {
222            return 0;
223        }
224
225        // Extract factors of 2, 3, 5 (divisors of 360)
226        let mut residue = n;
227        let mut factor_hash = 1u32;
228
229        while residue % 2 == 0 {
230            factor_hash = factor_hash.wrapping_mul(2);
231            residue /= 2;
232        }
233        while residue % 3 == 0 {
234            factor_hash = factor_hash.wrapping_mul(3);
235            residue /= 3;
236        }
237        while residue % 5 == 0 {
238            factor_hash = factor_hash.wrapping_mul(5);
239            residue /= 5;
240        }
241
242        // Combine with residue
243        factor_hash.wrapping_add(residue)
244    }
245
246    /// Find the index of the closest prime residue class
247    fn find_prime_residue(&self, residue: usize) -> usize {
248        let residue = residue as u16;
249        let mut min_dist = u16::MAX;
250        let mut min_idx = 0;
251
252        for (idx, &prime_residue) in PRIME_RESIDUE_CLASSES.iter().enumerate() {
253            let dist = if residue > prime_residue {
254                residue - prime_residue
255            } else {
256                prime_residue - residue
257            };
258
259            let wraparound_dist = 360 - dist;
260            let effective_dist = dist.min(wraparound_dist);
261
262            if effective_dist < min_dist {
263                min_dist = effective_dist;
264                min_idx = idx;
265            }
266        }
267
268        min_idx
269    }
270}
271
272/// Recursive sequence for 360-prime pattern
273pub struct RecursiveSequence {
274    /// Current scale (m)
275    scale: u32,
276    /// Current index in sequence
277    index: u32,
278    /// Current value
279    value: u32,
280}
281
282impl RecursiveSequence {
283    /// Create a new recursive sequence for given scale
284    pub fn new(scale: u32) -> Self {
285        // N_1 = (m-1) × 360 + 181
286        let value = (scale - 1) * 360 + 181;
287        RecursiveSequence {
288            scale,
289            index: 1,
290            value,
291        }
292    }
293
294    /// Get the current value
295    pub fn value(&self) -> u32 {
296        self.value
297    }
298
299    /// Advance to next term: N_i = N_(i-1) + i
300    pub fn next(&mut self) -> u32 {
301        self.index += 1;
302        self.value += self.index;
303        self.value
304    }
305
306    /// Check if sequence has exceeded the scale range
307    pub fn in_range(&self) -> bool {
308        self.value <= self.scale * 360 + 180
309    }
310}
311
312#[cfg(test)]
313mod tests {
314    use super::*;
315
316    #[test]
317    fn test_residue_classes() {
318        // φ(360) = 96
319        assert_eq!(PRIME_RESIDUE_CLASSES.len(), RESIDUE_CLASSES);
320
321        // Verify all are coprime to 360
322        for &residue in &PRIME_RESIDUE_CLASSES {
323            assert_eq!(gcd(residue as u32, 360), 1);
324        }
325    }
326
327    #[test]
328    fn test_category_distribution() {
329        // 48 + 46 + 2 = 96
330        assert_eq!(
331            FACTOR_COVERED.len() + SEQUENCE_COVERED.len() + SCALE_DEPENDENT.len(),
332            RESIDUE_CLASSES
333        );
334    }
335
336    #[test]
337    fn test_extended_prime() {
338        assert!(ExtendedPrime::is_prime(-1)); // Sign prime
339        assert!(ExtendedPrime::is_prime(2));
340        assert!(ExtendedPrime::is_prime(3));
341        assert!(ExtendedPrime::is_prime(5));
342        assert!(ExtendedPrime::is_prime(7));
343
344        assert!(!ExtendedPrime::is_prime(0));
345        assert!(!ExtendedPrime::is_prime(1));
346        assert!(!ExtendedPrime::is_prime(4));
347        assert!(!ExtendedPrime::is_prime(6));
348
349        // -2, -3, -5 are not prime (can be factored as (-1) × 2, etc.)
350        assert!(!ExtendedPrime::is_prime(-2));
351        assert!(!ExtendedPrime::is_prime(-3));
352    }
353
354    #[test]
355    fn test_quantization_table() {
356        let table = QuantizationTable::new(10);
357
358        // Base Q should be 10
359        assert_eq!(table.base_q, 10);
360
361        // Should have 96 weights
362        assert_eq!(table.weights.len(), RESIDUE_CLASSES);
363
364        // Get step for various positions
365        let step1 = table.get_step(0, 0);
366        let step2 = table.get_step(100, 100);
367
368        // Steps should be close to base_q with weighting
369        assert!(step1 >= 8 && step1 <= 12);
370        assert!(step2 >= 8 && step2 <= 12);
371    }
372
373    #[test]
374    fn test_ppf_hash_determinism() {
375        let hash = PpfHash::new(42);
376
377        // Same input should give same output
378        let h1 = hash.hash(10, 20);
379        let h2 = hash.hash(10, 20);
380        assert_eq!(h1, h2);
381
382        // Different inputs should give different outputs
383        let h3 = hash.hash(10, 21);
384        assert_ne!(h1, h3);
385
386        // Hash should be in [0, 1)
387        assert!(h1 >= 0.0 && h1 < 1.0);
388    }
389
390    #[test]
391    fn test_recursive_sequence() {
392        // Test scale m=1
393        let mut seq = RecursiveSequence::new(1);
394        assert_eq!(seq.value(), 181); // N_1 = (1-1)×360 + 181
395
396        assert_eq!(seq.next(), 183); // N_2 = 181 + 2
397        assert_eq!(seq.next(), 186); // N_3 = 183 + 3
398        assert_eq!(seq.next(), 190); // N_4 = 186 + 4
399
400        // Test scale m=2
401        let mut seq2 = RecursiveSequence::new(2);
402        assert_eq!(seq2.value(), 541); // N_1 = (2-1)×360 + 181 = 541
403    }
404
405    #[test]
406    fn test_sequence_coverage() {
407        // Verify sequence stays in range for scale m=1
408        let mut seq = RecursiveSequence::new(1);
409        let mut count = 0;
410
411        while seq.in_range() && count < 1000 {
412            seq.next();
413            count += 1;
414        }
415
416        // Should have generated a reasonable number of terms
417        assert!(count > 10);
418        assert!(count < 1000); // Should terminate
419    }
420
421    // Helper function for GCD
422    fn gcd(mut a: u32, mut b: u32) -> u32 {
423        while b != 0 {
424            let temp = b;
425            b = a % b;
426            a = temp;
427        }
428        a
429    }
430}