2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
60
61
62
63
64
68 explicit Vertex(uint32_t id)
noexcept :
id(id) {}
75
76
77
78
99
100
101
102
103template <FieldType T>
120
121
122
123
124
125
126
127
128template <FiniteFieldType T>
130 constexpr size_t q = T::get_size();
131 constexpr double L_MAX = 1.0e6;
133 std::array<
double, q> costs;
135 for (size_t a = 1; a < q; ++a) costs[a] = std::clamp(llrs(a - 1, s), -L_MAX, L_MAX);
139inline double max_star(
double a,
double b)
noexcept {
140 constexpr double neg_inf = -
std::numeric_limits<
double>::infinity();
141 if (a == neg_inf)
return b;
142 if (b == neg_inf)
return a;
143 const double d =
std::abs(a - b);
144 return std::max(a, b) + ((d > 500.0) ? 0.0 :
std::log(1.0 +
std::exp(-d)));
149template <FieldType T>
151template <FieldType T>
152std::ostream& operator<<(
std::ostream& os,
const Trellis<T>& Tr);
153template <FieldType T>
157
158
159
160
161
162
163
164
165
166template <FieldType T>
169
170
176
177
178
179
180
181
182
183
184
185 void add_edge(size_t segment, uint32_t id_from, uint32_t id_to, T value);
188
189
190
191
192
193
194
195
196
197
198
199 void add_parallel_edge(size_t segment, uint32_t id_from, uint32_t id_to, T value);
202
203
204
205
206
207
213
214
222
223
226
227
228
229
230
231
232
233 template <
typename cost_t>
243
244
245
246
250
251
252
253
254
255
256
257
262
263
264
265
266
267
268
269
270
289
290
291
292
293
294
295
296
297 template <
typename cost_t>
307
308
309
310
314
315
316
317
318
319
324
325
326
327
328
329
340
341
342
343
344
350
351
352
353
357
358
359
360
361
362
363
364
381
382
385
386
387
388
389
390
391 template <
typename WS>
392 std::ostream&
print(
std::ostream& os,
const WS* ws)
const;
395
396
397
398
399
403 friend std::ostream& operator<< <>(
std::ostream& os,
const Trellis& Tr);
406
407
408
409
410 template <
typename WS>
414
415
416
417
418
419
420
421
422 template <
typename WS>
423 void tikz_picture(
std::ostream& file,
const WS* ws, size_t frontier)
const
427
428
429
430
431
432
433
434 template <
typename WS>
439
440
441
442
443
444
451
452
455
456
457
458
459
460
461
462
475template <FieldType T>
477 V[0].emplace_back(details::Vertex<T>(0));
480template <FieldType T>
482 if (segment < E.size() &&
483 std::ranges::any_of(E[segment], [&](
const auto& e) {
return e.from_id == id_from && e.to_id == id_to; }))
484 throw std::invalid_argument(
"Edge (" + std::to_string(id_from) +
" -> " + std::to_string(id_to) +
485 ") already exists in E[" + std::to_string(segment) +
"]!");
486 add_parallel_edge(segment, id_from, id_to, value);
489template <FieldType T>
491 if (segment >= E.size()) {
492 V.resize(segment + 2);
493 E.resize(segment + 1);
496 auto& Vf = V[segment];
497 if (std::ranges::find_if(Vf, [id_from](
const auto& v) {
return v.id == id_from; }) == Vf.cend())
498 throw std::invalid_argument(
"Start node id " + std::to_string(id_from) +
" not found in V[" +
499 std::to_string(segment) +
"]!");
501 auto& Vt = V[segment + 1];
502 if (std::ranges::find_if(Vt, [id_to](
const auto& v) {
return v.id == id_to; }) == Vt.cend()) {
503 if (id_to != Vt.size())
504 throw std::invalid_argument(
"New sink id " + std::to_string(id_to) +
" in V[" +
505 std::to_string(segment + 1) +
506 "] must equal its position (id==index invariant)!");
507 Vt.emplace_back(id_to);
510 E[segment].emplace_back(id_from, id_to, value);
513template <FieldType T>
515 if (E.size() != other.E.size())
throw std::invalid_argument(
"Trellises must have the same number of segments!");
517 const size_t num_segments = E.size();
518 std::vector<std::unordered_map<uint64_t, uint32_t>> vmap(num_segments + 1);
520 auto get_or_add = [&](size_t s, uint32_t a, uint32_t b) -> uint32_t {
521 const uint64_t key = (
static_cast<uint64_t>(a) << 32) | b;
522 auto [it, inserted] = vmap[s].try_emplace(key,
static_cast<uint32_t>(vmap[s].size()));
530 for (size_t s = 0; s < num_segments; ++s) {
531 for (
auto e1 = E[s].cbegin(); e1 != E[s].cend(); ++e1) {
532 for (
auto e2 = other.E[s].cbegin(); e2 != other.E[s].cend(); ++e2) {
533 result.add_parallel_edge(s, get_or_add(s, e1->from_id, e2->from_id),
534 get_or_add(s + 1, e1->to_id, e2->to_id), e1->value + e2->value);
542template <FieldType T>
545 for (size_t s = 0; s < V.size(); ++s)
546 if (V[s].size() > max) max = V[s].size();
551template <
typename cost_t>
574#ifdef CECCO_ERASURE_SUPPORT
590 throw std::
invalid_argument(
"LLR matrix must have q-1 rows and one column per trellis segment!");
618#ifdef CECCO_ERASURE_SUPPORT
634 throw std::
invalid_argument(
"LLR matrix must have q-1 rows and one column per trellis segment!");
642template <FieldType T>
644 alpha.reserve(tr.V.size());
645 beta.reserve(tr.V.size());
646 for (size_t s = 0; s < tr.V.size(); ++s) {
647 alpha.emplace_back(tr.V[s].size(), -std::numeric_limits<
double>::infinity());
648 beta.emplace_back(tr.V[s].size(), -std::numeric_limits<
double>::infinity());
650 edge_costs.reserve(tr.E.size());
651 for (size_t s = 0; s < tr.E.size(); ++s) edge_costs.emplace_back(tr.E[s].size());
654template <FieldType T>
659 throw std::
invalid_argument(
"LLR matrix must have q-1 rows and one column per trellis segment!");
668template <
typename WS>
670 if (E.empty())
return os;
671 for (size_t i = 0; i < E.size(); ++i) {
672 if (E[i].empty())
return os;
673 for (size_t j = 0; j < E[i].size(); ++j) {
674 os <<
"(" << E[i][j].from_id <<
"--" << E[i][j].value;
675 if (ws) os <<
"[" << ws->edge_costs[i][j] <<
"]";
676 os <<
"--" << E[i][j].to_id <<
")";
677 if (j < E[i].size() - 1) os <<
", ";
679 if (i != E.size() - 1) os << std::endl;
684template <FieldType T>
686 return this->
template print<Viterbi_Workspace<uint16_t>>(os,
nullptr);
689template <FieldType T>
695template <
typename WS>
697 const double arrow_scale = std::min(1.4 / E.size() * 9.0, 1.4);
698 const double vertex_size = std::min(5.0 / E.size() * 9.0, 5.0);
700 file << R"(% required in preamble:
701% \usepackage{amsfonts}
702% \usepackage{bm}
703% \usepackage{tikz}
704% \usetikzlibrary{arrows.meta, backgrounds, calc, positioning}
705
706\tikzset{>={Stealth[scale=)"
707 << arrow_scale << R"(]}}
708\tikzstyle{trellisvertex}=[circle, draw=black, outer sep=0pt, inner sep=0pt, minimum size=)"
709 << vertex_size << R"(pt, left color=gray!80, right color=gray!20])";
711 if constexpr (WS::is_soft) {
713\tikzstyle{trellisvertexprev}=[trellisvertex, font=\tiny, inner sep=1pt, left color=blue!80, right color=blue!20, text width=4ex, align=center]
714\tikzstyle{trellisvertexcurr}=[trellisvertex, font=\tiny, inner sep=1pt, left color=green!80, right color=green!20, text width=4ex, align=center])";
717\tikzstyle{trellisvertexprev}=[trellisvertex, font=\footnotesize, inner sep=2pt, left color=blue!80, right color=blue!20]
718\tikzstyle{trellisvertexcurr}=[trellisvertex, font=\footnotesize, inner sep=2pt, left color=green!80, right color=green!20])";
721 if constexpr (std::is_same_v<WS, BCJR_Workspace>) {
723\tikzstyle{trellisvertexsplit}=[trellisvertexprev, inner sep=1pt, circle split, font=\tiny, inner sep=1pt])";
728\tikzstyle{trellisarrow}=[draw, ->, fill=black]
729\tikzstyle{trellisarrowone}=[trellisarrow, fill=red, draw=red]
730\tikzstyle{trellisarrowzero}=[trellisarrow, densely dashed, fill=black, draw=black]
731\tikzstyle{trellisedgelabel}=[below, sloped]
732\tikzstyle{trellispath}=[double distance=.075cm, thick, line join=round, cap=round])";
736template <
typename WS>
740 file <<
"\n\n\\begin{tikzpicture}[x=\\linewidth/" <<
E.
size() <<
", y=1cm]";
744 const char*
style =
"trellisvertex";
748 style =
"trellisvertexsplit";
752 style =
"trellisvertexcurr";
755 style =
"trellisvertexprev";
761 file <<
"\n \\node[" <<
style <<
"] (" <<
s <<
"_" <<
V[
s][
i].
id <<
") at (" <<
s <<
", "
762 << -
static_cast<
int>(
V[
s][
i].
id) <<
") {};";
767 file <<
"\n \\node[" <<
style <<
"] (" <<
s <<
"_" <<
V[
s][
i].
id <<
") at (" <<
s <<
", "
768 << -
static_cast<
int>(
V[
s][
i].
id) <<
") {$" <<
ws->
alpha[
s][
i] <<
"$\\nodepart{lower} $"
771 file <<
"\n \\node[" <<
style <<
"] (" <<
s <<
"_" <<
V[
s][
i].
id <<
") at (" <<
s <<
", "
772 << -
static_cast<
int>(
V[
s][
i].
id) <<
") {$"
776 file <<
"\n \\node[" <<
style <<
"] (" <<
s <<
"_" <<
V[
s][
i].
id <<
") at (" <<
s <<
", "
777 << -
static_cast<
int>(
V[
s][
i].
id) <<
") {$"
786 const auto&
e =
E[
s][
j];
789 file <<
"\n \\path[trellisarrowzero]";
791 file <<
"\n \\path[trellisarrowone]";
797 file <<
"\n \\definecolor{color}{RGB}{" <<
static_cast<
int>(
r) <<
", " <<
static_cast<
int>(
g) <<
", "
798 <<
static_cast<
int>(
b) <<
"}\\path[trellisarrow, draw=color, fill=color]";
802 file <<
" edge[trellisedgelabel] node[black] {\\tiny$";
812 file <<
"\n \\begin{scope}[on background layer]";
815 file <<
"\n \\draw [dotted, shorten >=-5mm] (" <<
s <<
"_0) to (" <<
s <<
", "
816 << -
static_cast<
int>(
maxdepth) + 1 <<
");";
830 if (
edge ==
nullptr) {
838 file <<
"\n \\draw[trellispath, green, double=green!15] (" <<
frontier - 1 <<
"_" <<
path[1]
841 file <<
"\n \\draw[trellispath, blue, double=blue!15]";
852 file <<
"\n \\end{scope}"
853 <<
"\n \\node[node distance=.0cm, below left=of 0_0] {$\\mathfrak{s}$};"
854 <<
"\n \\node[node distance=.0cm, below right=of " <<
E.
size() <<
"_0] {$\\mathfrak{t}$};";
858 file <<
"\n \\node[anchor=east] at ($(0_0)+(0,.5)$) {\\small$\\bm{v}=$};";
860 file <<
"\n \\node at ($(" <<
s <<
"_0)!0.5!(" <<
s + 1 <<
"_0)+(0,.5)$) {\\small$";
861 if (
s == 0)
file <<
"(";
870 file << (
s + 1 ==
E.
size() ?
")" :
",") <<
"$};";
875 file <<
"\n\\end{tikzpicture}\n";
879template <
typename WS>
890template <FieldType T>
947
948
949
951
952
953
954template <FieldType T>
957 explicit TannerGraph(size_t n) : n(n) {}
960
961
962
963
965
967 void add_edge(size_t check, uint32_t var_id, T value) {
969 throw std::invalid_argument(
"Variable node id " + std::to_string(var_id) +
970 " out of range for Tanner graph with " + std::to_string(n) +
" variable nodes");
971 if (check >= checks.size()) checks.resize(check + 1);
972 checks[check].emplace_back(var_id, value);
976
977
978
979
980
981 struct BP_Workspace {
983 static constexpr size_t q = T::get_size();
986
987
988
989
990 explicit BP_Workspace(
const TannerGraph& g) : intrinsic(g.n), posterior(g.n), var_edges(g.n) {
991 m_vc.reserve(g.checks.size());
992 m_cv.reserve(g.checks.size());
993 for (size_t i = 0; i < g.checks.size(); ++i) {
994 m_vc.emplace_back(g.checks[i].size());
995 m_cv.emplace_back(g.checks[i].size());
996 for (size_t e = 0; e < g.checks[i].size(); ++e) var_edges[g.checks[i][e].var_id].emplace_back(i, e);
1002
1004
1006
1008 void calculate_intrinsic(
const TannerGraph& g,
const Matrix<
double>& llrs)
1009 requires FiniteFieldType<T>
1011 for (size_t j = 0; j < g.n; ++j) {
1012 const auto costs = details::symbol_costs_from_llrs<T>(llrs, j);
1013 for (size_t a = 0; a < q; ++a) intrinsic[j][a] = -costs[a];
1018 std::vector<std::vector<std::array<
double, q>>> m_vc;
1020 std::vector<std::vector<std::array<
double, q>>> m_cv;
1022 std::vector<std::array<
double, q>> intrinsic;
1024 std::vector<std::array<
double, q>> posterior;
1026 std::vector<std::vector<std::pair<size_t, size_t>>> var_edges;
1028 std::optional<Matrix<
double>> v;
1032
1033
1034
1035
1036 void tikz_header(
std::ostream& file)
const {
1037 file << R"(% required in preamble:
1038% \usepackage{amsfonts}
1039% \usepackage{bm}
1040% \usepackage{tikz}
1041% \usetikzlibrary{calc, positioning}
1042
1043\tikzstyle{tannervariable}=[circle, draw=black, inner sep=0pt, minimum size=6pt, left color=gray!80, right color=gray!20]
1044\tikzstyle{tannervariablesoft}=[circle, draw=black, font=\tiny, inner sep=1pt, left color=green!80, right color=green!20, text width=4ex, align=center]
1045\tikzstyle{tannercheck}=[rectangle, draw=black, inner sep=0pt, minimum size=7pt, fill=gray!40]
1046\tikzstyle{tanneredge}=[draw])";
1050
1051
1052
1053
1054
1055
1056
1057 void tikz_picture(
std::ostream& file,
const BP_Workspace* ws)
const
1061 const double width = (
n > 1) ?
static_cast<
double>(
n - 1) : 1.0;
1062 file <<
"\n\n\\begin{tikzpicture}[x=\\linewidth/" <<
width <<
", y=1cm]";
1067 file <<
"\n \\node[tannervariablesoft] (v" <<
j <<
") at (" <<
j <<
", 0) {$" <<
std::
fixed
1070 file <<
"\n \\node[anchor=north, font=\\tiny] at ($(v" <<
j <<
")-(0,0.3)$) {$" <<
dec <<
"$};";
1074 file <<
"\n \\node[tannervariable] (v" <<
j <<
") at (" <<
j <<
", 0) {};";
1079 file <<
"\n \\node[tannercheck] (c" <<
i <<
") at (" <<
x <<
", 3) {};";
1086 file <<
"\n \\path[tanneredge, draw=red]";
1100 file <<
"\n \\node[anchor=south, font=\\tiny] at ($(v" <<
j <<
")+(0,0.3)$) {$" <<
std::
fixed
1106 file <<
"\n\\end{tikzpicture}\n";
1113
1114
1116
1128
1129
1130
1131
1132
1133
1143 std::vector<std::vector<details::CheckEdge<T>>> checks;
Contains implementation details not to be exposed to the user. Functions and classes here may change ...
std::array< double, T::get_size()> symbol_costs_from_llrs(const Matrix< double > &llrs, size_t s)
Convert one column of symbol-level LLRs to trellis costs.
double max_star(double a, double b) noexcept
Provides a framework for error correcting codes.
Tanner graph of a parity-check matrix.
Workspace for BCJR forward-backward decoding.
std::vector< std::vector< double > > alpha
Forward metrics by layer and vertex id.
void calculate_edge_costs(const Trellis &trellis, const Matrix< double > &llrs)
Set BCJR edge costs from symbol-level LLRs.
std::vector< std::vector< double > > beta
Backward metrics by layer and vertex id.
BCJR_Workspace(const Trellis &tr)
Allocate metric arrays for tr.
std::vector< std::vector< double > > edge_costs
Edge cost for each segment and edge index.
static constexpr bool is_soft
BCJR uses floating-point soft metrics.
Layered trellis with field-labelled edges.
void add_parallel_edge(size_t segment, uint32_t id_from, uint32_t id_to, T value)
Add one edge in segment segment, allowing a parallel edge.
std::vector< std::vector< details::Edge< T > > > E
Edges by segment; E[s] connects V[s] to V[s + 1].
void tikz_header(std::ostream &file) const
Write TikZ style definitions.
void add_edge(size_t segment, uint32_t id_from, uint32_t id_to, T value)
Add one edge in segment segment.
std::vector< std::vector< details::Vertex< T > > > V
Vertices by layer; V[s][i].id == i.
Trellis operator*(const Trellis &other) const
Product trellis with componentwise added edge labels.
std::ostream & print(std::ostream &os, const WS *ws) const
Write a segment-by-segment text representation.
Trellis()
Construct the one-vertex source trellis.
size_t get_maximum_depth() const noexcept
Maximum number of vertices in any layer.
std::ostream & print(std::ostream &os) const
Write a segment-by-segment text representation without edge costs.
void export_as_tikz(const std::string &filename) const
Export a standalone TikZ fragment without workspace metrics.
void export_as_tikz(const std::string &filename, const WS *ws) const
Export a standalone TikZ fragment with workspace metrics.
void tikz_picture(std::ostream &file, const WS *ws, size_t frontier) const
Write one TikZ picture of the trellis.
Edge from a check node to a variable node in a Tanner graph.
CheckEdge(uint32_t var_id, T value)
Construct an edge to variable node var_id with label value.
T value
Parity-check coefficient (label).
uint32_t var_id
Incident variable-node id.
Edge between two adjacent trellis layers.
uint32_t from_id
Source vertex id.
Edge(uint32_t from_id, uint32_t to_id, T value)
Construct an edge from_id --value--> to_id.
uint32_t to_id
Target vertex id.
Vertex in one trellis layer.
uint32_t id
Layer-local vertex id.
Vertex(uint32_t id) noexcept
Construct a vertex with layer-local id id.