ldpc_toolbox/decoder/
arithmetic.rs

1//! LDPC decoder arithmetic.
2//!
3//! This module contains the trait [`DecoderArithmetic`], which defines generic
4//! arithmetic rules used by a belief propagation LDPC decoder, and implementors
5//! of that trait. The LDCP decoders, such as the flooding schedule
6//! [`Decoder`](super::flooding::Decoder), are generic over the
7//! `DecoderArithmetic` trait, so it can be used to obtain monomorphized
8//! implementations for different arithemtic rules.
9//!
10//! # References
11//!
12//! Most of the arithmetic rules implemented here are taken from:
13//!
14//! \[1\] Jon Hamkins, [Performance of Low-Density Parity-Check Coded Modulation](https://ipnpr.jpl.nasa.gov/progress_report/42-184/184D.pdf),
15//! IPN Progress Report 42-184, February 15, 2011.
16//!
17//! Another good resource is this book:
18//!
19//! \[2\] Sarah J. Johnson, Iterative Error Correction: Turbo, Low-Density
20//! Parity-Check and Repeat-Accumulate Codes. Cambridge University Press. June
21//! 2012.
22//!
23//! Other references:
24//!
25//! \[3\] C. Jones, et al. “Approximate-MIN* Constraint Node Updating for LDPC
26//! Code Decoding.” In Proceedings of MILCOM 2003 (Boston, Massachusetts),
27//! 1-157-1-162. Piscataway, NJ: IEEE, October 2003.
28//!
29
30use super::{Message, SentMessage};
31use std::convert::identity;
32
33/// LDPC decoder arithmetic.
34///
35/// This trait models generic arithmetic rules for a belief propagation LDPC
36/// decoder. The trait defines the data types to use for LLRs and messages, and
37/// how to compute the check node and variable node messages.
38///
39/// The LDPC decoders are generic over objects implementing this trait.
40///
41/// The methods in this trait depend on `&self` or `&mut self` so that the
42/// decoder arithmetic object can have an internal state implement lookup
43/// tables, caching, etc.
44pub trait DecoderArithmetic: std::fmt::Debug + Send {
45    /// LLR.
46    ///
47    /// Defines the type used to represent LLRs.
48    type Llr: std::fmt::Debug + Copy + Default + Send;
49    /// Check node message.
50    ///
51    /// Defines the type used to represent check node messages.
52    type CheckMessage: std::fmt::Debug + Copy + Default + Send;
53    /// Variable node message.
54    ///
55    /// Defines the type used to represent variable node messages.
56    type VarMessage: std::fmt::Debug + Copy + Default + Send;
57    /// Variable LLR.
58    ///
59    /// Defines the type used to represent variable node LLRs in the horizontal
60    /// layered schedule.
61    type VarLlr: std::fmt::Debug + Copy + Default + Send;
62
63    /// Quantization function for input LLRs.
64    ///
65    /// Defines how the channel LLRs (the input to the decoder) are quantized
66    /// and represented internally as a [`Self::Llr`].
67    fn input_llr_quantize(&self, llr: f64) -> Self::Llr;
68
69    /// Hard decision on LLRs.
70    ///
71    /// Returns the hard decision bit corresponding to an LLR.
72    fn llr_hard_decision(&self, llr: Self::Llr) -> bool;
73
74    /// Transform LLR to variable message.
75    ///
76    /// Defines how to transform an LLR into a variable message. This is used in
77    /// the first iteration of the belief propagation algorithm, where the
78    /// variable messages are simply the channel LLRs.
79    fn llr_to_var_message(&self, llr: Self::Llr) -> Self::VarMessage;
80
81    /// Transform LLR to variable LLR.
82    ///
83    /// Defines how to transform an LLR into a variable node LLR.
84    fn llr_to_var_llr(&self, llr: Self::Llr) -> Self::VarLlr;
85
86    /// Transform variable LLR to LLR.
87    ///
88    /// Defines how to transform a variable LLR into an LLR.
89    fn var_llr_to_llr(&self, var_llr: Self::VarLlr) -> Self::Llr;
90
91    /// Send check messages from a check node.
92    ///
93    /// This function is called with the list of variable messages arriving to a
94    /// check node, and closure that must be called to send each check message
95    /// outgoing from that check node.
96    ///
97    /// This function should compute the values of the check node messages and
98    /// call the `send` closure for each of the variable nodes connected to the
99    /// check node being processed.
100    fn send_check_messages<F>(&mut self, var_messages: &[Message<Self::VarMessage>], send: F)
101    where
102        F: FnMut(SentMessage<Self::CheckMessage>);
103
104    /// Send variable messages from a variable node.
105    ///
106    /// This function is called with the channel LLR corresponding to a variable
107    /// node, a list of check messages arriving to that variable node, and
108    /// closure that must be called to send each variable message outgoing from
109    /// that variable node.
110    ///
111    /// This function should compute the values of the variable node messages and
112    /// call the `send` closure for each of the check nodes connected to the
113    /// variable node being processed.
114    ///
115    /// Additionally, the function returns the new LLR for this variable node.
116    fn send_var_messages<F>(
117        &mut self,
118        input_llr: Self::Llr,
119        check_messages: &[Message<Self::CheckMessage>],
120        send: F,
121    ) -> Self::Llr
122    where
123        F: FnMut(SentMessage<Self::VarMessage>);
124
125    /// Update check messages and variable values for a check node.
126    ///
127    /// This function is used in the horizontal layered decoder. It is called
128    /// with the messages sent by a check node and the list of LLRs of all the
129    /// variables. The function computes and updates the new check messages and
130    /// the new variable LLRs (for the variables directly connected to this
131    /// check messages).
132    fn update_check_messages_and_vars(
133        &mut self,
134        check_messages: &mut [SentMessage<Self::CheckMessage>],
135        vars: &mut [Self::VarLlr],
136    );
137}
138
139// The usual variable message update rule, without any clipping.
140fn send_var_messages_no_clip<T, F>(input_llr: T, check_messages: &[Message<T>], mut send: F) -> T
141where
142    T: std::iter::Sum + std::ops::Add<Output = T> + std::ops::Sub<Output = T> + Copy,
143    F: FnMut(SentMessage<T>),
144{
145    // Compute new LLR
146    let llr: T = input_llr + check_messages.iter().map(|m| m.value).sum::<T>();
147    // Exclude the contribution of each check node to generate message for
148    // that check node
149    for msg in check_messages.iter() {
150        send(SentMessage {
151            dest: msg.source,
152            value: llr - msg.value,
153        });
154    }
155    llr
156}
157
158macro_rules! impl_phif {
159    ($ty:ident, $f:ty, $min_x:expr) => {
160        /// LDPC decoder arithmetic with `$f` and `phi(x)` involution.
161        ///
162        /// This is a [`DecoderArithmetic`] that uses `$f` to represent the LLRs and
163        /// messages and computes the check node messages using the involution `phi(x) =
164        /// -log(tanh(x/2))`.
165        ///
166        /// See (2.33) in page 68 in \[2\].
167        #[derive(Debug, Clone, Default)]
168        pub struct $ty {
169            phis: Vec<$f>,
170        }
171
172        impl $ty {
173            /// Creates a new [`$ty`] decoder arithmetic object.
174            pub fn new() -> $ty {
175                <$ty>::default()
176            }
177        }
178
179        impl $ty {
180            fn phi(x: $f) -> $f {
181                // Ensure that x is not zero. Otherwise the output will be +inf, which gives
182                // problems when computing (+inf) - (+inf).
183                let x = x.max($min_x);
184                -((0.5 * x).tanh().ln())
185            }
186        }
187
188        impl DecoderArithmetic for $ty {
189            type Llr = $f;
190            type CheckMessage = $f;
191            type VarMessage = $f;
192            type VarLlr = $f;
193
194            fn input_llr_quantize(&self, llr: f64) -> $f {
195                llr as $f
196            }
197
198            fn llr_hard_decision(&self, llr: $f) -> bool {
199                llr <= 0.0
200            }
201
202            fn llr_to_var_message(&self, llr: $f) -> $f {
203                llr
204            }
205
206            fn llr_to_var_llr(&self, llr: $f) -> $f {
207                llr
208            }
209
210            fn var_llr_to_llr(&self, var_llr: $f) -> $f {
211                var_llr
212            }
213
214            fn send_check_messages<F>(&mut self, var_messages: &[Message<$f>], mut send: F)
215            where
216                F: FnMut(SentMessage<$f>),
217            {
218                // Compute combination of all variable messages
219                let mut sign: u32 = 0;
220                let mut sum = 0.0;
221                if self.phis.len() < var_messages.len() {
222                    self.phis.resize(var_messages.len(), 0.0);
223                }
224                for (msg, phi) in var_messages.iter().zip(self.phis.iter_mut()) {
225                    let x = msg.value;
226                    let phi_x = Self::phi(x.abs());
227                    *phi = phi_x;
228                    sum += phi_x;
229                    if x < 0.0 {
230                        sign ^= 1;
231                    }
232                }
233
234                // Exclude the contribution of each variable to generate message for
235                // that variable
236                for (msg, phi) in var_messages.iter().zip(self.phis.iter()) {
237                    let x = msg.value;
238                    let y = Self::phi(sum - phi);
239                    let s = if x < 0.0 { sign ^ 1 } else { sign };
240                    let val = if s == 0 { y } else { -y };
241                    send(SentMessage {
242                        dest: msg.source,
243                        value: val,
244                    });
245                }
246            }
247
248            fn send_var_messages<F>(
249                &mut self,
250                input_llr: $f,
251                check_messages: &[Message<$f>],
252                send: F,
253            ) -> $f
254            where
255                F: FnMut(SentMessage<$f>),
256            {
257                send_var_messages_no_clip(input_llr, check_messages, send)
258            }
259
260            fn update_check_messages_and_vars(
261                &mut self,
262                check_messages: &mut [SentMessage<$f>],
263                vars: &mut [$f],
264            ) {
265                // Compute combination of all variables messages
266                let mut sign: u32 = 0;
267                let mut sum = 0.0;
268                if self.phis.len() < check_messages.len() {
269                    self.phis.resize(check_messages.len(), 0.0);
270                }
271                for (msg, phi) in check_messages.iter().zip(self.phis.iter_mut()) {
272                    // Subtract contribution of this check node message
273                    let x = vars[msg.dest] - msg.value;
274                    let phi_x = Self::phi(x.abs());
275                    *phi = phi_x;
276                    sum += phi_x;
277                    if x < 0.0 {
278                        sign ^= 1;
279                    }
280                }
281
282                // Exclude the contribution of each variable to generate new
283                // check message for that variable. Update variable.
284                for (msg, phi) in check_messages.iter_mut().zip(self.phis.iter()) {
285                    let x = vars[msg.dest] - msg.value;
286                    let rcv = Self::phi(sum - phi);
287                    let s = if x < 0.0 { sign ^ 1 } else { sign };
288                    let rcv = if s == 0 { rcv } else { -rcv };
289                    msg.value = rcv;
290                    vars[msg.dest] = x + rcv;
291                }
292            }
293        }
294    };
295}
296
297impl_phif!(Phif64, f64, 1e-30);
298impl_phif!(Phif32, f32, 1e-30);
299
300macro_rules! impl_tanhf {
301    ($ty:ident, $f:ty, $tanh_clamp:expr) => {
302        /// LDPC decoder arithmetic with `$f` and `2 * atanh(\Prod tanh(x/2)` rule.
303        ///
304        /// This is a [`DecoderArithmetic`] that uses `$f` to represent the LLRs
305        /// and messages and computes the check node messages using the usual
306        /// tanh product rule.
307        ///
308        /// See (33) in \[1\].
309        #[derive(Debug, Clone, Default)]
310        pub struct $ty {
311            tanhs: Vec<$f>,
312        }
313
314        impl $ty {
315            /// Creates a new [`$ty`] decoder arithmetic object.
316            pub fn new() -> $ty {
317                <$ty>::default()
318            }
319        }
320
321        impl DecoderArithmetic for $ty {
322            type Llr = $f;
323            type CheckMessage = $f;
324            type VarMessage = $f;
325            type VarLlr = $f;
326
327            fn input_llr_quantize(&self, llr: f64) -> $f {
328                llr as $f
329            }
330
331            fn llr_hard_decision(&self, llr: $f) -> bool {
332                llr <= 0.0
333            }
334
335            fn llr_to_var_message(&self, llr: $f) -> $f {
336                llr
337            }
338
339            fn llr_to_var_llr(&self, llr: $f) -> $f {
340                llr
341            }
342
343            fn var_llr_to_llr(&self, var_llr: $f) -> $f {
344                var_llr
345            }
346
347            fn send_check_messages<F>(&mut self, var_messages: &[Message<$f>], mut send: F)
348            where
349                F: FnMut(SentMessage<$f>),
350            {
351                // Compute tanh's of all variable messages
352                if self.tanhs.len() < var_messages.len() {
353                    self.tanhs.resize(var_messages.len(), 0.0);
354                }
355                for (msg, tanh) in var_messages.iter().zip(self.tanhs.iter_mut()) {
356                    let x = msg.value;
357                    let t = (0.5 * x).clamp(-$tanh_clamp, $tanh_clamp).tanh();
358                    *tanh = t;
359                }
360
361                for exclude_msg in var_messages.iter() {
362                    // product of all the tanh's except that of exclude_msg
363                    let product = var_messages
364                        .iter()
365                        .zip(self.tanhs.iter())
366                        .filter_map(|(msg, tanh)| {
367                            if msg.source != exclude_msg.source {
368                                Some(tanh)
369                            } else {
370                                None
371                            }
372                        })
373                        .product::<$f>();
374                    send(SentMessage {
375                        dest: exclude_msg.source,
376                        value: 2.0 * product.atanh(),
377                    })
378                }
379            }
380
381            fn send_var_messages<F>(
382                &mut self,
383                input_llr: $f,
384                check_messages: &[Message<$f>],
385                send: F,
386            ) -> $f
387            where
388                F: FnMut(SentMessage<$f>),
389            {
390                send_var_messages_no_clip(input_llr, check_messages, send)
391            }
392
393            fn update_check_messages_and_vars(
394                &mut self,
395                check_messages: &mut [SentMessage<$f>],
396                vars: &mut [$f],
397            ) {
398                // Compute tanh's of all variable messages
399                if self.tanhs.len() < check_messages.len() {
400                    self.tanhs.resize(check_messages.len(), 0.0);
401                }
402                for (msg, tanh) in check_messages.iter().zip(self.tanhs.iter_mut()) {
403                    let x = vars[msg.dest] - msg.value;
404                    let t = (0.5 * x).clamp(-$tanh_clamp, $tanh_clamp).tanh();
405                    *tanh = t;
406                }
407
408                for j in 0..check_messages.len() {
409                    // product of all the tanh's except that of exclude_msg
410                    let exclude_msg = &check_messages[j];
411                    let product = check_messages
412                        .iter()
413                        .zip(self.tanhs.iter())
414                        .filter_map(|(msg, tanh)| {
415                            if msg.dest != exclude_msg.dest {
416                                Some(tanh)
417                            } else {
418                                None
419                            }
420                        })
421                        .product::<$f>();
422                    let rcv = 2.0 * product.atanh();
423                    vars[exclude_msg.dest] += rcv - exclude_msg.value;
424                    check_messages[j].value = rcv;
425                }
426            }
427        }
428    };
429}
430
431// For f64, tanh(19) already gives 1.0 (and we want to avoid computing
432// atanh(1.0) = inf).
433impl_tanhf!(Tanhf64, f64, 18.0);
434// For f32, tanh(10) already gives 1.0.
435impl_tanhf!(Tanhf32, f32, 9.0);
436
437macro_rules! impl_minstarapproxf {
438    ($ty:ident, $f:ty) => {
439        /// LDPC decoder arithmetic with `$f` and the following approximation to
440        /// the min* function:
441        ///
442        /// min*(x,y) approx = sign(xy) * [min(|x|,|y|) - log(1 + exp(-||x|-|y||))].
443        ///
444        /// This is a [`DecoderArithmetic`] that uses `$f` to represent the LLRs
445        /// and messages and computes the check node messages using an approximation
446        /// to the min* rule.
447        ///
448        /// See (35) in \[1\].
449        #[derive(Debug, Clone, Default)]
450        pub struct $ty {
451            minstars: Vec<$f>,
452        }
453
454        impl $ty {
455            /// Creates a new [`$ty`] decoder arithmetic object.
456            pub fn new() -> $ty {
457                <$ty>::default()
458            }
459        }
460
461        impl DecoderArithmetic for $ty {
462            type Llr = $f;
463            type CheckMessage = $f;
464            type VarMessage = $f;
465            type VarLlr = $f;
466
467            fn input_llr_quantize(&self, llr: f64) -> $f {
468                llr as $f
469            }
470
471            fn llr_hard_decision(&self, llr: $f) -> bool {
472                llr <= 0.0
473            }
474
475            fn llr_to_var_message(&self, llr: $f) -> $f {
476                llr
477            }
478
479            fn llr_to_var_llr(&self, llr: $f) -> $f {
480                llr
481            }
482
483            fn var_llr_to_llr(&self, var_llr: $f) -> $f {
484                var_llr
485            }
486
487            fn send_check_messages<F>(&mut self, var_messages: &[Message<$f>], mut send: F)
488            where
489                F: FnMut(SentMessage<$f>),
490            {
491                for exclude_msg in var_messages.iter() {
492                    let mut sign: u32 = 0;
493                    let mut minstar = None;
494                    for msg in var_messages
495                        .iter()
496                        .filter(|msg| msg.source != exclude_msg.source)
497                    {
498                        let x = msg.value;
499                        if x < 0.0 {
500                            sign ^= 1;
501                        }
502                        let x = x.abs();
503                        minstar = Some(match minstar {
504                            None => x,
505                            // We clamp the output to 0 from below because we
506                            // are doing min* of positive numbers, but since
507                            // we've thrown away a positive term in the
508                            // approximation to min*, the approximation could
509                            // come out negative.
510                            Some(y) => (x.min(y) - (-(x - y).abs()).exp().ln_1p()).max(0.0),
511                        });
512                    }
513                    let minstar =
514                        minstar.expect("only one variable message connected to check node");
515                    let minstar = if sign == 0 { minstar } else { -minstar };
516                    send(SentMessage {
517                        dest: exclude_msg.source,
518                        value: minstar,
519                    })
520                }
521            }
522
523            fn send_var_messages<F>(
524                &mut self,
525                input_llr: $f,
526                check_messages: &[Message<$f>],
527                send: F,
528            ) -> $f
529            where
530                F: FnMut(SentMessage<$f>),
531            {
532                send_var_messages_no_clip(input_llr, check_messages, send)
533            }
534
535            fn update_check_messages_and_vars(
536                &mut self,
537                check_messages: &mut [SentMessage<$f>],
538                vars: &mut [$f],
539            ) {
540                // Compute all min*'s
541                if self.minstars.len() < check_messages.len() {
542                    self.minstars.resize(check_messages.len(), 0.0);
543                }
544                for (exclude_msg, minstar) in check_messages.iter().zip(self.minstars.iter_mut()) {
545                    let mut sign: u32 = 0;
546                    let mut mstar = None;
547                    for msg in check_messages
548                        .iter()
549                        .filter(|msg| msg.dest != exclude_msg.dest)
550                    {
551                        let x = vars[msg.dest] - msg.value;
552                        if x < 0.0 {
553                            sign ^= 1;
554                        }
555                        let x = x.abs();
556                        mstar = Some(match mstar {
557                            None => x,
558                            // We clamp the output to 0 from below because we
559                            // are doing min* of positive numbers, but since
560                            // we've thrown away a positive term in the
561                            // approximation to min*, the approximation could
562                            // come out negative.
563                            Some(y) => (x.min(y) - (-(x - y).abs()).exp().ln_1p()).max(0.0),
564                        });
565                    }
566                    let mstar = mstar.expect("only one variable message connected to check node");
567                    *minstar = if sign == 0 { mstar } else { -mstar };
568                }
569                // Update Rcv's and Qv's
570                for (msg, &minstar) in check_messages.iter_mut().zip(self.minstars.iter()) {
571                    vars[msg.dest] += minstar - msg.value;
572                    msg.value = minstar;
573                }
574            }
575        }
576    };
577}
578
579impl_minstarapproxf!(Minstarapproxf64, f64);
580impl_minstarapproxf!(Minstarapproxf32, f32);
581
582macro_rules! impl_8bitquant {
583    ($ty:ident) => {
584        impl $ty {
585            const QUANTIZER_C: f64 = 8.0;
586
587            /// Creates a new [`$ty`] decoder arithmetic object.
588            pub fn new() -> $ty {
589                let table = (0..=127)
590                    .map_while(|t| {
591                        let x = (Self::QUANTIZER_C
592                            * (-(t as f64 / Self::QUANTIZER_C)).exp().ln_1p())
593                        .round() as i8;
594                        if x > 0 { Some(x) } else { None }
595                    })
596                    .collect::<Vec<_>>()
597                    .into_boxed_slice();
598                $ty {
599                    table,
600                    _minstars: Vec::new(),
601                }
602            }
603
604            fn lookup(table: &[i8], x: i8) -> i8 {
605                assert!(x >= 0);
606                table.get(x as usize).copied().unwrap_or(0)
607            }
608
609            fn clip(x: i16) -> i8 {
610                if x >= 127 {
611                    127
612                } else if x <= -127 {
613                    -127
614                } else {
615                    x as i8
616                }
617            }
618        }
619    };
620}
621
622macro_rules! impl_send_var_messages_i8 {
623    ($degree_one_clip:expr, $jones_clip:expr) => {
624        #[allow(clippy::redundant_closure_call)]
625        fn send_var_messages<F>(
626            &mut self,
627            input_llr: i8,
628            check_messages: &[Message<i8>],
629            mut send: F,
630        ) -> i8
631        where
632            F: FnMut(SentMessage<i8>),
633        {
634            let degree_one = check_messages.len() == 1;
635            // Compute new LLR. We use an i16 to avoid overflows.
636            let llr = i16::from($degree_one_clip(input_llr, degree_one))
637                + check_messages
638                    .iter()
639                    .map(|m| i16::from(m.value))
640                    .sum::<i16>();
641            // Optional Jones clipping
642            let llr = $jones_clip(llr);
643            // Exclude the contribution of each check node to generate message for
644            // that check node
645            for msg in check_messages.iter() {
646                send(SentMessage {
647                    dest: msg.source,
648                    value: Self::clip(llr - i16::from(msg.value)),
649                });
650            }
651            Self::clip(llr)
652        }
653    };
654}
655
656macro_rules! impl_minstarapproxi8 {
657    ($ty:ident, $jones_clip:expr, $check_hardlimit:expr, $degree_one_clip:expr) => {
658        /// LDPC decoder arithmetic with 8-bit quantization and an approximation to the
659        /// min* function.
660        ///
661        /// This is a [`DecoderArithmetic`] that uses `i8` to represent the LLRs
662        /// and messages and computes the check node messages using an approximation
663        /// to the min* rule.
664        ///
665        /// See (36) in \[1\].
666        #[derive(Debug, Clone)]
667        pub struct $ty {
668            table: Box<[i8]>,
669            _minstars: Vec<i8>,
670        }
671
672        impl_8bitquant!($ty);
673
674        impl Default for $ty {
675            fn default() -> $ty {
676                <$ty>::new()
677            }
678        }
679
680        impl DecoderArithmetic for $ty {
681            type Llr = i8;
682            type CheckMessage = i8;
683            type VarMessage = i8;
684            // An i16 is used for variable LLRs so that the sum of the channel
685            // LLR (i8) and the N check node messages checking the variable can
686            // be stored without clipping (in general, ceil(log2(N)) bit growth
687            // is needed).
688            type VarLlr = i16;
689
690            fn input_llr_quantize(&self, llr: f64) -> i8 {
691                let x = Self::QUANTIZER_C * llr;
692                if x >= 127.0 {
693                    127
694                } else if x <= -127.0 {
695                    -127
696                } else {
697                    x.round() as i8
698                }
699            }
700
701            fn llr_hard_decision(&self, llr: i8) -> bool {
702                llr <= 0
703            }
704
705            fn llr_to_var_message(&self, llr: i8) -> i8 {
706                llr
707            }
708
709            fn llr_to_var_llr(&self, llr: i8) -> i16 {
710                i16::from(llr)
711            }
712
713            fn var_llr_to_llr(&self, var_llr: i16) -> i8 {
714                Self::clip(var_llr)
715            }
716
717            #[allow(clippy::redundant_closure_call)]
718            fn send_check_messages<F>(&mut self, var_messages: &[Message<i8>], mut send: F)
719            where
720                F: FnMut(SentMessage<i8>),
721            {
722                for exclude_msg in var_messages.iter() {
723                    let mut sign: u32 = 0;
724                    let mut minstar = None;
725                    for msg in var_messages
726                        .iter()
727                        .filter(|msg| msg.source != exclude_msg.source)
728                    {
729                        let x = msg.value;
730                        if x < 0 {
731                            sign ^= 1;
732                        }
733                        let x = x.abs();
734                        minstar = Some(match minstar {
735                            None => x,
736                            // We clamp the output to 0 from below because we
737                            // are doing min* of positive numbers, but since
738                            // we've thrown away a positive term in the
739                            // approximation to min*, the approximation could
740                            // come out negative.
741                            Some(y) => (x.min(y) - Self::lookup(&self.table, (x - y).abs())).max(0),
742                        });
743                    }
744                    let minstar =
745                        minstar.expect("only one variable message connected to check node");
746                    let minstar = if sign == 0 { minstar } else { -minstar };
747                    // Optional partial hard-limiting
748                    let minstar = $check_hardlimit(minstar);
749                    send(SentMessage {
750                        dest: exclude_msg.source,
751                        value: minstar,
752                    })
753                }
754            }
755
756            impl_send_var_messages_i8!($degree_one_clip, $jones_clip);
757
758            #[allow(clippy::redundant_closure_call)]
759            fn update_check_messages_and_vars(
760                &mut self,
761                check_messages: &mut [SentMessage<i8>],
762                vars: &mut [i16],
763            ) {
764                // Compute all min*'s
765                if self._minstars.len() < check_messages.len() {
766                    self._minstars.resize(check_messages.len(), 0);
767                }
768                for (exclude_msg, minstar) in check_messages.iter().zip(self._minstars.iter_mut()) {
769                    let mut sign: u32 = 0;
770                    let mut mstar = None;
771                    for msg in check_messages
772                        .iter()
773                        .filter(|msg| msg.dest != exclude_msg.dest)
774                    {
775                        let x = Self::clip(vars[msg.dest] - i16::from(msg.value));
776                        if x < 0 {
777                            sign ^= 1;
778                        }
779                        let x = x.abs();
780                        mstar = Some(match mstar {
781                            None => x,
782                            // We clamp the output to 0 from below because we
783                            // are doing min* of positive numbers, but since
784                            // we've thrown away a positive term in the
785                            // approximation to min*, the approximation could
786                            // come out negative.
787                            Some(y) => (x.min(y) - Self::lookup(&self.table, (x - y).abs())).max(0),
788                        });
789                    }
790                    let mstar = mstar.expect("only one variable message connected to check node");
791                    let mstar = if sign == 0 { mstar } else { -mstar };
792                    // Optional partial hard-limiting
793                    let mstar = $check_hardlimit(mstar);
794                    *minstar = mstar;
795                }
796                // Update Rcv's and Qv's
797                for (msg, &minstar) in check_messages.iter_mut().zip(self._minstars.iter()) {
798                    vars[msg.dest] += i16::from(minstar) - i16::from(msg.value);
799                    msg.value = minstar;
800                }
801            }
802        }
803    };
804}
805
806macro_rules! jones_clip {
807    () => {
808        |x| i16::from(Self::clip(x))
809    };
810}
811
812macro_rules! partial_hard_limit {
813    () => {
814        |x| {
815            if x <= -100 {
816                -127
817            } else if x >= 100 {
818                127
819            } else {
820                x
821            }
822        }
823    };
824}
825
826macro_rules! degree_one_clipping {
827    () => {
828        |x, degree_one| {
829            if degree_one {
830                if x <= -116 {
831                    -116
832                } else if x >= 116 {
833                    116
834                } else {
835                    x
836                }
837            } else {
838                x
839            }
840        }
841    };
842}
843
844macro_rules! degree_one_no_clipping {
845    () => {
846        |x, _| x
847    };
848}
849
850impl_minstarapproxi8!(
851    Minstarapproxi8,
852    identity,
853    identity,
854    degree_one_no_clipping!()
855);
856impl_minstarapproxi8!(
857    Minstarapproxi8Jones,
858    jones_clip!(),
859    identity,
860    degree_one_no_clipping!()
861);
862impl_minstarapproxi8!(
863    Minstarapproxi8PartialHardLimit,
864    identity,
865    partial_hard_limit!(),
866    degree_one_no_clipping!()
867);
868impl_minstarapproxi8!(
869    Minstarapproxi8JonesPartialHardLimit,
870    jones_clip!(),
871    partial_hard_limit!(),
872    degree_one_no_clipping!()
873);
874impl_minstarapproxi8!(
875    Minstarapproxi8Deg1Clip,
876    identity,
877    identity,
878    degree_one_clipping!()
879);
880impl_minstarapproxi8!(
881    Minstarapproxi8JonesDeg1Clip,
882    jones_clip!(),
883    identity,
884    degree_one_clipping!()
885);
886impl_minstarapproxi8!(
887    Minstarapproxi8PartialHardLimitDeg1Clip,
888    identity,
889    partial_hard_limit!(),
890    degree_one_clipping!()
891);
892impl_minstarapproxi8!(
893    Minstarapproxi8JonesPartialHardLimitDeg1Clip,
894    jones_clip!(),
895    partial_hard_limit!(),
896    degree_one_clipping!()
897);
898
899macro_rules! impl_aminstarf {
900    ($ty:ident, $f:ty) => {
901        /// LDPC decoder arithmetic with `$f` and the A-Min*-BP described in \[3\].
902        ///
903        /// This is a [`DecoderArithmetic`] that uses `$f` to represent the LLRs
904        /// and messages and computes the check node messages using an approximation
905        /// to the min* rule.
906        #[derive(Debug, Clone, Default)]
907        pub struct $ty {}
908
909        impl $ty {
910            /// Creates a new [`$ty`] decoder arithmetic object.
911            pub fn new() -> $ty {
912                <$ty>::default()
913            }
914        }
915
916        impl DecoderArithmetic for $ty {
917            type Llr = $f;
918            type CheckMessage = $f;
919            type VarMessage = $f;
920            type VarLlr = $f;
921
922            fn input_llr_quantize(&self, llr: f64) -> $f {
923                llr as $f
924            }
925
926            fn llr_hard_decision(&self, llr: $f) -> bool {
927                llr <= 0.0
928            }
929
930            fn llr_to_var_message(&self, llr: $f) -> $f {
931                llr
932            }
933
934            fn llr_to_var_llr(&self, llr: $f) -> $f {
935                llr
936            }
937
938            fn var_llr_to_llr(&self, var_llr: $f) -> $f {
939                var_llr
940            }
941
942            fn send_check_messages<F>(&mut self, var_messages: &[Message<$f>], mut send: F)
943            where
944                F: FnMut(SentMessage<$f>),
945            {
946                let (argmin, msgmin) = var_messages
947                    .iter()
948                    .enumerate()
949                    .min_by(|(_, msg1), (_, msg2)| {
950                        msg1.value.abs().partial_cmp(&msg2.value.abs()).unwrap()
951                    })
952                    .expect("var_messages is empty");
953                let mut sign: u32 = 0;
954                let mut delta = None;
955                for (j, msg) in var_messages.iter().enumerate() {
956                    let x = msg.value;
957                    if x < 0.0 {
958                        sign ^= 1;
959                    }
960                    if j != argmin {
961                        let x = x.abs();
962                        delta = Some(match delta {
963                            None => x,
964                            Some(y) => {
965                                (x.min(y) - (-(x - y).abs()).exp().ln_1p()
966                                    + (-(x + y)).exp().ln_1p())
967                            }
968                        });
969                    }
970                }
971                let delta = delta.expect("var_messages_empty");
972
973                send(SentMessage {
974                    dest: msgmin.source,
975                    value: if (sign != 0) ^ (msgmin.value < 0.0) {
976                        -delta
977                    } else {
978                        delta
979                    },
980                });
981
982                let vmin = msgmin.value.abs();
983                let delta = delta.min(vmin) - (-(delta - vmin).abs()).exp().ln_1p()
984                    + (-(delta + vmin)).exp().ln_1p();
985                for msg in var_messages
986                    .iter()
987                    .enumerate()
988                    .filter_map(|(j, msg)| if j != argmin { Some(msg) } else { None })
989                {
990                    send(SentMessage {
991                        dest: msg.source,
992                        value: if (sign != 0) ^ (msg.value < 0.0) {
993                            -delta
994                        } else {
995                            delta
996                        },
997                    });
998                }
999            }
1000
1001            fn send_var_messages<F>(
1002                &mut self,
1003                input_llr: $f,
1004                check_messages: &[Message<$f>],
1005                send: F,
1006            ) -> $f
1007            where
1008                F: FnMut(SentMessage<$f>),
1009            {
1010                send_var_messages_no_clip(input_llr, check_messages, send)
1011            }
1012
1013            fn update_check_messages_and_vars(
1014                &mut self,
1015                check_messages: &mut [SentMessage<$f>],
1016                vars: &mut [$f],
1017            ) {
1018                let (argmin, msgmin) = check_messages
1019                    .iter()
1020                    .map(|msg| vars[msg.dest] - msg.value)
1021                    .enumerate()
1022                    .min_by(|(_, msg1), (_, msg2)| msg1.abs().partial_cmp(&msg2.abs()).unwrap())
1023                    .expect("var_messages is empty");
1024                let mut sign: u32 = 0;
1025                let mut delta = None;
1026                for (j, msg) in check_messages.iter().enumerate() {
1027                    let x = vars[msg.dest] - msg.value;
1028                    if x < 0.0 {
1029                        sign ^= 1;
1030                    }
1031                    if j != argmin {
1032                        let x = x.abs();
1033                        delta = Some(match delta {
1034                            None => x,
1035                            Some(y) => {
1036                                (x.min(y) - (-(x - y).abs()).exp().ln_1p()
1037                                    + (-(x + y)).exp().ln_1p())
1038                            }
1039                        });
1040                    }
1041                }
1042                let delta = delta.expect("var_messages_empty");
1043                let msgmin_rcv = if (sign != 0) ^ (msgmin < 0.0) {
1044                    -delta
1045                } else {
1046                    delta
1047                };
1048
1049                let vmin = msgmin.abs();
1050                let delta = delta.min(vmin) - (-(delta - vmin).abs()).exp().ln_1p()
1051                    + (-(delta + vmin)).exp().ln_1p();
1052                for (j, msg) in check_messages.iter_mut().enumerate() {
1053                    let x = vars[msg.dest] - msg.value;
1054                    let rcv = if j == argmin {
1055                        msgmin_rcv
1056                    } else {
1057                        if (sign != 0) ^ (x < 0.0) {
1058                            -delta
1059                        } else {
1060                            delta
1061                        }
1062                    };
1063                    msg.value = rcv;
1064                    vars[msg.dest] = x + rcv;
1065                }
1066            }
1067        }
1068    };
1069}
1070
1071impl_aminstarf!(Aminstarf64, f64);
1072impl_aminstarf!(Aminstarf32, f32);
1073
1074macro_rules! impl_aminstari8 {
1075    ($ty:ident, $jones_clip:expr, $check_hardlimit:expr, $degree_one_clip:expr) => {
1076        /// LDPC decoder arithmetic with 8-bit quantization and the A-Min*-BP
1077        /// described in \[3\].
1078        ///
1079        /// This is a [`DecoderArithmetic`] that uses `i8` to represent the LLRs
1080        /// and messages and computes the check node messages using an approximation
1081        /// to the min* rule.
1082        #[derive(Debug, Clone)]
1083        pub struct $ty {
1084            table: Box<[i8]>,
1085            _minstars: Vec<i8>,
1086        }
1087
1088        impl_8bitquant!($ty);
1089
1090        impl Default for $ty {
1091            fn default() -> $ty {
1092                <$ty>::new()
1093            }
1094        }
1095
1096        impl DecoderArithmetic for $ty {
1097            type Llr = i8;
1098            type CheckMessage = i8;
1099            type VarMessage = i8;
1100            type VarLlr = i16;
1101
1102            fn input_llr_quantize(&self, llr: f64) -> i8 {
1103                let x = Self::QUANTIZER_C * llr;
1104                if x >= 127.0 {
1105                    127
1106                } else if x <= -127.0 {
1107                    -127
1108                } else {
1109                    x.round() as i8
1110                }
1111            }
1112
1113            fn llr_hard_decision(&self, llr: i8) -> bool {
1114                llr <= 0
1115            }
1116
1117            fn llr_to_var_message(&self, llr: i8) -> i8 {
1118                llr
1119            }
1120
1121            fn llr_to_var_llr(&self, llr: i8) -> i16 {
1122                i16::from(llr)
1123            }
1124
1125            fn var_llr_to_llr(&self, var_llr: i16) -> i8 {
1126                Self::clip(var_llr)
1127            }
1128
1129            #[allow(clippy::redundant_closure_call)]
1130            fn send_check_messages<F>(&mut self, var_messages: &[Message<i8>], mut send: F)
1131            where
1132                F: FnMut(SentMessage<i8>),
1133            {
1134                let (argmin, msgmin) = var_messages
1135                    .iter()
1136                    .enumerate()
1137                    .min_by_key(|(_, msg)| msg.value.abs())
1138                    .expect("var_messages is empty");
1139                let mut sign: u32 = 0;
1140                let mut delta = None;
1141                for (j, msg) in var_messages.iter().enumerate() {
1142                    let x = msg.value;
1143                    if x < 0 {
1144                        sign ^= 1;
1145                    }
1146                    if j != argmin {
1147                        let x = x.abs();
1148                        delta = Some(match delta {
1149                            None => x,
1150                            // We clamp the output to 0 from below because we
1151                            // are doing min* of positive numbers, but since
1152                            // we've thrown away a positive term in the
1153                            // approximation to min*, the approximation could
1154                            // come out negative.
1155                            Some(y) => (x.min(y) - Self::lookup(&self.table, (x - y).abs())
1156                                + Self::lookup(&self.table, x.saturating_add(y)))
1157                            .max(0),
1158                        });
1159                    }
1160                }
1161                let delta = delta.expect("var_messages_empty");
1162                let delta_hl = $check_hardlimit(delta);
1163
1164                send(SentMessage {
1165                    dest: msgmin.source,
1166                    value: if (sign != 0) ^ (msgmin.value < 0) {
1167                        -delta_hl
1168                    } else {
1169                        delta_hl
1170                    },
1171                });
1172
1173                let vmin = msgmin.value.abs();
1174                let delta = (delta.min(vmin) - Self::lookup(&self.table, (delta - vmin).abs())
1175                    + Self::lookup(&self.table, delta.saturating_add(vmin)))
1176                .max(0);
1177                let delta_hl = $check_hardlimit(delta);
1178                for msg in var_messages
1179                    .iter()
1180                    .enumerate()
1181                    .filter_map(|(j, msg)| if j != argmin { Some(msg) } else { None })
1182                {
1183                    send(SentMessage {
1184                        dest: msg.source,
1185                        value: if (sign != 0) ^ (msg.value < 0) {
1186                            -delta_hl
1187                        } else {
1188                            delta_hl
1189                        },
1190                    });
1191                }
1192            }
1193
1194            impl_send_var_messages_i8!($degree_one_clip, $jones_clip);
1195
1196            #[allow(clippy::redundant_closure_call)]
1197            fn update_check_messages_and_vars(
1198                &mut self,
1199                check_messages: &mut [SentMessage<i8>],
1200                vars: &mut [i16],
1201            ) {
1202                let (argmin, msgmin) = check_messages
1203                    .iter()
1204                    .map(|msg| Self::clip(vars[msg.dest] - i16::from(msg.value)))
1205                    .enumerate()
1206                    .min_by_key(|(_, msg)| msg.abs())
1207                    .expect("var_messages is empty");
1208                let mut sign: u32 = 0;
1209                let mut delta = None;
1210                for (j, msg) in check_messages.iter().enumerate() {
1211                    let x = Self::clip(vars[msg.dest] - i16::from(msg.value));
1212                    if x < 0 {
1213                        sign ^= 1;
1214                    }
1215                    if j != argmin {
1216                        let x = x.abs();
1217                        delta = Some(match delta {
1218                            None => x,
1219                            // We clamp the output to 0 from below because we
1220                            // are doing min* of positive numbers, but since
1221                            // we've thrown away a positive term in the
1222                            // approximation to min*, the approximation could
1223                            // come out negative.
1224                            Some(y) => (x.min(y) - Self::lookup(&self.table, (x - y).abs())
1225                                + Self::lookup(&self.table, x.saturating_add(y)))
1226                            .max(0),
1227                        });
1228                    }
1229                }
1230                let delta = delta.expect("var_messages_empty");
1231                let delta_hl = $check_hardlimit(delta);
1232                let msgmin_rcv = if (sign != 0) ^ (msgmin < 0) {
1233                    -delta_hl
1234                } else {
1235                    delta_hl
1236                };
1237
1238                let vmin = msgmin.abs();
1239                let delta = (delta.min(vmin) - Self::lookup(&self.table, (delta - vmin).abs())
1240                    + Self::lookup(&self.table, delta.saturating_add(vmin)))
1241                .max(0);
1242                let delta_hl = $check_hardlimit(delta);
1243                for (j, msg) in check_messages.iter_mut().enumerate() {
1244                    let x = vars[msg.dest] - i16::from(msg.value);
1245                    let rcv = if j == argmin {
1246                        msgmin_rcv
1247                    } else {
1248                        if (sign != 0) ^ (x < 0) {
1249                            -delta_hl
1250                        } else {
1251                            delta_hl
1252                        }
1253                    };
1254                    vars[msg.dest] = x + i16::from(rcv);
1255                    msg.value = rcv;
1256                }
1257            }
1258        }
1259    };
1260}
1261
1262impl_aminstari8!(Aminstari8, identity, identity, degree_one_no_clipping!());
1263impl_aminstari8!(
1264    Aminstari8Jones,
1265    jones_clip!(),
1266    identity,
1267    degree_one_no_clipping!()
1268);
1269impl_aminstari8!(
1270    Aminstari8PartialHardLimit,
1271    identity,
1272    partial_hard_limit!(),
1273    degree_one_no_clipping!()
1274);
1275impl_aminstari8!(
1276    Aminstari8JonesPartialHardLimit,
1277    jones_clip!(),
1278    partial_hard_limit!(),
1279    degree_one_no_clipping!()
1280);
1281impl_aminstari8!(
1282    Aminstari8Deg1Clip,
1283    identity,
1284    identity,
1285    degree_one_clipping!()
1286);
1287impl_aminstari8!(
1288    Aminstari8JonesDeg1Clip,
1289    jones_clip!(),
1290    identity,
1291    degree_one_clipping!()
1292);
1293impl_aminstari8!(
1294    Aminstari8PartialHardLimitDeg1Clip,
1295    identity,
1296    partial_hard_limit!(),
1297    degree_one_clipping!()
1298);
1299impl_aminstari8!(
1300    Aminstari8JonesPartialHardLimitDeg1Clip,
1301    jones_clip!(),
1302    partial_hard_limit!(),
1303    degree_one_clipping!()
1304);