comnum_math/gf2m.rs
1//! The Galois fields $\mathrm{GF}(2^{m})$: the arithmetic on which `comnum_fec` builds the
2//! BCH and Reed–Solomon codes.
3//!
4//! # Model
5//!
6//! The field is generated by a primitive element $\alpha$, a root of a primitive
7//! polynomial $p(x)$ of degree $m$ over $\mathrm{GF}(2)$: its $2^{m} - 1$ non-zero
8//! elements are the powers $\alpha^{0}, \dots, \alpha^{2^{m}-2}$. An element is stored as
9//! the $m$ bits of its polynomial representation (bit $i$ the coefficient of
10//! $\alpha^{i}$), so that the addition is the exclusive or, and a table of the powers
11//! and of the logarithms gives the product
12//!
13//! $$
14//! \alpha^{i} \alpha^{j} = \alpha^{(i + j) \bmod (2^{m} - 1)}.
15//! $$
16//!
17//! The minimal polynomial of $\alpha^{i}$ is the product over its conjugates,
18//! $m_{i}(x) = \prod_{j} \left(x - \alpha^{i 2^{j}}\right)$, a binary polynomial.
19//!
20//! [`primitive_polynomial`] gives the conventional primitive polynomial of every degree
21//! 2 to 16: the ones of the tables of Lin & Costello (Table 2.7) and of the coding
22//! standards (DVB, CD, QR codes use $x^{8} + x^{4} + x^{3} + x^{2} + 1$ for $m = 8$).
23//!
24//! # Examples
25//! ```
26//! use comnum_math::gf2m::{Field, primitive_polynomial};
27//! // GF(16) with p(x) = 1 + x + x⁴: α⁴ = α + 1 = 0b0011
28//! let f = Field::new(4, primitive_polynomial(4).unwrap())?;
29//! assert_eq!(f.pow(4), 0b0011);
30//! assert_eq!(f.mul(f.pow(7), f.pow(12)), f.pow(4)); // α⁷ α¹² = α¹⁹ = α⁴
31//! assert_eq!(f.minimal_polynomial(1), vec![1, 1, 0, 0, 1]); // p(x) itself
32//! # Ok::<(), comnum_core::Error>(())
33//! ```
34//!
35//! # References
36//!
37//! 1. S. Lin and D. J. Costello, *Error Control Coding*, 2nd ed., Pearson, 2004, ch. 2
38//! (Table 2.7: primitive polynomials, Table 2.8: GF($2^{4}$)).
39
40use comnum_core::{Result, ensure};
41
42/// The conventional primitive polynomial of degree `m` (bit $i$ the coefficient of
43/// $x^{i}$) for $2 \le m \le 16$, `None` otherwise.
44///
45/// Lin & Costello, Table 2.7, except $m = 8$ where the polynomial of the coding
46/// standards (ETSI EN 300 744, ISO/IEC 18004, the compact disc) is given,
47/// $x^{8} + x^{4} + x^{3} + x^{2} + 1$.
48pub fn primitive_polynomial(m: u32) -> Option<u32> {
49 const POLYS: [u32; 15] = [
50 0x7, // x² + x + 1
51 0xB, // x³ + x + 1
52 0x13, // x⁴ + x + 1
53 0x25, // x⁵ + x² + 1
54 0x43, // x⁶ + x + 1
55 0x89, // x⁷ + x³ + 1
56 0x11D, // x⁸ + x⁴ + x³ + x² + 1
57 0x211, // x⁹ + x⁴ + 1
58 0x409, // x¹⁰ + x³ + 1
59 0x805, // x¹¹ + x² + 1
60 0x1053, // x¹² + x⁶ + x⁴ + x + 1
61 0x201B, // x¹³ + x⁴ + x³ + x + 1
62 0x4443, // x¹⁴ + x¹⁰ + x⁶ + x + 1
63 0x8003, // x¹⁵ + x + 1
64 0x1100B, // x¹⁶ + x¹² + x³ + x + 1
65 ];
66 (2..=16).contains(&m).then(|| POLYS[m as usize - 2])
67}
68
69/// The Galois field $\mathrm{GF}(2^{m})$ by exponential and logarithm tables.
70#[derive(Clone, Debug, PartialEq, Eq)]
71pub struct Field {
72 m: u32,
73 exp: Vec<u32>,
74 log: Vec<u32>,
75}
76
77impl Field {
78 /// The field of degree `m` (2 to 20) generated by the primitive polynomial `poly`
79 /// (bit $i$ the coefficient of $x^{i}$).
80 ///
81 /// # Errors
82 /// [`comnum_core::Error::Config`] for a degree outside 2 to 20, a polynomial of another
83 /// degree, or one that is not primitive (its powers of $x$ do not run through every
84 /// non-zero element).
85 pub fn new(m: u32, poly: u32) -> Result<Field> {
86 ensure((2..=20).contains(&m), || format!("GF(2^m): the field degree must be 2 to 20, got {m}"))?;
87 ensure(poly >> m == 1, || format!("GF(2^m): {poly:#x} is not a polynomial of degree {m}"))?;
88 let q = 1usize << m;
89 let mut exp = vec![0u32; 2 * q];
90 let mut log = vec![0u32; q];
91 let mut x = 1u32;
92 for (i, e) in exp.iter_mut().enumerate().take(q - 1) {
93 *e = x;
94 ensure(i == 0 || x != 1, || format!("GF(2^m): {poly:#x} is not a primitive polynomial of degree {m}"))?;
95 log[x as usize] = i as u32;
96 x <<= 1;
97 if x & (1 << m) != 0 {
98 x ^= poly;
99 }
100 }
101 ensure(x == 1, || format!("GF(2^m): {poly:#x} is not a primitive polynomial of degree {m}"))?;
102 for i in q - 1..2 * q {
103 exp[i] = exp[i - (q - 1)];
104 }
105 Ok(Field { m, exp, log })
106 }
107
108 /// The degree $m$.
109 pub fn m(&self) -> u32 {
110 self.m
111 }
112
113 /// The number of non-zero elements $2^{m} - 1$.
114 pub fn order(&self) -> usize {
115 (1 << self.m) - 1
116 }
117
118 /// $\alpha^{e}$ for any non-negative exponent.
119 pub fn pow(&self, e: usize) -> u32 {
120 self.exp[e % self.order()]
121 }
122
123 /// The exponent $e$ of a non-zero element $\alpha^{e}$ (0 for the element 0).
124 pub fn log(&self, a: u32) -> usize {
125 self.log[a as usize] as usize
126 }
127
128 /// The product $ab$.
129 pub fn mul(&self, a: u32, b: u32) -> u32 {
130 if a == 0 || b == 0 { 0 } else { self.exp[(self.log[a as usize] + self.log[b as usize]) as usize] }
131 }
132
133 /// The quotient $a / b$ of a non-zero `b` ($0 / b = 0$).
134 ///
135 /// # Panics
136 /// In debug builds, for `b = 0` (a division by zero is a broken invariant of the
137 /// caller: release builds return a meaningless element instead of checking it in the
138 /// inner loops of the decoders).
139 pub fn div(&self, a: u32, b: u32) -> u32 {
140 debug_assert!(b != 0, "division by zero in GF(2^{})", self.m());
141 if a == 0 {
142 0
143 } else {
144 let n = self.order() as u32;
145 self.exp[((self.log[a as usize] + n - self.log[b as usize]) % n) as usize]
146 }
147 }
148
149 /// The inverse $a^{-1}$ of a non-zero element.
150 ///
151 /// # Panics
152 /// In debug builds, for `a = 0` (as [`Field::div`]).
153 pub fn inv(&self, a: u32) -> u32 {
154 self.div(1, a)
155 }
156
157 /// The minimal polynomial of $\alpha^{i}$ (binary coefficients, lowest degree first).
158 pub fn minimal_polynomial(&self, i: usize) -> Vec<u8> {
159 // the conjugates α^{i 2^j}
160 let n = self.order();
161 let mut conj = vec![i % n];
162 let mut e = (2 * i) % n;
163 while e != i % n {
164 conj.push(e);
165 e = (2 * e) % n;
166 }
167 // Π (x - α^e) over GF(2^m)
168 let mut p = vec![1u32];
169 for &c in &conj {
170 let r = self.pow(c);
171 let mut next = vec![0u32; p.len() + 1];
172 for (d, &a) in p.iter().enumerate() {
173 next[d + 1] ^= a;
174 next[d] ^= self.mul(a, r);
175 }
176 p = next;
177 }
178 p.iter().map(|&a| u8::from(a == 1)).collect()
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185
186 #[test]
187 #[cfg(debug_assertions)]
188 #[should_panic(expected = "division by zero")]
189 fn the_inverse_of_zero_is_a_broken_invariant_in_debug_builds() {
190 let _ = Field::new(4, 0x13).unwrap().inv(0);
191 }
192
193 #[test]
194 fn a_polynomial_of_the_wrong_degree_is_an_error_not_a_panic() {
195 // x⁸ + x⁴ + x³ + x² + 1 for GF(16), and x³ + x + 1 for GF(16)
196 assert!(Field::new(4, 0x11D).is_err());
197 assert!(Field::new(4, 0xB).is_err());
198 assert!(Field::new(4, 0).is_err());
199 assert!(Field::new(8, u32::MAX).is_err());
200 // x⁴ + x³ + x² + x + 1 has the degree but is not primitive (order 5)
201 assert!(Field::new(4, 0x1F).is_err());
202 assert!(Field::new(4, 0x13).is_ok());
203 }
204
205 #[test]
206 fn every_default_polynomial_is_primitive_and_the_arithmetic_is_consistent() {
207 for m in 2..=16 {
208 let f = Field::new(m, primitive_polynomial(m).unwrap()).unwrap();
209 assert_eq!(f.order(), (1 << m) - 1);
210 let a = f.pow(5);
211 let b = f.pow(f.order() - 2);
212 assert_eq!(f.div(f.mul(a, b), b), a, "m = {m}");
213 assert_eq!(f.mul(a, f.inv(a)), 1, "m = {m}");
214 assert_eq!(f.log(f.pow(5)), 5 % f.order());
215 }
216 assert!(primitive_polynomial(1).is_none() && primitive_polynomial(17).is_none());
217 // x⁴ + x³ + x² + x + 1 has order 5: not primitive
218 assert!(Field::new(4, 0b1_1111).is_err());
219 }
220}