code_rs/coding/bmcf.rs
1//! Decodes Reed Solomon and BCH codes using the Berlekamp-Massey, Chien Search, and
2//! Forney algorithms.
3//!
4//! # Decoding Procedure
5//!
6//! The standard [1]-[11] procedure for Reed Solomon/BCH error correction has the
7//! following steps:
8//!
9//! 1. Generate the syndrome polynomial s(x) = s<sub>1</sub> + s<sub>2</sub>x + ··· +
10//! s<sub>2t</sub>x<sup>2t-1</sup>, where s<sub>i</sub> = r(α<sup>i</sup>) using the
11//! received word polynomial r(x).
12//! 2. Use s(x) to build the error locator polynomial Λ(x) = (1 + a<sub>1</sub>x) ··· (1 +
13//! a<sub>e</sub>x), where deg(Λ(x)) = e ≤ t is the number of detected errors.
14//! 3. Find the roots a<sub>1</sub><sup>-1</sup>, ..., a<sub>E</sub><sup>-1</sup> of Λ(x),
15//! such that Λ(a<sub>i</sub><sup>-1</sup>) = 0. Then for each, if
16//! a<sub>i</sub><sup>-1</sup> = α<sup>k<sub>i</sub></sup>, the error location within
17//! the received word is taken as m<sub>i</sub> in α<sup>m<sub>i</sub></sup> ≡
18//! α<sup>-k<sub>i</sub></sup> (modulo the field).
19//! 4. Verify that e = E.
20//! 5. Construct the error evaluator polynomial Ω(x) = Λ(x)s(x) mod x<sup>2t</sup> and
21//! compute each error pattern b<sub>i</sub> = Ω(a<sub>i</sub><sup>-1</sup>) /
22//! Λ'(a<sub>i</sub><sup>-1</sup>).
23//! 6. For each (m<sub>i</sub>, b<sub>i</sub>) pair, correct the symbol at location
24//! m<sub>i</sub> using the bit pattern b<sub>i</sub>.
25//!
26//! This module implements steps 2 through 5. The implementation uses several well-known
27//! techniques exist to perform these steps relatively efficiently: the Berlekamp-Massey
28//! algorithm for step 2, Chien Search for step 3, and the Forney algorithm for step 5.
29//!
30//! # Berlekamp-Massey Algorithm
31//!
32//! The Berlekamp-Massey algorithm has many variants [1], [2], [9], [12], [13] mostly with
33//! subtle differences. The key observation from Massey's generalization is to view Λ(x)
34//! as the "connection polynomial" of a linear feedback shift register (LFSR) that
35//! generates the sequence of syndromes s<sub>1</sub>, ..., s<sub>2t</sub>. The algorithm
36//! then synthesizes Λ(x) when constructing the corresponding unique shortest LFSR that
37//! generates those syndromes.
38//!
39//! # Chien Search
40//!
41//! With Λ(x) = Λ<sub>0</sub> + Λ<sub>1</sub>x + Λ<sub>2</sub>x<sup>2</sup> + ··· +
42//! Λ<sub>e</sub>x<sup>e</sup> (where Λ<sub>0</sub> = 1), let P<sub>i</sub> =
43//! [p<sub>0</sub>, ..., p<sub>e</sub>], 0 ≤ i < n, which is indexed as P<sub>i</sub>[k],
44//! 0 ≤ k ≤ e.
45//!
46//! Starting with the base case i = 0, let P<sub>0</sub>[k] = Λ<sub>k</sub> so that
47//! Λ(α<sup>0</sup>) = Λ(1) = Λ<sub>0</sub> + Λ<sub>1</sub> + ··· + Λ<sub>e</sub> =
48//! sum(P<sub>0</sub>).
49//!
50//! Then for i > 0, let P<sub>i</sub>[k] = P<sub>i-1</sub>[k]⋅α<sup>k</sup> so that
51//! Λ(α<sup>i</sup>) = sum(P<sub>i</sub>).
52//!
53//! # Forney Algorithm
54//!
55//! The Forney algorithm reduces the problem of computing error patterns to evaluating
56//! Ω(x) / Λ'(x), where Ω(x) = s(x)Λ(x) mod x<sup>2t</sup>. This requires no polynomial
57//! long division, just a one-time polynomial multiplication and derivative evaluation
58//! to create Ω(x), then two polynomial evaluations and one codeword division for each
59//! error.
60
61use std;
62
63use collect_slice::CollectSlice;
64
65use crate::coding::galois::{GaloisField, P25Codeword, P25Field, Polynomial, PolynomialCoefs};
66
67/// Finds the error location polynomial Λ(x) from the syndrome polynomial s(x).
68///
69/// This uses Hankerson et al's version of the Berlekamp-Massey algorithm, with the result
70/// being Λ(x) = p<sub>2t</sub>(x) = σ<sub>R</sub>(x).
71pub struct ErrorLocator<P: PolynomialCoefs> {
72 /// Saved p polynomial: p<sub>zi-1</sub>.
73 p_saved: Polynomial<P>,
74 /// Previous iteration's p polynomial: p<sub>i-1</sub>.
75 p_cur: Polynomial<P>,
76 /// Saved q polynomial: q<sub>zi-1</sub>.
77 q_saved: Polynomial<P>,
78 /// Previous iteration's q polynomial: q<sub>i-1</sub>.
79 q_cur: Polynomial<P>,
80 /// Degree-related term of saved p polynomial: D<sub>zi-1</sub>
81 deg_saved: usize,
82 /// Degree-related term of previous p polynomial: D<sub>i-1</sub>.
83 deg_cur: usize,
84}
85
86impl<P: PolynomialCoefs> ErrorLocator<P> {
87 /// Construct a new `ErrorLocator` from the given syndrome polynomial s(x).
88 pub fn new(syn: Polynomial<P>) -> ErrorLocator<P> {
89 ErrorLocator {
90 // Compute 1 + s(x).
91 q_saved: Polynomial::new(
92 std::iter::once(P25Codeword::for_power(0))
93 .chain(syn.iter().take(P::syndromes()).cloned()),
94 ),
95 q_cur: syn,
96 // Compute x^{2t+1}.
97 p_saved: Polynomial::unit_power(P::syndromes() + 1),
98 // Compute x^{2t}.
99 p_cur: Polynomial::unit_power(P::syndromes()),
100 deg_saved: 0,
101 deg_cur: 1,
102 }
103 }
104
105 /// Construct the error locator polynomial Λ(x).
106 pub fn build(mut self) -> Polynomial<P> {
107 for _ in 0..P::syndromes() {
108 self.step();
109 }
110
111 self.p_cur
112 }
113
114 /// Perform one iterative step of the algorithm, updating the state polynomials and
115 /// degrees.
116 fn step(&mut self) {
117 let (save, q, p, d) = if self.q_cur.constant().zero() {
118 self.reduce()
119 } else {
120 self.transform()
121 };
122
123 if save {
124 self.q_saved = self.q_cur;
125 self.p_saved = self.p_cur;
126 self.deg_saved = self.deg_cur;
127 }
128
129 self.q_cur = q;
130 self.p_cur = p;
131 self.deg_cur = d;
132 }
133
134 /// Shift the polynomials since they have no degree-0 term.
135 fn reduce(&mut self) -> (bool, Polynomial<P>, Polynomial<P>, usize) {
136 (
137 false,
138 self.q_cur.shift(),
139 self.p_cur.shift(),
140 2 + self.deg_cur,
141 )
142 }
143
144 /// Normalize out the degree-0 terms and shift the polynomials.
145 fn transform(&mut self) -> (bool, Polynomial<P>, Polynomial<P>, usize) {
146 let mult = self.q_cur.constant() / self.q_saved.constant();
147
148 (
149 self.deg_cur >= self.deg_saved,
150 (self.q_cur + self.q_saved * mult).shift(),
151 (self.p_cur + self.p_saved * mult).shift(),
152 2 + std::cmp::min(self.deg_cur, self.deg_saved),
153 )
154 }
155}
156
157/// Finds the roots of the given error locator polynomial Λ(x).
158///
159/// This performs the standard brute force method, evaluating each Λ(α<sup>i</sup>) for 0
160/// ≤ i < 2<sup>r</sup> - 1, with the Chien Search optimization.
161pub struct PolynomialRoots<P: PolynomialCoefs> {
162 /// Error locator polynomial: Λ(x).
163 ///
164 /// This field isn't exactly interpreted as a polynomial, more like a list of
165 /// coefficient A = [Λ<sub>0</sub>, ..., Λ<sub>e</sub>] such that Λ(α<sup>i</sup>) =
166 /// sum(A) for the current power i.
167 loc: Polynomial<P>,
168 /// Current codeword power the polynomial is being evaluated with.
169 pow: std::ops::Range<usize>,
170}
171
172impl<P: PolynomialCoefs> PolynomialRoots<P> {
173 /// Construct a new `PolynomialRoots` from the given error locator polynomial Λ(x).
174 pub fn new(loc: Polynomial<P>) -> Self {
175 PolynomialRoots {
176 loc,
177 pow: 0..P25Field::size(),
178 }
179 }
180
181 /// Update each term's coefficient to its value when evaluated for the next codeword
182 /// power.
183 fn update_terms(&mut self) {
184 for (pow, term) in self.loc.iter_mut().enumerate() {
185 *term = *term * P25Codeword::for_power(pow);
186 }
187 }
188
189 /// Compute Λ(α<sup>i</sup>), where i is the current power.
190 fn eval(&self) -> P25Codeword {
191 self.loc
192 .iter()
193 .fold(P25Codeword::default(), |sum, &x| sum + x)
194 }
195}
196
197/// Iterate over all roots α<sup>i</sup> of Λ(x).
198impl<P: PolynomialCoefs> Iterator for PolynomialRoots<P> {
199 type Item = P25Codeword;
200
201 fn next(&mut self) -> Option<Self::Item> {
202 loop {
203 // Current codeword power: i in α^i.
204 let pow = match self.pow.next() {
205 Some(pow) => pow,
206 None => return None,
207 };
208
209 // Compute Λ(α^i).
210 let eval = self.eval();
211 // Update to Λ(α^{i+1}).
212 self.update_terms();
213
214 // Yield α^i if Λ(α^i) = 0.
215 if eval.zero() {
216 return Some(P25Codeword::for_power(pow));
217 }
218 }
219 }
220}
221
222/// Computes error locations and patterns from the roots of the error locator polynomial
223/// Λ(x).
224///
225/// This uses the Forney algorithm for error pattern evaluation, which avoids polynomial
226/// long division.
227pub struct ErrorDescriptions<P: PolynomialCoefs> {
228 /// Derivative of error locator polynomial: Λ'(x).
229 deriv: Polynomial<P>,
230 /// Error evaluator polynomial: Ω(x) = Λ(x)s(x) mod x<sup>2t</sup>.
231 vals: Polynomial<P>,
232}
233
234impl<P: PolynomialCoefs> ErrorDescriptions<P> {
235 /// Create a new `ErrorDescriptions` from the given syndrome polynomial s(x) and error
236 /// locator polynomial Λ(x).
237 pub fn new(syn: Polynomial<P>, loc: Polynomial<P>) -> Self {
238 ErrorDescriptions {
239 // Compute Λ'(x).
240 deriv: loc.deriv(),
241 // Compute Λ(x)s(x) mod x^{2t}.
242 vals: (loc * syn).truncate(P::syndromes() - 1),
243 }
244 }
245
246 /// Compute the error location and pattern for the given root
247 /// a<sub>i</sub><sup>-1</sup> of Λ(x).
248 pub fn for_root(&self, root: P25Codeword) -> (usize, P25Codeword) {
249 (
250 // If Λ(α^i) = 0, then the error location is m ≡ -i (modulo the field.)
251 root.invert().power().unwrap(),
252 // Compute Ω(α^i) / Λ'(α^i).
253 self.vals.eval(root) / self.deriv.eval(root),
254 )
255 }
256}
257
258/// Decodes and iterates over codeword errors.
259pub struct Errors<P: PolynomialCoefs> {
260 /// Roots of the error locator polynomial.
261 ///
262 /// Note that this field isn't interpreted as a polynomial -- the `Polynomial` type
263 /// just provides a conveniently sized buffer for root codewords.
264 roots: Polynomial<P>,
265 /// Computes location and pattern for each error.
266 descs: ErrorDescriptions<P>,
267 /// Current error being evaluated in iteration.
268 pos: std::ops::Range<usize>,
269}
270
271impl<P: PolynomialCoefs> Errors<P> {
272 /// Create a new `Errors` decoder from the given syndrome polynomial s(x).
273 ///
274 /// If decoding was sucessful, return `Some((nerr, errs))`, where `nerr` is the number
275 /// of detected errors and `errs` is the error iterator. Otherwise, return `None` to
276 /// indicate an unrecoverable error.
277 pub fn new(syn: Polynomial<P>) -> Option<(usize, Self)> {
278 // Compute error locator polynomial Λ(x).
279 let loc = ErrorLocator::new(syn).build();
280 // If e = deg(Λ), then e ≤ t and e represents the number of detected errors.
281 let errors = loc.degree().expect("invalid error polynomial");
282
283 // Find the roots a_i of Λ(x). These are buffered before processing them because
284 // if the number of found roots ends up unequal to deg(Λ(x)), all the roots are
285 // invalid, and processing them before checking this can cause behavior like
286 // divide-by-zero.
287 let mut roots = Polynomial::<P>::default();
288 let nroots = PolynomialRoots::new(loc).collect_slice_exhaust(&mut roots[..]);
289
290 // If the number of computed roots is different than deg(Λ), then the roots are
291 // invalid and the codeword is unrecoverable [1, p3], [2, p48], [3, p22].
292 if nroots != errors {
293 return None;
294 }
295
296 Some((
297 errors,
298 Errors {
299 roots,
300 descs: ErrorDescriptions::new(syn, loc),
301 pos: 0..errors,
302 },
303 ))
304 }
305}
306
307/// Iterate over detected errors, yielding the location and pattern of each error.
308impl<P: PolynomialCoefs> Iterator for Errors<P> {
309 type Item = (usize, P25Codeword);
310
311 fn next(&mut self) -> Option<Self::Item> {
312 self.pos.next().map(|i| self.descs.for_root(self.roots[i]))
313 }
314}
315
316#[cfg(test)]
317mod test {
318 use super::*;
319 use crate::coding::galois::{P25Codeword, Polynomial, PolynomialCoefs};
320 use collect_slice::CollectSlice;
321 use std;
322
323 impl_polynomial_coefs!(TestCoefs, 9);
324 type TestPolynomial = Polynomial<TestCoefs>;
325
326 #[test]
327 fn test_roots() {
328 // p(x) = (1+α^42x)(1+α^13x)(1+α^57x)
329 let p = TestPolynomial::new(
330 [P25Codeword::for_power(0), P25Codeword::for_power(42)]
331 .iter()
332 .cloned(),
333 ) * TestPolynomial::new(
334 [P25Codeword::for_power(0), P25Codeword::for_power(13)]
335 .iter()
336 .cloned(),
337 ) * TestPolynomial::new(
338 [P25Codeword::for_power(0), P25Codeword::for_power(57)]
339 .iter()
340 .cloned(),
341 );
342
343 let mut r = PolynomialRoots::new(p);
344 let mut roots = [P25Codeword::default(); 3];
345 r.collect_slice_checked(&mut roots[..]);
346
347 assert!(roots.contains(&P25Codeword::for_power(42).invert()));
348 assert!(roots.contains(&P25Codeword::for_power(13).invert()));
349 assert!(roots.contains(&P25Codeword::for_power(57).invert()));
350
351 let p = TestPolynomial::unit_power(0);
352
353 let mut r = PolynomialRoots::new(p);
354 assert!(r.next().is_none());
355 }
356}