1pub mod fano;
12
13use alloc::vec;
14
15#[cfg(not(feature = "std"))]
16use num_traits::Float;
17
18use super::FecCodec;
19use crate::core::{FecOpts, FecResult};
20
21#[derive(Copy, Clone, Debug, Default)]
27pub struct ConvFano;
28
29impl ConvFano {
30 pub const NBITS: usize = 81;
32 pub const DEFAULT_DELTA: i32 = 17;
36 pub const DEFAULT_MAX_CYCLES: u64 = 10_000;
38 pub const METRIC_SCALE: f32 = 16.0;
40 pub const METRIC_BIAS: f32 = 1.0;
49}
50
51fn pack_msg_with_tail(info: &[u8]) -> [u8; 11] {
54 assert_eq!(info.len(), 50, "WSPR info payload must be 50 bits");
55 let mut packed = [0u8; 11];
56 for (i, &b) in info.iter().enumerate() {
57 if b & 1 != 0 {
58 packed[i / 8] |= 1 << (7 - (i % 8));
59 }
60 }
61 packed
63}
64
65impl FecCodec for ConvFano {
66 const N: usize = 162;
67 const K: usize = 50;
68
69 fn encode(&self, info: &[u8], codeword: &mut [u8]) {
70 assert_eq!(info.len(), Self::K);
71 assert_eq!(codeword.len(), Self::N);
72 let packed = pack_msg_with_tail(info);
73 let mut out = vec![0u8; 2 * Self::NBITS];
74 fano::conv_encode(&packed, Self::NBITS, &mut out);
75 codeword.copy_from_slice(&out);
76 }
77
78 fn decode_soft(&self, llr: &[f32], _opts: &FecOpts) -> Option<FecResult> {
79 assert_eq!(llr.len(), Self::N);
80 let bm = fano::build_branch_metrics(llr, Self::METRIC_BIAS, Self::METRIC_SCALE);
81 let res = fano::fano_decode(
82 &bm,
83 Self::NBITS,
84 Self::DEFAULT_DELTA,
85 Self::DEFAULT_MAX_CYCLES,
86 );
87 if !res.converged {
88 return None;
89 }
90
91 let mut info = vec![0u8; Self::K];
93 for i in 0..Self::K {
94 info[i] = (res.data[i / 8] >> (7 - (i % 8))) & 1;
95 }
96
97 let mut reencoded = vec![0u8; Self::N];
99 self.encode(&info, &mut reencoded);
100 let hard_errors = llr
101 .iter()
102 .zip(reencoded.iter())
103 .filter(|&(&l, &c)| (c == 1) != (l < 0.0))
104 .count() as u32;
105
106 Some(FecResult {
107 info,
108 hard_errors,
109 iterations: 0,
110 })
111 }
112}
113
114#[derive(Copy, Clone, Debug, Default)]
121pub struct ConvFano232;
122
123impl ConvFano232 {
124 pub const NBITS: usize = 103;
126 pub const DEFAULT_DELTA: i32 = 170;
130 pub const DEFAULT_MAX_CYCLES: u64 = 10_000;
134}
135
136#[rustfmt::skip]
145const JT9_XX0: [f32; 256] = [
146 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000,
147 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000,
148 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000,
149 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000,
150 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000,
151 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000,
152 0.988, 1.000, 0.991, 0.993, 1.000, 0.995, 1.000, 0.991,
153 1.000, 0.991, 0.992, 0.991, 0.990, 0.990, 0.992, 0.996,
154 0.990, 0.994, 0.993, 0.991, 0.992, 0.989, 0.991, 0.987,
155 0.985, 0.989, 0.984, 0.983, 0.979, 0.977, 0.971, 0.975,
156 0.974, 0.970, 0.970, 0.970, 0.967, 0.962, 0.960, 0.957,
157 0.956, 0.953, 0.942, 0.946, 0.937, 0.933, 0.929, 0.920,
158 0.917, 0.911, 0.903, 0.895, 0.884, 0.877, 0.869, 0.858,
159 0.846, 0.834, 0.821, 0.806, 0.790, 0.775, 0.755, 0.737,
160 0.713, 0.691, 0.667, 0.640, 0.612, 0.581, 0.548, 0.510,
161 0.472, 0.425, 0.378, 0.328, 0.274, 0.212, 0.146, 0.075,
162 0.000,-0.079,-0.163,-0.249,-0.338,-0.425,-0.514,-0.606,
163 -0.706,-0.796,-0.895,-0.987,-1.084,-1.181,-1.280,-1.376,
164 -1.473,-1.587,-1.678,-1.790,-1.882,-1.992,-2.096,-2.201,
165 -2.301,-2.411,-2.531,-2.608,-2.690,-2.829,-2.939,-3.058,
166 -3.164,-3.212,-3.377,-3.463,-3.550,-3.768,-3.677,-3.975,
167 -4.062,-4.098,-4.186,-4.261,-4.472,-4.621,-4.623,-4.608,
168 -4.822,-4.870,-4.652,-4.954,-5.108,-5.377,-5.544,-5.995,
169 -5.632,-5.826,-6.304,-6.002,-6.559,-6.369,-6.658,-7.016,
170 -6.184,-7.332,-6.534,-6.152,-6.113,-6.288,-6.426,-6.313,
171 -9.966,-6.371,-9.966,-7.055,-9.966,-6.629,-6.313,-9.966,
172 -5.858,-9.966,-9.966,-9.966,-9.966,-9.966,-9.966,-9.966,
173 -9.966,-9.966,-9.966,-9.966,-9.966,-9.966,-9.966,-9.966,
174 -9.966,-9.966,-9.966,-9.966,-9.966,-9.966,-9.966,-9.966,
175 -9.966,-9.966,-9.966,-9.966,-9.966,-9.966,-9.966,-9.966,
176 -9.966,-9.966,-9.966,-9.966,-9.966,-9.966,-9.966,-9.966,
177 -9.966,-9.966,-9.966,-9.966,-9.966,-9.966,-9.966,-9.966,
178];
179
180fn build_jt9_mettab() -> [[i32; 2]; 256] {
193 const BIAS: f32 = 0.5;
194 const SCALE: f32 = 50.0;
195 const IB: usize = 160;
196 const SLOPE: i32 = 2;
197 let mut mettab = [[0i32; 2]; 256];
198 let mut col0 = [0i32; 256];
200 for (i, val) in JT9_XX0.iter().enumerate() {
201 col0[i] = (SCALE * (val - BIAS)).round() as i32;
202 }
203 let pivot = col0[IB];
204 for i in (IB + 1)..=255 {
205 col0[i] = pivot - SLOPE * (i as i32 - IB as i32);
206 }
207 for i in 0..=255 {
208 mettab[i][0] = col0[i];
209 }
210 for i in 1..=255 {
215 mettab[256 - i][1] = col0[i];
216 }
217 mettab[0][1] = mettab[1][1];
218 mettab
219}
220
221fn jt9_branch_metrics(llrs: &[f32]) -> alloc::vec::Vec<[i32; 2]> {
226 let mettab = build_jt9_mettab();
227 llrs.iter()
228 .map(|&l| {
229 let i4 = (-l).round().clamp(-127.0, 127.0) as i32;
231 let idx = (i4 + 128) as usize;
232 [mettab[idx][0], mettab[idx][1]]
234 })
235 .collect()
236}
237
238fn pack_msg_with_tail_jt9(info: &[u8]) -> [u8; 13] {
242 assert_eq!(info.len(), 72, "JT9 info payload must be 72 bits");
243 let mut packed = [0u8; 13];
244 for (i, &b) in info.iter().enumerate() {
245 if b & 1 != 0 {
246 packed[i / 8] |= 1 << (7 - (i % 8));
247 }
248 }
249 packed
251}
252
253impl FecCodec for ConvFano232 {
254 const N: usize = 206;
255 const K: usize = 72;
256
257 fn encode(&self, info: &[u8], codeword: &mut [u8]) {
258 assert_eq!(info.len(), Self::K);
259 assert_eq!(codeword.len(), Self::N);
260 let packed = pack_msg_with_tail_jt9(info);
261 let mut out = vec![0u8; 2 * Self::NBITS];
262 fano::conv_encode(&packed, Self::NBITS, &mut out);
263 codeword.copy_from_slice(&out);
264 }
265
266 fn decode_soft(&self, llr: &[f32], opts: &FecOpts) -> Option<FecResult> {
267 assert_eq!(llr.len(), Self::N);
268 let bm = jt9_branch_metrics(llr);
275 let max_cycles = opts.max_cycles_per_bit.unwrap_or(Self::DEFAULT_MAX_CYCLES);
276 let res = fano::fano_decode(&bm, Self::NBITS, Self::DEFAULT_DELTA, max_cycles);
277 if !res.converged {
278 return None;
279 }
280 let mut info = vec![0u8; Self::K];
281 for i in 0..Self::K {
282 info[i] = (res.data[i / 8] >> (7 - (i % 8))) & 1;
283 }
284 let mut reencoded = vec![0u8; Self::N];
285 self.encode(&info, &mut reencoded);
286 let hard_errors = llr
287 .iter()
288 .zip(reencoded.iter())
289 .filter(|&(&l, &c)| (c == 1) != (l < 0.0))
290 .count() as u32;
291 Some(FecResult {
292 info,
293 hard_errors,
294 iterations: 0,
295 })
296 }
297}
298
299#[cfg(test)]
300mod tests {
301 use super::*;
302
303 #[test]
304 fn encode_then_decode_roundtrip() {
305 let codec = ConvFano;
306 let mut info = vec![0u8; 50];
308 for (i, slot) in info.iter_mut().enumerate() {
309 *slot = (((i * 7) ^ 0x2a) & 1) as u8;
310 }
311 let mut cw = vec![0u8; 162];
312 codec.encode(&info, &mut cw);
313
314 let llr: Vec<f32> = cw
316 .iter()
317 .map(|&b| if b == 0 { 8.0 } else { -8.0 })
318 .collect();
319 let r = codec
320 .decode_soft(&llr, &FecOpts::default())
321 .expect("perfect LLRs must decode");
322 assert_eq!(r.info, info);
323 assert_eq!(r.hard_errors, 0);
324 }
325
326 #[test]
327 fn jt9_encode_decode_roundtrip() {
328 let codec = ConvFano232;
329 let mut info = vec![0u8; 72];
330 for (i, slot) in info.iter_mut().enumerate() {
331 *slot = (((i * 11) ^ 0x55) & 1) as u8;
332 }
333 let mut cw = vec![0u8; 206];
334 codec.encode(&info, &mut cw);
335 let llr: Vec<f32> = cw
336 .iter()
337 .map(|&b| if b == 0 { 8.0 } else { -8.0 })
338 .collect();
339 let r = codec
340 .decode_soft(&llr, &FecOpts::default())
341 .expect("perfect LLRs must decode");
342 assert_eq!(r.info, info);
343 assert_eq!(r.hard_errors, 0);
344 }
345
346 #[test]
347 fn jt9_tolerates_a_few_errors() {
348 let codec = ConvFano232;
349 let info: Vec<u8> = (0..72).map(|i| i as u8 & 1).collect();
350 let mut cw = vec![0u8; 206];
351 codec.encode(&info, &mut cw);
352 let mut llr: Vec<f32> = cw
357 .iter()
358 .map(|&b| if b == 0 { 100.0 } else { -100.0 })
359 .collect();
360 for &pos in &[3usize, 17, 42, 91, 155, 199] {
361 llr[pos] = -llr[pos] * 0.3;
362 }
363 let r = codec
364 .decode_soft(&llr, &FecOpts::default())
365 .expect("should correct 6 weak errors");
366 assert_eq!(r.info, info);
367 }
368
369 #[test]
370 fn tolerates_a_few_errors() {
371 let codec = ConvFano;
372 let info: Vec<u8> = (0..50).map(|i| i as u8 & 1).collect();
373 let mut cw = vec![0u8; 162];
374 codec.encode(&info, &mut cw);
375 let mut llr: Vec<f32> = cw
377 .iter()
378 .map(|&b| if b == 0 { 6.0 } else { -6.0 })
379 .collect();
380 for &pos in &[3usize, 17, 42, 91, 155] {
383 llr[pos] = -llr[pos] * 0.3;
384 }
385 let r = codec
386 .decode_soft(&llr, &FecOpts::default())
387 .expect("should correct 5 weak errors");
388 assert_eq!(r.info, info);
389 }
390}