1use crate::blocks::schema::{ParamType, variants_schema};
5use crate::blocks::{ParamInfo, Params};
6use comnum_core::{Error, Result};
7use comnum_fec::block::BlockCode;
8use comnum_fec::conv::ConvolutionalCode;
9use comnum_fec::crc::Crc;
10use comnum_fec::ldpc::{self, CheckRule, LdpcCode};
11use comnum_fec::polar::PolarCode;
12use comnum_fec::puncture::{Punctured, Puncturer};
13use comnum_fec::rs::ReedSolomon;
14use comnum_fec::turbo::{TurboCode, TurboDecoder};
15use comnum_math::Jacobian;
16use serde::Deserialize;
17use serde_json::Value as Json;
18
19block_params! {
22 struct ConvSpec {
23 k: usize [required] "bits" >= 1, "information bits per codeword";
24 hard: bool [false], "hard-decision Viterbi decoding";
25 puncture: Option<String> [absent "unpunctured"],
26 "puncturing pattern of comnum_fec::puncture::Puncturer::from_name (ieee80211_2_3, dvbs_3_4…)";
27 }
28}
29
30block_params! {
31 struct RsSpec {
32 m: u32 [required] "bits" >= 2 <= 16, "bits per symbol: the field GF(2^m)";
33 t: usize [required] "symbols", "symbol errors corrected";
34 n: Option<usize> [absent "2^m - 1"] "symbols", "length of the shortened code";
35 first_root: usize [1], "first root b of the generator α^b, …";
36 root_step: usize [1], "step c between the roots of the generator";
37 poly: Option<u32> [absent "the conventional primitive polynomial of degree m"],
38 "field polynomial, bit i the coefficient of x^i (CCSDS: 391 = 0x187, x^8 + x^7 + x^2 + x + 1)";
39 }
40}
41
42block_params! {
43 struct TurboSpec {
44 k: usize [required] "bits" >= 8, "information bits: an interleaver length of LTE";
45 iterations: usize [8], "decoding iterations";
46 max_log: bool [false], "max-log-MAP instead of log-MAP";
47 }
48}
49
50block_params! {
51 pub(super) struct LdpcSettings {
55 var_degree: usize [ldpc::DEFAULT_VAR_DEGREE => "3"] >= 1, "variable-node degree of the PEG code";
56 seed: u64 [ldpc::DEFAULT_PEG_SEED => "1"], "seed of the PEG construction";
57 rule: String [ldpc::DEFAULT_RULE.name() => "sum_product"]
58 one_of ["sum_product", "min_sum", "normalized_min_sum", "offset_min_sum"],
59 "check-node rule";
60 alpha: f64 [ldpc::DEFAULT_ALPHA => "0.8"], "scale or offset of min-sum";
61 layered: bool [ldpc::DEFAULT_LAYERED => "true"], "layered schedule";
62 iterations: usize [ldpc::DEFAULT_MAX_ITERATIONS => "50"] >= 1, "decoding iterations at most";
63 }
64}
65
66fn peg(n: usize, k: usize, var_degree: usize, seed: u64) -> Result<LdpcCode> {
71 type Built = Vec<((usize, usize, usize, u64), LdpcCode)>;
73 static BUILT: std::sync::Mutex<Built> = std::sync::Mutex::new(Vec::new());
74 const KEPT: usize = 8;
75 let key = (n, k, var_degree, seed);
76 let found = BUILT.lock().ok().and_then(|b| b.iter().find(|(k, _)| *k == key).map(|(_, c)| c.clone()));
77 if let Some(c) = found {
78 return Ok(c);
79 }
80 let c = LdpcCode::peg(n, k, var_degree, seed)?;
81 if let Ok(mut b) = BUILT.lock() {
82 if b.len() == KEPT {
83 b.remove(0);
84 }
85 b.push((key, c.clone()));
86 }
87 Ok(c)
88}
89
90impl LdpcSettings {
91 pub(super) fn code(&self, n: usize, k: usize) -> Result<LdpcCode> {
94 let mut c = peg(n, k, self.var_degree, self.seed)?;
95 c.decoder.rule = CheckRule::from_name(&self.rule, self.alpha)?;
96 c.decoder.layered = self.layered;
97 c.decoder.max_iterations = self.iterations;
98 Ok(c)
99 }
100}
101
102block_params! {
103 struct LdpcSpec {
104 n: usize [required] "bits", "length of the codeword";
105 k: usize [required] "bits", "information bits";
106 settings: LdpcSettings [flatten], "the construction and the decoder";
107 }
108}
109
110block_params! {
111 struct PolarSpec {
112 n: usize [required] "bits", "length of the codeword, a power of 2";
113 k: usize [required] "bits", "information bits (a CRC comes on top)";
114 design_ebn0_db: f64 [2.0] "dB", "Eb/N0 of the Gaussian-approximation design";
115 crc: Option<String> [absent "no CRC"], "CRC of CRC-aided SCL: crc6, crc11, crc16, crc24a, crc24b, crc24c";
116 list: usize [1], "list size (1: successive cancellation)";
117 }
118}
119
120type ParamsFn = fn() -> Vec<ParamInfo>;
122
123const CODES: [(&str, ParamsFn); 5] = [
126 ("conv", ConvSpec::params),
127 ("rs", RsSpec::params),
128 ("turbo", TurboSpec::params),
129 ("ldpc", LdpcSpec::params),
130 ("polar", PolarSpec::params),
131];
132
133#[derive(Clone, Deserialize)]
136#[serde(transparent)]
137pub(super) struct Code(pub(super) Json);
138
139impl ParamType for Code {
140 fn schema() -> Json {
141 variants_schema(&Code::variants())
142 }
143
144 fn variants() -> Vec<(&'static str, Vec<ParamInfo>)> {
145 CODES.iter().map(|&(kind, params)| (kind, params())).collect()
146 }
147}
148
149pub fn build_code(v: &Json) -> Result<Box<dyn BlockCode + Send + Sync>> {
154 let mut p = match v {
155 Json::Object(o) => o.clone(),
156 other => return Err(Error::Config(format!("a code is a mapping with a type, got {other}"))),
157 };
158 let names = CODES.map(|(k, _)| k);
159 let Some(Json::String(given)) = p.remove("type") else {
160 return Err(Error::Config(format!("a code needs a type among {names:?}")));
161 };
162 let kind =
163 crate::blocks::schema::choice(&names, &given).ok_or_else(|| crate::unknown("code type", &given, &names))?;
164 let what = format!("{kind} code");
165 let p = Json::Object(p);
166 Ok(match kind {
167 "conv" => {
168 let ConvSpec { k, hard, puncture } = ConvSpec::parse(&what, p)?;
169 let mut code = ConvolutionalCode::k7(k)?;
170 code.hard_decision = hard;
171 match puncture {
172 None => Box::new(code),
173 Some(name) => {
174 Box::new(Punctured::new(code, Puncturer::from_name(&name).map_err(|e| e.context("conv"))?)?)
175 }
176 }
177 }
178 "rs" => {
179 let RsSpec { m, t, n, first_root, root_step, poly } = RsSpec::parse(&what, p)?;
180 let n = n.unwrap_or((1usize << m) - 1);
181 let poly = poly.or_else(|| comnum_math::gf2m::primitive_polynomial(m)).unwrap_or(0);
182 Box::new(ReedSolomon::with_field(m, poly, first_root, root_step, t, n)?)
183 }
184 "turbo" => {
185 let TurboSpec { k, iterations, max_log } = TurboSpec::parse(&what, p)?;
186 let mut code = TurboCode::lte(k)?;
187 code.decoder = TurboDecoder { iterations, jacobian: Jacobian::from_max_log(max_log) };
188 Box::new(code)
189 }
190 "ldpc" => {
191 let q = LdpcSpec::parse(&what, p)?;
192 Box::new(q.settings.code(q.n, q.k)?)
193 }
194 "polar" => {
195 let PolarSpec { n, k, design_ebn0_db, crc, list } = PolarSpec::parse(&what, p)?;
196 let mut c = PolarCode::new(n, k, design_ebn0_db)?;
197 if let Some(name) = crc {
198 c = c.with_crc(Crc::from_name(&name)?)?;
199 }
200 Box::new(c.with_list(list)?)
201 }
202 _ => unreachable!("a name of CODES"),
203 })
204}
205
206#[cfg(test)]
207mod tests {
208 use super::super::pas::PasSpec;
210 use super::*;
211 use serde_json::json;
212
213 #[test]
214 fn the_ccsds_recipe_of_the_rs_block_builds_the_code_of_ccsds() {
215 let spec = json!({"type": "rs", "m": 8, "t": 16, "first_root": 112, "root_step": 11, "poly": 391});
218 let code = build_code(&spec).unwrap();
219 let ccsds = comnum_fec::ccsds::CcsdsRs::new(16, 1, 0).unwrap();
220 let info: Vec<u8> = (0..code.k()).map(|i| u8::from(i % 7 == 3)).collect();
221 assert_eq!(code.encode_block(&info).unwrap(), ccsds.code().encode(&info).unwrap());
222 let other = json!({"type": "rs", "m": 8, "t": 16, "first_root": 112, "root_step": 11});
223 assert_ne!(build_code(&other).unwrap().encode_block(&info).unwrap(), ccsds.code().encode(&info).unwrap());
224 }
225
226 #[test]
227 fn the_ldpc_defaults_are_those_of_com_fec() {
228 use crate::blocks::schema::ParamDefault;
229 for params in [PasSpec::params(), LdpcSpec::params()] {
231 let shown: Vec<(&str, String, Json)> = params
232 .into_iter()
233 .filter_map(|p| match p.default {
234 ParamDefault::Value(v, text) => Some((p.name, text, v)),
235 _ => None,
236 })
237 .collect();
238 let get = |name: &str| shown.iter().find(|(n, _, _)| *n == name).cloned().unwrap();
239 for (name, value) in [
240 ("var_degree", json!(ldpc::DEFAULT_VAR_DEGREE)),
241 ("seed", json!(ldpc::DEFAULT_PEG_SEED)),
242 ("rule", json!(ldpc::DEFAULT_RULE.name())),
243 ("alpha", json!(ldpc::DEFAULT_ALPHA)),
244 ("layered", json!(ldpc::DEFAULT_LAYERED)),
245 ("iterations", json!(ldpc::DEFAULT_MAX_ITERATIONS)),
246 ] {
247 let (_, text, v) = get(name);
248 assert_eq!(v, value, "{name}");
249 assert_eq!(text, value.as_str().map_or_else(|| value.to_string(), str::to_string), "{name}");
251 }
252 }
253 let q = LdpcSpec::parse("ldpc code", json!({"n": 128, "k": 64})).unwrap().settings;
254 assert_eq!(
255 (q.var_degree, q.seed, q.rule.as_str()),
256 (ldpc::DEFAULT_VAR_DEGREE, ldpc::DEFAULT_PEG_SEED, "sum_product")
257 );
258 assert_eq!(
259 (q.alpha, q.layered, q.iterations),
260 (ldpc::DEFAULT_ALPHA, ldpc::DEFAULT_LAYERED, ldpc::DEFAULT_MAX_ITERATIONS)
261 );
262 }
263}