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}