C(++)ECCO
C++ Error Control COding: a header-only library for ECC simulations and experiments, modeling complete coding systems across arbitrary finite fields and complex inter-field relationships - Christian Senger <senger@inue.uni-stuttgart.de>
Loading...
Searching...
No Matches
codes.hpp
Go to the documentation of this file.
1/**
2 * @file codes.hpp
3 * @brief Error control codes library
4 * @author Christian Senger <senger@inue.uni-stuttgart.de>
5 * @version 2.5.5
6 * @date 2026
7 *
8 * @copyright
9 * Copyright (c) 2026, Christian Senger <senger@inue.uni-stuttgart.de>
10 *
11 * Licensed for noncommercial use only, including academic teaching, research, and personal non-profit purposes.
12 * Commercial use is prohibited without a separate commercial license. See the [LICENSE](../../LICENSE) file in the
13 * repository root for full terms and how to request a commercial license.
14 */
15
16#ifndef CODES_HPP
17#define CODES_HPP
18
19#include <bit>
20#include <queue>
21
22#include "code_bounds.hpp"
23#include "graphs.hpp"
24/*
25// transitive
26#include <algorithm>
27#include <array>
28#include <cmath>
29#include <concepts>
30#include <cstddef>
31#include <cstdint>
32#include <exception>
33#include <fstream>
34#include <iomanip>
35#include <iostream>
36#include <iterator>
37#include <limits>
38#include <numeric>
39#include <optional>
40#include <random>
41#include <ranges>
42#include <set>
43#include <stdexcept>
44#include <string>
45#include <type_traits>
46#include <unordered_map>
47#include <utility>
48#include <vector>
49
50#include "fields.hpp"
51#include "helpers.hpp"
52#include "matrices.hpp"
53#include "polynomials.hpp"
54#include "vectors.hpp"
55*/
56
57#define BOLD(x) "\033[1m" x "\033[0m"
58
59// Documentation note: largest decoding table (in MB of payload) that the ML and BD paradigm
60// decoders build voluntarily. The limit applies to standard array and to Meggitt table,
61// and beyond it the paradigm decoders (BD, ML) fall back from standard array to Viterbi decoding on the minimal
62// trellis. The dec_*standard_array methods and dec_Meggitt build their tables regardless of size, and a build larger
63// than the limit announces itself on std::clog. Override with -DDECODING_TABLES_LIMIT=<value>.
64#ifndef DECODING_TABLES_LIMIT
65#define DECODING_TABLES_LIMIT 256
66#endif
67
68// Documentation note: default list size Dec_list uses for list_method_t::Viterbi and
69// list_method_t::Viterbi_soft, so it applies to list Viterbi only. Override with -DVITERBI_LIST_SIZE=<value> or via
70// Dec_list::set_viterbi_list_size.
71#ifndef VITERBI_LIST_SIZE
72#define VITERBI_LIST_SIZE 2
73#endif
74
75// Documentation note: default cap (aligned with practical systems as DVB-S2, WiFi, 5G) on the number of
76// belief-propagation iterations. The default value of the max_iterations parameter on the dec_BP virtuals in Code and
77// on overrides, and the initial value of Dec's bp_max_iterations knob. Override with
78// -DBP_MAX_ITERATIONS=<value>.
79#ifndef BP_MAX_ITERATIONS
80#define BP_MAX_ITERATIONS 50
81#endif
82
83// Documentation note: default OSD order on the dec_OSD virtuals in Code and overrides, and the initial value
84// of the osd_order knob of Dec. Override with -DOSD_ORDER=<value>.
85#ifndef OSD_ORDER
86#define OSD_ORDER 2
87#endif
88
89// Documentation note: default LC-OSD constraint degree on the dec_LC_OSD virtuals in Code and on overrides, and the
90// initial value of the lc_osd_delta knob of Dec. Override with -DLC_OSD_DELTA=<value>.
91#ifndef LC_OSD_DELTA
92#define LC_OSD_DELTA 4
93#endif
94
95// Documentation note: default maximum number of re-encoded candidates on the dec_LC_OSD virtuals in Code and on
96// overrides, and the initial value of the lc_osd_ell_max knob of Dec. Override with -DLC_OSD_LIST_SIZE=<value>.
97#ifndef LC_OSD_LIST_SIZE
98#define LC_OSD_LIST_SIZE 1024
99#endif
100
101// Documentation note: default interpolation parameters of the algebraic list decoders on the
102// dec_SA_list/dec_GSA_list/dec_KVA_list virtuals in Code and on overrides, and the initial values of the corresponding
103// Dec_list knobs. The list size determines the maximal y-degree of the bivariate interpolation polynomial, the GSA
104// multiplicity is the interpolation multiplicity placed on each received symbol, and the KVA strength scales the
105// posterior symbol probabilities before they are truncated to interpolation multiplicities (proportional multiplicity
106// assignment). Override with -DSA_LIST_SIZE=<value> etc.
107#ifndef SA_LIST_SIZE
108#define SA_LIST_SIZE 2
109#endif
110
111#ifndef GSA_MULTIPLICITY
112#define GSA_MULTIPLICITY 2
113#endif
114
115#ifndef GSA_LIST_SIZE
116#define GSA_LIST_SIZE 4
117#endif
118
119#ifndef KVA_STRENGTH
120#define KVA_STRENGTH 7
121#endif
122
123#ifndef KVA_LIST_SIZE
124#define KVA_LIST_SIZE 8
125#endif
126
127namespace CECCO {
128
129template <FiniteFieldType T>
131 constexpr size_t q = T::get_size();
132
133 auto a = Vector<InfInt>(n + 1);
134 for (size_t i = 0; i < n + 1; ++i) a.set_component(i, A[i]);
135
136 Matrix<InfInt> K(n + 1, n + 1);
137 for (size_t i = 0; i < n + 1; ++i) {
138 if (a[i] == 0) continue;
139 for (size_t j = 0; j < n + 1; ++j) {
140 InfInt sum = 0;
141 // for (size_t h = 0; h <= std::min<size_t>(i, j); ++h) {
142 for (size_t h = 0; h <= j; ++h) {
143 /*
144 sum += sqm<InfInt>(-1, h) * bin<InfInt>(i, h) * bin<InfInt>(n - i, j - h) *
145 sqm<InfInt>(q - 1, j - h);
146 */
147 InfInt a = bin<InfInt>(n - i, j - h);
148 if (a != 0) {
149 const auto b = bin<InfInt>(i, h);
150 if (b != 0) {
151 const auto c = sqm<InfInt>(q - 1, j - h);
152 if (h % 2)
153 sum -= a * b * c;
154 else
155 sum += a * b * c;
156 }
157 }
158 }
159 K.set_component(i, j, sum);
160 }
161 }
162 return Polynomial<InfInt>(a * K / sqm<InfInt>(q, k));
163}
164
165namespace details {
166
167inline const int index = std::ios_base::xalloc();
168
169/// @brief Verbosity levels stored in `os.iword(index)`, set by the show* manipulators
170inline constexpr long basic = 0;
171inline constexpr long most = 1;
172inline constexpr long all = 2;
173inline constexpr long special = 3;
174
175/// @brief Restores the stream's verbosity level on scope exit (used when printing info for codes constructed from other
176/// codes)
178 public:
179 explicit verbosity_guard(std::ostream& os) : os(os), saved(os.iword(index)) {}
180 ~verbosity_guard() { os.iword(index) = saved; }
181
182 private:
183 std::ostream& os;
184 long saved;
185};
186
187} // namespace details
188
189inline std::ostream& showbasic(std::ostream& os) {
190 os.iword(details::index) = details::basic;
191 return os;
192}
193
194inline std::ostream& showmost(std::ostream& os) {
195 os.iword(details::index) = details::most;
196 return os;
197}
198
199inline std::ostream& showall(std::ostream& os) {
200 os.iword(details::index) = details::all;
201 return os;
202}
203
204inline std::ostream& showspecial(std::ostream& os) {
206 return os;
207}
208
209template <ComponentType T>
210class Code {
211 public:
212 Code(size_t n) noexcept : n(n) {}
213
214 Code(const Code& other) noexcept : n(other.n) {}
215
216 Code(Code&&) noexcept = default;
217
218 virtual ~Code() = default;
219
220 Code& operator=(const Code& other) noexcept {
221 if (this != &other) {
222 n = other.n;
223 }
224 return *this;
225 }
226
227 Code& operator=(Code&&) noexcept = default;
228
229 size_t get_n() const noexcept { return n; }
230
231 virtual void get_info(std::ostream&) const {}
232 virtual Vector<T> enc(const Vector<T>&) const { throw std::logic_error("Encoding not supported for this code!"); }
233 virtual Vector<T> encinv(const Vector<T>&) const {
234 throw std::logic_error("Inverse encoding not supported for this code!");
235 }
236 virtual Vector<T> dec_BD(const Vector<T>&) const {
237 throw std::logic_error("Bounded distance decoding not supported for this code!");
238 }
239 virtual Vector<T> dec_standard_array(const Vector<T>&) const {
240 throw std::logic_error("Standard array decoding not supported for this code!");
241 }
242 virtual Vector<T> dec_BD_standard_array(const Vector<T>&) const {
243 throw std::logic_error("Standard array BD decoding not supported for this code!");
244 }
245 virtual Vector<T> dec_boosted_BD_standard_array(const Vector<T>&) const {
246 throw std::logic_error("Boosted standard array BD decoding not supported for this code!");
247 }
248 virtual Vector<T> dec_ML(const Vector<T>&) const {
249 throw std::logic_error("ML decoding not supported for this code!");
250 }
251 virtual Vector<T> dec_Viterbi(const Vector<T>&, const std::string& = "") const {
252 throw std::logic_error("Viterbi decoding not supported for this code!");
253 }
254 virtual std::vector<Vector<T>> dec_Viterbi_list(const Vector<T>&, size_t) const {
255 throw std::logic_error("List Viterbi decoding not supported for this code!");
256 }
257 virtual Vector<T> dec_recursive(const Vector<T>&) const {
258 throw std::logic_error("Recursive decoding not supported for this code!");
259 }
260 virtual Vector<T> dec_Meggitt(const Vector<T>&) const {
261 throw std::logic_error("Meggitt decoding not supported for this code!");
262 }
263 virtual Vector<T> dec_WBA(const Vector<T>&) const {
264 throw std::logic_error("Welch-Berlekamp decoding not supported for this code!");
265 }
266 virtual Vector<T> dec_WBA_optimized(const Vector<T>&) const {
267 throw std::logic_error("Welch-Berlekamp decoding not supported for this code!");
268 }
269 virtual Vector<T> dec_BMA(const Vector<T>&) const {
270 throw std::logic_error("Berlekamp-Massey decoding not supported for this code!");
271 }
272 virtual std::vector<Vector<T>> dec_SA_list(const Vector<T>&, size_t = SA_LIST_SIZE) const {
273 throw std::logic_error("Sudan list decoding not supported for this code!");
274 }
276 throw std::logic_error("Sudan list decoding not supported for this code!");
277 }
279 size_t = GSA_LIST_SIZE) const {
280 throw std::logic_error("Guruswami-Sudan list decoding not supported for this code!");
281 }
283 size_t = GSA_LIST_SIZE) const {
284 throw std::logic_error("Guruswami-Sudan list decoding not supported for this code!");
285 }
286 // Documentation note: all soft-input decoders share one symbol-level LLR format: a matrix with q−1 rows
287 // and one column per code symbol, where row a−1 of column s holds L_s(a) = ln(P(·|0)/P(·|a)) for
288 // symbol a = 1, …, q−1, cf. details::symbol_costs_from_llrs in graphs.hpp. For binary codes this
289 // is a single row; the Vector<double> overloads are that binary special case. LLRCalculator
290 // produces this format directly. This lossless format makes the soft output (below) reusable as
291 // soft input, e.g. for block turbo decoding over any field.
292 virtual Vector<T> dec_ML_soft(const Vector<double>&) const {
293 throw std::logic_error("Soft-input ML decoding not supported for this code!");
294 }
295 virtual Vector<T> dec_ML_soft(const Matrix<double>&) const {
296 throw std::logic_error("Soft-input ML decoding not supported for this code!");
297 }
298 virtual Vector<T> dec_Viterbi_soft(const Vector<double>&, const std::string& = "") const {
299 throw std::logic_error("Soft-input Viterbi decoding not supported for this code!");
300 }
301 virtual Vector<T> dec_Viterbi_soft(const Matrix<double>&, const std::string& = "") const {
302 throw std::logic_error("Soft-input Viterbi decoding not supported for this code!");
303 }
304 virtual std::vector<Vector<T>> dec_Viterbi_soft_list(const Vector<double>&, size_t) const {
305 throw std::logic_error("Soft-input list Viterbi decoding not supported for this code!");
306 }
307 virtual std::vector<Vector<T>> dec_Viterbi_soft_list(const Matrix<double>&, size_t) const {
308 throw std::logic_error("Soft-input list Viterbi decoding not supported for this code!");
309 }
310 // Documentation note: pass nullptr as Lambda_ptr when only the filename argument is needed.
311 // Lambda_ptr receives a-posteriori LLRs in the same symbol-level format as the soft input
312 // matrix: q−1 rows and n columns. These are posterior, not extrinsic, LLRs.
313 virtual Vector<T> dec_BCJR(const Vector<double>&, Matrix<double>* = nullptr, const std::string& = "") const {
314 throw std::logic_error("BCJR decoding not supported for this code!");
315 }
316 virtual Vector<T> dec_BCJR(const Matrix<double>&, Matrix<double>* = nullptr, const std::string& = "") const {
317 throw std::logic_error("BCJR decoding not supported for this code!");
318 }
319 virtual Vector<T> dec_ssMAP(const Vector<double>&, Matrix<double>* = nullptr, const std::string& = "") const {
320 throw std::logic_error("Symbol-wise MAP decoding not supported for this code!");
321 }
322 virtual Vector<T> dec_ssMAP(const Matrix<double>&, Matrix<double>* = nullptr, const std::string& = "") const {
323 throw std::logic_error("Symbol-wise MAP decoding not supported for this code!");
324 }
325 // Documentation note: the max_iterations default is repeated on the base virtuals and on the LinearCode overrides
326 // because virtual default arguments bind from the static type of the call; keep the two in sync
327 virtual Vector<T> dec_BP(const Vector<double>&, size_t = BP_MAX_ITERATIONS, Matrix<double>* = nullptr,
328 const std::string& = "", size_t* = nullptr) const {
329 throw std::logic_error("Belief-propagation decoding not supported for this code!");
330 }
331 virtual Vector<T> dec_BP(const Matrix<double>&, size_t = BP_MAX_ITERATIONS, Matrix<double>* = nullptr,
332 const std::string& = "", size_t* = nullptr) const {
333 throw std::logic_error("Belief-propagation decoding not supported for this code!");
334 }
335 virtual Vector<T> dec_OSD(const Vector<double>&, size_t = OSD_ORDER) const {
336 throw std::logic_error("OSD decoding not supported for this code!");
337 }
338 virtual Vector<T> dec_OSD(const Matrix<double>&, size_t = OSD_ORDER) const {
339 throw std::logic_error("OSD decoding not supported for this code!");
340 }
341 virtual Vector<T> dec_LC_OSD(const Vector<double>&, size_t = LC_OSD_DELTA, size_t = LC_OSD_LIST_SIZE) const {
342 throw std::logic_error("LC-OSD decoding not supported for this code!");
343 }
344 virtual Vector<T> dec_LC_OSD(const Matrix<double>&, size_t = LC_OSD_DELTA, size_t = LC_OSD_LIST_SIZE) const {
345 throw std::logic_error("LC-OSD decoding not supported for this code!");
346 }
347 virtual std::vector<Vector<T>> dec_KVA_list(const Vector<double>&, size_t = KVA_STRENGTH,
348 size_t = KVA_LIST_SIZE) const {
349 throw std::logic_error("Koetter-Vardy list decoding not supported for this code!");
350 }
351 virtual std::vector<Vector<T>> dec_KVA_list(const Matrix<double>&, size_t = KVA_STRENGTH,
352 size_t = KVA_LIST_SIZE) const {
353 throw std::logic_error("Koetter-Vardy list decoding not supported for this code!");
354 }
355#ifdef CECCO_ERASURE_SUPPORT
356 virtual Vector<T> dec_BD_EE(const Vector<T>&) const {
357 throw std::logic_error("BD error/erasure decoding not supported for this code!");
358 }
359 virtual Vector<T> dec_BD_standard_array_EE(const Vector<T>&) const {
360 throw std::logic_error("Standard array BD error/erasure decoding not supported for this code!");
361 }
362 virtual Vector<T> dec_ML_EE(const Vector<T>&) const {
363 throw std::logic_error("ML error/erasure decoding not supported for this code!");
364 }
365 virtual Vector<T> dec_Viterbi_EE(const Vector<T>&, const std::string& = "") const {
366 throw std::logic_error("Viterbi error/erasure decoding not supported for this code!");
367 }
368 virtual Vector<T> dec_recursive_EE(const Vector<T>&) const {
369 throw std::logic_error("Recursive error/erasure decoding not supported for this code!");
370 }
371 virtual Vector<T> dec_WBA_EE(const Vector<T>&) const {
372 throw std::logic_error("Welch-Berlekamp error/erasure decoding not supported for this code!");
373 }
374 virtual Vector<T> dec_BMA_EE(const Vector<T>&) const {
375 throw std::logic_error("Berlekamp-Massey error/erasure decoding not supported for this code!");
376 }
377 virtual Vector<T> dec_GMD(const Vector<double>&) const {
378 throw std::logic_error("GMD decoding not supported for this code!");
379 }
380 virtual Vector<T> dec_GMD(const Matrix<double>&) const {
381 throw std::logic_error("GMD decoding not supported for this code!");
382 }
383#endif
384
385 protected:
386 // Documentation note: shared framing of the get_info overrides. Prints the BASE layer (qualified call,
387 // so no virtual dispatch) followed by a line break, except at details::special, which requests
388 // the derived payload only. Returns whether the payload should be printed.
389 template <class BASE>
390 bool info_prelude(std::ostream& os) const {
391 const long level = os.iword(details::index);
392 if (level < details::special) {
393 static_cast<const BASE&>(*this).BASE::get_info(os);
394 if (level > details::basic) os << std::endl;
395 }
396 return level > details::basic;
397 }
398
400#ifdef CECCO_ERASURE_SUPPORT
402 std::vector<size_t> X;
403 for (size_t i = 0; i < n; ++i) {
404 if (r[i].is_erased()) X.push_back(i);
405 }
406 return X;
407 }
408#endif
409};
410
411template <ComponentType T>
412class EmptyCode : public Code<T> {
413 public:
414 EmptyCode(size_t n) noexcept : Code<T>(n) {}
415
416 void get_info(std::ostream& os) const override {
417 if (os.iword(details::index) > details::basic) os << "Empty code";
418 }
419};
420
450
452
454
455template <FieldType T>
456class LinearCode;
457
458template <FiniteFieldType T>
459class UniverseCode;
460
461template <FiniteFieldType T>
462class ZeroCode;
463
464template <FiniteFieldType T>
465class SimplexCode;
466
467template <FiniteFieldType T>
469
470template <FieldType T, class B>
471 requires std::derived_from<B, LinearCode<T>>
472class ExtendedCode;
473
474// Documentation note: input iterator. operator++ updates the internal state in place, so any reference returned
475// by a prior operator*() is invalidated by the increment; copy the codeword if it must outlive ++.
476template <FiniteFieldType T>
478 // Documentation note: past-the-end sentinel returned by LinearCode::cend() (C++20 sentinel
479 // pattern).
480 struct Sentinel {};
481
482 friend LinearCode<T>;
483 friend bool operator==(const CodewordIterator& a, const CodewordIterator& b) {
484 return a.C == b.C && a.counter == b.counter;
485 }
486 friend bool operator!=(const CodewordIterator& a, const CodewordIterator& b) { return !(a == b); }
487 friend bool operator==(const CodewordIterator& a, Sentinel) { return a.counter == a.C->get_size(); }
488
489 // Documentation note: only LinearCode::cbegin()/cend() create codeword iterators; iteration
490 // always starts at the zero codeword.
491 explicit CodewordIterator(const LinearCode<T>& C) : C(&C), counter(0), d(C.get_k(), 0), c(C.get_n()) {}
492
493 public:
494 // Documentation note: the iterator protocol types must stay public: std::iterator_traits and
495 // the ranges concepts read them from namespace scope, where no friendship applies.
496 using iterator_category = std::input_iterator_tag;
497 using value_type = Vector<T>;
498 using difference_type = std::ptrdiff_t;
499 using pointer = const Vector<T>*;
500 using reference = const Vector<T>&;
501
502 const Vector<T>& operator*() const noexcept { return c; }
503
505 constexpr size_t q = T::get_size();
506 const size_t k = d.size();
507 const size_t n = c.get_n();
508 const Matrix<T>& G = C->get_G();
509 ++counter;
510 for (size_t i = 0; i < k; ++i) {
511 const size_t old_label = d[i];
512 const size_t new_label = (old_label + 1 == q) ? 0 : old_label + 1;
513 const T delta = T(new_label) - T(old_label);
514 for (size_t j = 0; j < n; ++j) c.set_component(j, c[j] + delta * G(i, j));
515 d[i] = new_label;
516 if (new_label != 0) break;
517 }
518 return *this;
519 }
520
522 CodewordIterator tmp = *this;
523 ++(*this);
524 return tmp;
525 }
526
527 private:
528 const LinearCode<T>* C;
529 InfInt counter;
530 std::vector<size_t> d;
531 Vector<T> c;
532};
533
534class decoding_failure : public std::exception {
535 public:
536 decoding_failure(const std::string& message) : message(message) {}
537 const char* what() const noexcept override { return message.c_str(); }
538
539 private:
540 const std::string message;
541};
542
543template <FieldType T>
544class LinearCode : public Code<T> {
545 public:
546 // expose base field (used by the extend() factories)
547 using FIELD = T;
548
549 // Documentation note: X is interpreted as generator matrix G if it has k rows, or as
550 // parity-check matrix H if it has n-k rows. If k == n-k, the ambiguity is resolved in favor
551 // of generator matrix.
552 LinearCode(size_t n, size_t k, const Matrix<T>& X) : Code<T>(n), k(k), MI(k, k) {
553 if (X.get_n() != n) throw std::invalid_argument("G must have " + std::to_string(n) + " columns");
554 if (k == 0) {
555 if (!X.is_zero() && !X.is_invertible())
556 throw std::invalid_argument("Cannot construct linear code: G must be a zero matrix");
557 G = ZeroMatrix<T>(1, n);
558 HT = IdentityMatrix<T>(n);
559 return;
560 }
561
562 const size_t redundancy = n - k;
563 if (X.get_m() == k) {
564 if (X.rank() != k)
565 throw std::invalid_argument("Cannot construct linear code: G must have full rank " + std::to_string(k));
566 G = X;
567 const auto Gp = rref(G);
568 HT = Gp.basis_of_nullspace().rref().transpose();
569 size_t i = 0;
570 for (size_t j = 0; j < k; ++j) {
571 const auto u = unit_vector<T>(k, j);
572 while (Gp.get_col(i) != u) ++i;
573 infoset.push_back(i);
574 }
575 } else if (X.get_m() == redundancy) {
576 if (X.rank() != redundancy)
577 throw std::invalid_argument("Cannot construct linear code: H must have full rank " +
578 std::to_string(redundancy));
579 G = X.basis_of_nullspace();
580 HT = transpose(X);
581 G.rref();
582 size_t i = 0;
583 for (size_t j = 0; j < k; ++j) {
584 const auto u = unit_vector<T>(k, j);
585 while (G.get_col(i) != u) ++i;
586 infoset.push_back(i);
587 }
588 } else {
589 throw std::invalid_argument("Cannot construct linear code: matrix must have " + std::to_string(k) +
590 " rows (as G) or " + std::to_string(redundancy) + " rows (as H), got " +
591 std::to_string(X.get_m()));
592 }
593 size_t j = 0;
594 for (auto it = infoset.cbegin(); it != infoset.cend(); ++it) {
595 MI.set_submatrix(0, j, G.get_submatrix(0, *it, k, 1));
596 ++j;
597 }
598 MI.invert();
599 }
600
601 // Documentation note: the eagerly filled polynomial/gamma caches must match what the lazy
602 // is_polynomial() computation would find, so gamma must not be divisible by x and is stored
603 // normalized.
604 LinearCode(size_t k, Polynomial<T> gamma) try
605 : LinearCode(
606 k + gamma.degree(), k,
607 ToeplitzMatrix(pad_back(pad_front(Vector<T>(gamma), k + gamma.degree()), 2 * k + gamma.degree() - 1), k,
608 k + gamma.degree())) {
609 if (gamma.trailing_degree() > 0) throw std::invalid_argument("generator polynomial must not be divisible by x");
610 set_gamma(normalize(std::move(gamma)));
611 } catch (const std::invalid_argument& e) {
612 throw std::invalid_argument(std::string("Cannot construct linear code from polynomial: ") + e.what());
613 }
614
615 LinearCode(const std::vector<Vector<T>>& codewords) : LinearCode(G_from_codewords(codewords)) {}
616
617 size_t get_k() const noexcept { return k; }
618 double get_R() const noexcept { return static_cast<double>(k) / this->n; }
619
622 {
623 size.call_once([this] {
624 if (size.has_value()) return;
626 });
627 return size.value();
628 }
629
630 const Matrix<T>& get_G() const noexcept { return G; }
631 const Matrix<T>& get_HT() const noexcept { return HT; }
632 Matrix<T> get_H() const { return transpose(HT); }
633
634 virtual size_t get_dmin() const {
635 dmin.call_once([this] {
636 if (dmin.has_value()) return;
637 if (k == 0) throw std::logic_error("Cannot calculate dmin of a dimension zero code!");
638
640 if constexpr (FiniteFieldType<T>) {
642 for (size_t i = 1; i <= d; ++i) {
643 if (weight_enumerator.value()[i] != 0) {
644 dmin = i;
645 return;
646 }
647 }
648 }
649 } else {
650 if (k == 1) {
651 dmin = G.get_row(0).wH();
652 return;
653 }
654
655 // for small codes the weight enumerator detour has approx. the same cost and gives the weight
656 // enumerator "for free"
657 if constexpr (FiniteFieldType<T>) {
658 if (get_size() < 1000) {
661 for (size_t i = 1; i <= d; ++i) {
662 if (weight_enumerator.value()[i] != 0) {
663 dmin = i;
664 return;
665 }
666 }
667 }
668 }
669
670 std::clog << "--> Calculating dmin, this requires finding minimal number of linearly dependent "
671 "columns in H"
672 << std::endl;
673 const size_t n = this->n;
674 const size_t redundancy = n - k;
675 for (size_t d = 1; d <= redundancy + 1; ++d) {
676 Matrix<T> M(d, redundancy);
677 std::vector<bool> selection(n); // zero-initialized
678 std::fill(selection.begin() + n - d, selection.end(), true);
679
680 do {
681 size_t i = 0;
682 for (size_t j = 0; j < n; ++j) {
683 if (selection[j]) {
685 ++i;
686 }
687 }
688 if (M.rank() < d) {
689 dmin = d;
690 return;
691 }
693 }
694 }
695 });
696
697 return dmin.value();
698 }
699
700 size_t get_tmax() const {
701 if (k == 0) throw std::logic_error("Cannot calculate tmax of a dimension zero code!");
702 return (get_dmin() - 1) / 2;
703 }
704
705 virtual const Polynomial<InfInt>& get_weight_enumerator() const {
706 if constexpr (!FiniteFieldType<T>) {
707 throw std::logic_error("Cannot calculate weight enumerator of code over infinite field!");
708 } else {
710 constexpr size_t q = T::get_size();
711 if (weight_enumerator.has_value()) return;
712 const size_t n = this->n;
713 const size_t redundancy = n - k;
714 if (k == 0) {
716 } else if (k <= redundancy) { // calculate directly
717 std::clog << "--> Calculating weight enumerator, this requires iterating through "
718 << sqm<InfInt>(q, k) << " codewords" << std::endl;
720 for (auto it = cbegin(); it != cend(); ++it)
722 } else if (k == n) {
723 UniverseCode<T> Cp(n);
725 } else { // calculate based on dual code and MacWilliams identity
726 std::clog << "--> Using MacWilliams' identity for: " << std::endl;
729 }
730 });
731
732 return weight_enumerator.value();
733 }
734 }
735
736 // Documentation note: weight enumerator of the p-ary image code (length n·[T:F_p], dimension
737 // k·[T:F_p] over F_p, p the characteristic), i.e. codeword weights counted in prime-field
738 // coordinates of the as_vector()/DEMUX expansion. For codes over prime fields image and code
739 // coincide.
742 {
743 constexpr size_t m = details::degree_over_prime_v<T>;
744 if constexpr (m == 1) {
745 return get_weight_enumerator();
746 } else {
749 constexpr uint16_t p = T::get_characteristic();
750 const size_t n = this->n;
751
752 if (k == 0) {
754 return;
755 }
756
757 std::clog << "--> Calculating p-ary image weight enumerator for:" << std::endl;
758 Matrix<Fp<p>> Gp(k * m, n * m);
759 for (size_t i = 0; i < k; ++i) {
760 for (size_t j = 0; j < m; ++j) {
761 const T beta(static_cast<int>(sqm<size_t>(p, j)));
762 for (size_t pos = 0; pos < n; ++pos) {
763 const auto image = (beta * G(i, pos)).template as_vector<Fp<p>>();
764 Gp.set_submatrix(i * m + j, pos * m, Matrix<Fp<p>>(image));
765 }
766 }
767 }
769 });
770
772 }
773 }
774
775 long double P_word(double pe) const {
776 const size_t n = this->n;
777 long double res = 0.0;
778 for (size_t i = get_tmax() + 1; i <= n; ++i)
779 res += to_long_double(bin<InfInt>(n, i)) * std::pow(static_cast<long double>(pe), i) *
780 std::pow(1.0L - pe, n - i);
781 return res;
782 }
783
784 long double P_error(double pe) const
786 {
787 const auto& A = get_weight_enumerator();
788 const size_t tmax = get_tmax();
789 const size_t n = this->n;
790 long double res = 0.0;
791 for (size_t h = 1; h <= n; ++h) {
792 InfInt sum = 0;
793 for (size_t s = 0; s <= tmax; ++s) {
794 for (size_t ell = 1; ell <= n; ++ell) sum += A[ell] * N(ell, h, s);
795 }
796 res += std::pow(static_cast<long double>(pe) / (T::get_size() - 1), h) * std::pow(1.0L - pe, n - h) *
798 }
799 return res;
800 }
801
802 long double P_failure(double pe) const
804 {
805 long double res = P_word(pe) - P_error(pe);
806 if (std::abs(res) < 10 * std::numeric_limits<long double>::epsilon())
807 return 0;
808 else
809 return res;
810 }
811
812 // Documentation note: union-Bhattacharyya bound on ML decoding for transmission of the p-ary
813 // image (cf. DEMUX/as_vector()) over a channel with prime-field input, gamma e.g. from
814 // BI_AWGN::get_Bhattacharyya_param().
815 long double Bhattacharyya_bound(long double gamma) const
817 {
818 const auto& A = get_p_ary_image_weight_enumerator();
819
820 long double res = 0;
821 const size_t d = A.degree();
822 for (size_t i = get_dmin(); i <= d; ++i) res += to_long_double(A[i]) * std::pow(gamma, i);
823
824 return res;
825 }
826
827 const Polynomial<T>& get_gamma() const {
828 if (!is_polynomial())
829 throw std::logic_error("Cannot calculate generator polynomial of a code that is not polynomial!");
830 return gamma.value();
831 }
832
835 {
836 if (!is_cyclic())
837 throw std::logic_error("Cannot calculate parity-check polynomial of a code that is not cyclic!");
838 auto xnm1 = ZeroPolynomial<T>();
839 xnm1.set_coefficient(0, -T(1));
840 xnm1.set_coefficient(this->n, T(1));
841 return xnm1 / get_gamma();
842 }
843
844 // estimate: one length-n coset leader (plus its vector header) per syndrome
847 {
848 return sqm<InfInt>(T::get_size(), this->n - k) * (this->n * sizeof(T) + sizeof(Vector<T>)) / 1000000;
849 }
850
851 // estimate: one hash map node (key/value pair with padding, plus bookkeeping pointers) per
852 // correctable error pattern
855 {
856 const size_t tmax = get_tmax();
857 InfInt entries = 0;
858 for (size_t w = 1; w <= tmax; ++w) entries += bin<InfInt>(this->n, w) * sqm<InfInt>(T::get_size() - 1, w);
859 return entries * (sizeof(std::pair<const size_t, T>) + 3 * sizeof(void*)) / 1000000;
860 }
861
862 // estimate: one punctured code per erasure pattern with at most tau_max erasures: the cache
863 // slot, the matrices (exact while the dimension stays k, i.e. for tau below dmin, roughly
864 // (n - tau)^2 symbols beyond), and the standard array the punctured ML decoder caches once
865 // the pattern is decoded
868 {
869 const size_t dmin = get_dmin();
870 InfInt payload = 0;
871 for (size_t tau = 1; tau <= tau_max; ++tau) {
872 InfInt per_code = sizeof(std::optional<LinearCode<T>>);
873 if (tau <= dmin - 1)
874 per_code += (k * (this->n - tau) + (this->n - tau) * (this->n - tau - k) + k * k) * sizeof(T) +
875 k * sizeof(size_t);
876 else
877 per_code += InfInt(this->n - tau) * (this->n - tau) * sizeof(T);
878 if (tau <= this->n - k)
879 per_code +=
880 sqm<InfInt>(T::get_size(), this->n - k - tau) * ((this->n - tau) * sizeof(T) + sizeof(Vector<T>));
881 payload += bin<InfInt>(this->n, tau) * per_code;
882 }
883 return payload / 1000000;
884 }
885
888 {
890 if (standard_array.has_value()) return;
891
892 const auto MB = standard_array_MB();
894 std::clog << "--> Calculating standard array (" << MB << " MB)" << std::endl;
895
896 constexpr size_t q = T::get_size();
897 const size_t n = this->n;
898
899 const size_t nof_cosets = sqm<size_t>(q, n - k);
900 size_t count = 0;
901 bool done = false;
902
903 try {
905 tainted.emplace(std::vector<bool>(nof_cosets, false));
906 } catch (const std::bad_alloc&) {
908 tainted.reset();
909 std::cerr << "Memory allocation failed, standard array would be too large!" << std::endl;
910 throw;
911 }
912
915
916 for (size_t wt = 0; wt <= n; ++wt) {
917 std::vector<bool> mask(n, false);
918 std::fill(mask.begin(), mask.begin() + wt, true);
919
920 do {
922 pos.reserve(wt);
923 for (auto it = mask.cbegin(); it != mask.cend(); ++it) {
924 if (*it) pos.push_back(static_cast<size_t>(it - mask.cbegin()));
925 }
926
927 Vector<T> v(n, T(0));
928 for (size_t j = 0; j < wt; ++j) v.set_component(pos[j], T(1));
929
930 std::vector<size_t> digits(wt, 1);
931
932 for (;;) {
933 const auto s = v * HT;
934 const size_t i = s.as_integer();
935
936 if (standard_array.value()[i].is_empty()) {
937 standard_array.value()[i] = v;
938
939 leader_wH[i] = wt;
940 leader_tie_count[i] = 1;
941
942 ++count;
943 if (count == nof_cosets) done = true;
944 } else {
945 if (wt == leader_wH[i]) {
946 tainted.value()[i] = true;
948 standard_array.value()[i] = v;
949 }
950 }
951 }
952
953 size_t j = 0;
954 while (j < wt) {
955 if (digits[j] < q - 1) {
956 ++digits[j];
958 break;
959 }
960 digits[j] = 1;
961 v.set_component(pos[j], T(1));
962 ++j;
963 }
964 if (j == wt) break;
965 }
966 } while (std::prev_permutation(mask.begin(), mask.end()));
967
968 if (done) return;
969 }
970 });
971
972 return standard_array.value();
973 }
974
977 {
979 throw std::invalid_argument(
980 "Standard array LaTeX export supports only method_t::BD_standard_array and "
981 "method_t::boosted_BD_standard_array!");
982 if (k == 0) throw std::logic_error("Standard array LaTeX export not available for a dimension-zero code!");
983
985 const auto& sa = standard_array.value();
986 const size_t tmax = (method == method_t::BD_standard_array) ? get_tmax() : 0;
987
990 file << "\\begin{tabular}{c|c||c}\n";
991 file << " \\text{coset index} & $\\vec{r}\\mat{H}^T$ & unique coset leader \\\\\n";
992 file << " \\hline\\hline\n";
993 for (size_t i = 0; i < sa.size(); ++i) {
994 Vector<T> s;
995 s.from_integer(i, this->n - k);
996 const bool listed = (method == method_t::BD_standard_array) ? (sa[i].wH() <= tmax) : !tainted.value()[i];
997 file << " $" << i << "$ & $" << s << "$ & ";
998 if (listed)
999 file << "$" << sa[i] << "$";
1000 else
1001 file << "\\text{none}";
1002 file << (i + 1 < sa.size() ? " \\\\\n" : "\n");
1003 }
1004 file << "\\end{tabular}\n";
1005 file.close();
1006 }
1007
1010 {
1011 Meggitt_table.call_once([this] {
1012 if (Meggitt_table.has_value()) return;
1013
1014 if (k == 0) throw std::invalid_argument("Meggitt table only available for codes with k>0!");
1015 if (!is_cyclic()) throw std::logic_error("Meggitt table only available for cyclic codes!");
1016
1017 const auto MB = Meggitt_table_MB();
1018 if (MB > DECODING_TABLES_LIMIT) std::clog << "--> Calculating Meggitt table (" << MB << " MB)" << std::endl;
1019
1020 constexpr size_t q = T::get_size();
1021 const size_t n = this->n;
1022 const size_t tmax = get_tmax();
1023 const auto& gamma = get_gamma();
1024
1025 try {
1027 auto& table = Meggitt_table.value();
1028
1029 for (size_t wt = 1; wt <= tmax; ++wt) {
1030 std::vector<bool> mask(n, false);
1031 std::fill(mask.begin(), mask.begin() + wt, true);
1032
1033 do {
1034 std::vector<size_t> pos;
1035 pos.reserve(wt);
1036 for (size_t j = 0; j < n; ++j)
1037 if (mask[j]) pos.push_back(j);
1038
1039 Vector<T> v(n, T(0));
1040 for (size_t j = 0; j < wt; ++j) v.set_component(pos[j], T(1));
1041
1042 std::vector<size_t> digits(wt, 1);
1043
1044 for (;;) {
1046
1047 size_t j = 0;
1048 while (j < wt) {
1049 if (digits[j] < q - 1) {
1050 ++digits[j];
1051 v.set_component(pos[j], T(digits[j]));
1052 break;
1053 }
1054 digits[j] = 1;
1055 v.set_component(pos[j], T(1));
1056 ++j;
1057 }
1058 if (j == wt) break;
1059 }
1060 } while (std::prev_permutation(mask.begin() + 1, mask.end()));
1061 }
1062 } catch (const std::bad_alloc&) {
1064 std::cerr << "Memory allocation failed, Meggitt table too large!" << std::endl;
1065 throw;
1066 }
1067 });
1068
1069 return Meggitt_table.value();
1070 }
1071
1074 {
1075 const auto& table = get_Meggitt_table();
1076
1078 std::sort(entries.begin(), entries.end(), [](const auto& a, const auto& b) { return a.first < b.first; });
1079
1080 std::ofstream file;
1082 file << "\\begin{tabular}{c||c}\n";
1083 file << " $\\vec{s}$ & $\\widehat{e}_0$ \\\\\n";
1084 file << " \\hline\\hline\n";
1085 for (size_t idx = 0; idx < entries.size(); ++idx) {
1086 Vector<T> s;
1087 s.from_integer(entries[idx].first, this->n - k);
1088 file << " $" << s << "$ & $" << entries[idx].second << "$";
1089 file << (idx + 1 < entries.size() ? " \\\\\n" : "\n");
1090 }
1091 file << "\\end{tabular}\n";
1092 file.close();
1093 }
1094
1095 auto cbegin() const
1097 {
1098 return CodewordIterator<T>(*this);
1099 }
1100
1101 auto cend() const
1103 {
1104 return typename CodewordIterator<T>::Sentinel{};
1105 }
1106
1107 template <FieldType S>
1108 bool is_identical(const LinearCode<S>& other, Matrix<T>* L_ptr = nullptr) const {
1109 if constexpr (!std::is_same_v<T, S>) {
1110 return false;
1111 } else {
1112 const size_t n = this->n;
1113 if (n != other.n || k != other.k) return false;
1114 if (k == 0) { // two zero codes of equal length are identical
1115 if (L_ptr != nullptr) *L_ptr = IdentityMatrix<T>(k);
1116 return true;
1117 }
1118 if (this == &other) {
1119 if (L_ptr != nullptr) *L_ptr = IdentityMatrix<T>(k);
1120 return true;
1121 }
1122
1123 bool res;
1124 if (n - k < other.k)
1126 else
1127 res = rref(G) == rref(other.G);
1128
1129 if (res && L_ptr != nullptr) {
1130 auto Bp = transpose(Matrix<T>(other.get_G().get_col(infoset[0])));
1131 for (size_t t = 1; t < k; ++t)
1133 *L_ptr = Bp * MI;
1134 }
1135
1136 return res;
1137 }
1138 }
1139
1140 template <FieldType S>
1141 bool is_equivalent(const LinearCode<S>& other, Matrix<T>* L_ptr = nullptr, Matrix<T>* P_ptr = nullptr) const {
1142 if constexpr (!std::is_same_v<T, S>) {
1143 return false;
1144 } else {
1145 const size_t n = this->n;
1146 if (n != other.n || k != other.k) return false;
1147
1148 if (k == 0) { // two zero codes of equal length are equivalent
1149 if (L_ptr != nullptr) *L_ptr = IdentityMatrix<T>(k);
1150 if (P_ptr != nullptr) *P_ptr = IdentityMatrix<T>(n);
1151 return true;
1152 }
1153
1154 if (this == &other) {
1155 if (L_ptr != nullptr) *L_ptr = IdentityMatrix<T>(k);
1156 if (P_ptr != nullptr) *P_ptr = IdentityMatrix<T>(n);
1157 return true;
1158 }
1159
1160 if (n - k < k) {
1161 auto Cperp = this->get_dual();
1162 auto Cotherperp = other.get_dual();
1163
1164 Matrix<T> Pperp;
1165 Matrix<T>* Pperp_ptr = (P_ptr != nullptr || L_ptr != nullptr) ? &Pperp : nullptr;
1166
1167 // L on the dual side is not useful here.
1168 if (!Cperp.is_equivalent(Cotherperp, nullptr, Pperp_ptr)) return false;
1169
1170 if (P_ptr != nullptr) *P_ptr = Pperp;
1171
1172 if (L_ptr != nullptr) {
1173 const T one(1);
1174 std::vector<size_t> p(n, n);
1175 for (size_t i = 0; i < n; ++i) {
1176 for (size_t j = 0; j < n; ++j) {
1177 if (Pperp(i, j) == one) {
1178 p[i] = j;
1179 break;
1180 }
1181 }
1182 }
1183
1184 auto Bp = transpose(Matrix<T>(other.get_G().get_col(p[this->infoset[0]])));
1185 for (size_t t = 1; t < k; ++t)
1187 *L_ptr = Bp * this->MI;
1188 }
1189
1190 return true;
1191 } else {
1192 auto next_combination = [](std::vector<size_t>& comb, size_t n_total) -> bool {
1193 const size_t kk = comb.size();
1194 for (size_t i = kk; i-- > 0;) {
1195 if (comb[i] < n_total - kk + i) {
1196 ++comb[i];
1197 for (size_t j = i + 1; j < kk; ++j) comb[j] = comb[j - 1] + 1;
1198 return true;
1199 }
1200 }
1201 return false;
1202 };
1203
1205 Gp_cols.reserve(n);
1206 for (size_t j = 0; j < n; ++j) Gp_cols.push_back(other.get_G().get_col(j));
1207
1210 for (size_t j = 0; j < n; ++j) target_col_keys.push_back(Gp_cols[j].as_integer());
1212
1213 std::vector<size_t> J(k);
1214 std::iota(J.begin(), J.end(), 0);
1215
1216 do {
1217 auto Bp_sorted = transpose(Matrix<T>(Gp_cols[J[0]]));
1218 for (size_t t = 1; t < k; ++t) Bp_sorted.horizontal_join(transpose(Matrix<T>(Gp_cols[J[t]])));
1219
1220 if (Bp_sorted.rank() != k) continue;
1221
1222 std::vector<size_t> Jperm = J;
1223 do {
1224 auto Bp = transpose(Matrix<T>(Gp_cols[Jperm[0]]));
1225 for (size_t t = 1; t < k; ++t) Bp.horizontal_join(transpose(Matrix<T>(Gp_cols[Jperm[t]])));
1226
1227 const auto L = Bp * MI;
1228 const auto Lt = transpose(L); // buffered once per candidate
1229
1232
1233 for (size_t col = 0; col < n; ++col) {
1234 auto h = Vector<T>(G.get_col(col) * Lt);
1236 }
1238
1240 // Fast path: caller only wants yes/no
1241 if (L_ptr == nullptr && P_ptr == nullptr) return true;
1242
1243 if (L_ptr != nullptr) *L_ptr = L;
1244
1245 if (P_ptr != nullptr) {
1248 for (size_t col = 0; col < n; ++col)
1250
1251 std::vector<size_t> perm(n, n);
1252 std::vector<bool> used(n, false);
1253
1254 bool match_ok = true;
1255 for (size_t i = 0; i < n && match_ok; ++i) {
1256 bool found = false;
1257 for (size_t j = 0; j < n; ++j) {
1258 if (used[j]) continue;
1259 if (transformed_cols[i] == Gp_cols[j]) {
1260 perm[i] = j;
1261 used[j] = true;
1262 found = true;
1263 break;
1264 }
1265 }
1266 if (!found) match_ok = false;
1267 }
1268
1269 if (!match_ok) {
1270 // matching can fail spuriously due to key collisions
1271 continue;
1272 }
1273
1275 }
1276
1277 return true;
1278 }
1279
1280 } while (std::next_permutation(Jperm.begin(), Jperm.end()));
1281
1282 } while (next_combination(J, n));
1283
1284 return false;
1285 }
1286 }
1287 }
1288
1289 bool is_perfect() const
1291 {
1292 if (k == 0) return false;
1293 return std::abs(HammingUpperBound<T>(this->n, get_dmin()) - k) <
1294 10 * std::numeric_limits<long double>::epsilon();
1295 }
1296
1297 bool is_MDS() const {
1298 if (k == 0) return false;
1299 return SingletonUpperBound(this->n, get_dmin()) == k;
1300 }
1301
1302 bool is_equidistant() const
1304 {
1305 if (k == 0) return true;
1306 return std::abs(PlotkinUpperBound<T>(this->n, get_dmin()) - k) <
1307 10 * std::numeric_limits<long double>::epsilon();
1308 }
1309
1310 bool is_weakly_self_dual() const { return (G * transpose(G)).is_zero(); }
1311 bool is_dual_containing() const { return (transpose(HT) * HT).is_zero(); }
1312 bool is_self_dual() const { return 2 * k == this->n && is_weakly_self_dual(); }
1313
1314 bool is_polynomial() const {
1315 polynomial.call_once([this] {
1316 if (polynomial.has_value()) return;
1317 const size_t n = this->n;
1318
1319 if (k == 0) {
1320 polynomial.emplace(true);
1321 auto g = ZeroPolynomial<T>();
1322 g.set_coefficient(0, -T(1));
1323 g.set_coefficient(n, T(1));
1324 gamma.emplace(g);
1325 return;
1326 }
1327
1328 auto g = ZeroPolynomial<T>();
1329 for (size_t i = 0; i < k; ++i) {
1330 g = GCD(std::move(g), Polynomial<T>(G.get_row(i)));
1331 if (!g.is_zero() && g.degree() < n - k) {
1332 polynomial.emplace(false);
1333 return;
1334 }
1335 }
1336 if (g.trailing_degree() > 0) {
1337 polynomial.emplace(false);
1338 return;
1339 }
1340 g = normalize(g); // degree at this point can only be n-k
1341 polynomial.emplace(true);
1342 gamma.emplace(g);
1343 });
1344 return polynomial.value();
1345 }
1346
1347 bool is_cyclic() const {
1348 cyclic.call_once([this] {
1349 if (cyclic.has_value()) return;
1350 if (!is_polynomial()) {
1351 cyclic.emplace(false);
1352 return;
1353 }
1354 if (k == 0) {
1356 cyclic.emplace(true);
1357 return;
1358 }
1359
1360 auto p = ZeroPolynomial<T>();
1361 p.set_coefficient(0, -T(1));
1362 p.set_coefficient(gamma.value().degree() + k, T(1));
1363 p %= gamma.value();
1364 if (p.is_zero()) {
1366 cyclic.emplace(true);
1367 return;
1368 }
1369 cyclic.emplace(false);
1370 });
1371 return cyclic.value();
1372 }
1373
1374 bool is_reversible() const {
1375 if (!is_cyclic()) return false;
1376 const auto& g = get_gamma();
1377 return normalize(reciprocal(g)) == g;
1378 }
1379
1380 // Documentation note: the shift amounts that leave a code invariant form a subgroup of Z_n, so they are
1381 // exactly the multiples of a single divisor of n; ell_ptr receives this minimal shift index (1 for
1382 // cyclic codes, n when only the trivial full rotation applies). Testing divisors suffices because
1383 // invariance under any shift implies invariance under its gcd with n.
1384 bool is_quasi_cyclic(size_t* ell_ptr = nullptr) const {
1385 shift_index.call_once([this] {
1386 if (shift_index.has_value()) return;
1387 const size_t n = this->n;
1388 const auto A = rref(G);
1389 for (size_t ell = 1; ell < n; ++ell) {
1390 if (n % ell != 0) continue;
1391 const auto rotated = horizontal_join(G.get_submatrix(0, n - ell, G.get_m(), ell),
1392 G.get_submatrix(0, 0, G.get_m(), n - ell));
1393 if (rref(rotated) == A) {
1395 return;
1396 }
1397 }
1399 });
1400 if (ell_ptr != nullptr) *ell_ptr = shift_index.value();
1401 return shift_index.value() < this->n;
1402 }
1403
1405 const long level = os.iword(details::index);
1406 if (level != details::special) {
1407 if (level > details::basic) Code<T>::get_info(os);
1408 if constexpr (FiniteFieldType<T>)
1409 os << "[F_" << T::get_size() << "; " << this->n << ", " << k << "]";
1410 else
1411 os << "[Q; " << this->n << ", " << k << "]";
1412 if (level > details::most) {
1413 if constexpr (FiniteFieldType<T>) get_weight_enumerator();
1414 os << ", dmin = ";
1415 try {
1416 os << get_dmin();
1417 } catch (const std::logic_error&) {
1418 os << "undefined";
1419 }
1420 }
1421 }
1422 if (level > details::basic) {
1423 if (level != details::special) os << std::endl;
1424 os << BOLD("Linear code") " with properties: { ";
1425 if (level != details::special) {
1426 os << std::endl;
1427 os << "G = " << std::endl;
1428 os << G << std::endl;
1429 os << "H = " << std::endl;
1430 os << get_H();
1431 os << std::endl;
1432 }
1433 os << std::flush;
1434 }
1435 if (level > details::most && level < details::special) {
1436 if constexpr (FiniteFieldType<T>) os << "A(x) = " << get_weight_enumerator() << std::setfill(' ') << " ";
1437 os << "tmax = ";
1438 try {
1439 os << get_tmax() << " ";
1440 } catch (const std::logic_error&) {
1441 os << "undefined ";
1442 }
1443 }
1444
1445 if (level > details::most) {
1446 if (is_polynomial()) {
1447 os << "polynomial(";
1448 if (is_cyclic()) {
1449 os << "cyclic, ";
1450 if (is_reversible()) os << "reversible, ";
1451 }
1452 os << "gamma = " << get_gamma() << ") ";
1453 }
1454 if (!is_cyclic()) {
1455 size_t ell;
1456 if (is_quasi_cyclic(&ell)) os << "quasi-cyclic(ell = " << ell << ") ";
1457 }
1458 if constexpr (FiniteFieldType<T>)
1459 if (is_perfect()) os << "perfect ";
1460 if (is_MDS()) os << "MDS ";
1461 if constexpr (FiniteFieldType<T>)
1462 if (is_equidistant()) os << "equidistant ";
1463 }
1464 if (level > details::basic) {
1466 const bool dual_containing = is_dual_containing();
1467 const bool self_dual = is_self_dual();
1468 if (!self_dual && weakly_self_dual) os << "weakly_self-dual ";
1469 if (!self_dual && dual_containing) os << "dual-containing ";
1470 if (self_dual) os << "self-dual ";
1471 if (level != details::special) os << std::endl;
1472 os << "}" << std::flush;
1473 }
1474 }
1475
1477 const size_t n = this->n;
1478 auto dual_code = LinearCode<T>(n, n - k, get_H());
1479 if constexpr (FiniteFieldType<T>) {
1482 }
1483 return dual_code;
1484 }
1485
1487 const size_t n = this->n;
1488 auto Gp = G;
1489 Gp.rref();
1490 for (size_t i = 0; i < k; ++i) {
1491 const auto u = unit_vector<T>(k, i);
1492 for (size_t j = 0; j < n; ++j) {
1493 if (Gp.get_col(j) == u) {
1494 if (j != i) Gp.swap_columns(i, j);
1495 break;
1496 }
1497 }
1498 }
1499 LinearCode<T> res(n, k, Gp);
1500 if (dmin.has_value()) res.set_dmin(*dmin);
1501 if constexpr (FiniteFieldType<T>) {
1505 }
1506 return res;
1507 }
1508
1522
1524 if (!is_polynomial()) throw std::invalid_argument("Code is not polynomial, cannot bring G in polynomial form!");
1525
1526 const size_t n = this->n;
1527 const auto& g = get_gamma();
1528 auto Gp = ToeplitzMatrix(pad_back(pad_front(Vector<T>(g), n), n + k - 1), k, n);
1529 return Gp;
1530 }
1531
1533 const size_t n = this->n;
1534 const T zero(0);
1535 const T one(1);
1536 const T minus_one(-one);
1537 auto Gp = G;
1538 for (;;) {
1540 std::vector<size_t> ends(k);
1541 for (size_t i = 0; i < k; ++i) {
1542 for (size_t j = 0; j < n; ++j) {
1543 if (Gp(i, j) != zero) {
1544 starts[i] = j;
1545 break;
1546 }
1547 }
1548 for (size_t j = n; j-- > 0;) {
1549 if (Gp(i, j) != zero) {
1550 ends[i] = j;
1551 break;
1552 }
1553 }
1554 }
1555
1556 // trellis-oriented: all row starts and all row ends distinct
1559
1560 if (start_set.size() == k && end_set.size() == k) break;
1561
1562 bool found_start = false;
1563 for (size_t i = 0; i < k; ++i) {
1564 for (size_t j = i + 1; j < k; ++j) {
1565 if (starts[i] == starts[j]) {
1566 const size_t s = starts[i];
1567 Gp.scale_row(one / Gp(i, s), i);
1568 Gp.scale_row(one / Gp(j, s), j);
1569 if (ends[i] < ends[j])
1571 else
1573 found_start = true;
1574 break;
1575 }
1576 }
1577 if (found_start) break;
1578 }
1579
1580 if (!found_start) {
1581 bool found_end = false;
1582 for (size_t i = 0; i < k; ++i) {
1583 for (size_t j = i + 1; j < k; ++j) {
1584 if (ends[i] == ends[j]) {
1585 const size_t e = ends[i];
1586 Gp.scale_row(one / Gp(i, e), i);
1587 Gp.scale_row(one / Gp(j, e), j);
1588 if (starts[i] > starts[j])
1590 else
1592 found_end = true;
1593 break;
1594 }
1595 }
1596 if (found_end) break;
1597 }
1598 }
1599 }
1600
1601 return Gp;
1602 }
1603
1606 {
1607 const size_t n = this->n;
1608
1609 Trellis<T> res;
1610 if (n == 1) { // source and sink coincide, one parallel edge per codeword
1611 for (auto it = cbegin(); it != cend(); ++it) res.add_parallel_edge(0, 0, 0, (*it)[0]);
1612 return res;
1613 }
1614 size_t i = 0;
1615 for (auto it = cbegin(); it != cend(); ++it) {
1616 res.add_parallel_edge(0, 0, i, (*it)[0]);
1617 for (size_t j = 1; j < n - 1; ++j) {
1618 res.add_parallel_edge(j, i, i, (*it)[j]);
1619 }
1620 res.add_parallel_edge(n - 1, i, 0, (*it)[n - 1]);
1621 ++i;
1622 }
1623
1624 return res;
1625 }
1626
1629 {
1630 minimal_trellis.call_once([this] {
1631 if (minimal_trellis.has_value()) return;
1632 constexpr size_t q = T::get_size();
1633 const size_t n = this->n;
1634 const auto Gp = get_G_in_trellis_oriented_form();
1635 const T zero(0);
1636
1637 std::clog << "--> Calculating minimal trellis for code with " << sqm<InfInt>(q, k) << " codewords"
1638 << std::endl;
1639
1640 auto row_trellis = [n, &Gp, &zero](size_t i) {
1641 size_t s = 0, e = 0;
1642
1643 for (size_t j = 0; j < n; ++j) {
1644 if (Gp(i, j) != zero) {
1645 s = j;
1646 break;
1647 }
1648 }
1649 for (size_t j = n; j-- > 0;) {
1650 if (Gp(i, j) != zero) {
1651 e = j;
1652 break;
1653 }
1654 }
1655
1656 const bool span_one = (s == e);
1657 const bool open_ended = span_one && (e == n - 1);
1658
1659 Trellis<T> tr;
1660
1661 for (size_t j = 0; j < n; ++j) {
1662 if (j < s || (!open_ended && j > (span_one ? s + 1 : e)) || (open_ended && j > e)) {
1663 tr.add_edge(j, 0, 0, zero);
1664 } else if (j == s) {
1665 for (uint32_t a = 0; a < q; ++a) tr.add_edge(j, 0, a, T(a) * Gp(i, s));
1666 } else if (!open_ended && j == (span_one ? s + 1 : e)) {
1667 if (span_one) {
1668 for (uint32_t a = 0; a < q; ++a) tr.add_edge(j, a, 0, zero);
1669 } else {
1670 for (uint32_t a = 0; a < q; ++a) tr.add_edge(j, a, 0, T(a) * Gp(i, j));
1671 }
1672 } else {
1673 for (uint32_t a = 0; a < q; ++a) tr.add_edge(j, a, a, T(a) * Gp(i, j));
1674 }
1675 }
1676
1677 return tr;
1678 };
1679
1680 auto result = row_trellis(0);
1681 for (size_t i = 1; i < k; ++i) result = result * row_trellis(i);
1682
1684 });
1685 return *minimal_trellis;
1686 }
1687
1688 Vector<T> enc(const Vector<T>& u) const override { return u * G; }
1689
1690 Vector<T> encinv(const Vector<T>& c) const override {
1691 if (k == 0) throw std::logic_error("Cannot invert encoding wrt. a dimension zero code!");
1692
1693 Vector<T> c_sub(k);
1694 size_t i = 0;
1695 for (auto it = infoset.cbegin(); it != infoset.cend(); ++it) {
1696 c_sub.set_component(i, c[*it]);
1697 ++i;
1698 }
1699 return c_sub * MI;
1700 }
1701
1703 if constexpr (!FiniteFieldType<T>) {
1704 throw std::logic_error("Meggitt BD decoding only available for codes over finite fields!");
1705 } else {
1706#ifdef CECCO_ERASURE_SUPPORT
1708 throw std::invalid_argument("Trying to correct erasures with a Meggitt BD decoder!");
1709#endif
1711
1712 if (k == 0) return Vector<T>(this->n);
1713
1714 const size_t n = this->n;
1715 const size_t redundancy = n - k;
1716 const auto& gamma = get_gamma();
1717 const T lead_inv = T(1) / gamma[redundancy];
1718 const auto& table = get_Meggitt_table();
1719
1721
1722 if (s.is_zero()) return r;
1723
1724 Vector<T> c_est = r;
1725 bool corrected = false;
1726 for (size_t j = n; j-- > 0;) {
1727 const auto it = table.find(s.as_integer());
1728 if (it != table.end()) {
1730 corrected = true;
1731 }
1732 const T feedback = s[redundancy - 1] * lead_inv;
1733 for (size_t l = redundancy - 1; l > 0; --l) s.set_component(l, s[l - 1] - feedback * gamma[l]);
1734 s.set_component(0, -feedback * gamma[0]);
1735 }
1736
1737 if (!corrected || !(c_est * HT).is_zero() || dH(r, c_est) > this->get_tmax())
1738 throw decoding_failure("Meggitt BD decoder failed!");
1739
1740 return c_est;
1741 }
1742 }
1743
1744 // Documentation note: complete (minimum distance) decoding by coset leader lookup; the BD and
1745 // boosted BD variants below use the same standard array and differ only in the acceptance
1746 // step.
1748 if constexpr (!FiniteFieldType<T>) {
1749 throw std::logic_error("Standard array decoding only available for codes over finite fields!");
1750 } else {
1751#ifdef CECCO_ERASURE_SUPPORT
1752 if (LinearCode<T>::erasures_present(r)) return dec_ML_EE(r);
1753#endif
1755
1756 if (k == 0) return Vector<T>(this->n);
1757
1759 const auto s = r * HT;
1760 if (s.is_zero()) return r;
1761 return r - standard_array.value()[s.as_integer()];
1762 }
1763 }
1764
1766 if constexpr (!FiniteFieldType<T>) {
1767 throw std::logic_error("Standard array decoding only available for codes over finite fields!");
1768 } else {
1769#ifdef CECCO_ERASURE_SUPPORT
1771#endif
1773
1774 if (k == 0) return Vector<T>(this->n);
1775
1777 const auto s = r * HT;
1778 if (s.is_zero()) return r;
1779 const auto c_est = r - standard_array.value()[s.as_integer()];
1780 if (dH(r, c_est) > this->get_tmax())
1781 throw decoding_failure("Linear code standard array BD decoder failed!");
1782 return c_est;
1783 }
1784 }
1785
1787 if constexpr (!FiniteFieldType<T>) {
1788 throw std::logic_error("Standard array decoding only available for codes over finite fields!");
1789 } else {
1790#ifdef CECCO_ERASURE_SUPPORT
1792#endif
1794
1795 if (k == 0) return Vector<T>(this->n);
1796
1798 const auto s = r * HT;
1799 if (s.is_zero()) return r;
1800 const size_t i = s.as_integer();
1801 if (tainted.value()[i]) throw decoding_failure("Linear code boosted standard array BD decoder failed!");
1802 return r - standard_array.value()[i];
1803 }
1804 }
1805
1806 // Documentation note: Viterbi decoding with an explicit radius check realizes the BD
1807 // paradigm without any decoding table (but requires the minimal trellis).
1808 Vector<T> dec_BD_Viterbi(const Vector<T>& r) const {
1809 if constexpr (!FiniteFieldType<T>) {
1810 throw std::logic_error("BD decoding only available for codes over finite fields!");
1811 } else {
1812#ifdef CECCO_ERASURE_SUPPORT
1814#endif
1816
1817 if (k == 0) return Vector<T>(this->n);
1818
1819 const auto c_est = dec_Viterbi(r);
1820 if (dH(r, c_est) > this->get_tmax()) throw decoding_failure("Viterbi BD decoder failed!");
1821 return c_est;
1822 }
1823 }
1824
1825 // Documentation note: BD is a decoding paradigm, not an algorithm: every error pattern of
1826 // weight at most tmax is corrected, and the returned codeword is always within tmax of r;
1827 // beyond the radius the decoder throws decoding_failure (contributing to P_failure) or miscorrects (contributing to
1828 // P_error). This wrapper selects the best realization for the code at hand: dec_Meggitt for
1829 // cyclic codes and dec_BD_standard_array otherwise, each only while its table fits into DECODING_TABLES_LIMIT MB
1830 // (or is already cached), with Viterbi decoding and an explicit radius check as the fallback. Derived classes
1831 // override it with their own best realization.
1832 Vector<T> dec_BD(const Vector<T>& r) const override {
1833 if constexpr (!FiniteFieldType<T>) {
1834 throw std::logic_error("BD decoding only available for codes over finite fields!");
1835 } else {
1836#ifdef CECCO_ERASURE_SUPPORT
1837 if (LinearCode<T>::erasures_present(r)) return dec_BD_EE(r);
1838#endif
1840
1841 if (k == 0) return Vector<T>(this->n);
1842
1844 return dec_Meggitt(r);
1846 return dec_BD_standard_array(r);
1847 return dec_BD_Viterbi(r);
1848 }
1849 }
1850
1851 Vector<T> dec_Viterbi(const Vector<T>& r, const std::string& filename = "") const override {
1852 if constexpr (!FiniteFieldType<T>) {
1853 throw std::logic_error("Viterbi decoding only available for codes over finite fields!");
1854 } else {
1855#ifdef CECCO_ERASURE_SUPPORT
1857#endif
1859 if (!filename.empty() && T::get_size() > 64)
1860 throw std::invalid_argument("Viterbi trellis TikZ export not supported for fields with size > 64!");
1861 if (k == 0) return Vector<T>(this->n);
1862
1863 const auto& Tr = get_minimal_trellis();
1864 typename Trellis<T>::template Viterbi_Workspace<uint16_t> ws(Tr);
1866 if (!filename.empty()) ws.v.emplace(r);
1868 return c_est;
1869 }
1870 }
1871
1872 // Documentation note: ML is a decoding paradigm, not an algorithm: the returned codeword
1873 // minimizes the Hamming distance to r. This wrapper selects the realization by cost,
1874 // dec_standard_array when the standard array fits into DECODING_TABLES_LIMIT MB (or is
1875 // already cached) and Viterbi decoding on the minimal trellis otherwise.
1876 Vector<T> dec_ML(const Vector<T>& r) const override {
1877 if constexpr (!FiniteFieldType<T>) {
1878 throw std::logic_error("ML decoding only available for codes over finite fields!");
1879 } else {
1880#ifdef CECCO_ERASURE_SUPPORT
1881 if (LinearCode<T>::erasures_present(r)) return dec_ML_EE(r);
1882#endif
1884
1885 if (k == 0) return Vector<T>(this->n);
1886
1888 return dec_standard_array(r);
1889 return dec_Viterbi(r);
1890 }
1891 }
1892
1893 Vector<T> dec_Viterbi_soft(const Vector<double>& llrs, const std::string& filename = "") const override {
1894 if constexpr (!FiniteFieldType<T>) {
1895 throw std::logic_error("Soft-input Viterbi decoding only available for codes over finite fields!");
1896 } else if constexpr (T::get_size() != 2) {
1897 throw std::logic_error(
1898 "Soft-input Viterbi LLR vector decoding only available for binary codes; use the "
1899 "Matrix<double> overload!");
1900 } else {
1901 return dec_Viterbi_soft(Matrix<double>(llrs), filename);
1902 }
1903 }
1904
1905 Vector<T> dec_Viterbi_soft(const Matrix<double>& llrs, const std::string& filename = "") const override {
1906 if constexpr (!FiniteFieldType<T>) {
1907 throw std::logic_error("Soft-input Viterbi decoding only available for codes over finite fields!");
1908 } else {
1910 if (!filename.empty() && T::get_size() > 2)
1911 throw std::invalid_argument("Soft-input Viterbi trellis TikZ export only supported for binary codes!");
1912 if (k == 0) return Vector<T>(this->n);
1913
1914 const auto& Tr = get_minimal_trellis();
1915 typename Trellis<T>::template Viterbi_Workspace<double> ws(Tr);
1917 if (!filename.empty()) ws.v.emplace(llrs);
1919 }
1920 }
1921
1922 // Documentation note: the first entry of every returned list is a Viterbi-optimal codeword,
1923 // reservoir-selected among the ML-optimal candidates as elsewhere in the library; remaining entries
1924 // follow in nondecreasing trellis metric. Like the other soft decoders it agrees in cost with the
1925 // single-output dec_Viterbi but, under ties, need not select the same tied codeword.
1927 if constexpr (!FiniteFieldType<T>) {
1928 throw std::logic_error("Viterbi decoding only available for codes over finite fields!");
1929 } else {
1931 if (list_size == 0) throw std::invalid_argument("Viterbi list size must be positive!");
1932 if (k == 0) return {Vector<T>(this->n)};
1933
1934 const auto& Tr = get_minimal_trellis();
1935 typename Trellis<T>::template ListViterbi_Workspace<uint16_t> ws(Tr);
1937 return viterbi_list<uint16_t>(Tr, ws, list_size);
1938 }
1939 }
1940
1942 if constexpr (!FiniteFieldType<T>) {
1943 throw std::logic_error("Soft-input Viterbi decoding only available for codes over finite fields!");
1944 } else if constexpr (T::get_size() != 2) {
1945 throw std::logic_error(
1946 "Soft-input Viterbi LLR vector decoding only available for binary codes; use the "
1947 "Matrix<double> overload!");
1948 } else {
1949 return dec_Viterbi_soft_list(Matrix<double>(llrs), list_size);
1950 }
1951 }
1952
1954 if constexpr (!FiniteFieldType<T>) {
1955 throw std::logic_error("Soft-input Viterbi decoding only available for codes over finite fields!");
1956 } else {
1958 if (list_size == 0) throw std::invalid_argument("Viterbi list size must be positive!");
1959 if (k == 0) return {Vector<T>(this->n)};
1960
1961 const auto& Tr = get_minimal_trellis();
1962 typename Trellis<T>::template ListViterbi_Workspace<double> ws(Tr);
1964 return viterbi_list<double>(Tr, ws, list_size);
1965 }
1966 }
1967
1968 // Documentation note: soft-input ML is a decoding paradigm; Viterbi decoding on the minimal
1969 // trellis realizes it exactly and is never asymptotically worse than the exhaustive scan
1970 // (dec_exhaustive_ML_soft), since coset leader lookup has no soft-metric counterpart.
1971 Vector<T> dec_ML_soft(const Vector<double>& llrs) const override { return dec_Viterbi_soft(llrs); }
1972 Vector<T> dec_ML_soft(const Matrix<double>& llrs) const override { return dec_Viterbi_soft(llrs); }
1973
1975 if constexpr (!FiniteFieldType<T>) {
1976 throw std::logic_error("Soft-input ML decoding only available for codes over finite fields!");
1977 } else if constexpr (T::get_size() != 2) {
1978 throw std::logic_error(
1979 "Soft-input ML LLR vector decoding only available for binary codes; use the Matrix<double> overload!");
1980 } else {
1982 }
1983 }
1984
1986 if constexpr (!FiniteFieldType<T>) {
1987 throw std::logic_error("Soft-input ML decoding only available for codes over finite fields!");
1988 } else {
1990
1991 if (k == 0) return Vector<T>(this->n);
1992
1993 const size_t n = this->n;
1994 std::vector<std::array<double, T::get_size()>> costs;
1995 costs.reserve(n);
1997
1998 Vector<T> c_est;
1999 double best = std::numeric_limits<double>::max();
2000 uint16_t ties = 1;
2001
2002 if (this->get_size() <= ml_soft_cache_limit) {
2003 codewords.call_once([this] {
2004 if (codewords.has_value()) return;
2005 codewords.emplace(this->get_G().span());
2006 });
2007
2008 for (auto it = codewords.value().cbegin(); it != codewords.value().cend(); ++it) {
2009 double val = 0.0;
2010 for (size_t i = 0; i < n; ++i) val += costs[i][(*it)[i].get_label()];
2011 if (val < best) {
2012 c_est = *it;
2013 best = val;
2014 ties = 1;
2015 } else if (val == best) {
2016 if (details::reservoir_accept(++ties)) c_est = *it;
2017 }
2018 }
2019 } else {
2020 for (auto it = cbegin(); it != cend(); ++it) {
2021 double val = 0.0;
2022 for (size_t i = 0; i < n; ++i) val += costs[i][(*it)[i].get_label()];
2023 if (val < best) {
2024 c_est = *it;
2025 best = val;
2026 ties = 1;
2027 } else if (val == best) {
2028 if (details::reservoir_accept(++ties)) c_est = *it;
2029 }
2030 }
2031 }
2032
2033 return c_est;
2034 }
2035 }
2036
2037 // Documentation note: pass nullptr as Lambda_ptr when only the filename argument is needed.
2038 Vector<T> dec_BCJR(const Vector<double>& llrs, Matrix<double>* Lambda_ptr = nullptr,
2039 const std::string& filename = "") const override {
2040 if constexpr (!FiniteFieldType<T>) {
2041 throw std::logic_error("BCJR decoding only available for codes over finite fields!");
2042 } else if constexpr (T::get_size() != 2) {
2043 throw std::logic_error(
2044 "Soft-input BCJR LLR vector decoding only available for binary codes; use the Matrix<double> "
2045 "overload!");
2046 } else {
2047 return bcjr_decode(Matrix<double>(llrs), Lambda_ptr, filename);
2048 }
2049 }
2050
2051 Vector<T> dec_BCJR(const Matrix<double>& llrs, Matrix<double>* Lambda_ptr = nullptr,
2052 const std::string& filename = "") const override {
2053 if constexpr (!FiniteFieldType<T>) {
2054 throw std::logic_error("BCJR decoding only available for codes over finite fields!");
2055 } else {
2057 }
2058 }
2059
2060 // Documentation note: symbol-wise MAP is a decoding paradigm, realized by the BCJR algorithm
2061 // on the minimal trellis; all arguments are forwarded (cf. the Documentation note on dec_BCJR).
2062 Vector<T> dec_ssMAP(const Vector<double>& llrs, Matrix<double>* Lambda_ptr = nullptr,
2063 const std::string& filename = "") const override {
2064 return dec_BCJR(llrs, Lambda_ptr, filename);
2065 }
2066
2067 Vector<T> dec_ssMAP(const Matrix<double>& llrs, Matrix<double>* Lambda_ptr = nullptr,
2068 const std::string& filename = "") const override {
2069 return dec_BCJR(llrs, Lambda_ptr, filename);
2070 }
2071
2072 Vector<T> dec_OSD(const Vector<double>& llrs, size_t w = OSD_ORDER) const override {
2073 if constexpr (!FiniteFieldType<T>) {
2074 throw std::logic_error("OSD decoding only available for codes over finite fields!");
2075 } else if constexpr (T::get_size() != 2) {
2076 throw std::logic_error(
2077 "Soft-input OSD LLR vector decoding only available for binary codes; use the Matrix<double> overload!");
2078 } else {
2079 return dec_OSD(Matrix<double>(llrs), w);
2080 }
2081 }
2082
2083 // Documentation note: conventional order-w OSD: the candidates are all patterns deviating from the
2084 // hard-decision word on the most reliable basis in at most w positions (the trellis state is the
2085 // deviation count), enumerated in nondecreasing partial soft cost and judged by the full soft metric.
2086 Vector<T> dec_OSD(const Matrix<double>& llrs, size_t w = OSD_ORDER) const override {
2087 if constexpr (!FiniteFieldType<T>) {
2088 throw std::logic_error("OSD decoding only available for codes over finite fields!");
2089 } else {
2091
2092 const size_t n = this->n;
2093 if (k == 0) return Vector<T>(n);
2094
2095 constexpr size_t q = T::get_size();
2096 const size_t ww = std::min(w, k);
2097 const auto setup = prepare_soft_decoding(llrs);
2098
2099 Trellis<T> local;
2101 vid[0].emplace(0, 0);
2102 for (size_t i = 0; i < k; ++i)
2103 for (auto it = vid[i].cbegin(); it != vid[i].cend(); ++it) {
2104 const uint32_t from = it->second;
2105 const size_t dev = it->first;
2106 for (size_t a = 0; a < q; ++a) {
2107 const size_t ndev = dev + (a != setup.u0[i].get_label() ? 1 : 0);
2108 if (ndev > ww) continue;
2109 const auto to_it = vid[i + 1].try_emplace(ndev, static_cast<uint32_t>(vid[i + 1].size())).first;
2111 }
2112 }
2113
2115 }
2116 }
2117
2120 if constexpr (!FiniteFieldType<T>) {
2121 throw std::logic_error("LC-OSD decoding only available for codes over finite fields!");
2122 } else if constexpr (T::get_size() != 2) {
2123 throw std::logic_error(
2124 "Soft-input LC-OSD LLR vector decoding only available for binary codes; use the Matrix<double> "
2125 "overload!");
2126 } else {
2127 return dec_LC_OSD(Matrix<double>(llrs), delta, ell_max);
2128 }
2129 }
2130
2131 // Documentation note: LC-OSD (Liang/Ma, arXiv:2401.16709, binary; here generalized to q-ary symbol
2132 // costs): the candidates are the assignments of the k + delta extended-MRB positions satisfying the
2133 // delta local parity checks (the trellis state is the local syndrome), enumerated in nondecreasing
2134 // partial soft cost and judged by the full soft metric after re-encoding. delta = n - k with
2135 // ell_max = 1 is exact ML decoding; delta = 0 enumerates in pure soft-cost order without an
2136 // OSD-order restriction.
2139 if constexpr (!FiniteFieldType<T>) {
2140 throw std::logic_error("LC-OSD decoding only available for codes over finite fields!");
2141 } else {
2143 if (ell_max == 0) throw std::invalid_argument("LC-OSD list size must be positive");
2144
2145 const size_t n = this->n;
2146 if (k == 0) return Vector<T>(n);
2147
2148 constexpr size_t q = T::get_size();
2149 const size_t d = std::min(delta, n - k);
2150 if (sqm<InfInt>(q, d) > sqm<InfInt>(10, 6))
2151 throw std::out_of_range("LC-OSD trellis too big (more than 10^6 syndrome states) to compute");
2152
2153 const auto setup = prepare_soft_decoding(llrs);
2154 const size_t stages = k + d;
2155
2156 std::vector<std::vector<T>> hcol(stages, std::vector<T>(d, T(0)));
2157 for (size_t i = 0; i < k; ++i)
2158 for (size_t j = 0; j < d; ++j) hcol[i][j] = -setup.Gsys(i, k + j);
2159 const T one(1);
2160 for (size_t l = 0; l < d; ++l) hcol[k + l][l] = one;
2161
2162 Trellis<T> local;
2164 vid[0].emplace(0, 0);
2165 for (size_t i = 0; i < stages; ++i)
2166 for (auto it = vid[i].cbegin(); it != vid[i].cend(); ++it) {
2167 const uint32_t from = it->second;
2168 const size_t s = it->first;
2169 for (size_t a = 0; a < q; ++a) {
2170 const T alpha(a);
2171 size_t ns = 0, pw = 1;
2172 for (size_t j = 0; j < d; ++j) {
2173 ns += (T((s / pw) % q) + alpha * hcol[i][j]).get_label() * pw;
2174 pw *= q;
2175 }
2176 if (i + 1 == stages && ns != 0) continue;
2177 const auto to_it = vid[i + 1].try_emplace(ns, static_cast<uint32_t>(vid[i + 1].size())).first;
2179 }
2180 }
2181
2183 }
2184 }
2185
2188 {
2189 tanner_graph.call_once([this] {
2190 if (tanner_graph.has_value()) return;
2191 const size_t n = this->n;
2192 const size_t redundancy = n - k;
2193 TannerGraph<T> g(n);
2194 for (size_t i = 0; i < redundancy; ++i)
2195 for (size_t j = 0; j < n; ++j)
2196 if (!HT(j, i).is_zero()) g.add_edge(i, static_cast<uint32_t>(j), HT(j, i));
2198 });
2199 return tanner_graph.value();
2200 }
2201
2203 Matrix<double>* Lambda_ptr = nullptr, const std::string& filename = "",
2204 size_t* nof_iterations_ptr = nullptr) const override {
2205 if constexpr (!FiniteFieldType<T>) {
2206 throw std::logic_error("Belief-propagation decoding only available for codes over finite fields!");
2207 } else if constexpr (T::get_size() != 2) {
2208 throw std::logic_error(
2209 "Soft-input BP LLR vector decoding only available for binary codes; use the Matrix<double> overload!");
2210 } else {
2212 }
2213 }
2214
2216 Matrix<double>* Lambda_ptr = nullptr, const std::string& filename = "",
2217 size_t* nof_iterations_ptr = nullptr) const override {
2218 if constexpr (!FiniteFieldType<T>) {
2219 throw std::logic_error("Belief-propagation decoding only available for codes over finite fields!");
2220 } else {
2222 }
2223 }
2224
2225#ifdef CECCO_ERASURE_SUPPORT
2226 // completes the punctured estimate c_E on the erased positions X to a codeword consistent
2227 // with all parity checks; nullopt if no consistent completion exists
2229 const Vector<T>& c_E) const
2231 {
2232 const auto HT_E = delete_rows(this->HT, X);
2233 const auto b = -c_E * HT_E;
2234 const auto HT_X = delete_rows(this->HT, E);
2236
2237 for (size_t i = 0; i < B.get_m(); ++i) {
2238 const T a = B(i, B.get_n() - 1);
2239 if (!a.is_zero()) {
2240 auto sol = B.get_row(i).delete_component(B.get_n() - 1);
2241 sol /= -a;
2242 Vector<T> c_est(this->n);
2243 for (size_t j = 0; j < E.size(); ++j) c_est.set_component(E[j], c_E[j]);
2244 for (size_t j = 0; j < X.size(); ++j) c_est.set_component(X[j], sol[j]);
2245 return c_est;
2246 }
2247 }
2248 return std::nullopt;
2249 }
2250
2252 if constexpr (!FiniteFieldType<T>) {
2253 throw std::logic_error(
2254 "Standard array BD error/erasure decoding only available for codes over finite fields!");
2255 } else {
2257
2258 if (k == 0) return Vector<T>(this->n);
2259
2260 const size_t n = this->n;
2261 const size_t dmin = get_dmin();
2262
2263 std::vector<size_t> X;
2264 std::vector<size_t> E;
2265 for (size_t i = 0; i < n; ++i) {
2266 if (r[i].is_erased())
2267 X.push_back(i);
2268 else
2269 E.push_back(i);
2270 }
2271 const size_t tau = X.size();
2272
2273 if (tau == 0) return dec_BD_standard_array(r);
2274 if (tau > dmin - 1) {
2275 throw decoding_failure("Linear code standard array BD error/erasure decoder failed!");
2276 }
2277
2279
2280 const auto& PC = punctured_codes_BD.value()[pos_to_index(X)].value();
2281
2282 const auto c_est = complete_erasures(X, E, PC.dec_ML(delete_components(r, X)));
2283 if (!c_est) {
2284 throw decoding_failure("Linear code standard array BD error/erasure decoder failed!");
2285 }
2286
2287 size_t t = 0;
2288 for (size_t i = 0; i < n; ++i) {
2289 if (!r[i].is_erased() && r[i] != (*c_est)[i]) ++t;
2290 }
2291
2292 if (2 * t + tau > dmin - 1) {
2293 throw decoding_failure("Linear code standard array BD error/erasure decoder failed!");
2294 }
2295
2296 return *c_est;
2297 }
2298 }
2299
2300 // Documentation note: Viterbi error/erasure decoding with an explicit radius check
2301 // (2t + tau <= dmin - 1, errors counted on the non-erased positions) realizes the BD
2302 // paradigm without any decoding table.
2304 if constexpr (!FiniteFieldType<T>) {
2305 throw std::logic_error("BD error/erasure decoding only available for codes over finite fields!");
2306 } else {
2308
2309 if (k == 0) return Vector<T>(this->n);
2310
2311 const size_t n = this->n;
2312 const size_t dmin = get_dmin();
2313
2314 size_t tau = 0;
2315 for (size_t i = 0; i < n; ++i)
2316 if (r[i].is_erased()) ++tau;
2317 if (tau > dmin - 1) throw decoding_failure("Viterbi BD error/erasure decoder failed!");
2318
2319 const auto c_est = dec_Viterbi_EE(r);
2320 size_t t = 0;
2321 for (size_t i = 0; i < n; ++i)
2322 if (!r[i].is_erased() && r[i] != c_est[i]) ++t;
2323 if (2 * t + tau > dmin - 1) throw decoding_failure("Viterbi BD error/erasure decoder failed!");
2324 return c_est;
2325 }
2326 }
2327
2328 // Documentation note: Meggitt decoding has no erasure variant, so the BD paradigm selects
2329 // between the standard array realization and dec_BD_Viterbi_EE, gated by the estimated
2330 // aggregate size of the punctured code cache against DECODING_TABLES_LIMIT.
2331 Vector<T> dec_BD_EE(const Vector<T>& r) const override {
2332 if constexpr (!FiniteFieldType<T>) {
2333 throw std::logic_error("BD error/erasure decoding only available for codes over finite fields!");
2334 } else {
2336
2337 if (k == 0) return Vector<T>(this->n);
2338
2341 return dec_BD_Viterbi_EE(r);
2342 }
2343 }
2344
2345 Vector<T> dec_Viterbi_EE(const Vector<T>& r, const std::string& filename = "") const override {
2346 if constexpr (!FiniteFieldType<T>) {
2347 throw std::logic_error("Viterbi error/erasure decoding only available for codes over finite fields!");
2348 } else {
2350 if (!filename.empty() && T::get_size() > 64)
2351 throw std::invalid_argument("Viterbi trellis TikZ export not supported for fields with size > 64!");
2352 if (k == 0) return Vector<T>(this->n);
2353
2354 const auto& Tr = get_minimal_trellis();
2355 typename Trellis<T>::template Viterbi_Workspace<uint16_t> ws(Tr);
2357 if (!filename.empty()) ws.v.emplace(r);
2359 return c_est;
2360 }
2361 }
2362
2363 Vector<T> dec_ML_EE(const Vector<T>& r) const override {
2364 if constexpr (!FiniteFieldType<T>) {
2365 throw std::logic_error("ML error/erasure decoding only available for codes over finite fields!");
2366 } else {
2368
2369 if (k == 0) return Vector<T>(this->n);
2370
2371 const size_t n = this->n;
2372 const size_t dmin = get_dmin();
2373
2374 std::vector<size_t> X;
2375 std::vector<size_t> E;
2376 for (size_t i = 0; i < n; ++i) {
2377 if (r[i].is_erased())
2378 X.push_back(i);
2379 else
2380 E.push_back(i);
2381 }
2382 const size_t tau = X.size();
2383
2384 if (tau == 0) return dec_ML(r);
2385 if (tau == n) return Vector<T>(n);
2386
2387 const LinearCode<T>* pc_ptr;
2388
2389 if (tau <= dmin - 1) {
2391 return dec_Viterbi_EE(r);
2394 } else {
2396 return dec_Viterbi_EE(r);
2398 size_t bd_count = 0;
2399 for (size_t t = 1; t <= dmin - 1; ++t) bd_count += bin<size_t>(n, t);
2401 }
2402
2403 const auto& PC = *pc_ptr;
2404
2405 const auto c_est = complete_erasures(X, E, PC.dec_ML(delete_components(r, X)));
2406 if (!c_est) throw decoding_failure("Linear code ML error/erasure decoder failed!");
2407
2408 return *c_est;
2409 }
2410 }
2411
2412 Vector<T> dec_GMD(const Vector<T>& r, const Vector<double>& reliability) const
2414 {
2415 const size_t n = this->n;
2416
2417 if (reliability.get_n() != n)
2418 throw std::invalid_argument("GMD decoder: length of reliability vector must match code length");
2419
2420 std::vector<size_t> order(n);
2421 std::iota(order.begin(), order.end(), 0);
2422 std::shuffle(order.begin(), order.end(), gen());
2424 [&](size_t a, size_t b) { return reliability[a] < reliability[b]; });
2425
2427 double best_score = std::numeric_limits<double>::infinity();
2428
2429 const size_t d = get_dmin();
2430
2431 auto rp = r;
2432 const auto trial = [&]() {
2433 try {
2434 const auto c_est = dec_BD_EE(rp);
2435 double score = 0.0;
2436 for (size_t i = 0; i < n; ++i)
2437 if (c_est[i] != r[i]) score += reliability[i];
2438 if (score < best_score) {
2439 best_score = score;
2440 best = c_est;
2441 }
2442 } catch (const decoding_failure&) {
2443 }
2444 };
2445
2446 trial();
2447 for (size_t tau = 2; tau < d; tau += 2) {
2448 rp.erase_components({order[tau - 2], order[tau - 1]});
2449 trial();
2450 if (best_score == 0.0) break;
2451 }
2452
2453 if (!best.has_value()) throw decoding_failure("GMD decoder failed (all trials failed)!");
2454 return std::move(*best);
2455 }
2456
2457 Vector<T> dec_GMD(const Vector<double>& llrs) const override {
2458 if constexpr (!FiniteFieldType<T>) {
2459 throw std::logic_error("GMD decoding only available for codes over finite fields!");
2460 } else if constexpr (T::get_size() != 2) {
2461 throw std::logic_error(
2462 "Soft-input GMD LLR vector decoding only available for binary codes; use the Matrix<double> overload!");
2463 } else {
2464 return dec_GMD(Matrix<double>(llrs));
2465 }
2466 }
2467
2468 Vector<T> dec_GMD(const Matrix<double>& llrs) const override {
2469 if constexpr (!FiniteFieldType<T>) {
2470 throw std::logic_error("GMD decoding only available for codes over finite fields!");
2471 } else {
2473
2474 const size_t n = this->n;
2475 constexpr size_t q = T::get_size();
2476 Vector<T> r(n);
2477 Vector<double> reliabilities(n);
2478 for (size_t i = 0; i < n; ++i) {
2479 const auto c = details::symbol_costs_from_llrs<T>(llrs, i);
2480 size_t best_label = 0;
2481 double best = c[0];
2482 double second = std::numeric_limits<double>::infinity();
2483 for (size_t a = 1; a < q; ++a) {
2484 if (c[a] < best) {
2485 second = best;
2486 best = c[a];
2487 best_label = a;
2488 } else if (c[a] < second) {
2489 second = c[a];
2490 }
2491 }
2494 }
2495
2496 return dec_GMD(r, reliabilities);
2497 }
2498 }
2499#endif
2500
2501 protected:
2511
2512 // Documentation note: cache seeding for constructors and transformations that know these
2513 // quantities analytically; protected so that outside users cannot plant inconsistent values.
2514 void set_dmin(size_t d) const { dmin.emplace(d); }
2515
2516 void set_weight_enumerator(const Polynomial<InfInt>& p) const
2518 {
2520 }
2521
2527
2528 void set_gamma(const Polynomial<T>& g) const {
2529 polynomial.emplace(true);
2530 gamma.emplace(g);
2531 }
2532
2533 void set_gamma(Polynomial<T>&& g) const {
2534 polynomial.emplace(true);
2535 gamma.emplace(std::move(g));
2536 }
2537
2538#ifdef CECCO_ERASURE_SUPPORT
2539 static bool erasures_present(const Vector<T>& r) {
2540 for (size_t i = 0; i < r.get_n(); ++i) {
2541 if (r[i].is_erased()) return true;
2542 }
2543 return false;
2544 }
2545#endif
2546
2547 void validate_length(const Vector<T>& r) const {
2548 const size_t n = this->n;
2549 if (r.get_n() != n)
2550 throw std::invalid_argument(std::string("Received vector length must be ") + std::to_string(n));
2551 }
2552
2553 void validate_length(const Vector<double>& llrs) const {
2554 const size_t n = this->n;
2555 if (llrs.get_n() != n)
2556 throw std::invalid_argument(std::string("LLR vector length must be ") + std::to_string(n));
2557 }
2558
2559 void validate_length(const Matrix<double>& llrs) const
2561 {
2562 constexpr size_t rows = T::get_size() - 1;
2563 const size_t n = this->n;
2564 if (llrs.get_m() != rows || llrs.get_n() != n)
2565 throw std::invalid_argument("LLR matrix must have " + std::to_string(rows) + " rows and " +
2566 std::to_string(n) + " columns");
2567 }
2568
2569 private:
2571 mutable details::OnceCache<InfInt> size;
2572 mutable details::OnceCache<std::vector<Vector<T>>> standard_array;
2573 mutable details::OnceCache<std::vector<bool>> tainted;
2574 mutable details::OnceCache<std::unordered_map<size_t, T>> Meggitt_table;
2575#ifdef CECCO_ERASURE_SUPPORT
2578#endif
2579 mutable details::OnceCache<bool> polynomial;
2580 mutable details::OnceCache<Polynomial<T>> gamma;
2581 mutable details::OnceCache<size_t> shift_index;
2582 mutable details::OnceCache<bool> cyclic;
2583 mutable details::OnceCache<TannerGraph<T>> tanner_graph;
2584
2585 explicit LinearCode(Matrix<T> G) : LinearCode(G.get_n(), G.get_m(), G) {}
2586
2587 static Matrix<T> G_from_codewords(const std::vector<Vector<T>>& codewords) {
2588 if (codewords.empty())
2589 throw std::invalid_argument("Cannot construct linear code from codewords: no codewords provided");
2590
2591 const size_t n = codewords.front().get_n();
2592 Matrix<T> Gp(codewords.size(), n);
2593
2594 for (size_t i = 0; i < codewords.size(); ++i) {
2595 if (codewords[i].get_n() != n)
2596 throw std::invalid_argument(
2597 "Cannot construct linear code from codewords: codewords must have the same length");
2598
2599#ifdef CECCO_ERASURE_SUPPORT
2600 for (size_t j = 0; j < n; ++j) {
2601 if (codewords[i][j].is_erased())
2602 throw std::invalid_argument(
2603 "Cannot construct linear code from codewords: codewords must not contain erasures");
2604 }
2605#endif
2606
2607 Gp.set_submatrix(i, 0, Matrix<T>(codewords[i]));
2608 }
2609
2610 size_t k;
2611 Gp.rref(&k);
2612 if (k == 0) return Matrix<T>(0, n);
2613 return Gp.get_submatrix(0, 0, k, n);
2614 }
2615
2616#ifdef CECCO_ERASURE_SUPPORT
2617 void init_punctured_codes_BD() const {
2619 if (punctured_codes_BD.has_value()) return;
2620 std::clog << "--> Preparing punctured codes for BD error/erasure decoding" << std::endl;
2621 const size_t n = this->n;
2622 const size_t dmin = get_dmin();
2623 size_t count = 0;
2624 for (size_t tau = 1; tau <= dmin - 1; ++tau) count += bin<size_t>(n, tau);
2626 for (size_t tau = 1; tau <= dmin - 1; ++tau) {
2627 std::vector<bool> mask(n, false);
2628 std::fill(mask.begin(), mask.begin() + tau, true);
2629 do {
2630 std::vector<size_t> X;
2631 X.reserve(tau);
2632 for (auto it = mask.cbegin(); it != mask.cend(); ++it)
2633 if (*it) X.push_back(static_cast<size_t>(it - mask.cbegin()));
2635 } while (std::prev_permutation(mask.begin(), mask.end()));
2636 }
2637 });
2638 }
2639
2640 void init_punctured_codes_ML() const {
2642 if (punctured_codes_ML.has_value()) return;
2643 std::clog << "--> Preparing punctured codes for ML error/erasure decoding" << std::endl;
2644 const size_t n = this->n;
2645 const size_t dmin = get_dmin();
2646 size_t bd_count = 0;
2647 for (size_t t = 1; t <= dmin - 1; ++t) bd_count += bin<size_t>(n, t);
2648 size_t count = 0;
2649 for (size_t tau = dmin; tau <= n - 1; ++tau) count += bin<size_t>(n, tau);
2651 for (size_t tau = dmin; tau <= n - 1; ++tau) {
2652 std::vector<bool> mask(n, false);
2653 std::fill(mask.begin(), mask.begin() + tau, true);
2654 do {
2655 std::vector<size_t> X;
2656 X.reserve(tau);
2657 for (auto it = mask.cbegin(); it != mask.cend(); ++it)
2658 if (*it) X.push_back(static_cast<size_t>(it - mask.cbegin()));
2660 } while (std::prev_permutation(mask.begin(), mask.end()));
2661 }
2662 });
2663 }
2664
2665 size_t pos_to_index(const std::vector<size_t>& pos) const {
2666 const size_t n = this->n;
2667 const size_t tau = pos.size();
2668
2669 if (tau == 0) throw std::invalid_argument("Cannot calculate punctured code index from erasure positions!");
2670
2671 size_t offset = 0;
2672 for (size_t t = 1; t < tau; ++t) offset += bin<size_t>(n, t);
2673
2674 size_t rank = 0;
2675 for (size_t i = 0; i < tau; ++i) {
2676 const size_t start = (i == 0) ? 0 : (pos[i - 1] + 1);
2677 for (size_t x = start; x < pos[i]; ++x) rank += bin<size_t>(n - 1 - x, tau - 1 - i);
2678 }
2679 return offset + rank;
2680 }
2681#endif
2682
2683 template <typename cost_t>
2684 Vector<T> viterbi_forward_pass_and_traceback(const Trellis<T>& Tr,
2685 typename Trellis<T>::template Viterbi_Workspace<cost_t>& ws,
2686 const std::string& filename = "") const {
2687 const size_t n = this->n;
2688 using ws_t = typename Trellis<T>::template Viterbi_Workspace<cost_t>;
2689
2690 std::ofstream file;
2691 if constexpr (T::get_size() <= 64) {
2692 if (!filename.empty()) {
2693 file.open(filename);
2694 Tr.template tikz_header<ws_t>(file);
2695 }
2696 }
2697
2698 std::fill(ws.path_costs_prev.begin(), ws.path_costs_prev.end(), ws_t::init);
2699 ws.path_costs_prev[0] = cost_t{0};
2700
2701 for (size_t s = 0; s < n; ++s) {
2702 std::fill(ws.path_costs_curr.begin(), ws.path_costs_curr.end(), ws_t::init);
2703 std::fill(ws.tie_counts.begin(), ws.tie_counts.end(), 0);
2704 for (size_t j = 0; j < Tr.E[s].size(); ++j) {
2705 const auto& e = Tr.E[s][j];
2706 const cost_t cost = ws.path_costs_prev[e.from_id] + ws.edge_costs[s][j];
2707 if (cost < ws.path_costs_curr[e.to_id]) {
2708 ws.path_costs_curr[e.to_id] = cost;
2709 ws.backptrs[s + 1][e.to_id] = &e;
2710 ws.tie_counts[e.to_id] = 1;
2711 } else if (cost == ws.path_costs_curr[e.to_id]) {
2712 if (details::reservoir_accept(++ws.tie_counts[e.to_id])) ws.backptrs[s + 1][e.to_id] = &e;
2713 }
2714 }
2715 std::swap(ws.path_costs_prev, ws.path_costs_curr);
2716 if constexpr (T::get_size() <= 64) {
2717 if (!filename.empty()) Tr.tikz_picture(file, &ws, s + 1);
2718 }
2719 }
2720
2721 Vector<T> c_est(n);
2722
2723 size_t v = 0;
2724 cost_t best = ws.path_costs_prev[0];
2725 uint16_t sink_ties = 1;
2726 for (size_t u = 1; u < Tr.V[n].size(); ++u) {
2727 const cost_t c = ws.path_costs_prev[u];
2728 if (c < best) {
2729 best = c;
2730 v = u;
2731 sink_ties = 1;
2732 } else if (c == best) {
2733 if (details::reservoir_accept(++sink_ties)) v = u;
2734 }
2735 }
2736
2737 for (size_t s = n; s > 0; --s) {
2738 const auto* e = ws.backptrs[s][v];
2739 c_est.set_component(s - 1, e->value);
2740 v = e->from_id;
2741 }
2742
2743 return c_est;
2744 }
2745
2746 struct SoftDecodingSetup {
2747 std::vector<std::array<double, T::get_size()>> costs;
2748 std::vector<size_t> perm;
2749 Matrix<T> Gsys;
2750 Vector<T> u0;
2751 };
2752
2753 SoftDecodingSetup prepare_soft_decoding(const Matrix<double>& llrs) const
2755 {
2756 constexpr size_t q = T::get_size();
2757 const size_t n = this->n;
2758
2761 Vector<T> hard(n);
2762 std::vector<double> reliability(n);
2763 for (size_t i = 0; i < n; ++i) {
2764 const auto c = details::symbol_costs_from_llrs<T>(llrs, i);
2766 size_t best_label = 0;
2767 double best = c[0];
2768 double second = std::numeric_limits<double>::infinity();
2769 for (size_t a = 1; a < q; ++a) {
2770 if (c[a] < best) {
2771 second = best;
2772 best = c[a];
2773 best_label = a;
2774 } else if (c[a] < second) {
2775 second = c[a];
2776 }
2777 }
2779 reliability[i] = second - best;
2780 }
2781
2782 std::vector<size_t> order(n);
2783 std::iota(order.begin(), order.end(), 0);
2784 std::shuffle(order.begin(), order.end(), gen());
2786 [&](size_t a, size_t b) { return reliability[a] > reliability[b]; });
2787
2788 const auto columns = [&](const std::vector<size_t>& cols) {
2789 Matrix<T> M(k, cols.size());
2790 for (size_t j = 0; j < cols.size(); ++j) M.set_submatrix(0, j, G.get_submatrix(0, cols[j], k, 1));
2791 return M;
2792 };
2793
2794 // Documentation note: the MRIS is read off as the pivot columns of a single rref over the
2795 // reliability-ordered columns (same extraction as infoset in the LinearCode constructor);
2796 // the leftmost-pivot property of rref makes this exactly the greedy most reliable
2797 // independent set.
2798 const auto Gord = rref(columns(order));
2800 std::vector<bool> pivot(n, false);
2801 size_t pos = 0;
2802 for (size_t j = 0; j < k; ++j) {
2803 const auto u = unit_vector<T>(k, j);
2804 while (Gord.get_col(pos) != u) ++pos;
2805 pivot[pos] = true;
2807 }
2808 for (size_t i = 0; i < n; ++i)
2809 if (!pivot[i]) setup.perm.push_back(order[i]);
2810
2812
2813 setup.u0 = Vector<T>(k);
2814 for (size_t j = 0; j < k; ++j) setup.u0.set_component(j, hard[setup.perm[j]]);
2815
2816 return setup;
2817 }
2818
2820 const Matrix<double>& llrs, size_t list_size) const
2822 {
2823 constexpr size_t q = T::get_size();
2824 const size_t n = this->n;
2825 const size_t stages = local.E.size();
2826
2827 Matrix<double> local_llrs(q - 1, stages);
2828 for (size_t i = 0; i < stages; ++i)
2830 typename Trellis<T>::template ListViterbi_Workspace<double> ws(local);
2832
2833 const auto candidates = viterbi_list<double>(local, ws, list_size);
2834 Vector<T> best_c;
2835 double best_cost = std::numeric_limits<double>::infinity();
2836 uint16_t ties = 1;
2837 for (size_t p = 0; p < candidates.size(); ++p) {
2838 Vector<T> v(k);
2839 for (size_t j = 0; j < k; ++j) v.set_component(j, candidates[p][j]);
2840 const Vector<T> c_perm = v * setup.Gsys;
2841 double cost = 0.0;
2842 for (size_t j = 0; j < n; ++j) cost += setup.costs[setup.perm[j]][c_perm[j].get_label()];
2843 Vector<T> c(n);
2844 for (size_t j = 0; j < n; ++j) c.set_component(setup.perm[j], c_perm[j]);
2845 if (cost < best_cost) {
2846 best_cost = cost;
2847 best_c = std::move(c);
2848 ties = 1;
2849 } else if (cost == best_cost) {
2851 }
2852 }
2853
2854 return best_c;
2855 }
2856
2857 // Documentation note: priority-first (A*) enumeration of complete trellis paths. The exact backward cost is an
2858 // admissible, consistent heuristic, so the first path returned is optimal. The head is a reservoir pick over the
2859 // whole minimum-metric group, which is enumerated in full even when it exceeds list_size, so the tie-break is
2860 // proper down to list_size 1. Paths are identified by edge index (not vertex sequence), so parallel edges are
2861 // distinguished.
2862 template <typename cost_t>
2864 typename Trellis<T>::template ListViterbi_Workspace<cost_t>& ws,
2865 size_t list_size) const {
2866 using ws_t = typename Trellis<T>::template ListViterbi_Workspace<cost_t>;
2867 if (list_size == 0) throw std::invalid_argument("Viterbi list size must be positive!");
2868
2869 const size_t segments = Tr.E.size();
2870
2871 for (size_t s = 0; s < ws.cost_to_go.size(); ++s)
2874 for (size_t s = segments; s-- > 0;)
2875 for (size_t j = 0; j < Tr.E[s].size(); ++j) {
2876 const auto& e = Tr.E[s][j];
2877 const cost_t h = ws.cost_to_go[s + 1][e.to_id];
2878 if (h == ws_t::init) continue;
2879 const cost_t cand = ws.edge_costs[s][j] + h;
2881 }
2882
2884 for (size_t s = 0; s < segments; ++s) {
2885 out_edges[s].resize(Tr.V[s].size());
2886 for (size_t j = 0; j < Tr.E[s].size(); ++j) out_edges[s][Tr.E[s][j].from_id].push_back(j);
2887 }
2888
2889 struct PathNode {
2890 cost_t g;
2891 cost_t f;
2894 size_t parent;
2895 size_t edge;
2896 };
2899
2900 const auto node_cmp = [&pool](size_t a, size_t b) { return pool[a].f > pool[b].f; };
2902 heap.push(0);
2903
2904 constexpr size_t unstored = std::numeric_limits<size_t>::max();
2905 std::vector<Vector<T>> list;
2906 cost_t best = ws_t::init;
2907 uint16_t ties = 0;
2908 size_t head = 0;
2910 while (!heap.empty()) {
2911 const size_t ni = heap.top();
2912 if (list.size() >= list_size && pool[ni].f > best) break;
2913 heap.pop();
2914 if (pool[ni].f == ws_t::init) break;
2915
2916 if (pool[ni].segment == segments) {
2918 for (size_t cur = ni; pool[cur].segment > 0; cur = pool[cur].parent) {
2919 const size_t s = pool[cur].segment - 1;
2921 }
2922 if (list.empty()) best = pool[ni].g;
2923 if (pool[ni].g == best && details::reservoir_accept(++ties)) {
2924 if (list.size() < list_size)
2925 head = list.size();
2926 else {
2927 head = unstored;
2928 head_path = labels;
2929 }
2930 }
2932 continue;
2933 }
2934
2935 const size_t s = pool[ni].segment;
2936 const auto& out = out_edges[s][pool[ni].vertex];
2937 for (auto it = out.cbegin(); it != out.cend(); ++it) {
2938 const size_t j = *it;
2939 const auto& e = Tr.E[s][j];
2940 const cost_t h = ws.cost_to_go[s + 1][e.to_id];
2941 if (h == ws_t::init) continue;
2942 const cost_t g = pool[ni].g + ws.edge_costs[s][j];
2943 const cost_t f = static_cast<cost_t>(g + h);
2944 pool.push_back(PathNode{g, f, s + 1, e.to_id, ni, j});
2945 heap.push(pool.size() - 1);
2946 }
2947 }
2948
2949 if (head == unstored)
2950 list[0] = std::move(head_path);
2951 else if (head != 0)
2952 std::swap(list[0], list[head]);
2953 return list;
2954 }
2955
2956 Matrix<double> bcjr_forward_backward(const Trellis<T>& Tr, typename Trellis<T>::BCJR_Workspace& ws) const
2958 {
2959 constexpr double neg_inf = -std::numeric_limits<double>::infinity();
2960 constexpr size_t q = T::get_size();
2961 const size_t n = this->n;
2962
2963 for (auto layer = ws.alpha.begin(); layer != ws.alpha.end(); ++layer)
2964 std::fill(layer->begin(), layer->end(), neg_inf);
2965 for (auto layer = ws.beta.begin(); layer != ws.beta.end(); ++layer)
2966 std::fill(layer->begin(), layer->end(), neg_inf);
2967
2968 ws.alpha[0][0] = 0.0;
2969 for (size_t s = 0; s < n; ++s) {
2970 for (size_t j = 0; j < Tr.E[s].size(); ++j) {
2971 const auto& e = Tr.E[s][j];
2972 const double val = ws.alpha[s][e.from_id] - ws.edge_costs[s][j];
2973 ws.alpha[s + 1][e.to_id] = details::max_star(ws.alpha[s + 1][e.to_id], val);
2974 }
2975 }
2976
2977 for (size_t u = 0; u < Tr.V[n].size(); ++u) ws.beta[n][u] = 0.0;
2978 for (size_t s = n; s > 0; --s) {
2979 for (size_t j = 0; j < Tr.E[s - 1].size(); ++j) {
2980 const auto& e = Tr.E[s - 1][j];
2981 const double val = ws.beta[s][e.to_id] - ws.edge_costs[s - 1][j];
2982 ws.beta[s - 1][e.from_id] = details::max_star(ws.beta[s - 1][e.from_id], val);
2983 }
2984 }
2985
2986 Matrix<double> res(q - 1, n);
2987 std::array<double, q> Lambda;
2988 for (size_t s = 0; s < n; ++s) {
2990 for (size_t j = 0; j < Tr.E[s].size(); ++j) {
2991 const auto& e = Tr.E[s][j];
2992 const double val = ws.alpha[s][e.from_id] - ws.edge_costs[s][j] + ws.beta[s + 1][e.to_id];
2993 const size_t a = e.value.get_label();
2995 }
2996 for (size_t a = 1; a < q; ++a) res.set_component(a - 1, s, Lambda[0] - Lambda[a]);
2997 }
2998
2999 return res;
3000 }
3001
3002 Vector<T> bcjr_decode(const Matrix<double>& llrs, Matrix<double>* Lambda_ptr, const std::string& filename) const
3004 {
3006 if (!filename.empty() && T::get_size() > 2)
3007 throw std::invalid_argument("BCJR trellis TikZ export only supported for binary codes!");
3008
3009 constexpr size_t q = T::get_size();
3010 const size_t n = this->n;
3011
3012 if (k == 0) {
3013 if (Lambda_ptr != nullptr) *Lambda_ptr = Matrix<double>(q - 1, n, std::numeric_limits<double>::infinity());
3014 return Vector<T>(n);
3015 }
3016
3017 const auto& Tr = get_minimal_trellis();
3018 typename Trellis<T>::BCJR_Workspace ws(Tr);
3021 if constexpr (T::get_size() == 2) {
3022 if (!filename.empty()) {
3023 ws.v.emplace(llrs);
3025 }
3026 }
3027 if (Lambda_ptr != nullptr) *Lambda_ptr = Lambda_symbol;
3028
3029 Vector<T> c_est(n);
3030 for (size_t i = 0; i < n; ++i) {
3031 size_t label = 0;
3032 double min_LLR = 0.0; // the LLR of label 0 is 0 by definition
3033 uint16_t ties = 1;
3034 for (size_t a = 1; a < q; ++a) {
3035 const double L = Lambda_symbol(a - 1, i);
3036 if (L < min_LLR) {
3037 min_LLR = L;
3038 label = a;
3039 ties = 1;
3040 } else if (L == min_LLR) {
3041 if (details::reservoir_accept(++ties)) label = a;
3042 }
3043 }
3045 }
3046 return c_est;
3047 }
3048
3049 // Documentation note: exact log-domain sum-product (via details::max_star), not min-sum or any other approximation.
3050 Vector<T> bp_decode(const Matrix<double>& llrs, size_t max_iterations, Matrix<double>* Lambda_ptr,
3053 {
3055 if (!filename.empty() && T::get_size() > 2)
3056 throw std::invalid_argument("BP Tanner graph TikZ export only supported for binary codes!");
3057
3058 constexpr double neg_inf = -std::numeric_limits<double>::infinity();
3059 constexpr size_t q = T::get_size();
3060 const size_t n = this->n;
3061
3062 if (k == 0) {
3063 if (Lambda_ptr != nullptr) *Lambda_ptr = Matrix<double>(q - 1, n, std::numeric_limits<double>::infinity());
3064 if (nof_iterations_ptr != nullptr) *nof_iterations_ptr = 0;
3065 return Vector<T>(n);
3066 }
3067
3068 const auto& g = get_tanner_graph();
3069 typename TannerGraph<T>::BP_Workspace ws(g);
3071
3072 std::ofstream file;
3073 if constexpr (T::get_size() == 2) {
3074 if (!filename.empty()) {
3075 ws.v.emplace(llrs);
3078 }
3079 }
3080
3081 auto conv = [neg_inf](const std::array<double, q>& A, const std::array<double, q>& B) {
3082 std::array<double, q> R;
3083 R.fill(neg_inf);
3084 for (size_t x = 0; x < q; ++x) {
3085 if (A[x] == neg_inf) continue;
3086 for (size_t y = 0; y < q; ++y) {
3087 if (B[y] == neg_inf) continue;
3088 const size_t t = (T(x) + T(y)).get_label();
3089 R[t] = details::max_star(R[t], A[x] + B[y]);
3090 }
3091 }
3092 return R;
3093 };
3094
3095 auto normalize = [](std::array<double, q>& m) {
3096 const double ref = m[0];
3097 for (size_t i = 0; i < q; ++i) m[i] -= ref;
3098 };
3099
3100 Vector<T> c_est(n);
3101 auto decide = [&](size_t j) {
3102 size_t best = 0;
3103 double best_val = ws.posterior[j][0];
3104 uint16_t ties = 1;
3105 for (size_t a = 1; a < q; ++a) {
3106 const double val = ws.posterior[j][a];
3107 if (val > best_val) {
3108 best_val = val;
3109 best = a;
3110 ties = 1;
3111 } else if (val == best_val) {
3112 if (details::reservoir_accept(++ties)) best = a;
3113 }
3114 }
3116 };
3117
3118 for (size_t i = 0; i < g.checks.size(); ++i)
3119 for (size_t e = 0; e < g.checks[i].size(); ++e) ws.m_vc[i][e] = ws.intrinsic[g.checks[i][e].var_id];
3120
3121 // Intrinsic (channel-only) posterior and hard decision; this is the result for max_iterations == 0.
3122 for (size_t j = 0; j < n; ++j) {
3123 ws.posterior[j] = ws.intrinsic[j];
3124 decide(j);
3125 }
3126
3127 if constexpr (T::get_size() == 2) {
3128 if (!filename.empty()) g.tikz_picture(file, &ws);
3129 }
3130
3131 std::vector<std::array<double, q>> W, F, B;
3132 size_t iter = 0;
3133 while (iter < max_iterations && !(c_est * HT).is_zero()) {
3134 for (size_t i = 0; i < g.checks.size(); ++i) {
3135 const size_t d = g.checks[i].size();
3136 W.resize(d);
3137 F.resize(d + 1);
3138 B.resize(d + 1);
3139 for (size_t e = 0; e < d; ++e) {
3140 const T h = g.checks[i][e].value;
3141 W[e].fill(neg_inf);
3142 for (size_t a = 0; a < q; ++a) W[e][(h * T(a)).get_label()] = ws.m_vc[i][e][a];
3143 }
3144 F[0].fill(neg_inf);
3145 F[0][0] = 0.0;
3146 for (size_t e = 0; e < d; ++e) F[e + 1] = conv(F[e], W[e]);
3147 B[d].fill(neg_inf);
3148 B[d][0] = 0.0;
3149 for (size_t e = d; e-- > 0;) B[e] = conv(W[e], B[e + 1]);
3150 for (size_t e = 0; e < d; ++e) {
3151 const std::array<double, q> excl = conv(F[e], B[e + 1]);
3152 const T h = g.checks[i][e].value;
3153 for (size_t a = 0; a < q; ++a) ws.m_cv[i][e][a] = excl[(-(h * T(a))).get_label()];
3154 normalize(ws.m_cv[i][e]);
3155 }
3156 }
3157
3158 for (size_t j = 0; j < n; ++j) {
3159 ws.posterior[j] = ws.intrinsic[j];
3160 for (size_t t = 0; t < ws.var_edges[j].size(); ++t) {
3161 const auto [i, e] = ws.var_edges[j][t];
3162 for (size_t a = 0; a < q; ++a) ws.posterior[j][a] += ws.m_cv[i][e][a];
3163 }
3164 decide(j);
3165 for (size_t t = 0; t < ws.var_edges[j].size(); ++t) {
3166 const auto [i, e] = ws.var_edges[j][t];
3167 for (size_t a = 0; a < q; ++a) ws.m_vc[i][e][a] = ws.posterior[j][a] - ws.m_cv[i][e][a];
3168 normalize(ws.m_vc[i][e]);
3169 }
3170 }
3171
3172 if constexpr (T::get_size() == 2) {
3173 if (!filename.empty()) g.tikz_picture(file, &ws);
3174 }
3175 ++iter;
3176 }
3177
3178 if (nof_iterations_ptr != nullptr) *nof_iterations_ptr = iter;
3179
3180 if (Lambda_ptr != nullptr) {
3181 Matrix<double> res(q - 1, n);
3182 for (size_t j = 0; j < n; ++j)
3183 for (size_t a = 1; a < q; ++a) res.set_component(a - 1, j, ws.posterior[j][0] - ws.posterior[j][a]);
3184 *Lambda_ptr = std::move(res);
3185 }
3186
3187 return c_est;
3188 }
3189
3190 InfInt N(size_t ell, size_t h, size_t s) const {
3191 const size_t n = this->n;
3192
3193 if constexpr (std::is_same_v<T, Fp<2>>) {
3194 InfInt res = 0;
3195 for (size_t u = 0; u <= n; ++u) {
3196 for (size_t w = 0; w <= n; ++w) {
3197 if (u + w == s && ell + u - w == h) {
3198 res += bin<InfInt>(n - ell, u) * bin<InfInt>(ell, w);
3199 }
3200 }
3201 }
3202 return res;
3203 } else {
3204 constexpr size_t q = T::get_size();
3205 InfInt res = 0;
3206
3207 for (size_t u = 0; u <= n; ++u) {
3208 for (size_t v = 0; v <= n; ++v) {
3209 for (size_t w = 0; w <= n; ++w) {
3210 if (u + v + w == s && ell + u - w == h) {
3211 res += bin<InfInt>(n - ell, u) * bin<InfInt>(ell, v) * bin<InfInt>(ell - v, w) *
3212 sqm<InfInt>(q - 1, u) * sqm<InfInt>(q - 2, v);
3213 break; // no need to check for further matching w
3214 }
3215 }
3216 }
3217 }
3218
3219 /*
3220 // Blahut alternative
3221 for (size_t i=0; i<=n; ++i) {
3222 for (size_t j=0; j<=n; ++j) {
3223 if (i+2*j+h==s+ell) {
3224 if (ell>n || j+h-ell>n-ell || ell>j+h || i>ell || i>ell || j>ell-i) continue;
3225 sum+=bin<InfInt>(n-ell, j+h-ell)*bin<InfInt>(ell, i)*bin<InfInt>(ell-i,
3226 j)*powl(F::get_size(), j+h-ell)*powl(F::get_size()-2, i);
3227 }
3228 }
3229 }
3230 */
3231
3232 return res;
3233 }
3234 }
3235};
3236
3237template <FiniteFieldType T>
3238class UniverseCode : public LinearCode<T> {
3239 public:
3240 UniverseCode(size_t n) : LinearCode<T>(n, n, IdentityMatrix<T>(n)) {
3241 auto weight_enumerator = Polynomial<InfInt>();
3242 for (size_t i = 0; i <= n; ++i)
3243 weight_enumerator.set_coefficient(i, bin<InfInt>(n, i) * sqm<InfInt>(T::get_size() - 1, i));
3244 this->set_weight_enumerator(std::move(weight_enumerator));
3245 }
3246
3247 UniverseCode(const LinearCode<T>& C) : UniverseCode(Universe_n(C)) {}
3248
3250
3251 void get_info(std::ostream& os) const override {
3252 if (this->template info_prelude<LinearCode<T>>(os)) os << "Universe code";
3253 }
3254
3255 ZeroCode<T> get_dual() const { return ZeroCode<T>(this->n); }
3256 Vector<T> enc(const Vector<T>& u) const override { return u; }
3257 Vector<T> encinv(const Vector<T>& c) const override { return c; }
3258
3259 Vector<T> dec_BD(const Vector<T>& r) const override {
3260 this->validate_length(r);
3261#ifdef CECCO_ERASURE_SUPPORT
3262 if (LinearCode<T>::erasures_present(r))
3263 throw decoding_failure("Universe code BD decoder failed, received vector contains erasures!");
3264#endif
3265 return r;
3266 }
3267
3268 Vector<T> dec_ML(const Vector<T>& r) const override {
3269 this->validate_length(r);
3270#ifdef CECCO_ERASURE_SUPPORT
3271 if (LinearCode<T>::erasures_present(r)) return dec_ML_EE(r);
3272#endif
3273 return r;
3274 }
3275
3276#ifdef CECCO_ERASURE_SUPPORT
3277 Vector<T> dec_BD_EE(const Vector<T>& r) const override {
3278 this->validate_length(r);
3280 throw decoding_failure("Universe code BD error/erasure decoder failed, received vector contains erasures!");
3281 return r;
3282 }
3283 Vector<T> dec_ML_EE(const Vector<T>& r) const override {
3284 this->validate_length(r);
3285 auto c_est = r;
3286 const T zero(0);
3287 for (size_t i = 0; i < this->n; ++i) {
3289 }
3290 return c_est;
3291 }
3292#endif
3293
3294 private:
3295 static size_t Universe_n(const LinearCode<T>& C) {
3296 if (C.get_n() != C.get_k()) throw std::invalid_argument("Linear code cannot be converted into universe code!");
3297 return C.get_n();
3298 }
3299};
3300
3301template <FiniteFieldType T>
3302class ZeroCode : public LinearCode<T> {
3303 public:
3304 ZeroCode(size_t n) : LinearCode<T>(n, 0, ZeroMatrix<T>(1, n)) {}
3305
3306 ZeroCode(const LinearCode<T>& C) : LinearCode<T>(C) {
3307 if (this->k != 0) throw std::invalid_argument("Linear code cannot be converted into zero code!");
3308 }
3309
3310 ZeroCode<T> get_equivalent_code_in_standard_form() const { return ZeroCode<T>(this->n); }
3311
3312 void get_info(std::ostream& os) const override {
3313 if (this->template info_prelude<LinearCode<T>>(os)) os << "Zero code";
3314 }
3315};
3316
3317template <FiniteFieldType T>
3318class HammingCode : public LinearCode<T> {
3319 friend class SimplexCode<T>;
3320
3321 public:
3322 HammingCode(size_t s) : LinearCode<T>(Hamming_n(s), Hamming_k(s), Hamming_H(s)), s(s) {
3323 constexpr size_t q = T::get_size();
3324 const size_t n = this->n;
3325 Polynomial<InfInt> weight_enumerator_dual; // weight enumerator of the dual...
3326 weight_enumerator_dual.set_coefficient(0, 1); // ... code, a q-ary simplex code...
3327 weight_enumerator_dual.set_coefficient(sqm<size_t>(q, s - 1), sqm<InfInt>(q, s) - 1); // ... easy to calculate
3328 this->set_weight_enumerator(MacWilliamsIdentity<T>(weight_enumerator_dual, n, n - this->k));
3329 }
3330
3331 HammingCode(const LinearCode<T>& C) : LinearCode<T>(C), s(0) {
3332 for (size_t s_cand = 2; s_cand < std::numeric_limits<size_t>::max(); ++s_cand) {
3333 const size_t n = Hamming_n(s_cand);
3334 if (n > this->n) break;
3335 if (n != this->n || Hamming_k(s_cand) != this->k) continue;
3336
3337 std::vector<size_t> seen;
3338 seen.reserve(this->n);
3339 bool valid = true;
3340 const T one(1);
3341 for (size_t i = 0; i < this->n && valid; ++i) {
3342 const auto h = this->HT.get_row(i);
3343 T inv;
3344 for (size_t j = 0; j < h.get_n(); ++j)
3345 if (!h[j].is_zero()) {
3346 inv = one / h[j];
3347 break;
3348 }
3349 if (inv.is_zero()) {
3350 valid = false;
3351 break;
3352 }
3353 const size_t key = (inv * h).as_integer();
3354 if (std::ranges::find(seen, key) != seen.end()) {
3355 valid = false;
3356 break;
3357 }
3358 seen.push_back(key);
3359 }
3360 if (valid) {
3361 this->s = s_cand;
3362 this->set_dmin(3);
3363 return;
3364 }
3365 }
3366 throw std::invalid_argument("Linear code cannot be converted into Hamming code!");
3367 }
3368
3370 return HammingCode<T>(LinearCode<T>::get_equivalent_code_in_standard_form());
3371 }
3372
3373 size_t get_s() const noexcept { return s; }
3374
3375 void get_info(std::ostream& os) const override {
3376 if (this->template info_prelude<LinearCode<T>>(os))
3377 os << BOLD("Hamming code") " with properties: { s = " << s << " }";
3378 }
3379
3380 SimplexCode<T> get_dual() const { return SimplexCode<T>(LinearCode<T>::get_dual()); }
3381
3382 Vector<T> dec_BD(const Vector<T>& r) const override {
3383#ifdef CECCO_ERASURE_SUPPORT
3384 if (LinearCode<T>::erasures_present(r)) return dec_BD_EE(r);
3385#endif
3386 this->validate_length(r);
3387
3388 const auto s = r * this->HT;
3389 if (s.is_zero()) return r;
3390 auto c_est = r;
3391 for (size_t i = 0; i < this->n; ++i) {
3392 for (size_t j = 1; j < T::get_size(); ++j) {
3393 T a(j);
3394 if (s == a * this->HT.get_row(i)) {
3395 c_est.set_component(i, c_est[i] - a);
3396 return c_est;
3397 }
3398 }
3399 }
3400
3401 return c_est;
3402 }
3403
3404 Vector<T> dec_ML(const Vector<T>& r) const override {
3405#ifdef CECCO_ERASURE_SUPPORT
3406 if (LinearCode<T>::erasures_present(r)) return dec_ML_EE(r);
3407#endif
3408
3409 return dec_BD(r);
3410 }
3411
3412#ifdef CECCO_ERASURE_SUPPORT
3413 Vector<T> dec_BD_EE(const Vector<T>& r) const override {
3414 this->validate_length(r);
3415
3416 const auto X = this->erasure_positions(r);
3417 const size_t tau = X.size();
3418
3419 if (tau == 0) return dec_BD(r);
3420 if (tau > 2) throw decoding_failure("Hamming code BD error/erasure decoder failed!");
3421
3422 const size_t redundancy = this->n - this->k;
3423 auto s = Vector<T>(redundancy);
3424 for (size_t i = 0; i < this->n; ++i) {
3425 if (!r[i].is_erased()) s += r[i] * this->HT.get_row(i);
3426 }
3427
3428 auto c_est = r;
3429
3430 if (tau == 1) {
3431 if (s.is_zero()) {
3432 c_est.set_component(X[0], 0);
3433 } else {
3434 for (size_t i = 0; i < redundancy; ++i) {
3435 if (!this->HT(X[0], i).is_zero()) {
3436 c_est.set_component(X[0], -s[i] / this->HT(X[0], i));
3437 break;
3438 }
3439 }
3440 }
3441 } else if (tau == 2) {
3442 const auto c0 = transpose(Matrix<T>(this->HT.get_row(X[0])));
3443 const auto c1 = transpose(Matrix<T>(this->HT.get_row(X[1])));
3444 const auto c2 = -transpose(Matrix<T>(s));
3445 const auto M = horizontal_join(horizontal_join(c0, c1), c2);
3446 const auto B = M.basis_of_nullspace();
3447 if (B.get_m() == 0) throw decoding_failure("Hamming code BD error/erasure decoder failed!");
3448 c_est.set_component(X[0], B(0, 0));
3449 c_est.set_component(X[1], B(0, 1));
3450 }
3451
3452 if (!(c_est * this->HT).is_zero()) throw decoding_failure("Hamming code BD error/erasure decoder failed!");
3453
3454 return c_est;
3455 }
3456
3457 Vector<T> dec_ML_EE(const Vector<T>& r) const override { return dec_BD_EE(r); }
3458#endif
3459
3460 private:
3461 size_t s;
3462
3463 static size_t Hamming_n(size_t s) {
3464 if (s < 2) throw std::invalid_argument("Hamming and simplex code parameter s must be at least 2!");
3465 constexpr size_t q = T::get_size();
3466 return (sqm<size_t>(q, s) - 1) / (q - 1);
3467 }
3468
3469 static size_t Hamming_k(size_t s) { return Hamming_n(s) - s; }
3470
3471 static Matrix<T> Hamming_H(size_t s) {
3472 const size_t n = Hamming_n(s);
3473 auto H = Matrix<T>(n, s);
3474 const T one(1);
3475 size_t i = 0;
3476 for (size_t top = 0; top < s; ++top) {
3477 const auto v = IdentityMatrix<T>(s - top - 1).rowspace();
3478 for (size_t j = 0; j < v.size(); ++j) {
3479 H.set_component(n - i - 1, top, one);
3480 H.set_submatrix(n - i - 1, top + 1, Matrix(v[v.size() - j - 1]));
3481 ++i;
3482 }
3483 }
3484 H.transpose();
3485 return H;
3486 }
3487};
3488
3489template <FiniteFieldType T>
3490class SimplexCode : public LinearCode<T> {
3491 public:
3492 SimplexCode(size_t s)
3493 : LinearCode<T>(HammingCode<T>::Hamming_n(s), s, HammingCode<T>::Hamming_H(s)),
3494 s(s),
3495 cols_as_integers(this->n) {
3496 constexpr size_t q = T::get_size();
3497 Polynomial<InfInt> weight_enumerator;
3498 weight_enumerator.set_coefficient(0, 1);
3499 weight_enumerator.set_coefficient(sqm<size_t>(q, s - 1), sqm<InfInt>(q, s) - 1);
3500 this->set_weight_enumerator(std::move(weight_enumerator));
3501 for (size_t i = 0; i < this->n; ++i) cols_as_integers[i] = this->G.get_col(i).as_integer();
3502 }
3503
3504 SimplexCode(const LinearCode<T>& C) : LinearCode<T>(C), s(0), cols_as_integers(this->n) {
3505 for (size_t s_cand = 2; s_cand < std::numeric_limits<size_t>::max(); ++s_cand) {
3506 const size_t n = HammingCode<T>::Hamming_n(s_cand);
3507 if (n > this->n) break;
3508 if (n == this->n) {
3509 if (this->k == s_cand && this->get_dmin() == sqm<size_t>(T::get_size(), s_cand - 1)) {
3510 this->s = s_cand;
3511 for (size_t i = 0; i < this->n; ++i) cols_as_integers[i] = this->G.get_col(i).as_integer();
3512 return;
3513 }
3514 }
3515 }
3516 throw std::invalid_argument("Linear code cannot be converted into Simplex code!");
3517 }
3518
3520 return SimplexCode<T>(LinearCode<T>::get_equivalent_code_in_standard_form());
3521 }
3522
3523 size_t get_s() const noexcept { return s; }
3524
3525 void get_info(std::ostream& os) const override {
3526 if (this->template info_prelude<LinearCode<T>>(os))
3527 os << BOLD("Simplex code") " with properties: { s = " << s << " }";
3528 }
3529
3530 HammingCode<T> get_dual() const { return HammingCode<T>(LinearCode<T>::get_dual()); }
3531
3532 Vector<T> dec_BD(const Vector<T>& r) const override {
3533#ifdef CECCO_ERASURE_SUPPORT
3534 if (LinearCode<T>::erasures_present(r)) return dec_BD_EE(r);
3535#endif
3536 if constexpr (T::get_size() != 2) return LinearCode<T>::dec_BD(r);
3537
3538 const auto c_est = dec_ML(r);
3539 if (dH(r, c_est) > this->get_tmax())
3540 throw decoding_failure("Simplex code BD decoder failed!");
3541 else
3542 return c_est;
3543 }
3544
3545 Vector<T> dec_ML(const Vector<T>& r) const override {
3546#ifdef CECCO_ERASURE_SUPPORT
3547 if (LinearCode<T>::erasures_present(r)) return dec_ML_EE(r);
3548#endif
3549 if constexpr (T::get_size() != 2) return LinearCode<T>::dec_ML(r);
3550 this->validate_length(r);
3551
3552 const size_t n = this->n;
3553 Vector<double> y(n + 1, 0.0);
3554 for (size_t i = 0; i < n; ++i) y.set_component(cols_as_integers[i], r[i].is_zero() ? 1.0 : -1.0);
3555
3556 return FWHT_argmax(y);
3557 }
3558
3559 Vector<T> dec_ML_soft(const Vector<double>& llrs) const override {
3560 if constexpr (T::get_size() != 2) {
3561 return LinearCode<T>::dec_ML_soft(llrs);
3562 } else {
3563 return dec_ML_soft(Matrix<double>(llrs));
3564 }
3565 }
3566
3567 Vector<T> dec_ML_soft(const Matrix<double>& llrs) const override {
3568 if constexpr (T::get_size() != 2) {
3569 return LinearCode<T>::dec_ML_soft(llrs);
3570 } else {
3571 this->validate_length(llrs);
3572
3573 const size_t n = this->n;
3574 Vector<double> y(n + 1, 0.0);
3575 for (size_t i = 0; i < n; ++i) y.set_component(cols_as_integers[i], llrs(0, i));
3576
3577 return FWHT_argmax(y);
3578 }
3579 }
3580
3581#ifdef CECCO_ERASURE_SUPPORT
3582 Vector<T> dec_BD_EE(const Vector<T>& r) const override {
3583 if constexpr (T::get_size() != 2) return LinearCode<T>::dec_BD_EE(r);
3584 this->validate_length(r);
3585
3586 const size_t n = this->n;
3587 const size_t dmin = this->get_dmin();
3588
3589 size_t tau = 0;
3590 for (size_t i = 0; i < n; ++i) {
3591 if (r[i].is_erased()) ++tau;
3592 }
3593
3594 if (tau > dmin - 1) throw decoding_failure("Simplex code BD error/erasure decoder failed!");
3595
3596 const Vector<T> c_est = dec_ML_EE(r);
3597
3598 size_t t = 0;
3599 for (size_t i = 0; i < n; ++i) {
3600 if (!r[i].is_erased() && c_est[i] != r[i]) ++t;
3601 }
3602
3603 if (2 * t + tau > dmin - 1) throw decoding_failure("Simplex code BD error/erasure decoder failed!");
3604
3605 return c_est;
3606 }
3607
3608 Vector<T> dec_ML_EE(const Vector<T>& r) const override {
3609 if constexpr (T::get_size() != 2) return LinearCode<T>::dec_ML_EE(r);
3610 this->validate_length(r);
3611
3612 const size_t n = this->n;
3613 size_t tau = 0;
3614 for (size_t i = 0; i < n; ++i) {
3615 if (r[i].is_erased()) ++tau;
3616 }
3617 if (tau == n) return Vector<T>(n, T(0));
3618
3619 Vector<double> y(n + 1, 0.0);
3620 for (size_t i = 0; i < n; ++i)
3621 if (!r[i].is_erased()) y.set_component(cols_as_integers[i], r[i].is_zero() ? 1.0 : -1.0);
3622
3623 return FWHT_argmax(y);
3624 }
3625#endif
3626
3627 private:
3628 size_t s;
3629 std::vector<size_t> cols_as_integers;
3630
3631 Vector<T> FWHT_argmax(Vector<double>& y) const {
3632 const size_t n = y.get_n();
3633 if (!std::has_single_bit(n)) throw std::invalid_argument("FWHT requires length 2^m!");
3634 for (size_t len = 1; 2 * len <= n; len *= 2) {
3635 for (size_t i = 0; i < n; i += 2 * len) {
3636 for (size_t j = 0; j < len; ++j) {
3637 const double u = y[i + j];
3638 const double v = y[i + j + len];
3639 y.set_component(i + j, u + v);
3640 y.set_component(i + j + len, u - v);
3641 }
3642 }
3643 }
3644
3645 size_t hit = 0;
3646 double best = -std::numeric_limits<double>::infinity();
3647 uint16_t ties = 1;
3648 for (size_t i = 0; i < n; ++i) {
3649 if (y[i] > best) {
3650 best = y[i];
3651 hit = i;
3652 ties = 1;
3653 } else if (y[i] == best) {
3654 if (details::reservoir_accept(++ties)) hit = i;
3655 }
3656 }
3657
3658 Vector<T> u_est;
3659 u_est.from_integer(hit, this->k);
3660 return u_est * this->G;
3661 }
3662};
3663
3664template <FiniteFieldType T>
3665class RepetitionCode : public LinearCode<T> {
3666 friend class SingleParityCheckCode<T>;
3667
3668 public:
3669 RepetitionCode(size_t n) : LinearCode<T>(n, 1, Repetition_G(n)) {
3670 Polynomial<InfInt> weight_enumerator;
3671 weight_enumerator.set_coefficient(0, 1);
3672 weight_enumerator.set_coefficient(n, T::get_size() - 1);
3673 this->set_weight_enumerator(std::move(weight_enumerator));
3674 auto p = ZeroPolynomial<T>();
3675 p.set_coefficient(0, -T(1));
3676 p.set_coefficient(n, T(1));
3677 auto q = ZeroPolynomial<T>();
3678 q.set_coefficient(0, -T(1));
3679 q.set_coefficient(1, T(1));
3680 this->set_gamma(p / q);
3681 }
3682
3683 RepetitionCode(const LinearCode<T>& C) : LinearCode<T>(C) {
3684 if (this->k != 1) throw std::invalid_argument("Linear code cannot be converted into repetition code!");
3685 const auto& g = this->G.get_row(0);
3686 if (g[0].is_zero()) throw std::invalid_argument("Linear code cannot be converted into repetition code!");
3687 for (size_t i = 1; i < this->n; ++i)
3688 if (g[i] != g[0]) throw std::invalid_argument("Linear code cannot be converted into repetition code!");
3689 }
3690
3692
3694 return RepetitionCode<T>(LinearCode<T>::get_identical_code_in_polynomial_form());
3695 }
3696
3697 void get_info(std::ostream& os) const override {
3698 if (this->template info_prelude<LinearCode<T>>(os)) os << "Repetition code";
3699 }
3700
3701 SingleParityCheckCode<T> get_dual() const { return SingleParityCheckCode<T>(this->n); }
3702 Vector<T> enc(const Vector<T>& u) const override { return Vector<T>(this->n, u[0] * this->G(0, 0)); }
3703 Vector<T> encinv(const Vector<T>& c) const override { return Vector<T>(1, c[0] / this->G(0, 0)); }
3704
3705 Vector<T> dec_BD(const Vector<T>& r) const override {
3706#ifdef CECCO_ERASURE_SUPPORT
3707 if (LinearCode<T>::erasures_present(r)) return dec_BD_EE(r);
3708#endif
3709
3710 const auto c_est = dec_ML(r);
3711 if (T::get_size() != 2 || this->n % 2 == 0) {
3712 if (2 * dH(r, c_est) > this->get_dmin() - 1) throw decoding_failure("Repetition code BD decoder failed!");
3713 }
3714
3715 return c_est;
3716 }
3717
3718 Vector<T> dec_ML(const Vector<T>& r) const override {
3719#ifdef CECCO_ERASURE_SUPPORT
3720 if (LinearCode<T>::erasures_present(r)) return dec_ML_EE(r);
3721#endif
3722 this->validate_length(r);
3723
3724 const size_t n = this->n;
3725 constexpr size_t q = T::get_size();
3726 std::array<size_t, q> counters{};
3727 for (size_t i = 0; i < n; ++i) ++counters[r[i].get_label()];
3728 size_t label = 0;
3729 size_t best = counters[0];
3730 uint16_t ties = 1;
3731 for (size_t a = 1; a < q; ++a) {
3732 if (counters[a] > best) {
3733 best = counters[a];
3734 label = a;
3735 ties = 1;
3736 } else if (counters[a] == best) {
3737 if (details::reservoir_accept(++ties)) label = a;
3738 }
3739 }
3740
3741 return Vector<T>(n, T(label));
3742 }
3743
3744#ifdef CECCO_ERASURE_SUPPORT
3745 Vector<T> dec_BD_EE(const Vector<T>& r) const override {
3746 this->validate_length(r);
3747
3748 const size_t n = this->n;
3749 const size_t dmin = this->get_dmin();
3750 constexpr size_t q = T::get_size();
3751
3752 size_t tau = 0;
3753 for (size_t i = 0; i < n; ++i) {
3754 if (r[i].is_erased()) ++tau;
3755 }
3756
3757 if (tau == 0) return dec_BD(r);
3758 if (tau > dmin - 1) throw decoding_failure("Repetition code BD error/erasure decoder failed!");
3759
3760 std::array<size_t, q> counters{};
3761 for (size_t i = 0; i < n; ++i) {
3762 if (!r[i].is_erased()) ++counters[r[i].get_label()];
3763 }
3764 size_t label = 0;
3765 size_t best = counters[0];
3766 uint16_t ties = 1;
3767 for (size_t a = 1; a < q; ++a) {
3768 if (counters[a] > best) {
3769 best = counters[a];
3770 label = a;
3771 ties = 1;
3772 } else if (counters[a] == best) {
3773 if (details::reservoir_accept(++ties)) label = a;
3774 }
3775 }
3776
3777 const auto c_est = Vector<T>(n, T(label));
3778 size_t t = 0;
3779 for (size_t i = 0; i < n; ++i) {
3780 if (!r[i].is_erased() && r[i] != c_est[i]) ++t;
3781 }
3782
3783 if (q != 2 || n % 2 == 0) {
3784 if (2 * t + tau > dmin - 1) throw decoding_failure("Repetition code BD error/erasure decoder failed!");
3785 }
3786
3787 return c_est;
3788 }
3789
3790 Vector<T> dec_ML_EE(const Vector<T>& r) const override {
3791 this->validate_length(r);
3792
3793 const size_t n = this->n;
3794 size_t tau = 0;
3795 for (size_t i = 0; i < n; ++i) {
3796 if (r[i].is_erased()) ++tau;
3797 }
3798
3799 if (tau == 0) return dec_ML(r);
3800 if (tau == n) return Vector<T>(n, T(0));
3801
3802 constexpr size_t q = T::get_size();
3803 std::array<size_t, q> counters{};
3804 for (size_t i = 0; i < n; ++i) {
3805 if (!r[i].is_erased()) ++counters[r[i].get_label()];
3806 }
3807 size_t label = 0;
3808 size_t best = counters[0];
3809 uint16_t ties = 1;
3810 for (size_t a = 1; a < q; ++a) {
3811 if (counters[a] > best) {
3812 best = counters[a];
3813 label = a;
3814 ties = 1;
3815 } else if (counters[a] == best) {
3816 if (details::reservoir_accept(++ties)) label = a;
3817 }
3818 }
3819 return Vector<T>(n, T(label));
3820 }
3821#endif
3822
3823 private:
3824 static Matrix<T> Repetition_G(size_t n) {
3825 if (n == 0) throw std::invalid_argument("Repetition code length must be positive!");
3826 return Matrix<T>(1, n, T(1));
3827 }
3828};
3829
3830template <FiniteFieldType T>
3832 public:
3833 SingleParityCheckCode(size_t n) : LinearCode<T>(n, SingleParityCheck_k(n), RepetitionCode<T>::Repetition_G(n)) {
3834 Polynomial<InfInt> weight_enumerator_rep;
3835 weight_enumerator_rep.set_coefficient(0, 1);
3836 weight_enumerator_rep.set_coefficient(n, T::get_size() - 1);
3837 this->set_weight_enumerator(MacWilliamsIdentity<T>(weight_enumerator_rep, n, 1));
3838 auto q = ZeroPolynomial<T>();
3839 q.set_coefficient(0, -T(1));
3840 q.set_coefficient(1, T(1));
3841 this->set_gamma(q);
3842 }
3843
3845 const size_t n = this->n;
3846 if (this->k != n - 1)
3847 throw std::invalid_argument("Linear code cannot be converted into single parity check code!");
3848 const auto& HTp = this->get_HT();
3849 if (HTp(0, 0).is_zero())
3850 throw std::invalid_argument("Linear code cannot be converted into single parity check code!");
3851 for (size_t i = 1; i < n; ++i)
3852 if (HTp(i, 0) != HTp(0, 0))
3853 throw std::invalid_argument("Linear code cannot be converted into single parity check code!");
3854 }
3855
3857 auto Gp = this->G;
3858 Gp.rref();
3859 return SingleParityCheckCode<T>(LinearCode<T>(this->n, this->k, Gp));
3860 }
3861
3863 return SingleParityCheckCode<T>(LinearCode<T>::get_identical_code_in_polynomial_form());
3864 }
3865
3866 void get_info(std::ostream& os) const override {
3867 if (this->template info_prelude<LinearCode<T>>(os)) os << "Single parity check code";
3868 }
3869
3870 RepetitionCode<T> get_dual() const { return RepetitionCode<T>(this->n); }
3871 Vector<T> dec_BD(const Vector<T>& r) const override {
3872#ifdef CECCO_ERASURE_SUPPORT
3873 if (LinearCode<T>::erasures_present(r)) return dec_BD_EE(r);
3874#endif
3875 this->validate_length(r);
3876
3877 if (!(r * this->HT)[0].is_zero()) throw decoding_failure("Single parity check code BD decoder failed!");
3878
3879 return r;
3880 }
3881
3882 Vector<T> dec_ML(const Vector<T>& r) const override {
3883#ifdef CECCO_ERASURE_SUPPORT
3884 if (LinearCode<T>::erasures_present(r)) return dec_ML_EE(r);
3885#endif
3886 this->validate_length(r);
3887
3888 const T s = (r * this->HT)[0];
3889 if (s.is_zero()) return r;
3890
3891 // all n single-position corrections are ML-tied, pick one uniformly
3892 const size_t i = std::uniform_int_distribution<size_t>(0, this->n - 1)(gen());
3893 Vector<T> c_est = r;
3894 c_est.set_component(i, r[i] - s / (this->HT)(i, 0));
3895 return c_est;
3896 }
3897
3898#ifdef CECCO_ERASURE_SUPPORT
3899 Vector<T> dec_BD_EE(const Vector<T>& r) const override {
3900 this->validate_length(r);
3901
3902 const auto X = this->erasure_positions(r);
3903 const size_t tau = X.size();
3904
3905 if (tau == 0) return dec_BD(r);
3906 if (tau > 1) throw decoding_failure("Single parity check code BD error/erasure decoder failed!");
3907
3908 T s = T(0);
3909 for (size_t i = 0; i < this->n; ++i)
3910 if (!r[i].is_erased()) s += (this->HT)(i, 0) * r[i];
3911
3912 Vector<T> c_est = r;
3913 c_est.set_component(X[0], -s / (this->HT)(X[0], 0));
3914 return c_est;
3915 }
3916
3917 Vector<T> dec_ML_EE(const Vector<T>& r) const override {
3918 this->validate_length(r);
3919
3920 const auto X = this->erasure_positions(r);
3921 const size_t tau = X.size();
3922
3923 if (tau == 0) return dec_ML(r);
3924
3925 const size_t n = this->n;
3926 if (tau == n) return Vector<T>(n, T(0));
3927
3928 T s = T(0);
3929 for (size_t i = 0; i < n; ++i)
3930 if (!r[i].is_erased()) s += (this->HT)(i, 0) * r[i];
3931
3932 Vector<T> c_est = r;
3933 c_est.set_component(X[0], -s / (this->HT)(X[0], 0));
3934 const T zero(0);
3935 for (size_t i = 1; i < tau; ++i) c_est.set_component(X[i], zero);
3936 return c_est;
3937 }
3938#endif
3939
3940 private:
3941 static size_t SingleParityCheck_k(size_t n) {
3942 if (n == 0) throw std::invalid_argument("Single parity check code length must be positive!");
3943 return n - 1;
3944 }
3945};
3946
3947template <FiniteFieldType T>
3948 requires(std::is_same_v<T, Fp<2>> || std::is_same_v<T, Fp<3>>)
3949class GolayCode : public LinearCode<T> {
3950 public:
3952 if constexpr (T::get_size() == 2) {
3953 this->set_weight_enumerator(Polynomial<InfInt>(
3954 {1, 0, 0, 0, 0, 0, 0, 253, 506, 0, 0, 1288, 1288, 0, 0, 506, 253, 0, 0, 0, 0, 0, 0, 1}));
3955 this->set_gamma(Polynomial<T>({1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1}));
3956 } else {
3957 this->set_weight_enumerator(Polynomial<InfInt>({1, 0, 0, 0, 0, 132, 132, 0, 330, 110, 0, 24}));
3958 this->set_gamma(Polynomial<T>({2, 0, 1, 2, 1, 1}));
3959 }
3960 }
3961
3962 GolayCode(const LinearCode<T>& C) : LinearCode<T>(C) {
3963 // this is a non-trivial result, uses uniqueness of Steiner systems
3964 if (this->n == Golay_n() && this->k == Golay_k()) {
3965 if (std::is_same_v<T, Fp<2>> && this->get_dmin() == 7)
3966 return;
3967 else if (std::is_same_v<T, Fp<3>> && this->get_dmin() == 5)
3968 return;
3969 }
3970 throw std::invalid_argument("Linear code cannot be converted into Golay code!");
3971 }
3972
3979
3981 if (this->template info_prelude<LinearCode<T>>(os)) {
3982 if constexpr (std::is_same_v<T, Fp<2>>) {
3983 os << "Binary Golay code";
3984 } else {
3985 os << "Ternary Golay code";
3986 }
3987 }
3988 }
3989
3990 private:
3991 static constexpr size_t Golay_n() noexcept {
3992 if constexpr (std::is_same_v<T, Fp<2>>)
3993 return 23;
3994 else
3995 return 11;
3996 }
3997
3998 static constexpr size_t Golay_k() noexcept {
3999 if constexpr (std::is_same_v<T, Fp<2>>)
4000 return 12;
4001 else
4002 return 6;
4003 }
4004
4005 static Matrix<T> Golay_G() {
4007 if constexpr (std::is_same_v<T, Fp<2>>)
4008 gamma = Polynomial<T>({1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1});
4009 else
4010 gamma = Polynomial<T>({2, 0, 1, 2, 1, 1});
4011
4012 const size_t n = Golay_n();
4013 const size_t k = Golay_k();
4014
4015 return ToeplitzMatrix(pad_back(pad_front(Vector<T>(gamma), n), 2 * k + gamma.degree() - 1), k, n);
4016 }
4017};
4018
4019template <FieldType T>
4020class GRSCode : public LinearCode<T> {
4021 public:
4022 GRSCode(const Vector<T>& a, const Vector<T>& d, size_t k) try : LinearCode
4023 <T>(a.get_n(), k, GRS_G(a, d, k)), a(a), d(d) {
4024 const size_t n = this->n;
4025
4026 for (size_t i = 0; i < n; ++i) {
4027 if (d[i] == T(0)) throw std::invalid_argument("GRS codes must have nonzero column multipliers!");
4028 }
4029
4030 this->set_dmin(n - k + 1);
4031 }
4032 catch (const std::invalid_argument& e) {
4033 throw std::invalid_argument(std::string("Cannot construct GRS code: ") + e.what());
4034 }
4035
4036 // Documentation note: recognition from a plain linear code. The stored generator matrix must
4037 // be in canonical Vandermonde-times-diagonal form: row 0 (the column multipliers) has no zero
4038 // entry, and the columnwise ratios of consecutive rows (the locators) are constant and
4039 // pairwise distinct. For k = 1 the single row determines only the multipliers; the locators
4040 // are chosen as the field elements with labels 0 to n - 1, which requires the field to
4041 // supply n distinct locators. Either way the reconstructed code carries exactly the recognized
4042 // generator matrix. Punctured and expurgated GRS codes arrive here in canonical form as long
4043 // as puncturing did not lose rank.
4044 GRSCode(const LinearCode<T>& C) : GRSCode<T>(GRS_a_and_d(C), C.get_k()) {}
4045
4046 const Vector<T>& get_a() const noexcept { return a; }
4047 const Vector<T>& get_d() const noexcept { return d; }
4048
4050 auto Gp = this->G;
4051 Gp.rref();
4052 GRSCode res(a, d, this->k, std::move(Gp));
4053 if (this->dmin.has_value()) res.set_dmin(*(this->dmin));
4054 if constexpr (FiniteFieldType<T>) {
4055 if (this->weight_enumerator.has_value()) res.set_weight_enumerator(*(this->weight_enumerator));
4056 if (this->p_ary_image_weight_enumerator.has_value())
4057 res.p_ary_image_weight_enumerator = this->p_ary_image_weight_enumerator;
4058 }
4059 if (this->minimal_trellis.has_value()) res.minimal_trellis = this->minimal_trellis;
4060 if (this->codewords.has_value()) res.codewords = this->codewords;
4061 return res;
4062 }
4063
4064 GRSCode<T> get_dual() const {
4065 if (this->k == this->n)
4066 throw std::logic_error(
4067 "The dual of a full-dimension GRS code is the zero code, construct a ZeroCode instead!");
4068 return GRSCode<T>(a, dual_multipliers(), this->n - this->k);
4069 }
4070
4071 const Polynomial<InfInt>& get_weight_enumerator() const override {
4072 if constexpr (!FiniteFieldType<T>) {
4073 throw std::logic_error("Cannot calculate weight enumerator of code over infinite field!");
4074 } else {
4075 this->weight_enumerator.call_once([this] {
4076 if (this->weight_enumerator.has_value()) return;
4077 constexpr size_t q = T::get_size();
4078 const size_t n = this->n;
4079 const size_t k = this->k;
4080
4081 Polynomial<InfInt> enumerator;
4082 enumerator.set_coefficient(0, 1);
4083 for (size_t i = n - k + 1; i <= n; ++i) {
4084 InfInt sum = 0;
4085 for (size_t j = 0; j <= i - (n - k + 1); ++j) {
4086 InfInt s = j % 2 ? -1 : 1;
4087 sum += s * bin<InfInt>(i - 1, j) * sqm<InfInt>(q, i - j - (n - k + 1));
4088 }
4089 enumerator.set_coefficient(i, bin<InfInt>(n, i) * (q - 1) * sum);
4090 }
4091 this->weight_enumerator.emplace(std::move(enumerator));
4092 });
4093 return this->weight_enumerator.value();
4094 }
4095 }
4096
4097 bool is_singly_extended() const {
4098 for (size_t i = 0; i < this->n; ++i) {
4099 if (a[i] == T(0)) return true;
4100 }
4101 return false;
4102 }
4103
4104 bool is_primitive() const {
4105 if constexpr (!FiniteFieldType<T>) {
4106 return false;
4107 } else {
4108 const size_t n = this->n;
4109 if (n != T::get_size() - 1) return false;
4110 for (size_t i = 0; i < n; ++i) {
4111 if (a[i] == T(0)) return false;
4112 }
4113 return true;
4114 }
4115 }
4116
4117 bool is_narrow_sense() const {
4118 for (size_t i = 0; i < this->n; ++i) {
4119 if (d[i] != T(1)) return false;
4120 }
4121 return true;
4122 }
4123
4124 bool is_normalized() const { return a == d; }
4125
4126 void get_info(std::ostream& os) const override {
4127 if (this->template info_prelude<LinearCode<T>>(os)) {
4128 os << BOLD("GRS code") " with properties: { a = " << a << ", d = " << d;
4130 os << " singly-extended";
4131 }
4132 if (is_primitive()) {
4133 os << " primitive";
4134 }
4135 if (is_narrow_sense()) {
4136 os << " narrow-sense";
4137 }
4138 if (is_normalized()) {
4139 os << " normalized";
4140 }
4141 os << " }";
4142 }
4143 }
4144
4145 Vector<T> dec_WBA(const Vector<T>& r) const override {
4146#ifdef CECCO_ERASURE_SUPPORT
4147 if (LinearCode<T>::erasures_present(r)) return dec_WBA_EE(r);
4148#endif
4149 return dec_WBA_impl(r, {});
4150 }
4151
4152 Vector<T> dec_WBA_optimized(const Vector<T>& r) const override {
4153#ifdef CECCO_ERASURE_SUPPORT
4154 if (LinearCode<T>::erasures_present(r)) return dec_WBA_impl(r, this->erasure_positions(r), true);
4155#endif
4156 return dec_WBA_impl(r, {}, true);
4157 }
4158
4159 Vector<T> dec_BMA(const Vector<T>& r) const override {
4160#ifdef CECCO_ERASURE_SUPPORT
4161 if (LinearCode<T>::erasures_present(r)) return dec_BMA_EE(r);
4162#endif
4163 return dec_BMA_impl(r, {});
4164 }
4165
4166 // Documentation note: the BMA is the fastest true BD realization for GRS codes.
4167 Vector<T> dec_BD(const Vector<T>& r) const override { return dec_BMA(r); }
4168
4169#ifdef CECCO_ERASURE_SUPPORT
4170 Vector<T> dec_WBA_EE(const Vector<T>& r) const override { return dec_WBA_impl(r, this->erasure_positions(r)); }
4171 Vector<T> dec_BMA_EE(const Vector<T>& r) const override { return dec_BMA_impl(r, this->erasure_positions(r)); }
4172 Vector<T> dec_BD_EE(const Vector<T>& r) const override { return dec_BMA_EE(r); }
4173#endif
4174
4176 return dec_GSA_list(r, 1, list_size);
4177 }
4178
4180 return dec_GSA_list_optimized(r, 1, list_size);
4181 }
4182
4183 // Documentation note: the returned list contains every codeword c with multiplicity * (n - dH(r, c)) >= h,
4184 // where h is the weighted-degree bound chosen in GSA_degree_bound, i.e., every codeword within
4185 // Hamming distance n - ceil(h / multiplicity) of r; it may contain further codewords, and an
4186 // empty list indicates decoding failure. Erased positions are excluded from the interpolation
4187 // and reconstructed by re-encoding. The first entry is a closest candidate, reservoir-selected
4188 // among the distance-optimal candidates as elsewhere in the library, remaining entries follow
4189 // in nondecreasing Hamming distance from r. The optimized variant shrinks the interpolation
4190 // problem by the a-priori known prefactors (cf. dec_GSA_impl) and returns the same
4191 // candidate set.
4193 size_t list_size = GSA_LIST_SIZE) const override {
4194#ifdef CECCO_ERASURE_SUPPORT
4195 return dec_GSA_list_impl(r, this->erasure_positions(r), multiplicity, list_size, false);
4196#else
4197 return dec_GSA_list_impl(r, {}, multiplicity, list_size, false);
4198#endif
4199 }
4200
4202 size_t list_size = GSA_LIST_SIZE) const override {
4203#ifdef CECCO_ERASURE_SUPPORT
4204 return dec_GSA_list_impl(r, this->erasure_positions(r), multiplicity, list_size, true);
4205#else
4206 return dec_GSA_list_impl(r, {}, multiplicity, list_size, true);
4207#endif
4208 }
4209
4211 size_t list_size = KVA_LIST_SIZE) const override {
4212 if constexpr (!FiniteFieldType<T>) {
4213 throw std::logic_error("Koetter-Vardy decoding only available for codes over finite fields!");
4214 } else if constexpr (T::get_size() != 2) {
4215 throw std::logic_error(
4216 "Soft-input KVA LLR vector decoding only available for binary codes; use the Matrix<double> overload!");
4217 } else {
4218 return dec_KVA_list(Matrix<double>(llrs), strength, list_size);
4219 }
4220 }
4221
4222 // Documentation note: soft input in symbol-level LLR format (cf. the Documentation note).
4223 // The LLRs are converted into posterior symbol probabilities Pi (uniform prior), from which the interpolation
4224 // multiplicities Theta = floor(strength * Pi) are obtained by Koetter/Vardy's proportional multiplicity
4225 // assignment. Larger strength trades interpolation complexity for performance. The first entry is an ML
4226 // codeword among the candidates, reservoir-selected. Remaining entries follow according to nondecreasing soft-input
4227 // cost.
4229 size_t list_size = KVA_LIST_SIZE) const override {
4230 if constexpr (!FiniteFieldType<T>) {
4231 throw std::logic_error("Koetter-Vardy decoding only available for codes over finite fields!");
4232 } else {
4233 this->validate_length(llrs);
4234 if (strength == 0) throw std::invalid_argument("KVA strength must be positive!");
4235 if (strength > 1000000) throw std::invalid_argument("KVA strength must be at most 10^6!");
4236 if (list_size == 0) throw std::invalid_argument("KVA list size must be positive!");
4237
4238 constexpr size_t q = T::get_size();
4239 const size_t n = this->n;
4240
4241 // infinite LLRs are legitimate (impossible symbols, cf. the alternant expansion) and
4242 // handled by the clamp below, NaNs would reach a float-to-int conversion
4243 for (size_t beta = 1; beta < q; ++beta)
4244 for (size_t i = 0; i < n; ++i)
4245 if (std::isnan(llrs(beta - 1, i))) throw std::invalid_argument("KVA LLRs must not be NaN!");
4246
4247 auto Theta = ZeroMatrix<int>(q, n);
4248 bool nonzero = false;
4249 // LLRs clamped to magnitude 300: exp cannot overflow (q e^300 is far below the double
4250 // limit), beyond that magnitude the posteriors are indistinguishable from
4251 // certainty at double precision
4252 std::vector<double> weights(q - 1);
4253 for (size_t i = 0; i < n; ++i) {
4254 double denominator = 1.0;
4255 for (size_t beta = 1; beta < q; ++beta) {
4256 weights[beta - 1] = std::exp(-std::clamp(llrs(beta - 1, i), -300.0, 300.0));
4257 denominator += weights[beta - 1];
4258 }
4259 for (size_t beta = 0; beta < q; ++beta) {
4260 const double Pi = (beta == 0 ? 1.0 : weights[beta - 1]) / denominator;
4261 const int multiplicity = static_cast<int>(strength * Pi);
4262 if (multiplicity == 0) continue;
4263 Theta.set_component((T(beta) / d[i]).get_label(), i, multiplicity);
4264 nonzero = true;
4265 }
4266 }
4267 if (!nonzero) return {};
4268
4269 return GSA_rank_candidates(dec_KVA_impl(a, Theta, list_size), [&llrs](const Vector<T>& c) {
4270 // finite contributions are summed without saturation; a +inf contribution means the
4271 // candidate uses an impossible symbol and ranks last, a -inf contribution ranks it first
4272 double finite_sum = 0.0;
4273 bool pos_inf = false;
4274 bool neg_inf = false;
4275 for (size_t i = 0; i < c.get_n(); ++i) {
4276 const size_t label = c[i].get_label();
4277 if (label == 0) continue;
4278 const double l = llrs(label - 1, i);
4279 if (std::isinf(l))
4280 (l > 0.0 ? pos_inf : neg_inf) = true;
4281 else
4282 finite_sum += l;
4283 }
4284 if (pos_inf) return std::numeric_limits<double>::infinity();
4285 if (neg_inf) return -std::numeric_limits<double>::infinity();
4286 return finite_sum;
4287 });
4288 }
4289 }
4290
4291 protected:
4292 GRSCode(const Vector<T>& a, const Vector<T>& d, size_t k, Matrix<T> G)
4293 : LinearCode<T>(a.get_n(), k, std::move(G)), a(a), d(d) {}
4294
4295 static std::pair<Vector<T>, Vector<T>> GRS_a_and_d(const LinearCode<T>& C) {
4296 const auto& Gp = C.get_G();
4297 const size_t n = C.get_n();
4298 const size_t k = C.get_k();
4299 if (k == 0) throw std::invalid_argument("Linear code of dimension 0 cannot be converted into GRS code!");
4300
4301 Vector<T> d(n);
4302 for (size_t i = 0; i < n; ++i) {
4303 if (Gp(0, i).is_zero()) throw std::invalid_argument("Linear code cannot be converted into GRS code!");
4304 d.set_component(i, Gp(0, i));
4305 }
4306
4307 Vector<T> a(n);
4308
4309 if (k == 1) {
4310 if constexpr (FiniteFieldType<T>) {
4311 if (n > T::get_size()) throw std::invalid_argument("Linear code cannot be converted into GRS code!");
4312 }
4313 for (size_t i = 0; i < n; ++i) a.set_component(i, T(i));
4314 return {std::move(a), std::move(d)};
4315 }
4316
4317 for (size_t i = 0; i < n; ++i) a.set_component(i, Gp(1, i) / Gp(0, i));
4318 for (size_t j = 2; j < k; ++j)
4319 for (size_t i = 0; i < n; ++i)
4320 if (Gp(j, i) != Gp(j - 1, i) * a[i])
4321 throw std::invalid_argument("Linear code cannot be converted into GRS code!");
4322 if (!a.is_pairwise_distinct()) throw std::invalid_argument("Linear code cannot be converted into GRS code!");
4323
4324 return {std::move(a), std::move(d)};
4325 }
4326
4327 private:
4328 Vector<T> a;
4329 Vector<T> d;
4330 mutable details::OnceCache<Matrix<T>> G_canonical;
4331 mutable details::OnceCache<Matrix<T>> HT_canonical;
4332 mutable details::OnceCache<Matrix<T>> W_canonical;
4333 mutable details::OnceCache<Matrix<T>> R_canonical;
4334 mutable details::OnceCache<Polynomial<T>> P_canonical;
4335 mutable details::OnceCache<Polynomial<T>> PN_canonical;
4336
4337 Vector<T> dec_WBA_impl(const Vector<T>& r, const std::vector<size_t>& X, bool reencode = false) const {
4338 this->validate_length(r);
4339
4340 const size_t tau = X.size();
4341 if (tau > this->get_dmin() - 1)
4342 throw decoding_failure("GRS code WBA error/erasure decoder failed (too many erasures)!");
4343
4344 const size_t k = this->k;
4345
4346 const auto ap = delete_components(a, X);
4347 const auto dp = delete_components(d, X);
4348 const size_t np = this->n - tau;
4349 const size_t tmaxp = (np - k) / 2;
4350 const auto r_punct = delete_components(r, X);
4351
4352 G_canonical.call_once([this] {
4353 if (G_canonical.has_value()) return;
4354 G_canonical.emplace(VandermondeMatrix<T>(a, this->k) * DiagonalMatrix<T>(d));
4355 });
4356
4357 Vector<T> c_reenc;
4358 auto Q0 = ZeroPolynomial<T>();
4359 auto Q1 = ZeroPolynomial<T>();
4360 if (reencode) {
4361 Vector<T> rp;
4362 c_reenc = reencoding_projection(ap, dp, r_punct, rp);
4363 if (np == k) {
4364 if (dH(r, c_reenc) > tmaxp)
4365 throw decoding_failure(
4366 "GRS code WBA error/erasure decoder failed (identified error beyond BD radius)!");
4367 return c_reenc;
4368 }
4369
4370 // P divides Q0 for every valid solution pair, so Q0 = P U0
4371 Prefactors pf;
4372 reencoding_prefactors(pf, ap, 1, 1);
4373
4374 auto U0 = ZeroPolynomial<T>();
4375 if (np == this->n && tmaxp > 0) {
4376 // the constant left block Diag(P(a_i)) V^T extends to a received-independent
4377 // square matrix S, so with the cached R = inverse(S) the row-reduced system
4378 // R [M0 M1] = [I 0; A] leaves a kernel problem of tmaxp rows in Q1 alone
4379 R_canonical.call_once([this, &pf] {
4380 if (R_canonical.has_value()) return;
4381 const size_t k = this->k;
4382 const size_t n = this->n;
4383 auto S = ZeroMatrix<T>(n - k, n - k);
4384 for (size_t i = k; i < n; ++i) {
4385 const T scale = pf.F[0](a[i]);
4386 auto pow = T(1);
4387 for (size_t j = 0; j < n - k; ++j) {
4388 S.set_component(i - k, j, scale * pow);
4389 pow *= a[i];
4390 }
4391 }
4392 S.invert();
4393 R_canonical.emplace(std::move(S));
4394 });
4395
4396 auto M1 = ZeroMatrix<T>(np - k, np - tmaxp - k + 1);
4397 for (size_t i = k; i < np; ++i) {
4398 const T y = rp[i] / dp[i];
4399 auto pow = T(1);
4400 for (size_t j = 0; j <= np - tmaxp - k; ++j) {
4401 M1.set_component(i - k, j, y * pow);
4402 pow *= ap[i];
4403 }
4404 }
4405 const auto& R = R_canonical.value();
4406 const auto B = (R.get_submatrix(np - tmaxp - k, 0, tmaxp, np - k) * M1).basis_of_kernel();
4407 for (size_t j = 0; j <= np - tmaxp - k; ++j) Q1.set_coefficient(j, B(0, j));
4408
4409 // U0 = -R_top M1 q1, with M1 q1 evaluated as y_i Q1(a_i) instead of a product
4410 Vector<T> w(np - k);
4411 for (size_t i = k; i < np; ++i) w.set_component(i - k, M1(i - k, 0) * Q1(ap[i]));
4412 for (size_t i = 0; i < np - tmaxp - k; ++i) {
4413 auto s = T(0);
4414 for (size_t j = 0; j < np - k; ++j) s += R(i, j) * w[j];
4415 U0.set_coefficient(i, T(0) - s);
4416 }
4417 } else {
4418 Vector<T> ap_act(np - k), y_act(np - k), F0_act(np - k);
4419 for (size_t i = k; i < np; ++i) {
4420 ap_act.set_component(i - k, ap[i]);
4421 y_act.set_component(i - k, rp[i] / dp[i]);
4422 F0_act.set_component(i - k, pf.F[0](ap[i]));
4423 }
4424
4425 const auto M0 = DiagonalMatrix(F0_act) * transpose(VandermondeMatrix<T>(ap_act, np - tmaxp - k));
4426 const auto M1 = DiagonalMatrix(y_act) * transpose(VandermondeMatrix<T>(ap_act, np - tmaxp - k + 1));
4427 const auto M = horizontal_join(M0, M1);
4428
4429 const auto B = M.basis_of_kernel();
4430
4431 size_t i = 0;
4432 for (size_t j = 0; j < np - tmaxp - k; ++j, ++i) U0.set_coefficient(j, B(0, i));
4433 for (size_t j = 0; j <= np - tmaxp - k; ++j, ++i) Q1.set_coefficient(j, B(0, i));
4434 }
4435 Q0 = pf.F[0] * U0;
4436 } else {
4437 c_reenc = Vector<T>(this->n);
4438 Vector<T> rp_norm(np);
4439 for (size_t i = 0; i < np; ++i) rp_norm.set_component(i, r_punct[i] / dp[i]);
4440
4441 const auto M0 = transpose(VandermondeMatrix<T>(ap, np - tmaxp));
4442 const auto M1 = DiagonalMatrix(rp_norm) * transpose(VandermondeMatrix<T>(ap, np - tmaxp - k + 1));
4443 const auto M = horizontal_join(M0, M1);
4444
4445 const auto B = M.basis_of_kernel();
4446
4447 size_t i = 0;
4448 for (size_t j = 0; j <= np - tmaxp - 1; ++j, ++i) Q0.set_coefficient(j, B(0, i));
4449 for (size_t j = 0; j <= np - tmaxp - k; ++j, ++i) Q1.set_coefficient(j, B(0, i));
4450 }
4451
4452 const auto temp = poly_long_div(-Q0, Q1);
4453 const auto quotient = temp.first;
4454 const auto remainder = temp.second;
4455 if (!remainder.is_zero() || (!quotient.is_zero() && quotient.degree() >= k))
4456 throw decoding_failure("GRS code WBA error/erasure decoder failed (true error beyond BD radius)!");
4457
4458 const auto u_est = pad_back(Vector<T>(quotient), k);
4459 const auto c_est = u_est * G_canonical.value() + c_reenc;
4460 if (dH(r, c_est) > tmaxp)
4461 throw decoding_failure("GRS code WBA error/erasure decoder failed (identified error beyond BD radius)!");
4462
4463 return c_est;
4464 }
4465
4466 Vector<T> dec_BMA_impl(const Vector<T>& r, const std::vector<size_t>& X) const {
4467 this->validate_length(r);
4468
4469 const size_t n = this->n;
4470 const size_t redundancy = n - this->k;
4471 const size_t tau = X.size();
4472 if (tau > redundancy) throw decoding_failure("GRS code BMA error/erasure decoder failed (too many erasures)!");
4473 if (redundancy == 0) return r;
4474
4475 Vector<T> rp = r;
4476 for (size_t i = 0; i < tau; ++i) rp.set_component(X[i], T(0));
4477
4478 HT_canonical.call_once([this] {
4479 if (HT_canonical.has_value()) return;
4480 const auto D = DiagonalMatrix<T>(dual_multipliers());
4481 const auto V = VandermondeMatrix<T>(a, this->n - this->k);
4482 HT_canonical.emplace(transpose(V * D));
4483 });
4484 const auto& HT = HT_canonical.value();
4485
4486 const auto s = rp * HT;
4487 if (s.is_zero() && tau == 0) return r;
4488 const auto sx = Polynomial<T>(s);
4489
4490 auto Psi = OnePolynomial<T>();
4491 for (size_t i = 0; i < tau; ++i) Psi *= Polynomial<T>({-a[X[i]], T(1)});
4492
4493 // updated syndrome
4494 auto sxp = ZeroPolynomial<T>();
4495 for (size_t i = 0; i < redundancy - tau; ++i) {
4496 T coeff(0);
4497 for (size_t j = 0; j <= tau; ++j) coeff += Psi[j] * s[i + j];
4498 sxp.set_coefficient(i, coeff);
4499 }
4500
4501 // error locator (Berlekamp-Massey)
4502 auto Lambda_e = OnePolynomial<T>();
4503 size_t t = 0;
4504 auto B_ref = OnePolynomial<T>();
4505 size_t t_ref = 0;
4506 T discrepancy_ref(1);
4507 size_t last_update = 0;
4508
4509 for (size_t i = 0; i < redundancy - tau; ++i) {
4510 T discrepancy(0);
4511 const size_t j0 = (t > i ? t - i : 0);
4512 for (size_t j = j0; j <= t; ++j) discrepancy += Lambda_e[j] * sxp[i + j - t];
4513
4514 if (!discrepancy.is_zero()) {
4515 const auto ratio = discrepancy / discrepancy_ref;
4516 const size_t shift = i - last_update + 1;
4517
4518 if (shift + t_ref <= t) {
4519 Lambda_e -= shift_up(ratio * B_ref, t - shift - t_ref);
4520 } else {
4521 auto B = Lambda_e;
4522 const auto temp = t;
4523 Lambda_e.shift_up(shift + t_ref - t);
4524 Lambda_e -= ratio * B_ref;
4525 t = shift + t_ref;
4526 B_ref = std::move(B);
4527 t_ref = temp;
4528 discrepancy_ref = discrepancy;
4529 last_update = i + 1;
4530 }
4531 }
4532 }
4533
4534 if (2 * t + tau > redundancy)
4535 throw decoding_failure("GRS code BMA error/erasure decoder failed (identified error beyond BD radius)!");
4536
4537 // Chien search on the error locator alone: the erased positions are roots of Psi by
4538 // construction, and deg(Lambda_e) = t bounds the root count, allowing an early exit
4539 std::vector<size_t> E(X);
4540 E.reserve(tau + t);
4541 std::vector<bool> erased(n, false);
4542 for (size_t i = 0; i < tau; ++i) erased[X[i]] = true;
4543 for (size_t i = 0; i < n && E.size() < tau + t; ++i)
4544 if (!erased[i] && Lambda_e(a[i]).is_zero()) E.push_back(i);
4545
4546 if (E.size() != tau + t)
4547 throw decoding_failure("GRS code BMA error/erasure decoder failed (true error beyond BD radius)!");
4548
4549 auto Lambda = Psi * Lambda_e;
4550
4551 // Forney
4552 auto Omega = ZeroPolynomial<T>();
4553 for (size_t j = 0; j < tau + t; ++j) {
4554 T coeff(0);
4555 for (size_t m = j + 1; m <= tau + t; ++m) coeff += Lambda[m] * sx[m - j - 1];
4556 Omega.set_coefficient(j, coeff);
4557 }
4558
4559 Lambda.differentiate(1);
4560
4561 Vector<T> c_est = rp;
4562 for (size_t i = 0; i < E.size(); ++i) {
4563 const auto locator = a[E[i]];
4564 c_est.set_component(E[i], c_est[E[i]] - Omega(locator) / (HT(E[i], 0) * Lambda(locator)));
4565 }
4566
4567 if (2 * dH(r, c_est) + tau > redundancy)
4568 throw decoding_failure("GRS code BMA error/erasure decoder failed (result outside the decoding radius)!");
4569
4570 return c_est;
4571 }
4572
4573 // number of coefficients of Q_mu(x), i.e., of monomials x^nu y^mu with (1, k-1)-weighted
4574 // degree nu + mu (k - 1) less than h
4575 static size_t GSA_block_size(size_t k, size_t h, size_t mu) { return h > mu * (k - 1) ? h - mu * (k - 1) : 0; }
4576
4577 // smallest weighted-degree bound h with more unknowns than constraints, so that a nonzero
4578 // interpolation polynomial with y-degree at most ell exists
4579 static size_t GSA_degree_bound(size_t k, size_t ell, size_t cost) {
4580 size_t h = 0;
4581 size_t n_unknowns = 0;
4582 do {
4583 ++h;
4584 n_unknowns = 0;
4585 for (size_t mu = 0; mu <= ell; ++mu) n_unknowns += GSA_block_size(k, h, mu);
4586 } while (n_unknowns <= cost);
4587 return h;
4588 }
4589
4590 std::vector<Vector<T>> dec_GSA_list_impl(const Vector<T>& r, const std::vector<size_t>& X, size_t multiplicity,
4591 size_t list_size, bool optimized = true) const {
4592 if constexpr (!FiniteFieldType<T>) {
4593 throw std::logic_error("Guruswami-Sudan decoding only available for codes over finite fields!");
4594 } else {
4595 this->validate_length(r);
4596 if (multiplicity == 0) throw std::invalid_argument("GSA multiplicity must be positive!");
4597 if (list_size == 0) throw std::invalid_argument("GSA list size must be positive!");
4598
4599 const size_t tau = X.size();
4600 if (tau > this->get_dmin() - 1) return {};
4601
4602 const auto ap = delete_components(a, X);
4603 const auto dp = delete_components(d, X);
4604 const auto r_punct = delete_components(r, X);
4605
4606 return GSA_rank_candidates(dec_GSA_impl(ap, dp, r_punct, multiplicity, list_size, optimized, optimized),
4607 [&r_punct, &X](const Vector<T>& c) {
4608 return static_cast<double>(dH(r_punct, delete_components(c, X)));
4609 });
4610 }
4611 }
4612
4613 // per-block interpolation prefactors F[mu] dividing Q_mu(x), folded in by the *_prefactors
4614 // builders. reencoded counts the leading positions whose constraints are identically
4615 // satisfied and dropped.
4616 struct Prefactors {
4617 size_t reencoded = 0;
4618 std::vector<Polynomial<T>> F;
4619 std::vector<size_t> deg_F;
4620 };
4621
4622 Vector<T> reencoding_projection(const Vector<T>& ap, const Vector<T>& dp, const Vector<T>& r, Vector<T>& rp) const {
4623 const size_t k = this->k;
4624 const size_t np = ap.get_n();
4625 Vector<T> rJ(k);
4626 for (size_t i = 0; i < k; ++i) rJ.set_component(i, r[i]);
4627 rp = Vector<T>(np);
4628 if (np == this->n) {
4629 if (np == k) return r;
4630 W_canonical.call_once([this] {
4631 if (W_canonical.has_value()) return;
4632 W_canonical.emplace(this->MI * this->G.get_submatrix(0, this->k, this->k, this->n - this->k));
4633 });
4634 const auto p = rJ * W_canonical.value();
4635 for (size_t i = k; i < np; ++i) rp.set_component(i, r[i] - p[i - k]);
4636 return r - rp;
4637 }
4638 const auto Gp = VandermondeMatrix<T>(ap, k) * DiagonalMatrix<T>(dp);
4639 const auto u_reenc = rJ * Gp.get_submatrix(0, 0, k, k).invert();
4640 if (np > k) {
4641 const auto p = u_reenc * Gp.get_submatrix(0, k, k, np - k);
4642 for (size_t i = k; i < np; ++i) rp.set_component(i, r[i] - p[i - k]);
4643 }
4644 return u_reenc * canonical_G();
4645 }
4646
4647 static Polynomial<T> linear_factor(const T& root) {
4648 auto res = Monomial<T>(1, T(1));
4649 res.set_coefficient(0, T(0) - root);
4650 return res;
4651 }
4652
4653 Vector<T> dual_multipliers() const {
4654 const size_t n = this->n;
4655 Vector<T> dperp(n);
4656 for (size_t i = 0; i < n; ++i) {
4657 T prod = d[i];
4658 for (size_t j = 0; j < n; ++j)
4659 if (j != i) prod *= a[i] - a[j];
4660 dperp.set_component(i, T(1) / prod);
4661 }
4662 return dperp;
4663 }
4664
4665 const Matrix<T>& canonical_G() const {
4666 G_canonical.call_once([this] {
4667 if (G_canonical.has_value()) return;
4668 G_canonical.emplace(VandermondeMatrix<T>(a, this->k) * DiagonalMatrix<T>(d));
4669 });
4670 return G_canonical.value();
4671 }
4672
4673 void reencoding_prefactors(Prefactors& pf, const Vector<T>& ap, size_t multiplicity, size_t list_size) const {
4674 const size_t k = this->k;
4675 const size_t r = multiplicity;
4676 const size_t nf = std::min(r, list_size + 1);
4677 if (pf.F.empty()) {
4678 pf.F.assign(nf, OnePolynomial<T>());
4679 pf.deg_F.assign(nf, 0);
4680 }
4681
4682 Polynomial<T> P;
4683 if (ap.get_n() == this->n) {
4684 P_canonical.call_once([this] {
4685 if (P_canonical.has_value()) return;
4686 auto poly = OnePolynomial<T>();
4687 for (size_t i = 0; i < this->k; ++i) poly *= linear_factor(a[i]);
4688 P_canonical.emplace(std::move(poly));
4689 });
4690 P = P_canonical.value();
4691 } else {
4692 P = OnePolynomial<T>();
4693 for (size_t i = 0; i < k; ++i) P *= linear_factor(ap[i]);
4694 }
4695 std::vector<Polynomial<T>> P_pow(r + 1, OnePolynomial<T>());
4696 for (size_t e = 1; e <= r; ++e) P_pow[e] = P_pow[e - 1] * P;
4697
4698 for (size_t mu = 0; mu < nf; ++mu) {
4699 pf.F[mu] *= P_pow[r - mu];
4700 pf.deg_F[mu] += k * (r - mu);
4701 }
4702 pf.reencoded = k;
4703 }
4704
4705 void Sierpinski_prefactors(Prefactors& pf, const Vector<T>& ap, size_t multiplicity, size_t list_size,
4706 const SierpinskiTriangle& triangle) const {
4707 const size_t np = ap.get_n();
4708 const size_t r = multiplicity;
4709 const size_t ell = list_size;
4710 if (r < 2) return;
4711 const size_t nf = std::min(r, ell + 1);
4712
4713 // g[mu] < r iff column mu of the triangle is a zero column with resolvable spoilers,
4714 // g[mu] being its greatest spoiler (Theorem 3)
4715 std::vector<size_t> g(r, r);
4716 std::vector<bool> resolvable(r, false);
4717 for (size_t t = r; t-- > 0;) {
4718 bool ok = true;
4719 for (size_t v = t + 1; v <= ell && ok; ++v)
4720 if (triangle(v, t) != 0) ok = v < r && resolvable[v];
4721 resolvable[t] = ok;
4722 }
4723 for (size_t t = 0; t < r; ++t) {
4724 if (!resolvable[t]) continue;
4725 g[t] = t;
4726 for (size_t v = t + 1; v <= ell; ++v)
4727 if (triangle(v, t) != 0) g[t] = v;
4728 }
4729
4730 size_t e_max = 0;
4731 for (size_t mu = 0; mu < nf; ++mu)
4732 if (g[mu] < r) e_max = std::max(e_max, r - g[mu]);
4733 if (e_max == 0) return;
4734
4735 if (pf.F.empty()) {
4736 pf.F.assign(nf, OnePolynomial<T>());
4737 pf.deg_F.assign(nf, 0);
4738 }
4739
4740 Polynomial<T> PN;
4741 if (np == this->n && pf.reencoded == this->k) {
4742 PN_canonical.call_once([this] {
4743 if (PN_canonical.has_value()) return;
4744 auto poly = OnePolynomial<T>();
4745 for (size_t i = this->k; i < this->n; ++i) poly *= linear_factor(a[i]);
4746 PN_canonical.emplace(std::move(poly));
4747 });
4748 PN = PN_canonical.value();
4749 } else {
4750 PN = OnePolynomial<T>();
4751 for (size_t i = pf.reencoded; i < np; ++i) PN *= linear_factor(ap[i]);
4752 }
4753 std::vector<Polynomial<T>> PN_pow(e_max + 1, OnePolynomial<T>());
4754 for (size_t e = 1; e <= e_max; ++e) PN_pow[e] = PN_pow[e - 1] * PN;
4755
4756 for (size_t mu = 0; mu < nf; ++mu) {
4757 const size_t e = g[mu] < r ? r - g[mu] : 0;
4758 pf.F[mu] *= PN_pow[e];
4759 pf.deg_F[mu] += (np - pf.reencoded) * e;
4760 }
4761 }
4762
4763 // Documentation note: unified GSA interpolation engine. The reencode and Sierpinski flags select
4764 // which of the a-priori known prefactors of Senger, "Prefactor reduction of the Guruswami-Sudan
4765 // interpolation step" (IEEE Trans. Inf. Theory, 2014) shrink the interpolation problem. With
4766 // both flags off this is the textbook system. The candidate set is the same for every choice.
4767 std::vector<Vector<T>> dec_GSA_impl(const Vector<T>& ap, const Vector<T>& dp, const Vector<T>& r,
4768 size_t multiplicity, size_t list_size, bool reencode, bool Sierpinski) const {
4769 const size_t k = this->k;
4770 const size_t np = ap.get_n();
4771 const size_t ell = list_size;
4772 constexpr size_t p = T::get_characteristic();
4773 if (k == 0) return {Vector<T>(this->n)};
4774
4775 Vector<T> rp;
4776 Vector<T> c_reenc;
4777 if (reencode) {
4778 c_reenc = reencoding_projection(ap, dp, r, rp);
4779 if (np == k) return {c_reenc};
4780 } else {
4781 rp = r;
4782 c_reenc = Vector<T>(this->n);
4783 }
4784
4785 const size_t cost = np * bin<size_t>(multiplicity + 1, 2);
4786 const size_t h = GSA_degree_bound(k, ell, cost);
4787 const SierpinskiTriangle triangle(std::max(h - 1, ell), std::max(multiplicity - 1, ell), p);
4788
4789 // the fold order is fixed: the Sierpinski exponents apply to the positions left over by
4790 // re-encoding (Theorem 4), so reencoding_prefactors must fold first
4791 Prefactors pf;
4792 if (reencode) reencoding_prefactors(pf, ap, multiplicity, ell);
4793 if (Sierpinski) Sierpinski_prefactors(pf, ap, multiplicity, ell, triangle);
4794 const auto has_prefactor = [&pf](size_t mu) { return mu < pf.F.size() && pf.deg_F[mu] != 0; };
4795
4796 std::vector<size_t> width(ell + 1);
4797 std::vector<size_t> offset(ell + 1);
4798 size_t n_unknowns = 0;
4799 for (size_t mu = 0; mu <= ell; ++mu) {
4800 const size_t bs = GSA_block_size(k, h, mu);
4801 const size_t dF = mu < pf.F.size() ? pf.deg_F[mu] : 0;
4802 width[mu] = bs > dF ? bs - dF : 0;
4803 offset[mu] = n_unknowns;
4804 n_unknowns += width[mu];
4805 }
4806
4807 // prefactor Hasse derivatives at the interpolation points, entering the constraint rows
4808 // through the product rule D^s(G F) = sum_j D^j(G) D^(s-j)(F)
4809 std::vector<Matrix<T>> FD(pf.F.size());
4810 for (size_t mu = 0; mu < pf.F.size(); ++mu) {
4811 if (width[mu] == 0 || !has_prefactor(mu)) continue;
4812 FD[mu] = ZeroMatrix<T>(multiplicity, np - pf.reencoded);
4813 for (size_t j = 0; j < multiplicity; ++j) {
4814 const auto Fd = Hasse_derivative(pf.F[mu], j);
4815 for (size_t i = pf.reencoded; i < np; ++i) FD[mu].set_component(j, i - pf.reencoded, Fd(ap[i]));
4816 }
4817 }
4818
4819 // interpolation constraints D^(s, t) Q(a_i, rp_i / d_i) = 0 for all s + t < multiplicity at
4820 // the positions outside the re-encoding set
4821 auto M = ZeroMatrix<T>((np - pf.reencoded) * bin<size_t>(multiplicity + 1, 2), n_unknowns);
4822 std::vector<T> xpows(multiplicity, T(1)); // rolling window xpows[j] = ap[i]^(nu - j) along the nu loops
4823 size_t row = 0;
4824 for (size_t i = pf.reencoded; i < np; ++i) {
4825 const T y = rp[i] / dp[i];
4826 for (size_t s = 0; s < multiplicity; ++s) {
4827 for (size_t t = 0; s + t < multiplicity; ++t) {
4828 auto ypow = T(1);
4829 for (size_t mu = t; mu <= ell; ++mu) {
4830 const size_t ct = triangle(mu, t);
4831 if (ct != 0 && has_prefactor(mu)) {
4832 for (size_t j = 0; j <= s; ++j) xpows[j] = T(1);
4833 for (size_t nu = 0; nu < width[mu]; ++nu) {
4834 auto entry = T(0);
4835 for (size_t j = 0; j <= std::min(s, nu); ++j) {
4836 const size_t binomials = (ct * triangle(nu, j)) % p;
4837 if (binomials != 0)
4838 entry += binomials * (xpows[j] * FD[mu](s - j, i - pf.reencoded));
4839 }
4840 if (!entry.is_zero()) M.set_component(row, offset[mu] + nu, entry * ypow);
4841 for (size_t j = s; j > 0; --j) xpows[j] = xpows[j - 1];
4842 xpows[0] *= ap[i];
4843 }
4844 } else if (ct != 0) {
4845 auto xpow = T(1);
4846 for (size_t nu = s; nu < width[mu]; ++nu) {
4847 const size_t binomials = (ct * triangle(nu, s)) % p;
4848 if (binomials != 0) M.set_component(row, offset[mu] + nu, binomials * (xpow * ypow));
4849 xpow *= ap[i];
4850 }
4851 }
4852 ypow *= y;
4853 }
4854 ++row;
4855 }
4856 }
4857 }
4858
4859 const auto B = M.basis_of_kernel();
4860 if (B.get_m() == 0) return {};
4861 std::vector<Polynomial<T>> Q(ell + 1, ZeroPolynomial<T>());
4862 for (size_t mu = 0; mu <= ell; ++mu) {
4863 if (width[mu] == 0) continue;
4864 auto G = ZeroPolynomial<T>();
4865 for (size_t nu = 0; nu < width[mu]; ++nu) G.set_coefficient(nu, B(0, offset[mu] + nu));
4866 Q[mu] = has_prefactor(mu) ? G * pf.F[mu] : G;
4867 }
4868
4869 // the projection is undone at the codeword level
4870 std::vector<Polynomial<T>> roots;
4871 auto w = ZeroPolynomial<T>();
4872 find_y_roots(Q, k, 0, w, roots, triangle);
4873
4874 std::vector<Vector<T>> res;
4875 for (size_t j = 0; j < roots.size(); ++j)
4876 res.push_back(pad_back(Vector<T>(roots[j]), k) * canonical_G() + c_reenc);
4877 return res;
4878 }
4879
4880 // Roth-Ruckenstein root finding: appends to U all polynomials f with deg f < k and
4881 // Q(x, f(x)) = 0, where Q(x, y) = sum_mu Q[mu](x) y^mu; g accumulates the coefficients of the
4882 // current candidate along the recursion path
4883 static void find_y_roots(const std::vector<Polynomial<T>>& Q, size_t k, size_t lambda, Polynomial<T>& g,
4884 std::vector<Polynomial<T>>& U, const SierpinskiTriangle& triangle) {
4885 constexpr size_t q = T::get_size();
4886
4887 // largest m such that x^m divides Q(x, y)
4888 size_t m = std::numeric_limits<size_t>::max();
4889 for (size_t mu = 0; mu < Q.size(); ++mu)
4890 if (!Q[mu].is_zero()) m = std::min(m, Q[mu].trailing_degree());
4891 if (m == std::numeric_limits<size_t>::max()) return;
4892
4893 // R(x, y) = x^(-m) Q(x, y)
4894 std::vector<Polynomial<T>> shifted;
4895 if (m > 0) {
4896 shifted = Q;
4897 for (size_t mu = 0; mu < shifted.size(); ++mu) shifted[mu].shift_down(m);
4898 }
4899 const auto& R = m > 0 ? shifted : Q;
4900
4901 // f(y) = R(0, y)
4902 Polynomial<T> f;
4903 for (size_t mu = 0; mu < R.size(); ++mu) f.set_coefficient(mu, R[mu][0]);
4904
4905 std::vector<T> gamma_pow(R.size(), T(1));
4906 for (size_t label = 0; label < q; ++label) {
4907 const T gamma(label);
4908 if (!f(gamma).is_zero()) continue;
4909 for (size_t e = 1; e < gamma_pow.size(); ++e) gamma_pow[e] = gamma_pow[e - 1] * gamma;
4910 g.set_coefficient(lambda, gamma);
4911 if (lambda == k - 1) {
4912 auto acc = ZeroPolynomial<T>();
4913 for (size_t mu = 0; mu < R.size(); ++mu) {
4914 if (gamma_pow[mu].is_zero()) continue;
4915 acc += gamma_pow[mu] * R[mu];
4916 }
4917 if (acc.is_zero()) U.push_back(g);
4918 } else {
4919 // S(x, y) = R(x, x y + gamma)
4920 std::vector<Polynomial<T>> S(R.size());
4921 for (size_t t = 0; t < R.size(); ++t) {
4922 auto acc = ZeroPolynomial<T>();
4923 for (size_t mu = t; mu < R.size(); ++mu) {
4924 const T scale = triangle(mu, t) * gamma_pow[mu - t];
4925 if (scale.is_zero()) continue;
4926 acc += scale * R[mu];
4927 }
4928 acc.shift_up(t);
4929 S[t] = std::move(acc);
4930 }
4931 find_y_roots(S, k, lambda + 1, g, U, triangle);
4932 }
4933 }
4934 }
4935
4936 // first entry reservoir-selected among the metric-optimal candidates as elsewhere in the
4937 // library, remaining entries in nondecreasing metric
4938 template <class M>
4939 static std::vector<Vector<T>> GSA_rank_candidates(std::vector<Vector<T>> candidates, const M& metric) {
4940 if (candidates.size() < 2) return candidates;
4941
4942 std::vector<std::pair<double, Vector<T>>> ranked;
4943 ranked.reserve(candidates.size());
4944 for (size_t j = 0; j < candidates.size(); ++j) {
4945 ranked.emplace_back(metric(candidates[j]), std::move(candidates[j]));
4946 }
4947
4948 size_t choice = 0;
4949 uint16_t ties = 1;
4950 for (size_t j = 1; j < ranked.size(); ++j) {
4951 if (ranked[j].first < ranked[choice].first) {
4952 choice = j;
4953 ties = 1;
4954 } else if (ranked[j].first == ranked[choice].first) {
4955 if (details::reservoir_accept(++ties)) choice = j;
4956 }
4957 }
4958 std::swap(ranked[0], ranked[choice]);
4959 std::stable_sort(ranked.begin() + 1, ranked.end(),
4960 [](const auto& lhs, const auto& rhs) { return lhs.first < rhs.first; });
4961
4962 candidates.clear();
4963 for (size_t j = 0; j < ranked.size(); ++j) candidates.push_back(std::move(ranked[j].second));
4964 return candidates;
4965 }
4966
4967 std::vector<Vector<T>> dec_KVA_impl(const Vector<T>& ap, const Matrix<int>& Theta, size_t list_size) const {
4968 const size_t k = this->k;
4969 const size_t np = ap.get_n();
4970 const size_t q = Theta.get_m();
4971 const size_t ell = list_size;
4972 constexpr size_t p = T::get_characteristic();
4973 if (k == 0) return {Vector<T>(this->n)};
4974
4975 // number of Hasse derivative constraints: bin(m + 1, 2) per point with multiplicity m
4976 size_t cost = 0;
4977 size_t max_multiplicity = 0;
4978 for (size_t beta = 0; beta < q; ++beta)
4979 for (size_t i = 0; i < np; ++i) {
4980 const size_t multiplicity = static_cast<size_t>(Theta(beta, i));
4981 cost += bin<size_t>(multiplicity + 1, 2);
4982 max_multiplicity = std::max(max_multiplicity, multiplicity);
4983 }
4984
4985 const size_t h = GSA_degree_bound(k, ell, cost);
4986 std::vector<size_t> width(ell + 1);
4987 std::vector<size_t> offset(ell + 1);
4988 size_t n_unknowns = 0;
4989 for (size_t mu = 0; mu <= ell; ++mu) {
4990 width[mu] = GSA_block_size(k, h, mu);
4991 offset[mu] = n_unknowns;
4992 n_unknowns += width[mu];
4993 }
4994
4995 // interpolation constraints D^(s, t) Q(a_i, beta) = 0 for all s + t < Theta(beta, i)
4996 const SierpinskiTriangle triangle(std::max(h - 1, ell),
4997 std::max(max_multiplicity > 0 ? max_multiplicity - 1 : 0, ell), p);
4998 auto M = ZeroMatrix<T>(cost, n_unknowns);
4999 size_t row = 0;
5000 for (size_t i = 0; i < np; ++i) {
5001 for (size_t beta = 0; beta < q; ++beta) {
5002 const size_t multiplicity = static_cast<size_t>(Theta(beta, i));
5003 if (multiplicity == 0) continue;
5004 const T y(static_cast<int>(beta));
5005 for (size_t s = 0; s < multiplicity; ++s) {
5006 for (size_t t = 0; s + t < multiplicity; ++t) {
5007 auto ypow = T(1);
5008 for (size_t mu = t; mu <= ell; ++mu) {
5009 const size_t ct = triangle(mu, t);
5010 if (ct != 0) {
5011 auto xpow = T(1);
5012 for (size_t nu = s; nu < width[mu]; ++nu) {
5013 const size_t binomials = (ct * triangle(nu, s)) % p;
5014 if (binomials != 0)
5015 M.set_component(row, offset[mu] + nu, binomials * (xpow * ypow));
5016 xpow *= ap[i];
5017 }
5018 }
5019 ypow *= y;
5020 }
5021 ++row;
5022 }
5023 }
5024 }
5025 }
5026
5027 const auto B = M.basis_of_kernel();
5028 if (B.get_m() == 0) return {};
5029 std::vector<Polynomial<T>> Q(ell + 1, ZeroPolynomial<T>());
5030 for (size_t mu = 0; mu <= ell; ++mu)
5031 for (size_t nu = 0; nu < width[mu]; ++nu) Q[mu].set_coefficient(nu, B(0, offset[mu] + nu));
5032
5033 std::vector<Polynomial<T>> roots;
5034 auto g = ZeroPolynomial<T>();
5035 find_y_roots(Q, k, 0, g, roots, triangle);
5036
5037 std::vector<Vector<T>> res;
5038 for (size_t j = 0; j < roots.size(); ++j) res.push_back(pad_back(Vector<T>(roots[j]), k) * canonical_G());
5039 return res;
5040 }
5041
5042 static Matrix<T> GRS_G(const Vector<T>& a, const Vector<T>& d, size_t k) {
5043 if (a.get_n() != d.get_n())
5044 throw std::invalid_argument("code locators a and column multipliers d must have the same length");
5045 if (k > a.get_n())
5046 throw std::invalid_argument(
5047 "code dimension must not exceed the number of code locators/column multipliers");
5048 if (!a.is_pairwise_distinct())
5049 throw std::invalid_argument("GRS codes must have pairwise distinct code locators!");
5050 return VandermondeMatrix<T>(a, k) * DiagonalMatrix<T>(d);
5051 }
5052
5053 GRSCode(std::pair<Vector<T>, Vector<T>> params, size_t k) : GRSCode<T>(params.first, params.second, k) {}
5054};
5055
5056template <FiniteFieldType T>
5057class RSCode : public GRSCode<T> {
5058 public:
5059 RSCode(const T& alpha, size_t b, size_t k) try : RSCode(RS_a_and_D(alpha, b), k, alpha, b) {
5060 } catch (const std::invalid_argument& e) {
5061 throw std::invalid_argument(std::string("Cannot construct RS code: ") + e.what());
5062 }
5063
5064 // Documentation note: recognition from a plain linear code. When at least two canonical
5065 // generator rows survive (k >= 2), the canonical GRS form is recognized first, and on top of
5066 // it the locators must be the full ordered orbit of alpha = a_1 and the multipliers the
5067 // geometric sequence alpha^{i(1-b)}, whose discrete logarithm determines b. A punctured
5068 // canonical RS matrix is therefore recognized exactly when the surviving columns form the
5069 // required complete ordered locator orbit and multiplier sequence; starting from a full orbit,
5070 // the proper ordered suborbit patterns are the retained positions 0, s, 2s, ..., n - s with
5071 // s | n, giving alpha' = alpha^s, n' = n / s and b' = b mod n'. For k = 1 the surviving row
5072 // d_i = beta^i (with d_0 = 1) no longer determines the parameters; the element with smallest
5073 // label among those of multiplicative order n is chosen as alpha and b is derived from
5074 // alpha^{1-b} = beta, provided such an alpha exists and beta lies in its orbit. Either way the
5075 // reconstructed code carries exactly the recognized generator matrix.
5076 RSCode(const LinearCode<T>& C) try : RSCode(RS_recognize(C), C.get_k()) {
5077 } catch (const std::invalid_argument& e) {
5078 throw std::invalid_argument(std::string("Cannot construct RS code: ") + e.what());
5079 }
5080
5082 auto Gp = this->G;
5083 Gp.rref();
5084 RSCode<T> res(std::make_pair(this->get_a(), this->get_d()), this->k, alpha, b, std::move(Gp));
5085 if (this->dmin.has_value()) res.set_dmin(*(this->dmin));
5086 if (this->weight_enumerator.has_value()) res.set_weight_enumerator(*(this->weight_enumerator));
5087 if (this->p_ary_image_weight_enumerator.has_value())
5088 res.p_ary_image_weight_enumerator = this->p_ary_image_weight_enumerator;
5089 if (this->minimal_trellis.has_value()) res.minimal_trellis = this->minimal_trellis;
5090 if (this->codewords.has_value()) res.codewords = this->codewords;
5091 return res;
5092 }
5093
5095 RSCode<T> res(std::make_pair(this->get_a(), this->get_d()), this->k, alpha, b,
5096 this->get_G_in_polynomial_form());
5097 if (this->dmin.has_value()) res.set_dmin(*(this->dmin));
5098 if (this->weight_enumerator.has_value()) res.set_weight_enumerator(*(this->weight_enumerator));
5099 if (this->p_ary_image_weight_enumerator.has_value())
5100 res.p_ary_image_weight_enumerator = this->p_ary_image_weight_enumerator;
5101 if (this->minimal_trellis.has_value()) res.minimal_trellis = this->minimal_trellis;
5102 if (this->codewords.has_value()) res.codewords = this->codewords;
5103 return res;
5104 }
5105
5106 T get_alpha() const { return alpha; }
5107 size_t get_b() const noexcept { return b; }
5108
5109 void get_info(std::ostream& os) const override {
5110 if (this->template info_prelude<GRSCode<T>>(os)) {
5111 os << BOLD("RS code") " with properties: { alpha = " << alpha << ", b = " << b;
5112 if (this->is_primitive()) os << ", primitive";
5113 if (this->is_narrow_sense()) os << ", narrow-sense";
5114 if (this->is_normalized()) os << ", normalized";
5115 os << " }";
5116 }
5117 }
5118
5119 private:
5120 T alpha;
5121 size_t b;
5122
5123 RSCode(std::pair<Vector<T>, Vector<T>> params, size_t k, const T& alpha, size_t b)
5124 : GRSCode<T>(params.first, params.second, k), alpha(alpha), b(b) {
5125 this->set_gamma(RS_gamma(params.first, b, k));
5126 }
5127
5128 RSCode(std::pair<T, size_t> ab, size_t k) : RSCode(RS_a_and_D(ab.first, ab.second), k, ab.first, ab.second) {}
5129
5130 static std::pair<T, size_t> RS_recognize(const LinearCode<T>& C) {
5131 const size_t n = C.get_n();
5132 const T one(1);
5133
5134 if (C.get_k() >= 2) {
5135 const auto params = GRSCode<T>::GRS_a_and_d(C);
5136 const Vector<T>& a = params.first;
5137 const Vector<T>& d = params.second;
5138
5139 const T alpha = a[1];
5140 if (alpha.is_zero() || alpha.get_multiplicative_order() != n)
5141 throw std::invalid_argument("Linear code cannot be converted into RS code!");
5142 T pow = one;
5143 for (size_t i = 0; i < n; ++i) {
5144 if (a[i] != pow) throw std::invalid_argument("Linear code cannot be converted into RS code!");
5145 pow *= alpha;
5146 }
5147 const T beta = d[1];
5148 pow = one;
5149 for (size_t i = 0; i < n; ++i) {
5150 if (d[i] != pow) throw std::invalid_argument("Linear code cannot be converted into RS code!");
5151 pow *= beta;
5152 }
5153 size_t exponent = 0;
5154 T apow = one;
5155 while (apow != beta) {
5156 apow *= alpha;
5157 if (++exponent == n) throw std::invalid_argument("Linear code cannot be converted into RS code!");
5158 }
5159 return {alpha, (1 + n - exponent) % n};
5160 }
5161
5162 if (C.get_k() != 1) throw std::invalid_argument("Linear code cannot be converted into RS code!");
5163
5164 const auto& Gp = C.get_G();
5165 const T beta = n > 1 ? Gp(0, 1) : one;
5166 if (beta.is_zero()) throw std::invalid_argument("Linear code cannot be converted into RS code!");
5167 T pow = one;
5168 for (size_t i = 0; i < n; ++i) {
5169 if (Gp(0, i) != pow) throw std::invalid_argument("Linear code cannot be converted into RS code!");
5170 pow *= beta;
5171 }
5172
5173 T alpha = one;
5174 bool found = false;
5175 for (size_t label = 1; label < T::get_size(); ++label) {
5176 const T candidate(label);
5177 if (candidate.get_multiplicative_order() == n) {
5178 alpha = candidate;
5179 found = true;
5180 break;
5181 }
5182 }
5183 if (!found) throw std::invalid_argument("Linear code cannot be converted into RS code!");
5184
5185 size_t exponent = 0;
5186 T apow = one;
5187 while (apow != beta) {
5188 apow *= alpha;
5189 if (++exponent == n) throw std::invalid_argument("Linear code cannot be converted into RS code!");
5190 }
5191 return {alpha, (1 + n - exponent) % n};
5192 }
5193
5194 RSCode(std::pair<Vector<T>, Vector<T>> params, size_t k, const T& alpha, size_t b, Matrix<T> G)
5195 : GRSCode<T>(params.first, params.second, k, std::move(G)), alpha(alpha), b(b) {
5196 this->set_gamma(RS_gamma(params.first, b, k));
5197 }
5198
5199 static std::pair<Vector<T>, Vector<T>> RS_a_and_D(const T& alpha, size_t b) {
5200 if (alpha == T(0)) throw std::invalid_argument("alpha must not be zero");
5201 const size_t n = alpha.get_multiplicative_order();
5202 const T alpha_1mb = sqm<T>(alpha, n + 1 - b % n);
5203 Vector<T> a(n), d(n);
5204 T a_pow = T(1), d_pow = T(1);
5205 for (size_t i = 0; i < n; ++i) {
5206 a.set_component(i, a_pow);
5207 d.set_component(i, d_pow);
5208 a_pow *= alpha;
5209 d_pow *= alpha_1mb;
5210 }
5211 return {std::move(a), std::move(d)};
5212 }
5213
5214 static Polynomial<T> RS_gamma(const Vector<T>& a, size_t b, size_t k) {
5215 const size_t n = a.get_n();
5216 const size_t start = b % n; // index of alpha^b in a
5217 auto gamma = OnePolynomial<T>();
5218 for (size_t j = 0; j < n - k; ++j) gamma *= Polynomial<T>({-a[(start + j) % n], T(1)});
5219 return gamma;
5220 }
5221};
5222
5223class CordaroWagnerCode : public LinearCode<Fp<2>> {
5224 public:
5225 CordaroWagnerCode(size_t r, int8_t m)
5226 : LinearCode<Fp<2>>(CordaroWagner_n(r, m), 2, CordaroWagner_G(r, m)), r(r), m(m) {
5227 auto weight_enumerator = ZeroPolynomial<InfInt>();
5228 weight_enumerator.add_to_coefficient(0, 1);
5229 weight_enumerator.add_to_coefficient(2 * r, 1);
5230 weight_enumerator.add_to_coefficient(2 * r + m, 2);
5231 this->set_weight_enumerator(std::move(weight_enumerator));
5232 }
5233
5234 CordaroWagnerCode(const LinearCode<Fp<2>>& C) : LinearCode<Fp<2>>(C), r(0), m(0) {
5235 if (this->k != 2) throw std::invalid_argument("Linear code cannot be converted into Cordaro-Wagner code!");
5236
5237 const size_t n = this->n;
5238 r = std::floor(n / 3.0 + 1.0 / 2.0);
5239 m = n - 3 * r;
5240
5241 const auto c1 = this->G.get_row(0);
5242 const auto c2 = this->G.get_row(1);
5243 std::array<size_t, 3> actual = {c1.wH(), c2.wH(), (c1 + c2).wH()};
5244 std::array<size_t, 3> expected = {2 * r, n - r, n - r}; // n - r = 2r + m
5245 std::ranges::sort(actual);
5246 std::ranges::sort(expected);
5247 if (actual != expected)
5248 throw std::invalid_argument("Linear code cannot be converted into Cordaro-Wagner code!");
5249 }
5250
5252 return CordaroWagnerCode(LinearCode<Fp<2>>::get_equivalent_code_in_standard_form());
5253 }
5254
5255 size_t get_r() const noexcept { return r; }
5256 int8_t get_m() const noexcept { return m; }
5257
5258 void get_info(std::ostream& os) const override {
5259 if (info_prelude<LinearCode<Fp<2>>>(os))
5260 os << BOLD("Cordaro-Wagner code") " with properties: { r = " << r << ", m = " << static_cast<int>(m)
5261 << " }";
5262 }
5263
5264 Vector<Fp<2>> dec_BD(const Vector<Fp<2>>& r) const override {
5265#ifdef CECCO_ERASURE_SUPPORT
5266 if (LinearCode<Fp<2>>::erasures_present(r)) return dec_BD_EE(r);
5267#endif
5268 this->validate_length(r);
5269
5270 const size_t tmax = this->get_tmax();
5271 for (auto it = this->cbegin(); it != this->cend(); ++it) {
5272 if (dH(*it, r) <= tmax) return *it;
5273 }
5274 throw decoding_failure("Cordaro-Wagner code BD decoder failed!");
5275 }
5276
5277 Vector<Fp<2>> dec_ML(const Vector<Fp<2>>& r) const override {
5278#ifdef CECCO_ERASURE_SUPPORT
5279 if (LinearCode<Fp<2>>::erasures_present(r)) return dec_ML_EE(r);
5280#endif
5281 this->validate_length(r);
5282
5283 Vector<Fp<2>> best;
5284 size_t best_t = std::numeric_limits<size_t>::max();
5285 uint16_t ties = 1;
5286
5287 for (auto it = this->cbegin(); it != this->cend(); ++it) {
5288 const size_t t = dH(*it, r);
5289 if (t < best_t) {
5290 best = *it;
5291 best_t = t;
5292 ties = 1;
5293 } else if (t == best_t) {
5294 if (details::reservoir_accept(++ties)) best = *it;
5295 }
5296 }
5297 return best;
5298 }
5299
5300#ifdef CECCO_ERASURE_SUPPORT
5301 Vector<Fp<2>> dec_BD_EE(const Vector<Fp<2>>& r) const override {
5302 this->validate_length(r);
5303
5304 const size_t n = this->n;
5305 const size_t dmin = this->get_dmin();
5306
5307 size_t tau = 0;
5308 for (size_t i = 0; i < n; ++i) {
5309 if (r[i].is_erased()) ++tau;
5310 }
5311
5312 if (tau > dmin - 1) throw decoding_failure("Cordaro-Wagner code BD error/erasure decoder failed!");
5313
5314 for (auto it = this->cbegin(); it != this->cend(); ++it) {
5315 size_t t = 0;
5316 for (size_t i = 0; i < n; ++i) {
5317 if (!r[i].is_erased() && r[i] != (*it)[i]) ++t;
5318 }
5319
5320 if (2 * t + tau <= dmin - 1) return *it;
5321 }
5322
5323 throw decoding_failure("Cordaro-Wagner code BD error/erasure decoder failed!");
5324 }
5325
5326 Vector<Fp<2>> dec_ML_EE(const Vector<Fp<2>>& r) const override {
5327 this->validate_length(r);
5328
5329 const size_t n = this->n;
5330 size_t tau = 0;
5331 for (size_t i = 0; i < n; ++i) {
5332 if (r[i].is_erased()) ++tau;
5333 }
5334 if (tau == n) return Vector<Fp<2>>(n, Fp<2>(0));
5335
5336 Vector<Fp<2>> best;
5338 uint16_t ties = 1;
5339
5340 for (auto it = this->cbegin(); it != this->cend(); ++it) {
5341 size_t t = 0;
5342 for (size_t i = 0; i < n; ++i) {
5343 if (!r[i].is_erased() && r[i] != (*it)[i]) ++t;
5344 }
5345
5346 if (t < best_t) {
5347 best_t = t;
5348 best = *it;
5349 ties = 1;
5350 } else if (t == best_t) {
5351 if (details::reservoir_accept(++ties)) best = *it;
5352 }
5353 }
5354
5355 return best;
5356 }
5357#endif
5358
5359 private:
5360 size_t r;
5361 int8_t m;
5362
5363 static size_t CordaroWagner_n(size_t r, int8_t m) {
5364 if (r < 1) throw std::invalid_argument("Cordaro-Wagner codes must have r > 0!");
5365 if (m != -1 && m != 0 && m != 1)
5366 throw std::invalid_argument("Cordaro-Wagner codes must have m either -1, 0, or 1!");
5367 return 3 * r + m;
5368 }
5369
5370 static Matrix<Fp<2>> CordaroWagner_G(size_t r, int8_t m) {
5371 const size_t n = CordaroWagner_n(r, m);
5372 Matrix<Fp<2>> G(2, n);
5373
5374 const Matrix<Fp<2>> type1_column(2, 1, {Fp<2>(1), Fp<2>(0)});
5375 for (size_t s = 0; s < r; ++s) G.set_submatrix(0, s, type1_column);
5376
5377 const Matrix<Fp<2>> type2_column(2, 1, {Fp<2>(0), Fp<2>(1)});
5378 for (size_t s = r; s < 2 * r + m; ++s) G.set_submatrix(0, s, type2_column);
5379
5380 const Matrix<Fp<2>> type3_column(2, 1, {Fp<2>(1), Fp<2>(1)});
5381 for (size_t s = 2 * r + m; s < 3 * r + m; ++s) G.set_submatrix(0, s, type3_column);
5382
5383 return G;
5384 }
5385};
5386
5388
5389template <FiniteFieldType T>
5391 public:
5392 // Documentation note: the encoder image must be a free module of rank L * k_cc; a rank-deficient block
5393 // generator matrix is rejected by convolutional_G with a message naming the cause. Under either
5394 // termination rank loss occurs when the rows of G_cc are T[D]-linearly dependent with a
5395 // dependency of degree below L (zero rows, shifted duplicates, more rows than columns). Under
5396 // tailbiting it additionally strikes encoders of full rank over T(D) whenever they share factors
5397 // with D^L - 1.
5398 ConvolutionalCode(const Matrix<Polynomial<T>>& G_cc, size_t L, termination_t termination) try
5399 : ConvolutionalCode(G_cc, L, convolutional_G(G_cc, L, termination), termination) {
5400 } catch (const std::invalid_argument& e) {
5401 throw std::invalid_argument(std::string("Cannot construct convolutional code: ") + e.what());
5402 }
5403
5404 ConvolutionalCode(const Vector<Polynomial<T>>& G_cc, size_t L, termination_t termination)
5406
5407 // Documentation note: customary octal shorthand for binary encoders: each row lists the taps of one
5408 // encoder input MSB-first, so an integer value loses exactly its leading zeros (low-degree
5409 // taps). The row width u_i + 1 is recovered as the largest bit width within the row; this is
5410 // exact for encoders obeying the row-wise delay-free convention of the standard tables (no
5411 // row divisible by x, since a factored-out delay wastes memory and under tailbiting does not even
5412 // change the code, x being a unit modulo x^L - 1). Narrower entries pick up implied leading
5413 // zeros, e.g., the row (1, 2) with width 2 reads as (x, 1), and 0 entries are zero
5414 // polynomials. Each entry must be given as a C++ octal literal, which reproduces the tabulated
5415 // notation verbatim, e.g., {05, 07} or {0133, 0171}: only the binary value of the entry is read,
5416 // so a decimal literal whose value is at least 8 (0133 written as 133) silently denotes a
5417 // different polynomial and cannot be diagnosed. The std::vector<std::vector<unsigned>> overload
5418 // (and std::vector<unsigned> for a single input) takes the same tap words built at runtime; there
5419 // the caller supplies the values directly (e.g. parsed from base-8 input), since octal literals
5420 // exist only in source. The std::initializer_list overloads are not redundant with the
5421 // std::vector ones: they exist to disambiguate a braced integer literal, which without them would
5422 // tie between the std::vector octal overloads and the Vector/Matrix of Polynomial constructors (a
5423 // bare integer converts implicitly to a constant Polynomial); an initializer_list parameter
5424 // outranks class-type list-initialization and so binds the octal path uniquely. Available for
5425 // binary codes only.
5426 ConvolutionalCode(std::initializer_list<std::initializer_list<unsigned>> G_octal, size_t L,
5427 termination_t termination)
5428 requires std::same_as<T, Fp<2>>
5430
5434
5438
5442
5443 // Documentation note: This constructor performs complete recognition over the
5444 // enumerated parameter tuples (every k_cc dividing k with L = k / k_cc >= 2, every compatible
5445 // n_cc and u, both terminations). A code is accepted exactly when some enumerated tuple admits
5446 // a structured generator matrix whose rows lie in C and reach rank k (which forces row-space
5447 // equality with C), and rejection means that no zero-terminated or tailbitten convolutional
5448 // representation exists within those enumerated tuples, including realizations by encoders
5449 // with unequal row degrees and codes presented through an arbitrary generator matrix of the
5450 // same row space. Among multiple admissible representations the canonical one is selected:
5451 // minimal k_cc, then minimal n_cc, then minimal u, zero-terminated preferred over tailbitten on ties; G_cc is
5452 // recovered in RREF over the window coordinates, the canonical representative of the
5453 // invertible-input-transform ambiguity. The result is rebuilt through the polynomial
5454 // constructor, so a recognized code carries the structured band generator matrix instead of
5455 // C's representation.
5457
5458 const Matrix<Polynomial<T>>& get_G_cc() const noexcept { return G_cc; }
5459 size_t get_k_cc() const noexcept { return G_cc.get_m(); }
5460 size_t get_n_cc() const noexcept { return G_cc.get_n(); }
5461 size_t get_L() const noexcept { return this->k / get_k_cc(); }
5462
5463 size_t get_u() const noexcept {
5464 size_t u = 0;
5465 for (size_t i = 0; i < G_cc.get_m(); ++i)
5466 for (size_t j = 0; j < G_cc.get_n(); ++j)
5467 if (!G_cc(i, j).is_zero()) u = std::max(u, G_cc(i, j).degree());
5468 return u;
5469 }
5470
5471 termination_t get_termination() const noexcept { return termination; }
5472 bool is_zero_terminated() const noexcept { return termination == termination_t::zero_terminated; }
5473 bool is_tailbitten() const noexcept { return termination == termination_t::tailbitten; }
5474
5475 // Documentation note: the trellis is the natural decoding structure of a convolutional code,
5476 // so the BD and ML paradigms always decode by Viterbi and never build a decoding table.
5477 Vector<T> dec_BD(const Vector<T>& r) const override { return this->dec_BD_Viterbi(r); }
5478
5479 Vector<T> dec_ML(const Vector<T>& r) const override {
5480#ifdef CECCO_ERASURE_SUPPORT
5481 if (LinearCode<T>::erasures_present(r)) return this->dec_Viterbi_EE(r);
5482#endif
5483 return this->dec_Viterbi(r);
5484 }
5485
5486#ifdef CECCO_ERASURE_SUPPORT
5487 Vector<T> dec_BD_EE(const Vector<T>& r) const override { return this->dec_BD_Viterbi_EE(r); }
5488
5489 Vector<T> dec_ML_EE(const Vector<T>& r) const override { return this->dec_Viterbi_EE(r); }
5490#endif
5491
5493 if (this->template info_prelude<LinearCode<T>>(os)) {
5494 const size_t k_cc = get_k_cc();
5495 const size_t n_cc = get_n_cc();
5496 os << BOLD("Convolutional code") " with properties: { k_cc = " << k_cc << ", n_cc = " << n_cc
5497 << ", L = " << get_L() << ", u = " << get_u() << ", "
5498 << (is_tailbitten() ? "tailbitten" : "zero-terminated") << ", G_cc = [";
5499 for (size_t i = 0; i < k_cc; ++i) {
5500 os << (i == 0 ? "[" : ", [");
5501 for (size_t j = 0; j < n_cc; ++j) {
5502 if (j != 0) os << ", ";
5503 os << G_cc(i, j);
5504 }
5505 os << "]";
5506 }
5507 os << "] }";
5508 }
5509 }
5510
5511 private:
5513 termination_t termination;
5514
5515 ConvolutionalCode(const Matrix<Polynomial<T>>& G_cc, size_t L, const Matrix<T>& G, termination_t termination)
5516 : LinearCode<T>(G.get_n(), L * G_cc.get_m(), G), G_cc(G_cc), termination(termination) {}
5517
5518 template <class Rows>
5519 static Matrix<Polynomial<T>> octal_G_cc(const Rows& G_octal)
5520 requires std::same_as<T, Fp<2>>
5521 {
5522 const size_t k_cc = G_octal.size();
5523 const size_t n_cc = k_cc == 0 ? 0 : G_octal.begin()->size();
5524 if (k_cc == 0 || n_cc == 0)
5525 throw std::invalid_argument(
5526 "Cannot construct convolutional code: octal generator matrix must be non-empty");
5527
5528 const T one(1);
5530 size_t i = 0;
5531 for (auto row = G_octal.begin(); row != G_octal.end(); ++row) {
5532 if (row->size() != n_cc)
5533 throw std::invalid_argument(
5534 "Cannot construct convolutional code: octal generator matrix must be rectangular");
5535 size_t width = 0;
5536 for (auto g = row->begin(); g != row->end(); ++g) width = std::max<size_t>(width, std::bit_width(*g));
5537 size_t j = 0;
5538 for (auto g = row->begin(); g != row->end(); ++g) {
5539 Polynomial<T> p(0);
5540 for (size_t e = 0; e < width; ++e)
5541 if ((*g >> (width - 1 - e)) & 1u) p.set_coefficient(e, one);
5542 G_cc.set_component(i, j, std::move(p));
5543 ++j;
5544 }
5545 ++i;
5546 }
5547 return G_cc;
5548 }
5549
5550 // Documentation note: row t*k_cc + i of G is the shift-register encoding of the basis message with a unit
5551 // impulse on input stream i at time t: output stream j receives the coefficients of D^t * G_cc[i][j].
5552 // The output-time modulus never wraps in the zero-terminated case; under tailbiting wrapped
5553 // contributions accumulate, which is exactly reduction modulo D^L - 1 (so u >= L is handled
5554 // correctly).
5556 if (L == 0) throw std::invalid_argument("L must be positive");
5557 if (G_cc.is_empty()) throw std::invalid_argument("G_cc must contain at least one generator polynomial");
5558 const size_t k_cc = G_cc.get_m();
5559 const size_t n_cc = G_cc.get_n();
5560
5561 size_t u = 0;
5562 for (size_t i = 0; i < k_cc; ++i) {
5563 for (size_t j = 0; j < n_cc; ++j) {
5564 if (G_cc(i, j).is_empty()) throw std::invalid_argument("generator polynomials must not be empty");
5565 if (!G_cc(i, j).is_zero()) u = std::max(u, G_cc(i, j).degree());
5566 }
5567 }
5569
5570 Matrix<T> G(L * k_cc, time_steps * n_cc);
5571 for (size_t t = 0; t < L; ++t)
5572 for (size_t i = 0; i < k_cc; ++i) {
5573 const size_t row = t * k_cc + i;
5574 for (size_t j = 0; j < n_cc; ++j) {
5575 if (G_cc(i, j).is_zero()) continue;
5576 const size_t degree = G_cc(i, j).degree();
5577 for (size_t ell = 0; ell <= degree; ++ell) {
5578 const size_t col = ((t + ell) % time_steps) * n_cc + j;
5579 G.set_component(row, col, G(row, col) + G_cc(i, j)[ell]);
5580 }
5581 }
5582 }
5583 if (G.rank() != L * k_cc)
5584 throw std::invalid_argument(
5586 ? "tailbiting rank loss, the block-circulant generator matrix has rank smaller than L * k_cc"
5587 : "rank loss, the block generator matrix has rank smaller than L * k_cc");
5588 return G;
5589 }
5590
5591 // Documentation note: necessary-condition-driven recognition. Candidates (k_cc, n_cc, L, u, termination)
5592 // are screened by necessary conditions only (parameter arithmetic, trellis-oriented span,
5593 // support, and boundary projection bounds, quasi-cyclicity), and surviving candidates enter a
5594 // structured recovery that is complete per tuple; a successful selection provably spans C, so
5595 // acceptance requires no separate certificate and every screen can only save work, never accept
5596 // falsely.
5597 static ConvolutionalCode recognize(const LinearCode<T>& C) {
5598 const size_t n = C.get_n();
5599 const size_t k = C.get_k();
5600
5601 if (k > 1) {
5602 const Matrix<T>& HT = C.get_HT();
5603
5604 // Documentation note: trellis-oriented form as algebraic minimal-trellis proxy: in a minimal-span
5605 // generator basis (all row starts distinct, all row ends distinct) the rows with span
5606 // inside a coordinate window form a basis of the subcode supported there, so the rank of
5607 // the projection of C onto the first (last) m coordinates equals the number of rows
5608 // starting (ending) inside, and the minimal-trellis state profile equals the number of
5609 // straddling spans. The span multiset is a code invariant, so these counts do not depend
5610 // on the particular reduced basis; all boundary screens below therefore reduce to span
5611 // counting without building the trellis.
5613 const T zero(0);
5614 std::vector<size_t> prefix_rank(n + 1, 0);
5615 std::vector<size_t> suffix_rank(n + 1, 0);
5616 size_t span_max = 0;
5617 for (size_t i = 0; i < k; ++i) {
5618 size_t start = 0;
5619 size_t end = 0;
5620 for (size_t j = 0; j < n; ++j)
5621 if (Gtof(i, j) != zero) {
5622 start = j;
5623 break;
5624 }
5625 for (size_t j = n; j-- > 0;)
5626 if (Gtof(i, j) != zero) {
5627 end = j;
5628 break;
5629 }
5630 ++prefix_rank[start + 1];
5631 ++suffix_rank[n - end];
5632 span_max = std::max(span_max, end - start + 1);
5633 }
5634 for (size_t m = 1; m <= n; ++m) prefix_rank[m] += prefix_rank[m - 1];
5635 for (size_t m = 1; m <= n; ++m) suffix_rank[m] += suffix_rank[m - 1];
5636
5637 const auto attempt = [&](size_t kcc, size_t ncc, size_t L, size_t u,
5639 const size_t window = (u + 1) * ncc;
5640
5641 // Documentation note: membership constraints: every row of the structured block-Toeplitz
5642 // (zero-terminated) or block-circulant (tailbitten) generator template must satisfy
5643 // row * H^T = 0; the constraints decouple per input stream, so the admissible window
5644 // space S = { w : all L block shifts of w lie in C } is the nullspace of one
5645 // homogeneous system stacked from window slices of H^T, wrapped modulo n under
5646 // tailbiting.
5648 if (k < n) {
5649 Matrix<T> W(window, L * (n - k));
5650 for (size_t t = 0; t < L; ++t) {
5651 const size_t start = t * ncc;
5652 if (start + window <= n)
5653 W.set_submatrix(0, t * (n - k), HT.get_submatrix(start, 0, window, n - k));
5654 else {
5655 W.set_submatrix(0, t * (n - k), HT.get_submatrix(start, 0, n - start, n - k));
5656 W.set_submatrix(n - start, t * (n - k), HT.get_submatrix(0, 0, start + window - n, n - k));
5657 }
5658 }
5660 }
5661
5662 // Documentation note: the rows of any valid G_cc are k_cc linearly independent elements of S.
5663 const size_t s = B.get_m();
5664 if (s < kcc) return std::nullopt;
5665
5666 // Documentation note: projective window space: scalar multiples of a window row span the
5667 // same orbit rows, so one representative per projective class of S suffices, and
5668 // enumerating all (q^s - 1)/(q - 1) projective points of S makes the row-selection
5669 // step complete over finite fields: coefficient vectors over the basis rows whose
5670 // first nonzero entry is one form a transversal (the basis rows are linearly
5671 // independent), so normalization and deduplication hold by construction. The pool
5672 // is only large for degenerate tuples (for a zero-terminated candidate realized by
5673 // an encoder of full rank over T(D), uniqueness of the representation m(D) G_cc(D)
5674 // with deg m < L forces S to be exactly the span of the k_cc encoder rows), and the
5675 // search is finite but may exceed addressable matrix dimensions, so InfInt prevents
5676 // arithmetic overflow before converting the point count to size_t.
5677 const size_t q = T::get_size();
5678 const InfInt count = (sqm<InfInt>(q, s) - 1) / InfInt(q - 1);
5680 throw std::overflow_error("Convolutional recognition projective search space too large");
5681 const size_t points = static_cast<size_t>(count.toUnsignedLongLong());
5683 size_t r = 0;
5684 for (size_t lead = 0; lead < s; ++lead) {
5685 std::vector<size_t> digits(s, 0);
5686 bool more = true;
5687 while (more) {
5688 for (size_t p = 0; p < window; ++p) {
5689 T v = B(lead, p);
5690 for (size_t i = lead + 1; i < s; ++i)
5691 if (digits[i] != 0) v += T(digits[i]) * B(i, p);
5692 pool.set_component(r, p, v);
5693 }
5694 ++r;
5695 more = false;
5696 for (size_t i = lead + 1; i < s; ++i) {
5697 if (++digits[i] < q) {
5698 more = true;
5699 break;
5700 }
5701 digits[i] = 0;
5702 }
5703 }
5704 }
5705
5706 // Documentation note: exhaustive row selection with rank pruning: a valid G_cc exists for
5707 // the tuple iff some k_cc projective representatives stack to orbit rank k, where
5708 // window position p = ell n_cc + j of the row started at time t lands in column
5709 // (t n_cc + p) mod n, the column ordering of convolutional_G. In any valid selection
5710 // all L k_cc orbit rows are linearly independent, so every member orbit adds exactly
5711 // rank L in any order; a candidate whose orbit adds less is skipped without loss of
5712 // completeness, and branches with fewer remaining representatives than missing rows
5713 // are cut.
5715 const auto search = [&](auto&& self, size_t start, const Matrix<T>* stack) -> bool {
5716 if (chosen.size() == kcc) return true;
5717 for (size_t c = start; c + (kcc - chosen.size()) <= points; ++c) {
5718 Matrix<T> trial(L, n);
5719 for (size_t t = 0; t < L; ++t)
5720 for (size_t p = 0; p < window; ++p) trial.set_component(t, (t * ncc + p) % n, pool(c, p));
5721 if (stack != nullptr) trial.vertical_join(*stack);
5722 if (trial.rank() != (chosen.size() + 1) * L) continue;
5724 if (self(self, c + 1, &trial)) return true;
5725 chosen.pop_back();
5726 }
5727 return false;
5728 };
5729 if (!search(search, 0, nullptr)) return std::nullopt;
5730
5731 Matrix<T> R(kcc, window);
5732 for (size_t i = 0; i < kcc; ++i)
5733 for (size_t p = 0; p < window; ++p) R.set_component(i, p, pool(chosen[i], p));
5734 R.rref();
5736 for (size_t i = 0; i < kcc; ++i)
5737 for (size_t j = 0; j < ncc; ++j) {
5738 auto g = ZeroPolynomial<T>();
5739 for (size_t ell = 0; ell <= u; ++ell)
5740 if (!R(i, ell * ncc + j).is_zero()) g.set_coefficient(ell, R(i, ell * ncc + j));
5741 Gp.set_component(i, j, std::move(g));
5742 }
5743
5744 // Documentation note: acceptance needs no separate row-space certificate: the selected rows
5745 // lie in S, so every band row lies in C, and the selection has rank k = dim C, so
5746 // the band row space equals C; for zero-terminated candidates the trailing-support
5747 // screen forces the recovered memory to be exactly u, so the rebuilt band matrix
5748 // has length n (the tailbitten length is L n_cc regardless). This subsumes the rank
5749 // checks of convolutional_G and the base constructor, so the construction cannot
5750 // throw.
5751 return ConvolutionalCode(Gp, L, term);
5752 };
5753
5754 // Documentation note: canonical candidate policy: minimal k_cc, then minimal n_cc, then minimal
5755 // u, zero-terminated preferred on ties. A code may admit several convolutional
5756 // representations; minimal k_cc selects the one with the fewest independent input
5757 // streams, so a genuine rate-1 tailbitten code is never reported as an artificial
5758 // higher-input zero-terminated representation merely because that is found first.
5759 // Within one k_cc the shared arithmetic k = L k_cc fixes the time-step count L = k /
5760 // k_cc; L = 1 is excluded because every linear code is trivially convolutional with a
5761 // single time step.
5762 for (size_t kcc = 1; 2 * kcc <= k; ++kcc) {
5763 if (k % kcc != 0) continue;
5764 const size_t L = k / kcc;
5765
5766 for (size_t ncc = 1; ncc * L <= n; ++ncc) {
5767 // Documentation note: zero-terminated arithmetic: n = (L + u) n_cc with u >= 0
5768 // determines the memory once n_cc divides n into at least L output blocks.
5769 if (n % ncc != 0) continue;
5770 const size_t u = n / ncc - L;
5771
5772 // Documentation note: trailing-support screen: some row of a zero-terminated encoder
5773 // with memory u has degree exactly u, and its top coefficient block makes C
5774 // nonzero on the final n_cc coordinates; conversely, a full-rank selection of
5775 // window rows all of degree below u would span a code vanishing there, so this
5776 // screen is what forces the recovered memory to be exactly u.
5777 if (suffix_rank[ncc] == 0) continue;
5778
5779 // Documentation note: span-length screen: every row of a zero-terminated band basis is
5780 // supported in a window of u + 1 output blocks, and span reduction preserves
5781 // this bound: rows sharing a start both end before start + (u + 1) n_cc, so
5782 // their difference starts later and ends no later, and end collisions are
5783 // symmetric. Since the span multiset is basis-independent, every
5784 // trellis-oriented row of a representable code has span length at most
5785 // (u + 1) n_cc; wrapped tailbitten rows break the bound, so the screen applies
5786 // to zero-terminated candidates only.
5787 if (span_max > (u + 1) * ncc) continue;
5788
5789 // Documentation note: zero-terminated boundary bounds: row (t, i) of the band matrix
5790 // occupies output blocks t through t plus its row degree, so at most h k_cc rows
5791 // intersect the first h (equally: last h) blocks, and the projection of C onto
5792 // them has rank at most h k_cc for every h < L, regardless of the individual row
5793 // degrees.
5794 bool bounded = true;
5795 for (size_t h = 1; h < L && bounded; ++h)
5796 if (prefix_rank[h * ncc] > h * kcc || suffix_rank[h * ncc] > h * kcc) bounded = false;
5797 if (!bounded) continue;
5798
5799 if (auto res = attempt(kcc, ncc, L, u, termination_t::zero_terminated)) return std::move(*res);
5800 }
5801
5802 // Documentation note: tailbiting arithmetic: n = L n_cc with memory 0 <= u < L; u = 0
5803 // coincides with memoryless zero termination, which is already enumerated above, and
5804 // generators of degree at least L reduce coefficient-wise modulo D^L - 1 to an
5805 // equivalent representative with u < L and the identical block-circulant matrix.
5806 if (n % L != 0) continue;
5807 const size_t ncc = n / L;
5808
5809 // Documentation note: a tailbitten convolutional block code is invariant under rotation by
5810 // one time block of n_cc symbols, hence quasi-cyclic with minimal shift index
5811 // dividing n_cc.
5812 size_t shift;
5813 if (!C.is_quasi_cyclic(&shift) || ncc % shift != 0) continue;
5814
5815 for (size_t u = 1; u < L; ++u) {
5816 // Documentation note: block-circulant projection bound: a row of degree at most u
5817 // intersects a cyclic window of h consecutive blocks for at most h + u start
5818 // times, so the projection of C onto the first h blocks (representative for all
5819 // windows by quasi-cyclicity) has rank at most k_cc (h + u).
5820 bool bounded = true;
5821 for (size_t h = 1; h < L && kcc * (h + u) < k && bounded; ++h)
5822 if (prefix_rank[h * ncc] > kcc * (h + u)) bounded = false;
5823 if (!bounded) continue;
5824
5825 if (auto res = attempt(kcc, ncc, L, u, termination_t::tailbitten)) return std::move(*res);
5826 }
5827 }
5828 }
5829 throw std::invalid_argument("Linear code cannot be converted into convolutional code!");
5830 }
5831};
5832
5833namespace details {
5834
5835template <FieldType T>
5836Matrix<T> LDC_G(const Matrix<T>& G_U, const Matrix<T>& G_V) {
5837 const size_t n = G_U.get_n();
5838 if (G_V.get_n() != n) throw std::invalid_argument("Codes must have same length for LDC construction!");
5839
5840 const size_t kU = G_U.get_m();
5841 const size_t kV = G_V.get_m();
5842
5843 Matrix<T> G(kU + kV, 2 * n);
5844 G.set_submatrix(0, 0, G_U);
5845 G.set_submatrix(0, n, G_U);
5846 G.set_submatrix(kU, n, G_V);
5847 return G;
5848}
5849
5850} // namespace details
5851
5852template <class BU, class BV>
5853 requires(std::derived_from<BU, LinearCode<typename BU::FIELD>> &&
5854 std::derived_from<BV, LinearCode<typename BV::FIELD>> &&
5855 std::same_as<typename BU::FIELD, typename BV::FIELD>)
5856class LDCCode : public LinearCode<typename BU::FIELD> {
5857 public:
5858 using T = typename BU::FIELD;
5859
5860 LDCCode(const BU& U, const BV& V) try : LinearCode
5861 <T>(2 * U.get_n(), U.get_k() + V.get_k(), details::LDC_G(U.get_G(), V.get_G())), U(U), V(V) {}
5862 catch (const std::invalid_argument& e) {
5863 throw std::invalid_argument(std::string("Trying to perform invalid length-doubling construction: ") + e.what());
5864 }
5865
5866 const BU& get_U() const noexcept { return U; }
5867 const BV& get_V() const noexcept { return V; }
5868
5869 size_t get_dmin() const override {
5870 if constexpr (std::is_same_v<T, Fp<2>>) {
5871 this->dmin.call_once([this] {
5872 if (this->dmin.has_value()) return;
5873 this->dmin.emplace(std::min({2 * U.get_dmin(), V.get_dmin()}));
5874 });
5875 }
5876 return this->LinearCode<T>::get_dmin();
5877 }
5878
5879 void get_info(std::ostream& os) const override {
5880 if (this->template info_prelude<LinearCode<T>>(os)) {
5881 details::verbosity_guard guard(os);
5882 os << BOLD("LDC code") " with properties: { ";
5883 os << "U = " << showbasic;
5884 U.LinearCode<T>::get_info(os);
5885 os << ", V = ";
5886 V.LinearCode<T>::get_info(os);
5887 os << " }";
5888 }
5889 }
5890
5891 Vector<T> dec_recursive(const Vector<T>& r) const override {
5892#ifdef CECCO_ERASURE_SUPPORT
5893 if (LinearCode<T>::erasures_present(r)) return dec_recursive_EE(r);
5894#endif
5895 this->validate_length(r);
5896
5897 auto rl = r.get_subvector(0, U.get_n());
5898 auto rr = r.get_subvector(U.get_n(), U.get_n());
5899
5900 const Vector<T> cl_hat_1 = dec_wrapper(U, rl);
5901 const Vector<T> cr_hat = dec_wrapper(V, rr - rl);
5902 const Vector<T> cl_hat_2 = dec_wrapper(U, rr - cr_hat);
5903
5904 const auto c_est_1 = concatenate(cl_hat_1, cl_hat_1 + cr_hat);
5905 const auto c_est_2 = concatenate(cl_hat_2, cl_hat_2 + cr_hat);
5906 const size_t d_1 = dH(r, c_est_1);
5907 const size_t d_2 = dH(r, c_est_2);
5908 if (d_1 < d_2 || (d_1 == d_2 && !details::reservoir_accept(2)))
5909 return c_est_1;
5910 else
5911 return c_est_2;
5912 }
5913
5914#ifdef CECCO_ERASURE_SUPPORT
5916 this->validate_length(r);
5917
5918 const size_t n = this->n;
5919 size_t tau = 0;
5920 for (size_t i = 0; i < n; ++i) {
5921 if (r[i].is_erased()) ++tau;
5922 }
5923
5924 if (tau == 0) return dec_recursive(r);
5925 if (tau == n) return Vector<T>(n, T(0));
5926
5927 auto rl = r.get_subvector(0, U.get_n());
5928 auto rr = r.get_subvector(U.get_n(), U.get_n());
5929
5930 const Vector<T> cl_hat_1 = dec_wrapper_EE(U, rl);
5931 const Vector<T> cr_hat = dec_wrapper_EE(V, rr - rl);
5932 const Vector<T> cl_hat_2 = dec_wrapper_EE(U, rr - cr_hat);
5933
5934 const auto c_est_1 = concatenate(cl_hat_1, cl_hat_1 + cr_hat);
5935 const auto c_est_2 = concatenate(cl_hat_2, cl_hat_2 + cr_hat);
5936
5937 size_t t_1 = 0;
5938 size_t t_2 = 0;
5939 for (size_t i = 0; i < n; ++i) {
5940 if (!r[i].is_erased()) {
5941 if (r[i] != c_est_1[i]) ++t_1;
5942 if (r[i] != c_est_2[i]) ++t_2;
5943 }
5944 }
5945
5946 if (t_1 < t_2 || (t_1 == t_2 && !details::reservoir_accept(2)))
5947 return c_est_1;
5948 else
5949 return c_est_2;
5950 }
5951#endif
5952
5953 private:
5954 BU U;
5955 BV V;
5956
5957 static Vector<T> dec_wrapper(const LinearCode<T>& C, const Vector<T>& r) { return C.dec_ML(r); }
5958
5959 // Documentation note: the template overloads match every LDCCode instantiation; the injected class
5960 // name would match only LDCCode<BU, BV> itself and never a nested component of different type.
5961 template <class CU, class CV>
5962 static Vector<T> dec_wrapper(const LDCCode<CU, CV>& C, const Vector<T>& r) {
5963 return C.dec_recursive(r);
5964 }
5965
5966#ifdef CECCO_ERASURE_SUPPORT
5967 static Vector<T> dec_wrapper_EE(const LinearCode<T>& C, const Vector<T>& r) { return C.dec_ML_EE(r); }
5968
5969 template <class CU, class CV>
5970 static Vector<T> dec_wrapper_EE(const LDCCode<CU, CV>& C, const Vector<T>& r) {
5971 return C.dec_recursive_EE(r);
5972 }
5973#endif
5974};
5975
5976class RMCode : public LinearCode<Fp<2>> {
5977 public:
5978 using T = Fp<2>;
5979
5980 RMCode(size_t r, size_t m) try : LinearCode
5981 <T>(sqm(2, m), RM_k(r, m), RM_G(r, m)), r(r), m(m) { this->set_dmin(sqm(2, m - r)); }
5982 catch (const std::invalid_argument& e) {
5983 throw std::invalid_argument(std::string("Trying to construct invalid RM code: ") + e.what());
5984 }
5985
5986 size_t get_r() const noexcept { return r; }
5987 size_t get_m() const noexcept { return m; }
5988
5989 void get_info(std::ostream& os) const override {
5990 if (info_prelude<LinearCode<T>>(os))
5991 os << BOLD("RM code") " with properties: { r = " << r << ", m = " << m << " }";
5992 }
5993
5994 Vector<T> dec_recursive(const Vector<T>& r) const override {
5995 this->validate_length(r);
5996 return dec_wrapper(this->r, m, r);
5997 }
5998
5999#ifdef CECCO_ERASURE_SUPPORT
6001 this->validate_length(r);
6002
6003 const size_t n = this->n;
6004 size_t tau = 0;
6005 for (size_t i = 0; i < n; ++i) {
6006 if (r[i].is_erased()) ++tau;
6007 }
6008
6009 if (tau == 0) return dec_recursive(r);
6010 if (tau == n) return Vector<T>(n, T(0));
6011
6012 return dec_wrapper_EE(this->r, m, r);
6013 }
6014#endif
6015
6016 private:
6017 size_t r;
6018 size_t m;
6019
6020 static size_t RM_k(size_t r, size_t m) {
6021 size_t k = 0;
6022 for (size_t i = 0; i <= r; ++i) k += bin<size_t>(m, i);
6023 return k;
6024 }
6025
6026 static Matrix<T> RM_G(size_t r, size_t m) {
6027 if (r > m) throw std::invalid_argument("RM codes require 0 <= r <= m");
6028 const size_t n = sqm(2, m);
6029
6030 if (r == 0) return Matrix<T>(1, n, T(1));
6031 if (r == m) return IdentityMatrix<T>(n);
6032
6033 const auto GU = RM_G(r, m - 1);
6034 const auto GV = RM_G(r - 1, m - 1);
6035 return details::LDC_G(GU, GV);
6036 }
6037
6038 static Vector<T> dec_wrapper(size_t r, size_t m, const Vector<T>& v) {
6039 const size_t n = sqm(2, m);
6040
6041 // universe code
6042 if (r == m) return v;
6043
6044 // repetition code
6045 if (r == 0) {
6046 constexpr size_t q = T::get_size();
6047 std::array<size_t, q> counters{};
6048 for (size_t i = 0; i < n; ++i) ++counters[v[i].get_label()];
6049 size_t label = 0;
6050 size_t best = counters[0];
6051 uint16_t ties = 1;
6052 for (size_t a = 1; a < q; ++a) {
6053 if (counters[a] > best) {
6054 best = counters[a];
6055 label = a;
6056 ties = 1;
6057 } else if (counters[a] == best) {
6058 if (details::reservoir_accept(++ties)) label = a;
6059 }
6060 }
6061 return Vector<T>(n, T(label));
6062 }
6063
6064 const size_t np = n / 2;
6065 auto vl = v.get_subvector(0, np);
6066 auto vr = v.get_subvector(np, np);
6067
6068 const Vector<T> cl_hat_1 = dec_wrapper(r, m - 1, vl);
6069 const Vector<T> cr_hat = dec_wrapper(r - 1, m - 1, vr - vl);
6070 const Vector<T> cl_hat_2 = dec_wrapper(r, m - 1, vr - cr_hat);
6071
6072 const size_t d_1 = dH(vl, cl_hat_1) + dH(vr, cl_hat_1 + cr_hat);
6073 const size_t d_2 = dH(vl, cl_hat_2) + dH(vr, cl_hat_2 + cr_hat);
6074 if (d_1 < d_2 || (d_1 == d_2 && !details::reservoir_accept(2)))
6075 return concatenate(cl_hat_1, cl_hat_1 + cr_hat);
6076 else
6077 return concatenate(cl_hat_2, cl_hat_2 + cr_hat);
6078 }
6079
6080#ifdef CECCO_ERASURE_SUPPORT
6081 static Vector<T> dec_wrapper_EE(size_t r, size_t m, const Vector<T>& v) {
6082 const size_t n = sqm(2, m);
6083
6084 if (r == m) {
6085 auto c = v;
6086 const T zero(0);
6087 for (size_t i = 0; i < n; ++i)
6088 if (c[i].is_erased()) c.set_component(i, zero);
6089 return c;
6090 }
6091
6092 if (r == 0) {
6093 constexpr size_t q = T::get_size();
6094 std::array<size_t, q> counters{};
6095 for (size_t i = 0; i < n; ++i)
6096 if (!v[i].is_erased()) ++counters[v[i].get_label()];
6097 size_t label = 0;
6098 size_t best = counters[0];
6099 uint16_t ties = 1;
6100 for (size_t a = 1; a < q; ++a) {
6101 if (counters[a] > best) {
6102 best = counters[a];
6103 label = a;
6104 ties = 1;
6105 } else if (counters[a] == best) {
6106 if (details::reservoir_accept(++ties)) label = a;
6107 }
6108 }
6109 return Vector<T>(n, T(label));
6110 }
6111
6112 const size_t np = n / 2;
6113 auto vl = v.get_subvector(0, np);
6114 auto vr = v.get_subvector(np, np);
6115
6116 const Vector<T> cl_hat_1 = dec_wrapper_EE(r, m - 1, vl);
6117 const Vector<T> cr_hat = dec_wrapper_EE(r - 1, m - 1, vr - vl);
6118 const Vector<T> cl_hat_2 = dec_wrapper_EE(r, m - 1, vr - cr_hat);
6119
6120 const auto c_est_1 = concatenate(cl_hat_1, cl_hat_1 + cr_hat);
6121 const auto c_est_2 = concatenate(cl_hat_2, cl_hat_2 + cr_hat);
6122
6123 size_t t_1 = 0;
6124 size_t t_2 = 0;
6125 for (size_t i = 0; i < n; ++i) {
6126 if (!v[i].is_erased()) {
6127 if (v[i] != c_est_1[i]) ++t_1;
6128 if (v[i] != c_est_2[i]) ++t_2;
6129 }
6130 }
6131
6132 if (t_1 < t_2 || (t_1 == t_2 && !details::reservoir_accept(2)))
6133 return c_est_1;
6134 else
6135 return c_est_2;
6136 }
6137#endif
6138};
6139
6140template <class B>
6142class SubfieldSubcode : public LinearCode<typename B::FIELD::BASE_FIELD> {
6143 using SUPER = typename B::FIELD;
6144 using SUB = typename SUPER::BASE_FIELD;
6145
6146 public:
6148 } catch (const std::invalid_argument& e) {
6149 throw std::invalid_argument(
6150 std::string("Trying to construct a subfield subcode from a super code that is not over a superfield: ") +
6151 e.what());
6152 }
6153
6154 const B& get_SuperCode() const noexcept { return SuperCode; }
6155
6157 if (this->template info_prelude<LinearCode<SUB>>(os)) {
6159 os << BOLD("Subfield-subcode") " with properties: {" << std::endl;
6160 os << "Gamma = " << std::endl << Gamma << ", " << std::endl;
6161 os << "Supercode = " << showbasic;
6163 os << " " << showspecial << SuperCode << std::endl;
6164 os << "}";
6165 }
6166 }
6167
6169#ifdef CECCO_ERASURE_SUPPORT
6171#endif
6172 return project(SuperCode.dec_BD(Vector<SUPER>(r)));
6173 }
6174
6176#ifdef CECCO_ERASURE_SUPPORT
6178#endif
6179 return project(SuperCode.dec_ML(Vector<SUPER>(r)));
6180 }
6181
6182#ifdef CECCO_ERASURE_SUPPORT
6185 }
6186
6189 }
6190#endif
6191
6192 protected:
6193 static Vector<SUB> project(const Vector<SUPER>& c) {
6194 try {
6195 return Vector<SUB>(c);
6196 } catch (const std::invalid_argument& e) {
6197 throw decoding_failure(std::string("Subfield subcode supercode decoder failed: ") + e.what());
6198 }
6199 }
6200
6201 private:
6202 B SuperCode;
6204
6209
6210 static std::pair<size_t, Matrix<SUPER>> SSC_parameters(const B& SuperCode) {
6211 const size_t n = SuperCode.get_n();
6212 const size_t kp = SuperCode.get_k();
6213 const size_t m = SUPER(0).template as_vector<SUB>().get_n();
6214 const auto Gp = SuperCode.get_G();
6215
6216 auto T = [m](const SUPER& Gij) -> Matrix<SUB> {
6217 const auto v = pad_back(pad_front(Gij.template as_vector<SUB>().reverse(), 2 * m - 1), 3 * m - 2);
6218 return ToeplitzMatrix<SUB>(v, m, 2 * m - 1);
6219 };
6220
6221 const auto E = Matrix<SUB>(unit_vector<SUB>(m, 0));
6222 const auto C = CompanionMatrix<SUB>(SUPER::get_modulus()).transpose();
6223 const auto EC = vertical_join(E, C);
6224 auto R = IdentityMatrix<SUB>(2 * m - 1);
6225 for (size_t j = 2 * m - 1; j-- > m;) {
6226 const auto Rj = diagonal_join(IdentityMatrix<SUB>(j - m), EC);
6227 R *= Rj;
6228 }
6229
6230 Matrix<SUB> Gt(kp * m, n * (m - 1));
6231 for (size_t i = 0; i < kp; ++i) {
6232 for (size_t j = 0; j < n; ++j) Gt.set_submatrix(i * m, j * (m - 1), (T(Gp(i, j)) * R).delete_column(0));
6233 }
6234 const auto Gammat = Gt.transpose().basis_of_kernel();
6235 const size_t k = Gammat.rank();
6236 if (k == 0) return std::make_pair(0, Matrix<SUPER>(k, kp));
6237
6238 Matrix<SUPER> Gamma(k, kp);
6239 for (size_t i = 0; i < k; ++i) {
6240 for (size_t j = 0; j < kp; ++j)
6242 }
6243
6244 return std::make_pair(k, Gamma);
6245 }
6246};
6247
6248template <class B>
6250
6251template <class B>
6253
6254template <class B>
6255 requires std::derived_from<B, GRSCode<typename B::FIELD>>
6257 using Base = SubfieldSubcode<B>;
6258 using SUPER = typename B::FIELD;
6259 using SUB = typename SUPER::BASE_FIELD;
6260
6261 public:
6263 } catch (const std::invalid_argument& e) {
6264 throw std::invalid_argument(std::string("Cannot construct alternant code: ") + e.what());
6265 }
6266
6269 try : Base(GRSCode<SUPER>(a, d, k_from_delta(a.get_n(), delta))), delta(delta) {
6270 } catch (const std::invalid_argument& e) {
6271 throw std::invalid_argument(std::string("Cannot construct alternant code: ") + e.what());
6272 }
6273
6274 const Vector<SUPER>& get_a() const noexcept { return this->get_SuperCode().get_a(); }
6275 const Vector<SUPER>& get_d() const noexcept { return this->get_SuperCode().get_d(); }
6276 virtual size_t get_delta() const noexcept { return delta; }
6277
6279 if (this->template info_prelude<Base>(os)) {
6280 os << BOLD("Alternant code") " with properties: { delta = " << get_delta();
6281 if (this->get_SuperCode().is_primitive()) os << ", primitive";
6282 if (this->get_SuperCode().is_narrow_sense()) os << ", narrow-sense";
6283 if (this->get_SuperCode().is_normalized()) os << ", normalized";
6284 os << " }";
6285 }
6286 }
6287
6288 // Documentation note: algebraic bounded-distance decoding of an alternant code runs on the GRS
6289 // supercode and projects the result back, reaching only the design distance delta, not the code's
6290 // true minimum distance; it is therefore exposed as dec_supercode_BD / dec_supercode_BD_EE (base
6291 // class), which do not promise BD to the true dmin, unlike the dec_WBA / dec_BMA paradigms.
6295
6300
6301 // Documentation note: the supercode candidates that are not purely over the subfield are discarded, so
6302 // an empty list indicates decoding failure; the supercode ordering (closest/most likely first)
6303 // is preserved.
6308
6313
6316 if constexpr (SUB::get_size() != 2) {
6317 throw std::logic_error(
6318 "Soft-input KVA LLR vector decoding only available for binary codes; use the Matrix<double> overload!");
6319 } else {
6320 return dec_KVA_list(Matrix<double>(llrs), strength, list_size);
6321 }
6322 }
6323
6326 this->validate_length(llrs);
6327
6328 // subfield LLRs expanded to the supercode's symbol-level format: superfield symbols
6329 // outside the subfield have probability zero, i.e., infinite LLR
6330 constexpr size_t q = SUB::get_size();
6331 constexpr size_t Q = SUPER::get_size();
6332 const size_t n = this->n;
6333 Matrix<double> llrsp(Q - 1, n);
6334 for (size_t beta = 1; beta < Q; ++beta)
6335 for (size_t i = 0; i < n; ++i) llrsp.set_component(beta - 1, i, std::numeric_limits<double>::infinity());
6336 for (size_t s = 1; s < q; ++s) {
6337 const size_t beta = SUPER(SUB(s)).get_label();
6338 for (size_t i = 0; i < n; ++i) llrsp.set_component(beta - 1, i, llrs(s - 1, i));
6339 }
6340
6342 }
6343
6344 protected:
6346 if (delta == 0 || delta > n) throw std::invalid_argument("delta must satisfy 1 <= delta <= n");
6347 return n - delta + 1;
6348 }
6349
6350 private:
6351 size_t delta;
6352
6353 // keeps only supercode candidates that are purely over the subfield: as_matrix stores
6354 // coefficients MSB-first, so a candidate qualifies iff all coordinate rows of its subfield
6355 // image except the last are zero, and then that last row is its projection
6356 static std::vector<Vector<SUB>> project_list(const std::vector<Vector<SUPER>>& list) {
6357 std::vector<Vector<SUB>> res;
6358 for (size_t j = 0; j < list.size(); ++j) {
6359 const auto image = list[j].template as_matrix<SUB>();
6360 if (image.get_submatrix(0, 0, image.get_m() - 1, image.get_n()).is_zero())
6362 }
6363 return res;
6364 }
6365};
6366
6367template <class B>
6368 requires std::derived_from<B, RSCode<typename B::FIELD>>
6369class BCHCode : public AlternantCode<B> {
6370 using Base = AlternantCode<B>;
6371 using SUPER = typename B::FIELD;
6372 using SUB = typename SUPER::BASE_FIELD;
6373
6374 public:
6375 BCHCode(const B& rscode) try : Base(rscode) {
6376 } catch (const std::invalid_argument& e) {
6377 throw std::invalid_argument(std::string("Cannot construct BCH code: ") + e.what());
6378 }
6379
6383 } catch (const std::invalid_argument& e) {
6384 throw std::invalid_argument(std::string("Cannot construct BCH code: ") + e.what());
6385 }
6386
6387 SUPER get_alpha() const { return this->get_SuperCode().get_alpha(); }
6388 size_t get_b() const noexcept { return this->get_SuperCode().get_b(); }
6389
6391 if (this->template info_prelude<Base>(os)) os << BOLD("BCH code");
6392 }
6393};
6394
6395template <FiniteFieldType SUPER>
6397 using Base = AlternantCode<GRSCode<SUPER>>;
6398 using SUB = typename SUPER::BASE_FIELD;
6399
6400 public:
6401 GoppaCode(const Vector<SUPER>& a, size_t delta) try : GoppaCode(a, Goppa_polynomial(a, delta)) {
6402 } catch (const std::invalid_argument& e) {
6403 throw std::invalid_argument(std::string("Cannot construct Goppa code: ") + e.what());
6404 }
6405
6406 GoppaCode(const Vector<SUPER>& a, Polynomial<SUPER> g) try
6407 : Base(a, Goppa_multipliers(a, g), g.degree() + 1),
6408 g(std::move(g)),
6409 irreducible(this->g.is_irreducible()),
6410 squarefree(irreducible || GCD(this->g, derivative(this->g, 1)).degree() == 0) {
6411 if (this->g.degree() < 1) throw std::invalid_argument("Goppa polynomial must be nonconstant");
6412 } catch (const std::invalid_argument& e) {
6413 throw std::invalid_argument(std::string("Cannot construct Goppa code: ") + e.what());
6414 }
6415
6416 const Polynomial<SUPER>& get_g() const noexcept { return g; }
6417 bool is_irreducible() const noexcept { return irreducible; }
6418 bool is_squarefree() const noexcept { return squarefree; }
6419
6420 size_t get_delta() const noexcept override {
6421 if constexpr (std::is_same_v<SUB, Fp<2>>)
6422 if (squarefree) return 2 * g.degree() + 1;
6423 return Base::get_delta();
6424 }
6425
6426 void get_info(std::ostream& os) const override {
6427 if (this->template info_prelude<Base>(os)) {
6428 os << BOLD("Goppa code") " with properties: { g = " << g;
6429 if (irreducible)
6430 os << ", irreducible";
6431 else if (squarefree)
6432 os << ", square-free";
6433 os << " }";
6434 }
6435 }
6436
6437 // Documentation note: Patterson's square root step assumes an irreducible Goppa polynomial
6438 // (Patterson 1975), so the decoder is gated on irreducibility to guarantee the decoding radius;
6439 // the runtime square-root check then never fails for an accepted code.
6442 {
6443 this->validate_length(r);
6444
6445#ifdef CECCO_ERASURE_SUPPORT
6447 throw std::invalid_argument("Trying to correct erasures with the Patterson decoder!");
6448#endif
6449
6450 if (!irreducible) throw std::invalid_argument("Patterson decoder requires an irreducible Goppa polynomial");
6451
6452 const size_t n = this->n;
6453 const auto& a = this->get_a();
6454 const size_t t = g.degree();
6455
6456 if (t == 0) throw std::invalid_argument("Patterson decoder requires a nonconstant Goppa polynomial");
6457
6459 const auto& inv = *Patterson_inv_cache;
6460
6461 const auto one = OnePolynomial<SUPER>();
6462 const auto x = Monomial<SUPER>(1, SUPER(1));
6463 const auto zero = ZeroPolynomial<SUPER>();
6464
6465 // every inv[i] is reduced modulo g, so the accumulated syndrome stays below deg(g) without reduction
6466 const SUB sub_one(1);
6467 auto S = zero;
6468 for (size_t i = 0; i < n; ++i)
6469 if (r[i] == sub_one) S += inv[i];
6470 if (S.is_zero()) return r;
6471
6472 Polynomial<SUPER> v, u;
6473 const auto d = GCD(g, S, &v, &u);
6474 if (d.is_empty() || d.is_zero() || d.degree() != 0)
6475 throw decoding_failure("Patterson decoder failed (syndrome not invertible modulo g)");
6476 const auto h = (((SUPER(1) / d[0]) * u) + x) % g;
6477
6479 if (h.is_zero()) {
6480 sigma = x;
6481 } else {
6482 auto R = h;
6483 const size_t squarings = std::bit_width(SUPER::get_size() - 1) * t - 1;
6484 for (size_t i = 0; i < squarings; ++i) R = (R * R) % g;
6485 if ((R * R) % g != h) throw decoding_failure("Patterson decoder failed (no square root modulo g)");
6486
6487 auto r0 = g, r1 = R;
6488 auto t0 = zero, t1 = one;
6489 while (!r1.is_zero() && r1.degree() > t / 2) {
6490 const auto [q, rem] = poly_long_div(r0, r1);
6491 const auto next_t = t0 - q * t1;
6492 r0 = std::move(r1);
6493 r1 = rem;
6494 t0 = std::move(t1);
6495 t1 = next_t;
6496 }
6497 if (r1.is_zero()) throw decoding_failure("Patterson decoder failed (half-GCD)");
6498 sigma = r1 * r1 + shift_up(t1 * t1, 1);
6499 }
6500
6501 // Chien search
6502 std::vector<size_t> E;
6503 E.reserve(t);
6504 for (size_t i = 0; i < n; ++i)
6505 if (sigma(a[i]).is_zero()) E.push_back(i);
6506
6507 if (E.size() != sigma.degree())
6508 throw decoding_failure("Patterson decoder failed (error locator polynomial does not split)");
6509
6510 Vector<SUB> c_est = r;
6511 auto S_check = S;
6512 for (size_t i = 0; i < E.size(); ++i) {
6513 c_est.set_component(E[i], r[E[i]] + sub_one);
6514 S_check += inv[E[i]];
6515 }
6516 if (!S_check.is_zero()) throw decoding_failure("Patterson decoder failed (correction is not a codeword)");
6517
6518 return c_est;
6519 }
6520
6521 private:
6523 bool irreducible;
6524 bool squarefree;
6525 mutable details::OnceCache<std::vector<Polynomial<SUPER>>> Patterson_inv_cache;
6526
6527 void calculate_Patterson_inv_cache() const {
6528 Patterson_inv_cache.call_once([this] {
6529 if (Patterson_inv_cache.has_value()) return;
6530 const size_t n = this->get_n();
6531 const auto& a = this->get_a();
6532 std::vector<Polynomial<SUPER>> inv(n);
6533 for (size_t i = 0; i < n; ++i) {
6534 // the constructor rejects any locator that is a root of g, so gcd(g, x + a[i]) is a nonzero constant
6535 Polynomial<SUPER> v, u;
6536 const auto d = GCD(g, Polynomial<SUPER>({a[i], SUPER(1)}), &v, &u);
6537 inv[i] = ((SUPER(1) / d[0]) * u) % g;
6538 }
6539 Patterson_inv_cache.emplace(std::move(inv));
6540 });
6541 }
6542
6543 // Documentation note: these are G multipliers (in literature typically: H multipliers)
6544 static Vector<SUPER> Goppa_multipliers(const Vector<SUPER>& a, const Polynomial<SUPER>& h) {
6545 const size_t n = a.get_n();
6546 Vector<SUPER> res(n);
6547
6548 for (size_t i = 0; i < n; ++i) {
6549 const SUPER ha = h(a[i]);
6550 if (ha.is_zero()) throw std::invalid_argument("Goppa polynomial vanishes at a code locator");
6551
6552 SUPER denominator(1);
6553 for (size_t j = 0; j < n; ++j) {
6554 if (j == i) continue;
6555 const SUPER diff = a[i] - a[j]; // are guaranteed to be pairwise distinct
6556 denominator *= diff;
6557 }
6558 res.set_component(i, ha / denominator);
6559 }
6560
6561 return res;
6562 }
6563
6564 // Documentation note: an irreducible g of degree at least 2 has no roots in SUPER, so the
6565 // locator check passes on the first draw; only degree 1 (delta = 2) can require retries.
6566 static Polynomial<SUPER> Goppa_polynomial(const Vector<SUPER>& a, size_t delta) {
6567 if (delta < 2) throw std::invalid_argument("delta must be at least 2 for a nonconstant Goppa polynomial");
6568 if (delta == 2 && a.get_n() == SUPER::get_size())
6569 throw std::invalid_argument("no degree-1 Goppa polynomial avoids a full locator set");
6570 for (;;) {
6571 auto g = find_irreducible<SUPER>(delta - 1);
6572 bool vanishes = false;
6573 for (size_t i = 0; i < a.get_n() && !vanishes; ++i)
6574 if (g(a[i]).is_zero()) vanishes = true;
6575 if (!vanishes) return g;
6576 }
6577 }
6578};
6579
6580template <FieldType T, class B>
6582class ExtendedCode : public LinearCode<T> {
6583 public:
6584 ExtendedCode(const B& BaseCode, size_t i, const Vector<T>& v) try : LinearCode
6585 <T>(BaseCode.get_n() + 1, BaseCode.get_k() == 0 ? v.wH() : BaseCode.get_k(),
6586 Extended_G(BaseCode.get_G(), i, v)),
6587 BaseCode(BaseCode), i(i), v(v), parity(v == Extended_v(this->BaseCode.get_G())) {}
6588 catch (const std::invalid_argument& e) {
6589 throw std::invalid_argument(std::string("Trying to extend a code with invalid extension parameters: ") +
6590 e.what());
6591 }
6592
6593 ExtendedCode(B&& BaseCode, size_t i, const Vector<T>& v) try : LinearCode
6594 <T>(BaseCode.get_n() + 1, BaseCode.get_k() == 0 ? v.wH() : BaseCode.get_k(),
6595 Extended_G(BaseCode.get_G(), i, v)),
6596 BaseCode(std::move(BaseCode)), i(i), v(v), parity(v == Extended_v(this->BaseCode.get_G())) {}
6597 catch (const std::invalid_argument& e) {
6598 throw std::invalid_argument(std::string("Trying to extend a code with invalid extension parameters: ") +
6599 e.what());
6600 }
6601
6602 explicit ExtendedCode(const B& BaseCode)
6606 i(0),
6607 v(this->G.get_col(i)),
6608 parity(true) {}
6609
6610 size_t get_i() const noexcept { return i; }
6611 const B& get_BaseCode() const noexcept { return BaseCode; }
6612
6613 const Polynomial<InfInt>& get_weight_enumerator() const override {
6614 if constexpr (!FiniteFieldType<T>) {
6615 throw std::logic_error("Cannot calculate weight enumerator of code over infinite field!");
6616 } else {
6617 // In non-binary case, zero row sum is not the same as even Hamming distance so this really only
6618 // works for the binary Fp<2> case! Otherwise we fall back to linear code weight enumerator
6619 // calculation.
6620 if constexpr (std::is_same_v<T, Fp<2>>) {
6621 if (parity) {
6622 this->weight_enumerator.call_once([this] {
6623 if (this->weight_enumerator.has_value()) return;
6624 auto enumerator = BaseCode.get_weight_enumerator();
6625 for (size_t w = 0; w <= enumerator.degree(); ++w) {
6626 if (w % 2 && enumerator[w] != 0) {
6627 enumerator.add_to_coefficient(w + 1, enumerator[w]);
6628 enumerator.set_coefficient(w, 0);
6629 }
6630 }
6631 this->weight_enumerator.emplace(std::move(enumerator));
6632 });
6633 }
6634 }
6635 return LinearCode<T>::get_weight_enumerator();
6636 }
6637 }
6638
6639 void get_info(std::ostream& os) const override {
6640 if (this->template info_prelude<LinearCode<T>>(os)) {
6641 details::verbosity_guard guard(os);
6642 os << BOLD("Extended code") " with properties: { i = " << i;
6643 os << ", v = " << v;
6644 if (parity) os << ", zero sum parity";
6645 os << ", BaseCode = " << showbasic;
6646 BaseCode.LinearCode<T>::get_info(os);
6647 os << " " << showspecial << BaseCode;
6648 os << " }";
6649 }
6650 }
6651
6652 Vector<T> dec_BD(const Vector<T>& r) const override {
6653#ifdef CECCO_ERASURE_SUPPORT
6654 if (LinearCode<T>::erasures_present(r)) return dec_BD_EE(r);
6655#endif
6656 return LinearCode<T>::dec_BD(r);
6657 }
6658
6659 Vector<T> dec_ML(const Vector<T>& r) const override {
6660#ifdef CECCO_ERASURE_SUPPORT
6661 if (LinearCode<T>::erasures_present(r)) return dec_ML_EE(r);
6662#endif
6663 return LinearCode<T>::dec_ML(r);
6664 }
6665
6666#ifdef CECCO_ERASURE_SUPPORT
6667 Vector<T> dec_BD_EE(const Vector<T>& r) const override {
6668 if (!parity || !r[i].is_erased()) return LinearCode<T>::dec_BD_EE(r);
6669 this->validate_length(r);
6670
6671 const size_t n = this->n;
6672 const auto r_base = concatenate(r.get_subvector(0, i), r.get_subvector(i + 1, n - i - 1));
6673
6674 const auto cp_est = BaseCode.dec_ML_EE(r_base); // sic!
6675
6676 T p(0);
6677 for (size_t j = 0; j < cp_est.get_n(); ++j) p += cp_est[j];
6678
6679 Vector<T> c_est(n);
6683
6684 size_t tau = 0;
6685 size_t t = 0;
6686 for (size_t j = 0; j < n; ++j) {
6687 if (r[j].is_erased())
6688 ++tau;
6689 else if (r[j] != c_est[j])
6690 ++t;
6691 }
6692
6693 if (2 * t + tau > this->get_dmin() - 1)
6694 throw decoding_failure("Extended code BD error/erasure decoder failed!");
6695
6696 return c_est;
6697 }
6698
6699 Vector<T> dec_ML_EE(const Vector<T>& r) const override {
6700 if (!parity || !r[i].is_erased()) return LinearCode<T>::dec_ML_EE(r);
6701 this->validate_length(r);
6702
6703 const size_t n = this->n;
6704 const auto r_base = concatenate(r.get_subvector(0, i), r.get_subvector(i + 1, n - i - 1));
6705
6706 const auto cp_est = BaseCode.dec_ML_EE(r_base);
6707
6708 T p(0);
6709 for (size_t j = 0; j < cp_est.get_n(); ++j) p += cp_est[j];
6710
6711 Vector<T> c_est(n);
6715 return c_est;
6716 }
6717#endif
6718
6719 private:
6720 B BaseCode;
6721 size_t i;
6722 Vector<T> v;
6723 bool parity;
6724
6725 static Matrix<T> Extended_G(const Matrix<T>& Gp, size_t i, const Vector<T>& v) {
6726 const size_t n = Gp.get_n();
6727 const size_t k = Gp.get_m();
6728
6729 if (i > n) throw std::invalid_argument("Extension index invalid");
6730 if (v.get_n() != k) throw std::invalid_argument(std::string("Length of v must be ") + std::to_string(k));
6731
6732 Matrix<T> G(k, n + 1);
6733 for (size_t j = 0; j < i; ++j) G.set_submatrix(0, j, Gp.get_submatrix(0, j, k, 1));
6734 G.set_submatrix(0, i, transpose(Matrix<T>(v)));
6735 for (size_t j = i; j < n; ++j) G.set_submatrix(0, j + 1, Gp.get_submatrix(0, j, k, 1));
6736 return G;
6737 }
6738
6739 static Vector<T> Extended_v(const Matrix<T>& Gp) {
6740 const size_t n = Gp.get_n();
6741 const size_t k = Gp.get_m();
6742
6743 Vector<T> v(k);
6744 for (size_t j = 0; j < n; ++j) v += Gp.get_col(j);
6745 v *= -T(1);
6746 return v;
6747 }
6748};
6749
6750template <FieldType T, class B>
6752class AugmentedCode : public LinearCode<T> {
6753 public:
6754 AugmentedCode(const B& BaseCode, size_t j, const Vector<T>& w) try : LinearCode
6755 <T>(BaseCode.get_n(), BaseCode.get_k() + 1, Augmented_G(BaseCode.get_G(), j, w)), BaseCode(BaseCode), j(j),
6756 w(w) {}
6757 catch (const std::invalid_argument& e) {
6758 throw std::invalid_argument(std::string("Trying to augment a code by an invalid w: ") + e.what());
6759 }
6760
6761 AugmentedCode(B&& BaseCode, size_t j, const Vector<T>& w) try : LinearCode
6762 <T>(BaseCode.get_n(), BaseCode.get_k() + 1, Augmented_G(BaseCode.get_G(), j, w)), BaseCode(std::move(BaseCode)),
6763 j(j), w(w) {}
6764 catch (const std::invalid_argument& e) {
6765 throw std::invalid_argument(std::string("Trying to augment a code by an invalid w: ") + e.what());
6766 }
6767
6768 const B& get_BaseCode() const noexcept { return BaseCode; }
6769 size_t get_j() const noexcept { return j; }
6770 const Vector<T>& get_w() const noexcept { return w; }
6771
6772 void get_info(std::ostream& os) const override {
6773 if (this->template info_prelude<LinearCode<T>>(os)) {
6774 details::verbosity_guard guard(os);
6775 os << BOLD("Augmented code") " with properties: { w = " << w;
6776 os << ", BaseCode = " << showbasic;
6777 BaseCode.LinearCode<T>::get_info(os);
6778 os << " " << showspecial << BaseCode;
6779 os << " }";
6780 }
6781 }
6782
6783 Vector<T> dec_BD(const Vector<T>& r) const override {
6784#ifdef CECCO_ERASURE_SUPPORT
6785 if (LinearCode<T>::erasures_present(r)) return dec_BD_EE(r);
6786#endif
6787 if constexpr (!FiniteFieldType<T>) {
6788 return LinearCode<T>::dec_BD(r);
6789 } else {
6790 this->validate_length(r);
6791
6792 const size_t t = this->get_tmax();
6793
6794 for (size_t i = 0; i < T::get_size(); ++i) {
6795 const T alpha = T(i);
6796
6797 try {
6798 Vector<T> cp_est = BaseCode.dec_BD(r - alpha * w);
6799 Vector<T> c_est = cp_est + alpha * w;
6800 if (dH(r, c_est) <= t) return c_est;
6801 } catch (const decoding_failure&) {
6802 continue;
6803 }
6804 }
6805
6806 throw decoding_failure("Augmented code BD decoder failed!");
6807 }
6808 }
6809
6810 Vector<T> dec_ML(const Vector<T>& r) const override {
6811#ifdef CECCO_ERASURE_SUPPORT
6812 if (LinearCode<T>::erasures_present(r)) return dec_ML_EE(r);
6813#endif
6814 if constexpr (!FiniteFieldType<T>) {
6815 return LinearCode<T>::dec_ML(r);
6816 } else {
6817 this->validate_length(r);
6818
6819 const size_t tmax = this->get_tmax();
6820
6821 Vector<T> best = BaseCode.dec_ML(r); // r - 0*w
6822 size_t best_t = dH(r, best);
6823
6824 if (best_t <= tmax) return best;
6825
6826 uint16_t ties = 1;
6827 for (size_t i = 1; i < T::get_size(); ++i) {
6828 const T alpha = T(i);
6829
6830 Vector<T> cp_est = BaseCode.dec_ML(r - alpha * w);
6831 Vector<T> c_est = cp_est + alpha * w;
6832
6833 const size_t t = dH(r, c_est);
6834
6835 if (t <= tmax) return c_est;
6836
6837 if (t < best_t) {
6838 best_t = t;
6839 best = std::move(c_est);
6840 ties = 1;
6841 } else if (t == best_t) {
6842 if (details::reservoir_accept(++ties)) best = std::move(c_est);
6843 }
6844 }
6845
6846 return best;
6847 }
6848 }
6849
6850#ifdef CECCO_ERASURE_SUPPORT
6851 Vector<T> dec_BD_EE(const Vector<T>& r) const override {
6852 if constexpr (!FiniteFieldType<T>) {
6853 return LinearCode<T>::dec_BD_EE(r);
6854 } else {
6855 this->validate_length(r);
6856
6857 const size_t n = this->n;
6858 const size_t dmin = this->get_dmin();
6859
6860 size_t tau = 0;
6861 for (size_t j = 0; j < n; ++j)
6862 if (r[j].is_erased()) ++tau;
6863
6864 if (tau > dmin - 1) throw decoding_failure("Augmented code BD error/erasure decoder failed!");
6865
6866 for (size_t i = 0; i < T::get_size(); ++i) {
6867 const T alpha = T(i);
6868
6869 try {
6870 const auto cp_est = BaseCode.dec_BD_EE(r - alpha * w);
6871 const auto c_est = cp_est + alpha * w;
6872
6873 size_t t = 0;
6874 for (size_t j = 0; j < n; ++j) {
6875 if (!r[j].is_erased() && r[j] != c_est[j]) ++t;
6876 }
6877
6878 if (2 * t + tau <= dmin - 1) return c_est;
6879
6880 } catch (const decoding_failure&) {
6881 continue;
6882 }
6883 }
6884
6885 throw decoding_failure("Augmented code BD error/erasure decoder failed!");
6886 }
6887 }
6888
6889 Vector<T> dec_ML_EE(const Vector<T>& r) const override {
6890 if constexpr (!FiniteFieldType<T>) {
6891 return LinearCode<T>::dec_ML_EE(r);
6892 } else {
6893 this->validate_length(r);
6894
6895 const size_t n = this->n;
6896 Vector<T> best = BaseCode.dec_ML_EE(r); // r - 0*w
6897 size_t best_t = 0;
6898 for (size_t j = 0; j < n; ++j) {
6899 if (!r[j].is_erased() && r[j] != best[j]) ++best_t;
6900 }
6901
6902 uint16_t ties = 1;
6903 for (size_t i = 1; i < T::get_size(); ++i) {
6904 const T alpha = T(i);
6905
6906 const auto cp_est = BaseCode.dec_ML_EE(r - alpha * w);
6907 const auto c_est = cp_est + alpha * w;
6908
6909 size_t t = 0;
6910 for (size_t j = 0; j < n; ++j) {
6911 if (!r[j].is_erased() && r[j] != c_est[j]) ++t;
6912 }
6913
6914 if (t < best_t) {
6915 best_t = t;
6916 best = std::move(c_est);
6917 ties = 1;
6918 } else if (t == best_t) {
6920 }
6921 }
6922
6923 return best;
6924 }
6925 }
6926#endif
6927
6928 private:
6929 B BaseCode;
6930 size_t j;
6931 Vector<T> w;
6932
6933 static Matrix<T> Augmented_G(const Matrix<T>& Gp, size_t j, const Vector<T>& w) {
6934 const size_t n = Gp.get_n();
6935 const size_t k = Gp.get_m();
6936
6937 if (w.get_n() != n) throw std::invalid_argument(std::string("Length of w must be ") + std::to_string(n));
6938 if (j > k) throw std::invalid_argument("Augmentation index invalid");
6939
6940 if (Gp.get_m() == 1 && Gp.rank() == 0) return Matrix<T>(w);
6941
6942 if (j == 0) {
6943 const auto G = vertical_join(Matrix(w), Gp);
6944 return G;
6945 } else if (j == k) {
6946 const auto G = vertical_join(Gp, Matrix(w));
6947 return G;
6948 } else {
6949 const auto G =
6950 vertical_join(vertical_join(Gp.get_submatrix(0, 0, j, n), Matrix(w)), Gp.get_submatrix(j, 0, k - j, n));
6951 return G;
6952 }
6953 }
6954};
6955
6956template <FieldType T>
6957auto dual(const LinearCode<T>& C) {
6958 return C.get_dual();
6959}
6960
6961namespace details {
6962
6963inline bool validate_modification_pattern(const std::vector<size_t>& v, std::size_t n) {
6964 std::vector<bool> seen(n, false);
6965 for (const size_t i : v) {
6966 if (i >= n || seen[i]) return false;
6967 seen[i] = true;
6968 }
6969 return true;
6970}
6971
6972} // namespace details
6973
6974template <class B>
6976
6977template <class C>
6978using field_t = typename base_t<C>::FIELD;
6979
6980template <class B>
6981auto extend(B&& base, size_t i, const Vector<field_t<B>>& v) {
6982 using D = base_t<B>;
6983 using T = field_t<B>;
6984 return ExtendedCode<T, D>(std::forward<B>(base), i, v);
6985}
6986
6987template <class B>
6988auto extend(B&& base) {
6989 using D = base_t<B>;
6990 using T = field_t<B>;
6991 return ExtendedCode<T, D>(std::forward<B>(base));
6992}
6993
6994// Documentation note: unextend maintains special structure, puncture does not (recognition is the
6995// job of the from-LinearCode converting constructors)
6996template <FieldType T, class B>
6998 return C.get_BaseCode();
6999}
7000
7001template <class B>
7002auto augment(B&& base, size_t j, const Vector<field_t<B>>& w) {
7003 using D = base_t<B>;
7004 using T = field_t<B>;
7005 return AugmentedCode<T, D>(std::forward<B>(base), j, w);
7006}
7007
7008// Documentation note: unaugment maintains special structure, expurgate does not (recognition is the
7009// job of the from-LinearCode converting constructors)
7010template <FieldType T, class B>
7012 return C.get_BaseCode();
7013}
7014
7015template <class B>
7016auto lengthen(B&& base, size_t j, const Vector<field_t<B>>& w, size_t i, const Vector<field_t<B>>& v) {
7017 using D = base_t<B>;
7018 using T = field_t<B>;
7019 return ExtendedCode<T, AugmentedCode<T, D>>(AugmentedCode<T, D>(std::forward<B>(base), j, w), i, v);
7020}
7021
7022template <class B>
7023auto lengthen(B&& base, size_t j, const Vector<field_t<B>>& w) {
7024 using D = base_t<B>;
7025 using T = field_t<B>;
7026 return ExtendedCode<T, AugmentedCode<T, D>>(AugmentedCode<T, D>(std::forward<B>(base), j, w));
7027}
7028
7029// Documentation note: unlengthen maintains special structure, shorten does not
7030template <FieldType T, class D>
7032 return C.get_BaseCode().get_BaseCode();
7033}
7034
7035// Documentation note: puncture and expurgate act on the stored generator matrix and always return
7036// a plain LinearCode; an empty pattern returns the unmodified code. Turning the result back into a
7037// derived class is the caller's job via the from-LinearCode converting constructors, which
7038// construct or throw. Puncturing keeps the generator rows verbatim as long as their rank survives,
7039// so canonical structure stays recognizable; a rank drop forces the reduced echelon rows.
7040template <FieldType T>
7041LinearCode<T> puncture(const LinearCode<T>& C, const std::vector<size_t>& v) {
7042 if (!details::validate_modification_pattern(v, C.get_n()))
7043 throw std::invalid_argument("Invalid pattern for puncturing linear code!");
7044 if (v.empty()) return C;
7045 if (v.size() == C.get_n())
7046 throw std::invalid_argument(
7047 "Puncturing all columns of a linear code leaves an empty generator matrix, construct an EmptyCode "
7048 "instead!");
7049
7050 auto G = C.get_G();
7051 G.delete_columns(v);
7052 size_t rank;
7053 auto Gr = G;
7054 Gr.rref(&rank);
7055 if (rank == G.get_m()) return LinearCode<T>(C.get_n() - v.size(), rank, std::move(G));
7056 return LinearCode<T>(C.get_n() - v.size(), rank, Gr.get_submatrix(0, 0, rank, Gr.get_n()));
7057}
7058
7059template <FieldType T>
7060EmptyCode<T> puncture(const EmptyCode<T>&, const std::vector<size_t>&) {
7061 throw std::invalid_argument("Cannot puncture an empty code!");
7062}
7063
7064template <class C>
7067 return puncture(std::forward<C>(code), std::vector<size_t>{i});
7068}
7069
7070template <FieldType T>
7073 throw std::invalid_argument("Invalid pattern for expurgating linear code!");
7074 if (v.empty()) return C;
7075 if (v.size() == C.get_k())
7076 throw std::invalid_argument(
7077 "Expurgating all rows of a linear code leaves an empty generator matrix, construct an EmptyCode instead!");
7078
7079 auto G = C.get_G();
7080 G.delete_rows(v);
7081 return LinearCode<T>(C.get_n(), G.get_m(), std::move(G));
7082}
7083
7084template <FieldType T>
7086 throw std::invalid_argument("Cannot expurgate an empty code!");
7087}
7088
7089template <class C>
7092 return expurgate(std::forward<C>(code), std::vector<size_t>{j});
7093}
7094
7095template <class B>
7097 return puncture(expurgate(std::forward<B>(base), j), i);
7098}
7099
7100template <FieldType L, FieldType R>
7101bool operator==(const LinearCode<L>& lhs, const LinearCode<R>& rhs) {
7102 return lhs.is_identical(rhs);
7103}
7104
7105template <FieldType L, FieldType R>
7106bool operator!=(const LinearCode<L>& lhs, const LinearCode<R>& rhs) {
7107 return !(lhs == rhs);
7108}
7109
7110template <FieldType L, FieldType R>
7111bool identical(const LinearCode<L>& lhs, const LinearCode<R>& rhs) {
7112 return lhs.is_identical(rhs);
7113}
7114
7115template <FieldType L, FieldType R>
7116bool equivalent(const LinearCode<L>& lhs, const LinearCode<R>& rhs) {
7117 return lhs.is_equivalent(rhs);
7118}
7119
7120template <FieldType T>
7122 rhs.get_info(os);
7123 return os;
7124}
7125
7126template <FieldType T>
7127class Enc {
7128 public:
7129 Enc(const Code<T>& C) noexcept : C(C) {}
7130
7131 Vector<T> operator()(const Vector<T>& in) const { return C.enc(in); }
7132
7133 private:
7134 const Code<T>& C;
7135};
7136
7137template <FieldType T>
7138class Dec {
7139 public:
7140 explicit Dec(const Code<T>& C, method_t method = method_t::ML) noexcept : C(C), method(method) {}
7141
7142 Vector<T> operator()(const Vector<T>& in) const {
7143 switch (method) {
7144 case method_t::BD:
7145 return C.dec_BD(in);
7147 return C.dec_standard_array(in);
7149 return C.dec_BD_standard_array(in);
7151 return C.dec_boosted_BD_standard_array(in);
7152 case method_t::ML:
7153 return C.dec_ML(in);
7154 case method_t::Viterbi:
7155 return C.dec_Viterbi(in);
7157 return C.dec_recursive(in);
7158 case method_t::Meggitt:
7159 return C.dec_Meggitt(in);
7160 case method_t::WBA:
7161 return C.dec_WBA(in);
7163 return C.dec_WBA_optimized(in);
7164 case method_t::BMA:
7165 return C.dec_BMA(in);
7166#ifdef CECCO_ERASURE_SUPPORT
7167 case method_t::WBA_EE:
7168 return C.dec_WBA_EE(in);
7169 case method_t::BMA_EE:
7170 return C.dec_BMA_EE(in);
7171 case method_t::BD_EE:
7172 return C.dec_BD_EE(in);
7173 case method_t::ML_EE:
7174 return C.dec_ML_EE(in);
7175 case method_t::Viterbi_EE:
7176 return C.dec_Viterbi_EE(in);
7177 case method_t::recursive_EE:
7178 return C.dec_recursive_EE(in);
7179#endif
7180 default:
7181 throw std::logic_error("Selected decoding method does not support hard-decision input!");
7182 }
7183 }
7184
7185 Vector<T> operator()(const Vector<double>& in) const {
7186 if (method == method_t::Viterbi || method == method_t::Viterbi_soft)
7187 return C.dec_Viterbi_soft(in);
7188 else if (method == method_t::BCJR)
7189 return C.dec_BCJR(in);
7190 else if (method == method_t::ssMAP)
7191 return C.dec_ssMAP(in);
7192 else if (method == method_t::BP)
7193 return C.dec_BP(in, bp_max_iterations);
7194 else if (method == method_t::ML_soft)
7195 return C.dec_ML_soft(in);
7196 else if (method == method_t::OSD)
7197 return C.dec_OSD(in, osd_order);
7198 else if (method == method_t::LC_OSD)
7199 return C.dec_LC_OSD(in, lc_osd_delta, lc_osd_ell_max);
7200#ifdef CECCO_ERASURE_SUPPORT
7201 else if (method == method_t::GMD)
7202 return C.dec_GMD(in);
7203#endif
7204 throw std::logic_error(
7205 "Vector soft input requires a soft method (Viterbi_soft, BCJR, ssMAP, BP, ML_soft, OSD, LC_OSD, or "
7206 "GMD)!");
7207 }
7208
7209 Vector<T> operator()(const Matrix<double>& in) const {
7210 if (method == method_t::Viterbi || method == method_t::Viterbi_soft)
7211 return C.dec_Viterbi_soft(in);
7212 else if (method == method_t::BCJR)
7213 return C.dec_BCJR(in);
7214 else if (method == method_t::ssMAP)
7215 return C.dec_ssMAP(in);
7216 else if (method == method_t::BP)
7217 return C.dec_BP(in, bp_max_iterations);
7218 else if (method == method_t::ML_soft)
7219 return C.dec_ML_soft(in);
7220 else if (method == method_t::OSD)
7221 return C.dec_OSD(in, osd_order);
7222 else if (method == method_t::LC_OSD)
7223 return C.dec_LC_OSD(in, lc_osd_delta, lc_osd_ell_max);
7224#ifdef CECCO_ERASURE_SUPPORT
7225 else if (method == method_t::GMD)
7226 return C.dec_GMD(in);
7227#endif
7228 throw std::logic_error(
7229 "Matrix soft input requires a soft method (Viterbi_soft, BCJR, ssMAP, BP, ML_soft, OSD, LC_OSD, or "
7230 "GMD)!");
7231 }
7232
7233 // Documentation note: bp_max_iterations (BP), osd_order (OSD), and lc_osd_delta / lc_osd_ell_max
7234 // (LC_OSD) are method-specific knobs; all default sensibly and can be overridden after construction
7235 // via these setters.
7236 void set_bp_max_iterations(size_t l) { bp_max_iterations = l; }
7237 void set_osd_order(size_t w) { osd_order = w; }
7238 void set_lc_osd_delta(size_t d) { lc_osd_delta = d; }
7239 void set_lc_osd_ell_max(size_t l) { lc_osd_ell_max = l; }
7240
7241 private:
7242 const Code<T>& C;
7243 method_t method;
7244 size_t bp_max_iterations = BP_MAX_ITERATIONS;
7245 size_t osd_order = OSD_ORDER;
7246 size_t lc_osd_delta = LC_OSD_DELTA;
7247 size_t lc_osd_ell_max = LC_OSD_LIST_SIZE;
7248};
7249
7250// Documentation note: list-decoder counterpart of Dec, selecting among the dec_*_list members via
7251// list_method_t: Viterbi, SA, SA_optimized, GSA, and GSA_optimized decode hard input, Viterbi_soft
7252// and KVA decode soft input in the shared symbol-level LLR format; an empty result signals a
7253// decoding failure. ListProcessor reduces the list to a single estimate within a chain.
7254template <FieldType T>
7256 public:
7257 explicit Dec_list(const Code<T>& C, list_method_t method = list_method_t::Viterbi) noexcept
7258 : C(C), method(method) {}
7259
7260 std::vector<Vector<T>> operator()(const Vector<T>& in) const {
7261 switch (method) {
7262 case list_method_t::Viterbi:
7263 return C.dec_Viterbi_list(in, viterbi_list_size);
7264 case list_method_t::SA:
7265 return C.dec_SA_list(in, sa_list_size);
7266 case list_method_t::SA_optimized:
7267 return C.dec_SA_list_optimized(in, sa_list_size);
7268 case list_method_t::GSA:
7269 return C.dec_GSA_list(in, gsa_multiplicity, gsa_list_size);
7270 case list_method_t::GSA_optimized:
7271 return C.dec_GSA_list_optimized(in, gsa_multiplicity, gsa_list_size);
7272 default:
7273 throw std::logic_error("Selected decoding method does not support hard-decision input!");
7274 }
7275 }
7276
7277 std::vector<Vector<T>> operator()(const Vector<double>& in) const {
7278 if (method == list_method_t::Viterbi || method == list_method_t::Viterbi_soft)
7279 return C.dec_Viterbi_soft_list(in, viterbi_list_size);
7280 else if (method == list_method_t::KVA)
7281 return C.dec_KVA_list(in, kva_strength, kva_list_size);
7282 throw std::logic_error("Vector soft input requires a soft list method (Viterbi_soft or KVA)!");
7283 }
7284
7285 std::vector<Vector<T>> operator()(const Matrix<double>& in) const {
7286 if (method == list_method_t::Viterbi || method == list_method_t::Viterbi_soft)
7287 return C.dec_Viterbi_soft_list(in, viterbi_list_size);
7288 else if (method == list_method_t::KVA)
7289 return C.dec_KVA_list(in, kva_strength, kva_list_size);
7290 throw std::logic_error("Matrix soft input requires a soft list method (Viterbi_soft or KVA)!");
7291 }
7292
7293 // Documentation note: viterbi_list_size (Viterbi/Viterbi_soft), sa_list_size (SA), gsa_multiplicity /
7294 // gsa_list_size (GSA), and kva_strength / kva_list_size (KVA) are method-specific knobs; all
7295 // default sensibly and can be overridden after construction via these setters.
7296 void set_viterbi_list_size(size_t l) { viterbi_list_size = l; }
7297 void set_sa_list_size(size_t l) { sa_list_size = l; }
7298 void set_gsa_multiplicity(size_t s) { gsa_multiplicity = s; }
7299 void set_gsa_list_size(size_t l) { gsa_list_size = l; }
7300 void set_kva_strength(size_t s) { kva_strength = s; }
7301 void set_kva_list_size(size_t l) { kva_list_size = l; }
7302
7303 private:
7304 const Code<T>& C;
7305 list_method_t method;
7306 size_t viterbi_list_size = VITERBI_LIST_SIZE;
7307 size_t sa_list_size = SA_LIST_SIZE;
7308 size_t gsa_multiplicity = GSA_MULTIPLICITY;
7309 size_t gsa_list_size = GSA_LIST_SIZE;
7310 size_t kva_strength = KVA_STRENGTH;
7311 size_t kva_list_size = KVA_LIST_SIZE;
7312};
7313
7314// Documentation note: reduces the output of the list decoders (cf. Dec_list) to a single codeword estimate,
7315// either the first entry (the list decoders order their lists best-first) or a uniformly random
7316// one; an empty list is a decoding failure.
7318 public:
7319 explicit ListProcessor(list_processing_t mode = list_processing_t::pick_first) noexcept : mode(mode) {}
7320
7321 template <FieldType T>
7322 Vector<T> operator()(const std::vector<Vector<T>>& in) const {
7323 if (in.empty()) throw decoding_failure("List processor received an empty candidate list!");
7324 if (mode == list_processing_t::pick_random)
7325 return in[std::uniform_int_distribution<size_t>(0, in.size() - 1)(gen())];
7326 return in.front();
7327 }
7328
7329 private:
7330 list_processing_t mode;
7331};
7332
7333template <FiniteFieldType T>
7334class Encinv {
7335 public:
7336 Encinv(const Code<T>& C) noexcept : C(C) {}
7337
7338 Vector<T> operator()(const Vector<T>& in) const { return C.encinv(in); }
7339
7340 private:
7341 const Code<T>& C;
7342};
7343
7344} // namespace CECCO
7345
7346#endif
const Vector< T > & get_w() const noexcept
Definition codes.hpp:6770
AugmentedCode(B &&BaseCode, size_t j, const Vector< T > &w)
Definition codes.hpp:6761
void get_info(std::ostream &os) const override
Definition codes.hpp:6772
const B & get_BaseCode() const noexcept
Definition codes.hpp:6768
size_t get_j() const noexcept
Definition codes.hpp:6769
AugmentedCode(const B &BaseCode, size_t j, const Vector< T > &w)
Definition codes.hpp:6754
Vector< T > dec_BD(const Vector< T > &r) const override
Definition codes.hpp:6783
Vector< T > dec_ML(const Vector< T > &r) const override
Definition codes.hpp:6810
virtual Vector< T > dec_Viterbi(const Vector< T > &, const std::string &="") const
Definition codes.hpp:251
virtual Vector< T > dec_WBA_optimized(const Vector< T > &) const
Definition codes.hpp:266
virtual std::vector< Vector< T > > dec_GSA_list(const Vector< T > &, size_t=GSA_MULTIPLICITY, size_t=GSA_LIST_SIZE) const
Definition codes.hpp:278
virtual Vector< T > dec_BD(const Vector< T > &) const
Definition codes.hpp:236
virtual std::vector< Vector< T > > dec_SA_list(const Vector< T > &, size_t=SA_LIST_SIZE) const
Definition codes.hpp:272
Code(size_t n) noexcept
Definition codes.hpp:212
virtual Vector< T > dec_boosted_BD_standard_array(const Vector< T > &) const
Definition codes.hpp:245
virtual Vector< T > dec_ML_soft(const Vector< double > &) const
Definition codes.hpp:292
virtual Vector< T > dec_ML(const Vector< T > &) const
Definition codes.hpp:248
virtual std::vector< Vector< T > > dec_Viterbi_soft_list(const Vector< double > &, size_t) const
Definition codes.hpp:304
Code & operator=(Code &&) noexcept=default
Code(const Code &other) noexcept
Definition codes.hpp:214
virtual std::vector< Vector< T > > dec_Viterbi_list(const Vector< T > &, size_t) const
Definition codes.hpp:254
virtual Vector< T > dec_BCJR(const Vector< double > &, Matrix< double > *=nullptr, const std::string &="") const
Definition codes.hpp:313
virtual std::vector< Vector< T > > dec_GSA_list_optimized(const Vector< T > &, size_t=GSA_MULTIPLICITY, size_t=GSA_LIST_SIZE) const
Definition codes.hpp:282
virtual Vector< T > dec_LC_OSD(const Vector< double > &, size_t=LC_OSD_DELTA, size_t=LC_OSD_LIST_SIZE) const
Definition codes.hpp:341
virtual Vector< T > dec_recursive(const Vector< T > &) const
Definition codes.hpp:257
virtual Vector< T > dec_ssMAP(const Vector< double > &, Matrix< double > *=nullptr, const std::string &="") const
Definition codes.hpp:319
virtual Vector< T > encinv(const Vector< T > &) const
Definition codes.hpp:233
virtual Vector< T > dec_WBA(const Vector< T > &) const
Definition codes.hpp:263
virtual Vector< T > dec_BD_standard_array(const Vector< T > &) const
Definition codes.hpp:242
virtual std::vector< Vector< T > > dec_SA_list_optimized(const Vector< T > &, size_t=SA_LIST_SIZE) const
Definition codes.hpp:275
virtual Vector< T > dec_Meggitt(const Vector< T > &) const
Definition codes.hpp:260
virtual std::vector< Vector< T > > dec_KVA_list(const Vector< double > &, size_t=KVA_STRENGTH, size_t=KVA_LIST_SIZE) const
Definition codes.hpp:347
Code(Code &&) noexcept=default
size_t n
Definition codes.hpp:399
virtual void get_info(std::ostream &) const
Definition codes.hpp:231
virtual Vector< T > dec_BMA(const Vector< T > &) const
Definition codes.hpp:269
virtual Vector< T > enc(const Vector< T > &) const
Definition codes.hpp:232
virtual Vector< T > dec_Viterbi_soft(const Vector< double > &, const std::string &="") const
Definition codes.hpp:298
virtual Vector< T > dec_OSD(const Vector< double > &, size_t=OSD_ORDER) const
Definition codes.hpp:335
Code & operator=(const Code &other) noexcept
Definition codes.hpp:220
virtual ~Code()=default
virtual Vector< T > dec_BP(const Vector< double > &, size_t=BP_MAX_ITERATIONS, Matrix< double > *=nullptr, const std::string &="", size_t *=nullptr) const
Definition codes.hpp:327
bool info_prelude(std::ostream &os) const
Definition codes.hpp:390
size_t get_n() const noexcept
Definition codes.hpp:229
virtual Vector< T > dec_standard_array(const Vector< T > &) const
Definition codes.hpp:239
Vector< T > value_type
Definition codes.hpp:497
const Vector< T > * pointer
Definition codes.hpp:499
std::ptrdiff_t difference_type
Definition codes.hpp:498
friend bool operator==(const CodewordIterator &a, const CodewordIterator &b)
Definition codes.hpp:483
CodewordIterator & operator++()
Definition codes.hpp:504
const Vector< T > & reference
Definition codes.hpp:500
std::input_iterator_tag iterator_category
Definition codes.hpp:496
CodewordIterator operator++(int)
Definition codes.hpp:521
friend bool operator!=(const CodewordIterator &a, const CodewordIterator &b)
Definition codes.hpp:486
const Vector< T > & operator*() const noexcept
Definition codes.hpp:502
friend bool operator==(const CodewordIterator &a, Sentinel)
Definition codes.hpp:487
ConvolutionalCode(std::initializer_list< std::initializer_list< unsigned > > G_octal, size_t L, termination_t termination)
Definition codes.hpp:5426
ConvolutionalCode(const Matrix< Polynomial< T > > &G_cc, size_t L, termination_t termination)
Definition codes.hpp:5398
CordaroWagnerCode(const LinearCode< Fp< 2 > > &C)
Definition codes.hpp:5234
size_t get_r() const noexcept
Definition codes.hpp:5255
int8_t get_m() const noexcept
Definition codes.hpp:5256
Vector< Fp< 2 > > dec_ML(const Vector< Fp< 2 > > &r) const override
Definition codes.hpp:5277
CordaroWagnerCode(size_t r, int8_t m)
Definition codes.hpp:5225
CordaroWagnerCode get_equivalent_code_in_standard_form() const
Definition codes.hpp:5251
Vector< Fp< 2 > > dec_BD(const Vector< Fp< 2 > > &r) const override
Definition codes.hpp:5264
void get_info(std::ostream &os) const override
Definition codes.hpp:5258
void set_gsa_list_size(size_t l)
Definition codes.hpp:7299
Dec_list(const Code< T > &C, list_method_t method=list_method_t::Viterbi) noexcept
Definition codes.hpp:7257
void set_kva_list_size(size_t l)
Definition codes.hpp:7301
std::vector< Vector< T > > operator()(const Vector< double > &in) const
Definition codes.hpp:7277
std::vector< Vector< T > > operator()(const Vector< T > &in) const
Definition codes.hpp:7260
void set_sa_list_size(size_t l)
Definition codes.hpp:7297
void set_gsa_multiplicity(size_t s)
Definition codes.hpp:7298
void set_viterbi_list_size(size_t l)
Definition codes.hpp:7296
void set_kva_strength(size_t s)
Definition codes.hpp:7300
Vector< T > operator()(const Vector< T > &in) const
Definition codes.hpp:7142
Dec(const Code< T > &C, method_t method=method_t::ML) noexcept
Definition codes.hpp:7140
void set_bp_max_iterations(size_t l)
Definition codes.hpp:7236
void set_osd_order(size_t w)
Definition codes.hpp:7237
void set_lc_osd_delta(size_t d)
Definition codes.hpp:7238
void set_lc_osd_ell_max(size_t l)
Definition codes.hpp:7239
Vector< T > operator()(const Vector< double > &in) const
Definition codes.hpp:7185
EmptyCode(size_t n) noexcept
Definition codes.hpp:414
void get_info(std::ostream &os) const override
Definition codes.hpp:416
Encinv(const Code< T > &C) noexcept
Definition codes.hpp:7336
Vector< T > operator()(const Vector< T > &in) const
Definition codes.hpp:7338
Vector< T > dec_BD(const Vector< T > &r) const override
Definition codes.hpp:6652
const Polynomial< InfInt > & get_weight_enumerator() const override
Definition codes.hpp:6613
Vector< T > dec_ML(const Vector< T > &r) const override
Definition codes.hpp:6659
ExtendedCode(B &&BaseCode, size_t i, const Vector< T > &v)
Definition codes.hpp:6593
Vector< T > dec_BD_EE(const Vector< T > &r) const override
Definition codes.hpp:6667
const B & get_BaseCode() const noexcept
Definition codes.hpp:6611
ExtendedCode(const B &BaseCode)
Definition codes.hpp:6602
size_t get_i() const noexcept
Definition codes.hpp:6610
ExtendedCode(const B &BaseCode, size_t i, const Vector< T > &v)
Definition codes.hpp:6584
void get_info(std::ostream &os) const override
Definition codes.hpp:6639
Prime field 𝔽_p ≅ ℤ/pℤ
Definition fields.hpp:1663
GRSCode< T > get_equivalent_code_in_standard_form() const
Definition codes.hpp:4049
Vector< T > dec_WBA_optimized(const Vector< T > &r) const override
Definition codes.hpp:4152
GRSCode(const LinearCode< T > &C)
Definition codes.hpp:4044
const Vector< T > & get_a() const noexcept
Definition codes.hpp:4046
const Polynomial< InfInt > & get_weight_enumerator() const override
Definition codes.hpp:4071
GRSCode(const Vector< T > &a, const Vector< T > &d, size_t k, Matrix< T > G)
Definition codes.hpp:4292
bool is_singly_extended() const
Definition codes.hpp:4097
GRSCode(const Vector< T > &a, const Vector< T > &d, size_t k)
Definition codes.hpp:4022
GRSCode< T > get_dual() const
Definition codes.hpp:4064
bool is_narrow_sense() const
Definition codes.hpp:4117
std::vector< Vector< T > > dec_SA_list_optimized(const Vector< T > &r, size_t list_size=SA_LIST_SIZE) const override
Definition codes.hpp:4179
Vector< T > dec_BD(const Vector< T > &r) const override
Definition codes.hpp:4167
const Vector< T > & get_d() const noexcept
Definition codes.hpp:4047
void get_info(std::ostream &os) const override
Definition codes.hpp:4126
Vector< T > dec_WBA(const Vector< T > &r) const override
Definition codes.hpp:4145
static std::pair< Vector< T >, Vector< T > > GRS_a_and_d(const LinearCode< T > &C)
Definition codes.hpp:4295
bool is_normalized() const
Definition codes.hpp:4124
std::vector< Vector< T > > dec_GSA_list_optimized(const Vector< T > &r, size_t multiplicity=GSA_MULTIPLICITY, size_t list_size=GSA_LIST_SIZE) const override
Definition codes.hpp:4201
std::vector< Vector< T > > dec_KVA_list(const Vector< double > &llrs, size_t strength=KVA_STRENGTH, size_t list_size=KVA_LIST_SIZE) const override
Definition codes.hpp:4210
Vector< T > dec_BMA(const Vector< T > &r) const override
Definition codes.hpp:4159
bool is_primitive() const
Definition codes.hpp:4104
std::vector< Vector< T > > dec_SA_list(const Vector< T > &r, size_t list_size=SA_LIST_SIZE) const override
Definition codes.hpp:4175
std::vector< Vector< T > > dec_GSA_list(const Vector< T > &r, size_t multiplicity=GSA_MULTIPLICITY, size_t list_size=GSA_LIST_SIZE) const override
Definition codes.hpp:4192
GolayCode(const LinearCode< T > &C)
Definition codes.hpp:3962
GoppaCode(const Vector< SUPER > &a, Polynomial< SUPER > g)
Definition codes.hpp:6406
bool is_irreducible() const noexcept
Definition codes.hpp:6417
size_t get_delta() const noexcept override
Definition codes.hpp:6420
Vector< SUB > dec_Patterson(const Vector< SUB > &r) const
Definition codes.hpp:6440
bool is_squarefree() const noexcept
Definition codes.hpp:6418
const Polynomial< SUPER > & get_g() const noexcept
Definition codes.hpp:6416
void get_info(std::ostream &os) const override
Definition codes.hpp:6426
GoppaCode(const Vector< SUPER > &a, size_t delta)
Definition codes.hpp:6401
void get_info(std::ostream &os) const override
Definition codes.hpp:3375
SimplexCode< T > get_dual() const
Definition codes.hpp:3380
Vector< T > dec_BD(const Vector< T > &r) const override
Definition codes.hpp:3382
HammingCode< T > get_equivalent_code_in_standard_form() const
Definition codes.hpp:3369
Vector< T > dec_ML(const Vector< T > &r) const override
Definition codes.hpp:3404
HammingCode(const LinearCode< T > &C)
Definition codes.hpp:3331
size_t get_s() const noexcept
Definition codes.hpp:3373
HammingCode(size_t s)
Definition codes.hpp:3322
size_t get_dmin() const override
Definition codes.hpp:5869
void get_info(std::ostream &os) const override
Definition codes.hpp:5879
const BU & get_U() const noexcept
Definition codes.hpp:5866
const BV & get_V() const noexcept
Definition codes.hpp:5867
LDCCode(const BU &U, const BV &V)
Definition codes.hpp:5860
Vector< T > dec_recursive(const Vector< T > &r) const override
Definition codes.hpp:5891
Matrix< T > HT
Definition codes.hpp:2504
double get_R() const noexcept
Definition codes.hpp:618
LinearCode(size_t n, size_t k, const Matrix< T > &X)
Definition codes.hpp:552
LinearCode(const std::vector< Vector< T > > &codewords)
Definition codes.hpp:615
Matrix< T > G
Definition codes.hpp:2503
LinearCode(size_t k, Polynomial< T > gamma)
Definition codes.hpp:604
void set_weight_enumerator(const Polynomial< InfInt > &p) const
Definition codes.hpp:2516
size_t get_k() const noexcept
Definition codes.hpp:617
InfInt get_size() const
Definition codes.hpp:620
details::OnceCache< Polynomial< InfInt > > weight_enumerator
Definition codes.hpp:2507
void set_dmin(size_t d) const
Definition codes.hpp:2514
details::OnceCache< Trellis< T > > minimal_trellis
Definition codes.hpp:2510
details::OnceCache< size_t > dmin
Definition codes.hpp:2506
details::OnceCache< Polynomial< InfInt > > p_ary_image_weight_enumerator
Definition codes.hpp:2508
details::OnceCache< std::vector< Vector< T > > > codewords
Definition codes.hpp:2509
Matrix< T > MI
Definition codes.hpp:2505
Vector< T > operator()(const std::vector< Vector< T > > &in) const
Definition codes.hpp:7322
ListProcessor(list_processing_t mode=list_processing_t::pick_first) noexcept
Definition codes.hpp:7319
Dense m × n matrix over a CECCO::ComponentType.
Definition matrices.hpp:175
Univariate polynomial p(x) = a₀ + a₁x + … + aₙxⁿ over a CECCO::CoefficientType.
Fp< 2 > T
Definition codes.hpp:5978
void get_info(std::ostream &os) const override
Definition codes.hpp:5989
size_t get_r() const noexcept
Definition codes.hpp:5986
size_t get_m() const noexcept
Definition codes.hpp:5987
RMCode(size_t r, size_t m)
Definition codes.hpp:5980
Vector< T > dec_recursive(const Vector< T > &r) const override
Definition codes.hpp:5994
RSCode(const T &alpha, size_t b, size_t k)
Definition codes.hpp:5059
RSCode< T > get_identical_code_in_polynomial_form() const
Definition codes.hpp:5094
void get_info(std::ostream &os) const override
Definition codes.hpp:5109
RSCode(const LinearCode< T > &C)
Definition codes.hpp:5076
RSCode< T > get_equivalent_code_in_standard_form() const
Definition codes.hpp:5081
size_t get_b() const noexcept
Definition codes.hpp:5107
T get_alpha() const
Definition codes.hpp:5106
RepetitionCode< T > get_equivalent_code_in_standard_form() const
Definition codes.hpp:3691
RepetitionCode< T > get_identical_code_in_polynomial_form() const
Definition codes.hpp:3693
Vector< T > encinv(const Vector< T > &c) const override
Definition codes.hpp:3703
Vector< T > dec_ML(const Vector< T > &r) const override
Definition codes.hpp:3718
void get_info(std::ostream &os) const override
Definition codes.hpp:3697
SingleParityCheckCode< T > get_dual() const
Definition codes.hpp:3701
RepetitionCode(size_t n)
Definition codes.hpp:3669
Vector< T > enc(const Vector< T > &u) const override
Definition codes.hpp:3702
RepetitionCode(const LinearCode< T > &C)
Definition codes.hpp:3683
Vector< T > dec_BD(const Vector< T > &r) const override
Definition codes.hpp:3705
Sierpinski triangle (Pascal's triangle modulo p).
Definition helpers.hpp:330
Vector< T > dec_ML(const Vector< T > &r) const override
Definition codes.hpp:3545
SimplexCode(const LinearCode< T > &C)
Definition codes.hpp:3504
size_t get_s() const noexcept
Definition codes.hpp:3523
HammingCode< T > get_dual() const
Definition codes.hpp:3530
Vector< T > dec_ML_soft(const Vector< double > &llrs) const override
Definition codes.hpp:3559
SimplexCode< T > get_equivalent_code_in_standard_form() const
Definition codes.hpp:3519
void get_info(std::ostream &os) const override
Definition codes.hpp:3525
SimplexCode(size_t s)
Definition codes.hpp:3492
Vector< T > dec_BD(const Vector< T > &r) const override
Definition codes.hpp:3532
RepetitionCode< T > get_dual() const
Definition codes.hpp:3870
void get_info(std::ostream &os) const override
Definition codes.hpp:3866
Vector< T > dec_BD(const Vector< T > &r) const override
Definition codes.hpp:3871
Vector< T > dec_ML(const Vector< T > &r) const override
Definition codes.hpp:3882
SingleParityCheckCode(const LinearCode< T > &C)
Definition codes.hpp:3844
SingleParityCheckCode< T > get_identical_code_in_polynomial_form() const
Definition codes.hpp:3862
SingleParityCheckCode< T > get_equivalent_code_in_standard_form() const
Definition codes.hpp:3856
UniverseCode(size_t n)
Definition codes.hpp:3240
UniverseCode< T > get_equivalent_code_in_standard_form() const
Definition codes.hpp:3249
Vector< T > encinv(const Vector< T > &c) const override
Definition codes.hpp:3257
Vector< T > dec_ML(const Vector< T > &r) const override
Definition codes.hpp:3268
void get_info(std::ostream &os) const override
Definition codes.hpp:3251
UniverseCode(const LinearCode< T > &C)
Definition codes.hpp:3247
Vector< T > enc(const Vector< T > &u) const override
Definition codes.hpp:3256
Vector< T > dec_BD(const Vector< T > &r) const override
Definition codes.hpp:3259
ZeroCode< T > get_dual() const
Definition codes.hpp:3255
Vector v = (v₀, v₁, …, vₙ₋₁) over a CECCO::ComponentType.
Definition vectors.hpp:124
ZeroCode(const LinearCode< T > &C)
Definition codes.hpp:3306
void get_info(std::ostream &os) const override
Definition codes.hpp:3312
ZeroCode< T > get_equivalent_code_in_standard_form() const
Definition codes.hpp:3310
ZeroCode(size_t n)
Definition codes.hpp:3304
const char * what() const noexcept override
Definition codes.hpp:537
decoding_failure(const std::string &message)
Definition codes.hpp:536
Thread-safe single-value cache.
Definition helpers.hpp:639
Restores the stream's verbosity level on scope exit (used when printing info for codes constructed fr...
Definition codes.hpp:177
verbosity_guard(std::ostream &os)
Definition codes.hpp:179
#define BP_MAX_ITERATIONS
Definition codes.hpp:80
#define SA_LIST_SIZE
Definition codes.hpp:108
#define VITERBI_LIST_SIZE
Definition codes.hpp:72
#define BOLD(x)
Definition codes.hpp:57
#define KVA_LIST_SIZE
Definition codes.hpp:124
#define DECODING_TABLES_LIMIT
Definition codes.hpp:65
#define GSA_LIST_SIZE
Definition codes.hpp:116
#define LC_OSD_DELTA
Definition codes.hpp:92
#define GSA_MULTIPLICITY
Definition codes.hpp:112
#define LC_OSD_LIST_SIZE
Definition codes.hpp:98
#define OSD_ORDER
Definition codes.hpp:86
#define KVA_STRENGTH
Definition codes.hpp:120
Contains implementation details not to be exposed to the user. Functions and classes here may change ...
Definition blocks.hpp:59
bool validate_modification_pattern(const std::vector< size_t > &v, std::size_t n)
Definition codes.hpp:6963
Matrix< T > LDC_G(const Matrix< T > &G_U, const Matrix< T > &G_V)
Definition codes.hpp:5836
constexpr long special
Definition codes.hpp:173
constexpr long all
Definition codes.hpp:172
constexpr long most
Definition codes.hpp:171
const int index
Definition codes.hpp:167
constexpr long basic
Verbosity levels stored in os.iword(index), set by the show* manipulators.
Definition codes.hpp:170
Provides a framework for error correcting codes.
Definition blocks.hpp:57
std::ostream & showall(std::ostream &os)
Definition codes.hpp:199
auto unlengthen(const ExtendedCode< T, AugmentedCode< T, D > > &C)
Definition codes.hpp:7031
std::ostream & showspecial(std::ostream &os)
Definition codes.hpp:204
auto lengthen(B &&base, size_t j, const Vector< field_t< B > > &w)
Definition codes.hpp:7023
SubfieldSubcode(const B &) -> SubfieldSubcode< B >
EmptyCode< T > puncture(const EmptyCode< T > &, const std::vector< size_t > &)
Definition codes.hpp:7060
std::ostream & showmost(std::ostream &os)
Definition codes.hpp:194
std::ostream & showbasic(std::ostream &os)
Definition codes.hpp:189
auto extend(B &&base)
Definition codes.hpp:6988
auto augment(B &&base, size_t j, const Vector< field_t< B > > &w)
Definition codes.hpp:7002
Polynomial< InfInt > MacWilliamsIdentity(const Polynomial< InfInt > &A, size_t n, size_t k)
Definition codes.hpp:130
LinearCode< T > puncture(const LinearCode< T > &C, const std::vector< size_t > &v)
Definition codes.hpp:7041
auto dual(const LinearCode< T > &C)
Definition codes.hpp:6957
method_t
Definition codes.hpp:421
@ boosted_BD_standard_array
Definition codes.hpp:425
list_method_t
Definition codes.hpp:451
SubfieldSubcode(B &&) -> SubfieldSubcode< B >
termination_t
Definition codes.hpp:5387
auto extend(B &&base, size_t i, const Vector< field_t< B > > &v)
Definition codes.hpp:6981
B unextend(const ExtendedCode< T, B > &C)
Definition codes.hpp:6997
B unaugment(const AugmentedCode< T, B > &C)
Definition codes.hpp:7011
list_processing_t
Definition codes.hpp:453
auto lengthen(B &&base, size_t j, const Vector< field_t< B > > &w, size_t i, const Vector< field_t< B > > &v)
Definition codes.hpp:7016
Tanner graph of a parity-check matrix.
Definition graphs.hpp:938
Layered trellis with field-labelled edges.
Definition graphs.hpp:167