proof_engine/number_theory/
galois.rs1#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7pub struct GaloisField {
8 pub prime: u64,
9 pub power: u32,
10}
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub struct GfElement {
18 pub value: u64,
19 pub field: GaloisField,
20}
21
22impl GaloisField {
23 pub fn new(prime: u64, power: u32) -> Self {
24 assert!(prime >= 2);
25 assert!(power >= 1);
26 Self { prime, power }
27 }
28
29 pub fn order(&self) -> u64 {
31 self.prime.pow(self.power)
32 }
33
34 pub fn zero(self) -> GfElement {
36 GfElement { value: 0, field: self }
37 }
38
39 pub fn one(self) -> GfElement {
41 GfElement { value: 1, field: self }
42 }
43
44 pub fn element(self, value: u64) -> GfElement {
46 GfElement { value: value % self.order(), field: self }
47 }
48
49 fn irreducible_poly(&self) -> Vec<u64> {
53 if self.power == 1 {
54 return vec![0, 1]; }
56 match (self.prime, self.power) {
58 (2, 2) => vec![1, 1, 1], (2, 3) => vec![1, 1, 0, 1], (2, 4) => vec![1, 1, 0, 0, 1], (2, 8) => vec![1, 0, 1, 1, 1, 0, 0, 0, 1], (3, 2) => vec![1, 0, 1], (3, 3) => vec![2, 1, 0, 1], (5, 2) => vec![2, 0, 1], (7, 2) => vec![3, 0, 1], _ => {
67 self.find_irreducible()
69 }
70 }
71 }
72
73 fn find_irreducible(&self) -> Vec<u64> {
74 let p = self.prime;
75 let n = self.power as usize;
76 let total = p.pow(n as u32); for combo in 0..total {
79 let mut coeffs = Vec::with_capacity(n + 1);
80 let mut c = combo;
81 for _ in 0..n {
82 coeffs.push(c % p);
83 c /= p;
84 }
85 coeffs.push(1); if self.is_irreducible_poly(&coeffs) {
87 return coeffs;
88 }
89 }
90 let mut coeffs = vec![1; n + 1];
92 coeffs[n] = 1;
93 coeffs
94 }
95
96 fn is_irreducible_poly(&self, coeffs: &[u64]) -> bool {
97 let p = self.prime;
98 let n = coeffs.len() - 1;
99 if n <= 1 {
102 return n == 1;
103 }
104 for x in 0..p {
106 let mut val = 0u64;
107 let mut xpow = 1u64;
108 for &c in coeffs {
109 val = (val + c * xpow) % p;
110 xpow = xpow * x % p;
111 }
112 if val == 0 {
113 return false;
114 }
115 }
116 if n <= 3 {
117 return true; }
119 true }
121}
122
123impl GfElement {
124 fn to_poly(&self) -> Vec<u64> {
126 let p = self.field.prime;
127 let n = self.field.power as usize;
128 let mut coeffs = Vec::with_capacity(n);
129 let mut v = self.value;
130 for _ in 0..n {
131 coeffs.push(v % p);
132 v /= p;
133 }
134 coeffs
135 }
136
137 fn from_poly(coeffs: &[u64], field: GaloisField) -> Self {
139 let p = field.prime;
140 let mut value = 0u64;
141 let mut base = 1u64;
142 for &c in coeffs.iter().take(field.power as usize) {
143 value += (c % p) * base;
144 base *= p;
145 }
146 GfElement { value, field }
147 }
148
149 pub fn add(self, other: Self) -> Self {
151 assert_eq!(self.field, other.field);
152 let p = self.field.prime;
153 if self.field.power == 1 {
154 return GfElement {
155 value: (self.value + other.value) % p,
156 field: self.field,
157 };
158 }
159 let a = self.to_poly();
160 let b = other.to_poly();
161 let n = self.field.power as usize;
162 let mut c = vec![0u64; n];
163 for i in 0..n {
164 c[i] = (a.get(i).copied().unwrap_or(0) + b.get(i).copied().unwrap_or(0)) % p;
165 }
166 Self::from_poly(&c, self.field)
167 }
168
169 pub fn sub(self, other: Self) -> Self {
171 assert_eq!(self.field, other.field);
172 let p = self.field.prime;
173 if self.field.power == 1 {
174 return GfElement {
175 value: (self.value + p - other.value % p) % p,
176 field: self.field,
177 };
178 }
179 let a = self.to_poly();
180 let b = other.to_poly();
181 let n = self.field.power as usize;
182 let mut c = vec![0u64; n];
183 for i in 0..n {
184 c[i] = (a.get(i).copied().unwrap_or(0) + p
185 - b.get(i).copied().unwrap_or(0) % p)
186 % p;
187 }
188 Self::from_poly(&c, self.field)
189 }
190
191 pub fn multiply(self, other: Self) -> Self {
193 assert_eq!(self.field, other.field);
194 let p = self.field.prime;
195 if self.field.power == 1 {
196 return GfElement {
197 value: (self.value * other.value) % p,
198 field: self.field,
199 };
200 }
201 let a = self.to_poly();
202 let b = other.to_poly();
203 let n = self.field.power as usize;
204 let mut prod = vec![0u64; 2 * n];
206 for i in 0..n {
207 for j in 0..n {
208 prod[i + j] = (prod[i + j] + a[i] * b[j]) % p;
209 }
210 }
211 let irr = self.field.irreducible_poly();
213 poly_mod(&mut prod, &irr, p);
214 prod.truncate(n);
215 while prod.len() < n {
216 prod.push(0);
217 }
218 Self::from_poly(&prod, self.field)
219 }
220
221 pub fn inverse(self) -> Self {
223 assert!(self.value != 0, "cannot invert zero");
224 let field = self.field;
225 let order = field.order();
227 self.pow(order - 2)
228 }
229
230 pub fn pow(self, mut exp: u64) -> Self {
232 let mut result = self.field.one();
233 let mut base = self;
234 while exp > 0 {
235 if exp & 1 == 1 {
236 result = result.multiply(base);
237 }
238 base = base.multiply(base);
239 exp >>= 1;
240 }
241 result
242 }
243
244 pub fn is_zero(self) -> bool {
246 self.value == 0
247 }
248}
249
250fn poly_mod(poly: &mut Vec<u64>, modulus: &[u64], p: u64) {
252 let deg_mod = modulus.len() - 1;
253 let lead_inv = mod_inv(modulus[deg_mod], p);
254 while poly.len() > deg_mod {
255 let coeff = poly[poly.len() - 1];
256 if coeff == 0 {
257 poly.pop();
258 continue;
259 }
260 let factor = coeff * lead_inv % p;
261 let offset = poly.len() - modulus.len();
262 for (i, &m) in modulus.iter().enumerate() {
263 poly[offset + i] = (poly[offset + i] + p - factor * m % p) % p;
264 }
265 poly.pop();
266 }
267}
268
269fn mod_inv(a: u64, p: u64) -> u64 {
270 let mut result = 1u64;
272 let mut base = a % p;
273 let mut exp = p - 2;
274 while exp > 0 {
275 if exp & 1 == 1 {
276 result = result * base % p;
277 }
278 base = base * base % p;
279 exp >>= 1;
280 }
281 result
282}
283
284pub fn find_generator(field: GaloisField) -> GfElement {
286 let order = field.order() - 1; let factors = super::primes::prime_factorization(order);
288
289 for v in 1..field.order() {
290 let g = field.element(v);
291 let mut is_generator = true;
292 for &(p, _) in &factors {
293 if g.pow(order / p).value == 1 {
294 is_generator = false;
295 break;
296 }
297 }
298 if is_generator {
299 return g;
300 }
301 }
302 field.one() }
304
305pub fn multiplication_table(field: GaloisField) -> Vec<Vec<u64>> {
307 let n = field.order() as usize;
308 let mut table = vec![vec![0u64; n]; n];
309 for i in 0..n {
310 let a = field.element(i as u64);
311 for j in 0..n {
312 let b = field.element(j as u64);
313 table[i][j] = a.multiply(b).value;
314 }
315 }
316 table
317}
318
319#[cfg(test)]
322mod tests {
323 use super::*;
324
325 #[test]
326 fn gf5_basic() {
327 let f = GaloisField::new(5, 1);
328 let a = f.element(3);
329 let b = f.element(4);
330 assert_eq!(a.add(b).value, 2); assert_eq!(a.multiply(b).value, 2); }
333
334 #[test]
335 fn gf7_inverse() {
336 let f = GaloisField::new(7, 1);
337 for v in 1..7 {
338 let a = f.element(v);
339 let inv = a.inverse();
340 assert_eq!(a.multiply(inv).value, 1, "{}^-1 failed", v);
341 }
342 }
343
344 #[test]
345 fn gf5_additive_identity() {
346 let f = GaloisField::new(5, 1);
347 for v in 0..5 {
348 let a = f.element(v);
349 assert_eq!(a.add(f.zero()).value, a.value);
350 }
351 }
352
353 #[test]
354 fn gf5_multiplicative_identity() {
355 let f = GaloisField::new(5, 1);
356 for v in 0..5 {
357 let a = f.element(v);
358 assert_eq!(a.multiply(f.one()).value, a.value);
359 }
360 }
361
362 #[test]
363 fn gf4_arithmetic() {
364 let f = GaloisField::new(2, 2);
366 assert_eq!(f.order(), 4);
367 let a = f.element(2); let b = f.element(3); let sum = a.add(b);
371 assert_eq!(sum.value, 1);
373 let prod = a.multiply(b);
375 assert_eq!(prod.value, 1, "x * (x+1) should be 1 in GF(4)");
377 }
378
379 #[test]
380 fn gf4_inverse() {
381 let f = GaloisField::new(2, 2);
382 for v in 1..4 {
383 let a = f.element(v);
384 let inv = a.inverse();
385 assert_eq!(
386 a.multiply(inv).value,
387 1,
388 "inverse of {} failed (got {})",
389 v,
390 inv.value
391 );
392 }
393 }
394
395 #[test]
396 fn gf8_all_inverses() {
397 let f = GaloisField::new(2, 3);
398 assert_eq!(f.order(), 8);
399 for v in 1..8 {
400 let a = f.element(v);
401 let inv = a.inverse();
402 assert_eq!(a.multiply(inv).value, 1, "inverse of {} failed", v);
403 }
404 }
405
406 #[test]
407 fn generator_gf5() {
408 let f = GaloisField::new(5, 1);
409 let g = find_generator(f);
410 let mut seen = std::collections::HashSet::new();
412 let mut current = f.one();
413 for _ in 0..4 {
414 seen.insert(current.value);
415 current = current.multiply(g);
416 }
417 assert_eq!(seen.len(), 4, "generator should produce all 4 non-zero elements");
418 }
419
420 #[test]
421 fn generator_gf4() {
422 let f = GaloisField::new(2, 2);
423 let g = find_generator(f);
424 let mut seen = std::collections::HashSet::new();
425 let mut current = f.one();
426 for _ in 0..3 {
427 seen.insert(current.value);
428 current = current.multiply(g);
429 }
430 assert_eq!(seen.len(), 3);
431 }
432
433 #[test]
434 fn multiplication_table_gf3() {
435 let f = GaloisField::new(3, 1);
436 let table = multiplication_table(f);
437 assert_eq!(table.len(), 3);
438 assert_eq!(table[0], vec![0, 0, 0]); assert_eq!(table[1], vec![0, 1, 2]); assert_eq!(table[2], vec![0, 2, 1]); }
442
443 #[test]
444 fn distributive_law() {
445 let f = GaloisField::new(5, 1);
446 for a in 0..5 {
447 for b in 0..5 {
448 for c in 0..5 {
449 let ea = f.element(a);
450 let eb = f.element(b);
451 let ec = f.element(c);
452 let lhs = ea.multiply(eb.add(ec));
454 let rhs = ea.multiply(eb).add(ea.multiply(ec));
455 assert_eq!(lhs.value, rhs.value, "distributive failed for {},{},{}", a, b, c);
456 }
457 }
458 }
459 }
460
461 #[test]
462 fn gf9_order() {
463 let f = GaloisField::new(3, 2);
464 assert_eq!(f.order(), 9);
465 for v in 1..9 {
467 let a = f.element(v);
468 assert_eq!(a.pow(8).value, 1, "a^8 != 1 for a={}", v);
469 }
470 }
471}