1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
// Copyright 2017 Adam Greig
// Licensed under the MIT license, see LICENSE for details.

#![no_std]
#![warn(missing_docs)]

//! Labrador-LDPC implements a selection of LDPC error correcting codes,
//! including encoders and decoders.
//!
//! It is designed for use with other Labrador components but does not have any dependencies
//! on anything (including `std`) and thus may be used totally standalone. It is reasonably
//! efficient on both serious computers and on small embedded systems. Considerations have
//! been made to accommodate both use cases.
//!
//! No memory allocations are made inside this crate so most methods require you to pass in an
//! allocated block of memory for them to use. Check individual method documentation for further
//! details.
//!
//! ## Example
//!
//! ```
//! use labrador_ldpc::LDPCCode;
//!
//! // Pick the TC128 code, n=128 k=64
//! // (that's 8 bytes of user data encoded into 16 bytes)
//! let code = LDPCCode::TC128;
//!
//! // Generate some data to encode
//! let txdata: Vec<u8> = (0..8).collect();
//!
//! // Allocate memory for the encoded data
//! let mut txcode = vec![0u8; code.n()/8];
//!
//! // Encode, copying `txdata` into the start of `txcode` then computing the parity bits
//! code.copy_encode(&txdata, &mut txcode);
//!
//! // Copy the transmitted data and corrupt a few bits
//! let mut rxcode = txcode.clone();
//! rxcode[0] ^= 0x55;
//!
//! // Allocate some memory for the decoder's working area and output
//! let mut working = vec![0u8; code.decode_bf_working_len()];
//! let mut rxdata = vec![0u8; code.output_len()];
//!
//! // Decode for at most 20 iterations
//! code.decode_bf(&rxcode, &mut rxdata, &mut working, 20);
//!
//! // Check the errors got corrected
//! assert_eq!(&rxdata[..8], &txdata[..8]);
//! ```
//!
//! ## Codes
//!
//! *Nomenclature:* we use n to represent the code length (number of bits you have to
//! transmit per codeword), k to represent the code dimension (number of useful information bits
//! per codeword), and r to represent the *rate* k/n, the number of useful information bits per
//! bit transmitted.
//!
//! Several codes are available in a range of lengths and rates. Current codes come from two
//! sets of CCSDS recommendations, their TC (telecommand) short-length low-rate codes, and their
//! TM (telemetry) higher-length various-rates codes. These are all published and standardised
//! codes which have good performance.
//!
//! The TC codes are available in rate r=1/2 and dimensions k=128, k=256, and k=512.
//! They are the same codes defined in CCSDS document 231.1-O-1 and subsequent revisions (although
//! the n=256 code is eventually removed, it lives on here as it's quite useful).
//!
//! The TM codes are available in r=1/2, r=2/3, and r=4/5, for dimensions k=1024 and k=4096.
//! They are the same codes defined in CCSDS document 131.0-B-2 and subsequent revisions.
//!
//! For more information on the codes themselves please see the CCSDS publications:
//! https://public.ccsds.org/
//!
//! The available codes are the variants of the `LDPCCode` enum, and pretty much everything
//! else (encoders, decoders, utility methods) are implemented as methods on this enum.
//!
//! *Which code should I pick?*: for short and highly-reliable messages, the TC codes make sense,
//! especially if they need to be decoded on a constrained system such as an embedded platform.
//! For most other data transfer, the TM codes are more flexible and generally better suited.
//!
//! The very large k=16384 TM codes have not been included due to the complexity in generating
//! their generator matrices and the very long constants involved, but it would be theoretically
//! possible to include them. The relevant parity check constants are already included.
//!
//! ### Generator Matrices
//!
//! To encode a codeword, we need a generator matrix, which is a large binary matrix of shape
//! k rows by n columns. For each bit set in the data to encode, we sum the corresponding row
//! of the generator matrix to find the output codeword. Because all our codes are *systematic*,
//! the first k bits of our codewords are exactly the input data, which means we only need to
//! encode the final n-k parity bits at the end of the codeword.
//!
//! These final n-k columns of the generator are stored in a compact form, where only a small
//! number of the final rows are stored, and the rest can be inferred from those at runtime. Our
//! encoder methods just use this compact form directly, so it doesn't ever need to be expanded.
//!
//! The relevant constants are in the `codes.compact_generators` module, with names like `TC128_G`.
//!
//! ### Parity Check Matrices
//!
//! These are the counterpart to the generator matrices of the previous section. They are used by
//! the decoders to work out which bits are wrong and need to be changed. When fully expanded,
//! they are a large matrix with n-k rows (one per parity check) of n columns (one per input data
//! bit, or variable). We can store and use them in an extremely compact form due to the way these
//! specific codes have been constructed.
//!
//! The constants are in `codes.compact_parity_checks` and reflect the construction defined
//! in the CCSDS documents.
//!
//! ## Encoders
//!
//! There are two encoder methods implemented on `LDPCCode`: `encode` and `copy_encode`.
//!
//! `copy_encode` is a convenience wrapper that copies your data to encode into the codeword
//! memory first, and then performs the encode as usual. In comparison, `encode` requires that
//! your data is already at the start of the codeword memory, and just fills in the parity bits
//! at the end. It doesn't take very much time to do the copy, so use whichever is more convenient.
//!
//! The encode methods require you to pass in a slice of allocated codeword memory, `&mut [T]`,
//! which must be `n` bits long exactly. You can pass this as slices of `u8`, `u32`, or `u64`. In
//! general the larger types will encode up to three times faster, so it's usually worth using
//! them. They are interpreted as containing your data in little-endian, so you can directly
//! cast between the `&[u8]` and larger interpretations on all little-endian systems (which is to
//! say, most systems).
//!
//! The encode methods always return an `&mut [u8]` view on the codeword memory, which you
//! can use if you need this type for further use (such as transmission out of a radio), or if you
//! ignore the return value you can continue using your original slice of codeword memory.
//!
//! ```
//! # use labrador_ldpc::LDPCCode;
//! let code = LDPCCode::TC128;
//!
//! // Encode into u32, but then access results as u8
//! let mut codeword: [u32; 4] = [0x03020100, 0x07060504, 0x00000000, 0x00000000];
//! let txcode = code.encode(&mut codeword);
//! assert_eq!(txcode, [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
//!                     0x34, 0x99, 0x98, 0x87, 0x94, 0xE1, 0x62, 0x56]);
//!
//! // Encode into u64, but maintain access as a u64 afterwards
//! let mut codeword: [u64; 2] = [0x0706050403020100, 0x0000000000000000];
//! code.encode(&mut codeword);
//! assert_eq!(codeword, [0x0706050403020100, 0x5662E19487989934]);
//! ```
//!
//! The required memory (in bytes) to encode with each code is:
//!
//! Code   | Input (RAM) | Output (RAM)    | Generator const (text)
//! -------|-------------|-----------------|-----------------------
//!        | =k/8        | =n/8            |
//! TC128  |           8 |              16 |              32
//! TC256  |          16 |              32 |              64
//! TC512  |          32 |              64 |             128
//! TM1280 |         128 |             160 |            1024
//! TM1536 |         128 |             192 |            1024
//! TM2048 |         128 |             256 |            1024
//! TM5120 |         512 |             640 |            4096
//! TM6144 |         512 |             768 |            4096
//! TM8192 |         512 |            1024 |            4096
//!
//! ## Decoders
//!
//! There are two decoders available:
//!
//! * The low-memory decoder, `decode_bf`, uses a bit flipping algorithm with hard information.
//!   This is maybe 1 or 2dB from optimal for decoding, but requires much less RAM and is usually
//!   a few times faster. It's only really useful on something very slow or with very little memory
//!   available.
//! * The high-performance decoder, `decode_ms`, uses a modified min-sum decoding algorithm with
//!   soft information to perform near-optimal decoding albeit slower and with much higher memory
//!   overhead.  This decoder can operate on a variety of types for the soft information, with
//!   corresponding differences in the memory overhead.
//!
//! The required memory (in bytes) to decode with each code is:
//!
//! Code   | Hard input  | Soft input  |   Output | Parity const | `bf` overhead | `mp` overhead
//! -------|-------------|-------------|----------|--------------|---------------|---------------
//!        | (`bf`, RAM) | (`mp`, RAM) | (RAM)    | (text)       | (RAM)         | (RAM)
//!        | =n/8        | =n*T        | =(n+p)/8 |              |               |
//! TC128  |          16 |        128T |       16 |          132 |           128 | 1280T    +   8
//! TC256  |          32 |        256T |       32 |          132 |           256 | 2560T    +  16
//! TC512  |          64 |        512T |       64 |          132 |           512 | 5120T    +  32
//! TM1280 |         160 |       1280T |      176 |          366 |          1408 | 12160T   +  48
//! TM1536 |         192 |       1536T |      224 |          366 |          1792 | 15104T   +  96
//! TM2048 |         256 |       2048T |      320 |          366 |          2560 | 20992T   + 192
//! TM5120 |         640 |       5120T |      704 |          366 |          5632 | 48640T   + 192
//! TM6144 |         768 |       6144T |      896 |          366 |          7168 | 60416T   + 384
//! TM8192 |        1024 |       8192T |     1280 |          366 |         10240 | 83968T   + 768
//!
//! `T` reflects the size of the type for your soft information: for `i8` this is 1, for `i16` 2,
//! for `i32` and `f32` it's 4, and for `f64` it is 8. You should use a type commensurate with
//! the quality of your soft information; usually `i16` would suffice for instance.
//!
//! Both decoders require the same output storage and parity constants. The `bf` decoder takes
//! smaller hard inputs and has a much smaller working area, while the `mp` decoder requires
//! soft inputs and uses soft information internally, requiring a larger working area.
//!
//! The required sizes are available both at compile-time in the `CodeParams` consts, and at
//! runtime with methods on `LDPCCode` such as `decode_ms_working_len()`. You can therefore
//! allocate the required memory either statically or dynamically at runtime.
//!
//! Please see the individual decoder methods for more details on their requirements.
//!
//! ### Bit Flipping Decoder
//! This decoder is based on the original Gallagher decoder. It is not very optimal but is fast.
//! The idea is to see which bits are connected to the highest number of parity checks that are not
//! currently satisfied, and flip those bits, and iterate until things get better.
//! However, this routine cannot correct erasures (it only knows about bit flips). All of the TM
//! codes are *punctured*, which means some parity bits are not transmitted and so are unknown at
//! the receiver. We use a separate algorithm to decode the erasures first, based on a paper by
//! Archonta, Kanistras and Paliouras, doi:10.1109/MOCAST.2016.7495161.
//!
//! ### Message Passing Decoder
//! This is a modified min-sum decoder that computes the probability of each bit being set given
//! the other bits connected to it via the parity check matrix. It takes soft information in,
//! so inherently covers the punctured codes as well. This implementation is based on one described
//! by Savin, arXiv:0803.1090. It is both reasonably efficient (no `atahn` required), and
//! performs very close to optimal sum-product decoding.

#[cfg(test)]
#[macro_use]
extern crate std;

pub mod codes;
pub mod encoder;
pub mod decoder;
pub use codes::{LDPCCode};