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
trellises.hpp
Go to the documentation of this file.
1/**
2 * @file trellises.hpp
3 * @brief Trellis representation for code decoding
4 * @author Christian Senger <senger@inue.uni-stuttgart.de>
5 * @version 2.1.2
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 * @section Description
16 *
17 * Trellis storage and workspaces used by Viterbi and BCJR decoding. A trellis stores vertices
18 * in layers and edges between adjacent layers. Edge labels are field elements. The class also
19 * provides trellis products, segment merging, text output, and TikZ export for finite fields.
20 */
21
22#ifndef TRELLISES_HPP
23#define TRELLISES_HPP
24
25#include <variant>
26
27#include "fields.hpp"
28#include "vectors.hpp"
29
30/*
31// transitive
32#include <algorithm>
33#include <cmath>
34#include <concepts>
35#include <cstddef>
36#include <cstdint>
37#include <fstream>
38#include <iomanip>
39#include <iostream>
40#include <limits>
41#include <optional>
42#include <ranges>
43#include <stdexcept>
44#include <string>
45#include <type_traits>
46#include <unordered_map>
47#include <utility>
48#include <vector>
49
50#include "field_concepts_traits.hpp"
51#include "helpers.hpp"
52*/
53
54namespace CECCO {
55
56namespace details {
57
58/**
59 * @brief Vertex in one trellis layer
60 *
61 * The `id` is also the position within its layer. Decoding workspaces index
62 * arrays by this value.
63 */
64template <FieldType T>
65struct Vertex {
66 /// @brief Construct a vertex with layer-local id `id`
67 explicit Vertex(uint32_t id) noexcept;
68
69 /// @brief Layer-local vertex id
70 uint32_t id;
71};
72
73/**
74 * @brief Edge between two adjacent trellis layers
75 *
76 * @tparam T Field type used for edge labels
77 */
78template <FieldType T>
79struct Edge {
80 /**
81 * @brief Construct an edge `from_id --value--> to_id`
82 *
83 * @param from_id Source vertex id in layer `s`
84 * @param to_id Target vertex id in layer `s + 1`
85 * @param value Field label carried by the edge
86 */
87 Edge(uint32_t from_id, uint32_t to_id, T value);
88
89 /// @brief Source vertex id
90 uint32_t from_id;
91 /// @brief Target vertex id
92 uint32_t to_id;
93 /// @brief Edge label
95};
96
97} // namespace details
98
99template <FieldType T>
100struct Trellis;
101template <FieldType T>
102std::ostream& operator<<(std::ostream& os, const Trellis<T>& Tr);
103
104/**
105 * @brief Trellis with field-labelled edges between consecutive layers
106 *
107 * @tparam T Field type used for edge labels
108 *
109 * Vertices are stored by layer in @ref V and edges by segment in @ref E. Segment `s`
110 * connects layer `s` to layer `s + 1`. The invariant is `V[s][i].id == i`;
111 * Viterbi and BCJR workspaces index their arrays by edge `from_id` and `to_id`.
112 *
113 * Use @ref add_edge to build a trellis manually, @ref operator* to form trellis
114 * products, and @ref merge_segments to combine several base-field segments into
115 * one extension-field segment. Decoders in `codes.hpp` use the nested workspace
116 * types for path metrics and edge costs.
117 *
118 * @code{.cpp}
119 * Trellis<Fp<2>> Tr;
120 * Tr.add_edge(0, 0, 0, 0);
121 * Tr.add_edge(0, 0, 1, 1);
122 * size_t depth = Tr.get_maximum_depth();
123 * Tr.export_as_tikz("trellis.tikz"); // for fields of size <= 64
124 * @endcode
125 *
126 * @see @ref Vector<T> for received words and LLR vectors
127 * @see @ref FiniteFieldType for TikZ export constraints
128 */
129template <FieldType T>
130struct Trellis {
131 /** @name Construction
132 * @{
133 */
134
135 /// @brief Construct the one-vertex source trellis
136 Trellis();
137
138 /**
139 * @brief Add one edge in segment `segment`
140 *
141 * @param segment Segment index; connects layer `segment` to `segment + 1`
142 * @param id_from Source vertex id in layer `segment`
143 * @param id_to Target vertex id in layer `segment + 1`
144 * @param value Edge label
145 * @throws std::invalid_argument if `id_from` is missing, `id_to` breaks the id/index invariant,
146 * or the edge already exists
147 */
148 void add_edge(size_t segment, uint32_t id_from, uint32_t id_to, T value);
149
150 /**
151 * @brief Product trellis with componentwise added edge labels
152 *
153 * @param other Trellis with the same number of segments
154 * @return Trellis whose vertices are pairs of vertices from the operands
155 * @throws std::invalid_argument if the numbers of segments differ
156 */
157 Trellis operator*(const Trellis& other) const;
158
159 /** @} */
160
161 /** @name Information
162 * @{
163 */
164
165 /// @brief Maximum number of vertices in any layer
166 size_t get_maximum_depth() const noexcept;
167
168 /** @} */
169
170 /** @name Decoding Workspaces
171 * @{
172 */
173
174 /**
175 * @brief Workspace for Viterbi decoding on this trellis
176 *
177 * @tparam cost_t Integral type for hard-decision costs or floating-point type for soft costs
178 *
179 * Stores path metrics for two adjacent layers, edge costs, tie counts, and backpointers.
180 * Construct it from the trellis before calling the Viterbi routines in `codes.hpp`.
181 */
182 template <typename cost_t>
185 /// @brief True for floating-point soft metrics
186 static constexpr bool is_soft = std::is_floating_point_v<cost_t>;
187 /// @brief Initial path cost used for unreachable vertices
188 static constexpr cost_t init =
190
191 /**
192 * @brief Allocate metric arrays for `tr`
193 *
194 * @param tr Trellis whose layer and edge sizes define the workspace
195 */
196 explicit Viterbi_Workspace(const Trellis& tr);
197
198 /**
199 * @brief Set hard-decision edge costs from received word `r`
200 *
201 * @param tr Trellis whose edges define candidate symbols
202 * @param r Received word; length must equal the number of segments
203 * @throws std::invalid_argument if `r.get_n() != tr.E.size()`
204 *
205 * With erasure support, erased received symbols give cost 0 on all outgoing labels.
206 */
207 void calculate_edge_costs(const Trellis& tr, const Vector<T>& r)
209
210 /**
211 * @brief Set soft edge costs from binary LLRs
212 *
213 * @param tr Binary trellis whose edges define candidate bits
214 * @param llrs Log-likelihood ratios; length must equal the number of segments
215 * @throws std::invalid_argument if `llrs.get_n() != tr.E.size()`
216 */
217 void calculate_edge_costs(const Trellis& tr, const Vector<double>& llrs)
219
220 /// @brief Path costs from the previous layer
222 /// @brief Path costs for the current layer
224 /// @brief Backpointer selected for each vertex and layer
226 /// @brief Number of equal-cost paths seen for randomized tie-breaking
228 /// @brief Edge cost for each segment and edge index
230 /// @brief Optional received vector shown in TikZ output
232 };
233
234 /**
235 * @brief Workspace for binary BCJR forward-backward decoding
236 *
237 * Stores α and β metrics by layer and edge costs by segment. It is used by
238 * `LinearCode<Fp<2>>::dec_BCJR`.
239 */
241 /// @brief BCJR uses floating-point soft metrics
242 static constexpr bool is_soft = true;
243
244 /**
245 * @brief Allocate metric arrays for `tr`
246 *
247 * @param tr Trellis whose layer and edge sizes define the workspace
248 */
249 explicit BCJR_Workspace(const Trellis& tr);
250
251 /**
252 * @brief Set BCJR edge costs from binary LLRs
253 *
254 * @param tr Binary trellis whose edges define candidate bits
255 * @param llrs Log-likelihood ratios; length must equal the number of segments
256 * @throws std::invalid_argument if `llrs.get_n() != tr.E.size()`
257 */
258 void calculate_edge_costs(const Trellis& tr, const Vector<double>& llrs)
259 requires std::is_same_v<T, Fp<2>>;
260
261 /// @brief Forward metrics by layer and vertex id
263 /// @brief Backward metrics by layer and vertex id
265 /// @brief Edge cost for each segment and edge index
267 /// @brief Optional received vector shown in TikZ output
269 };
270
271 /** @} */
272
273 /** @name Text and TikZ Output
274 * @{
275 */
276
277 /**
278 * @brief Write a segment-by-segment text representation
279 *
280 * @param os Output stream
281 * @param ws Optional workspace; if non-null, edge costs are printed with edge labels
282 * @return Output stream
283 */
284 template <typename WS>
285 std::ostream& print(std::ostream& os, const WS* ws) const;
286
287 /**
288 * @brief Write a segment-by-segment text representation without edge costs
289 *
290 * @param os Output stream
291 * @return Output stream
292 */
293 std::ostream& print(std::ostream& os) const;
294
295 /// @brief Stream output via @ref print
296 friend std::ostream& operator<< <>(std::ostream& os, const Trellis& Tr);
297
298 /**
299 * @brief Write TikZ style definitions
300 *
301 * @param file Output stream
302 */
303 template <typename WS>
304 void tikz_header(std::ostream& file) const;
305
306 /**
307 * @brief Write one TikZ picture of the trellis
308 *
309 * @param file Output stream
310 * @param ws Optional decoding workspace; if non-null, metrics and edge costs are shown
311 * @param frontier Viterbi frontier layer to highlight
312 *
313 * Available only for finite fields of size at most 64.
314 */
315 template <typename WS>
316 void tikz_picture(std::ostream& file, const WS* ws, size_t frontier) const
317 requires FiniteFieldType<T> && (T::get_size() <= 64);
318
319 /**
320 * @brief Export a standalone TikZ fragment with workspace metrics
321 *
322 * @param filename Output filename
323 * @param ws Optional decoding workspace; if non-null, metrics and edge costs are shown
324 *
325 * Available only for finite fields of size at most 64.
326 */
327 template <typename WS>
328 void export_as_tikz(const std::string& filename, const WS* ws) const
329 requires FiniteFieldType<T> && (T::get_size() <= 64);
330
331 /**
332 * @brief Export a standalone TikZ fragment without workspace metrics
333 *
334 * @param filename Output filename
335 *
336 * Available only for finite fields of size at most 64.
337 */
338 void export_as_tikz(const std::string& filename) const
339 requires FiniteFieldType<T> && (T::get_size() <= 64);
340
341 /** @} */
342
343 /** @name Field Conversion
344 * @{
345 */
346
347 /**
348 * @brief Merge groups of base-field segments into extension-field labels
349 *
350 * @tparam U Extension field with `T ⊆ U`
351 * @return Trellis over `U`
352 *
353 * Consecutive groups of `[U:T]` segments are replaced by one segment whose labels
354 * are the corresponding `U` elements. A final incomplete group is zero-padded.
355 */
356 template <FiniteFieldType U>
358 Trellis<U> merge_segments() const;
359
360 /** @} */
361
362 /// @brief Vertices by layer; `V[s][i].id == i`
364 /// @brief Edges by segment; `E[s]` connects `V[s]` to `V[s + 1]`
366};
367
368namespace details {
369
370template <FieldType T>
371Vertex<T>::Vertex(uint32_t id) noexcept : id(id) {}
372
373template <FieldType T>
374Edge<T>::Edge(uint32_t from_id, uint32_t to_id, T value) : from_id(from_id), to_id(to_id), value(value) {}
375
376} // namespace details
377
378template <FieldType T>
379Trellis<T>::Trellis() : V(1) {
380 V[0].emplace_back(details::Vertex<T>(0));
381}
382
383template <FieldType T>
384void Trellis<T>::add_edge(size_t segment, uint32_t id_from, uint32_t id_to, T value) {
385 if (segment >= E.size()) {
386 V.resize(segment + 2);
387 E.resize(segment + 1);
388 }
389
390 auto& Vf = V[segment];
391 if (std::ranges::find_if(Vf, [id_from](const auto& v) { return v.id == id_from; }) == Vf.cend())
392 throw std::invalid_argument("Start node id " + std::to_string(id_from) + " not found in V[" +
393 std::to_string(segment) + "]!");
394
395 auto& Vt = V[segment + 1];
396 if (std::ranges::find_if(Vt, [id_to](const auto& v) { return v.id == id_to; }) == Vt.cend()) {
397 if (id_to != Vt.size())
398 throw std::invalid_argument("New sink id " + std::to_string(id_to) + " in V[" +
399 std::to_string(segment + 1) +
400 "] must equal its position (id==index invariant)!");
401 Vt.emplace_back(id_to);
402 }
403
404 auto& Es = E[segment];
405 if (std::ranges::any_of(Es, [&](const auto& e) { return e.from_id == id_from && e.to_id == id_to; }))
406 throw std::invalid_argument("Edge (" + std::to_string(id_from) + " -> " + std::to_string(id_to) +
407 ") already exists in E[" + std::to_string(segment) + "]!");
408 Es.emplace_back(id_from, id_to, value);
409}
410
411template <FieldType T>
412Trellis<T> Trellis<T>::operator*(const Trellis& other) const {
413 if (E.size() != other.E.size()) throw std::invalid_argument("Trellises must have the same number of segments!");
414
415 const size_t num_segments = E.size();
416 std::vector<std::unordered_map<uint64_t, uint32_t>> vmap(num_segments + 1);
417
418 auto get_or_add = [&](size_t s, uint32_t a, uint32_t b) -> uint32_t {
419 const uint64_t key = (static_cast<uint64_t>(a) << 32) | b;
420 auto [it, inserted] = vmap[s].try_emplace(key, static_cast<uint32_t>(vmap[s].size()));
421 return it->second;
422 };
423
424 get_or_add(0, 0, 0);
425
426 Trellis result;
427
428 for (size_t s = 0; s < num_segments; ++s) {
429 for (const auto& e1 : E[s]) {
430 for (const auto& e2 : other.E[s]) {
431 result.add_edge(s, get_or_add(s, e1.from_id, e2.from_id), get_or_add(s + 1, e1.to_id, e2.to_id),
432 e1.value + e2.value);
433 }
434 }
435 }
436
437 return result;
438}
439
440template <FieldType T>
441size_t Trellis<T>::get_maximum_depth() const noexcept {
442 size_t max = 0;
443 for (const auto& seg : V)
444 if (seg.size() > max) max = seg.size();
445 return max;
446}
447
448template <FieldType T>
449template <typename cost_t>
450 requires std::integral<cost_t> || std::floating_point<cost_t>
461
462template <FieldType T>
463template <typename cost_t>
468{
469 if (r.get_n() != tr.E.size()) throw std::invalid_argument("Vector length must match number of trellis segments!");
470 for (size_t s = 0; s < tr.E.size(); ++s)
471 for (size_t j = 0; j < tr.E[s].size(); ++j)
472#ifdef CECCO_ERASURE_SUPPORT
473 edge_costs[s][j] = r[s].is_erased() ? cost_t{0} : ((tr.E[s][j].value != r[s]) ? cost_t{1} : cost_t{0});
474#else
475 edge_costs[s][j] = (tr.E[s][j].value != r[s]) ? cost_t{1} : cost_t{0};
476#endif
477}
478
479template <FieldType T>
480template <typename cost_t>
484 const Vector<double>& llrs)
486{
487 if (llrs.get_n() != tr.E.size())
488 throw std::invalid_argument("Vector length must match number of trellis segments!");
489 for (size_t s = 0; s < tr.E.size(); ++s)
490 for (size_t j = 0; j < tr.E[s].size(); ++j) edge_costs[s][j] = (tr.E[s][j].value == T(0)) ? cost_t{0} : llrs[s];
491}
492
493template <FieldType T>
495 alpha.reserve(tr.V.size());
496 beta.reserve(tr.V.size());
497 for (size_t s = 0; s < tr.V.size(); ++s) {
498 alpha.emplace_back(tr.V[s].size(), -std::numeric_limits<double>::infinity());
499 beta.emplace_back(tr.V[s].size(), -std::numeric_limits<double>::infinity());
500 }
501 edge_costs.reserve(tr.E.size());
502 for (size_t s = 0; s < tr.E.size(); ++s) edge_costs.emplace_back(tr.E[s].size());
503}
504
505template <FieldType T>
506void Trellis<T>::BCJR_Workspace::calculate_edge_costs(const Trellis& tr, const Vector<double>& llrs)
507 requires std::is_same_v<T, Fp<2>>
508{
509 if (llrs.get_n() != tr.E.size())
510 throw std::invalid_argument("Vector length must match number of trellis segments!");
511 for (size_t s = 0; s < tr.E.size(); ++s)
512 for (size_t j = 0; j < tr.E[s].size(); ++j) edge_costs[s][j] = (tr.E[s][j].value != T(0)) ? llrs[s] : 0.0;
513}
514
515template <FieldType T>
516template <typename WS>
517std::ostream& Trellis<T>::print(std::ostream& os, const WS* ws) const {
518 if (E.empty()) return os;
519 for (size_t i = 0; i < E.size(); ++i) {
520 if (E[i].empty()) return os;
521 for (size_t j = 0; j < E[i].size(); ++j) {
522 os << "(" << E[i][j].from_id << "--" << E[i][j].value;
523 if (ws) os << "[" << ws->edge_costs[i][j] << "]";
524 os << "--" << E[i][j].to_id << ")";
525 if (j < E[i].size() - 1) os << ", ";
526 }
527 if (i != E.size() - 1) os << std::endl;
528 }
529 return os;
530}
531
532template <FieldType T>
534 return this->template print<Viterbi_Workspace<uint16_t>>(os, nullptr);
535}
536
537template <FieldType T>
539 return Tr.print(os);
540}
541
542template <FieldType T>
543template <typename WS>
545 const double arrow_scale = std::min(1.4 / E.size() * 9.0, 1.4);
546 const double vertex_size = std::min(5.0 / E.size() * 9.0, 5.0);
547
548 file << R"(% required in preamble:
549% \usepackage{amsfonts}
550% \usepackage{bm}
551% \usepackage{tikz}
552% \usetikzlibrary{arrows.meta, backgrounds, calc, positioning}
553
554\tikzset{>={Stealth[scale=)"
555 << arrow_scale << R"(]}}
556\tikzstyle{trellisvertex}=[circle, draw=black, outer sep=0pt, inner sep=0pt, minimum size=)"
557 << vertex_size << R"(pt, left color=gray!80, right color=gray!20])";
558
559 if constexpr (WS::is_soft) {
560 file << R"(
561\tikzstyle{trellisvertexprev}=[trellisvertex, inner sep=1pt, left color=blue!80, right color=blue!20, text width=4ex, align=center]
562\tikzstyle{trellisvertexcurr}=[trellisvertex, inner sep=1pt, left color=green!80, right color=green!20, text width=4ex, align=center])";
563 } else {
564 file << R"(
565\tikzstyle{trellisvertexprev}=[trellisvertex, inner sep=2pt, left color=blue!80, right color=blue!20]
566\tikzstyle{trellisvertexcurr}=[trellisvertex, inner sep=2pt, left color=green!80, right color=green!20])";
567 }
568
569 file << R"(
570\tikzstyle{trellisarrow}=[draw, ->, fill=black]
571\tikzstyle{trellisarrowone}=[trellisarrow, fill=red, draw=red]
572\tikzstyle{trellisarrowzero}=[trellisarrow, densely dashed, fill=black, draw=black]
573\tikzstyle{trellisedgelabel}=[below, sloped]
574\tikzstyle{trellispath}=[double distance=.075cm, thick, line join=round, cap=round])";
575}
576
577template <FieldType T>
578template <typename WS>
579void Trellis<T>::tikz_picture(std::ostream& file, const WS* ws, size_t frontier) const
580 requires FiniteFieldType<T> && (T::get_size() <= 64)
581{
582 file << "\n\n\\begin{tikzpicture}[x=\\linewidth/" << E.size() << ", y=1cm]";
583
584 for (size_t s = 0; s < V.size(); ++s) {
585 for (size_t i = 0; i < V[s].size(); ++i) {
586 const char* style = "trellisvertex";
587 bool labeled = false;
588 if (ws) {
589 if constexpr (std::is_same_v<WS, BCJR_Workspace>) {
590 style = "trellisvertexprev";
591 labeled = true;
592 } else {
593 if (s == frontier) {
594 style = "trellisvertexcurr";
595 labeled = true;
596 } else if (frontier > 0 && s == frontier - 1) {
597 style = "trellisvertexprev";
598 labeled = true;
599 }
600 }
601 }
602 file << "\n \\node[" << style << "] (" << s << "_" << V[s][i].id << ") at (" << s << ", "
603 << -static_cast<int>(V[s][i].id) << ") {\\tiny$";
604 if (labeled) {
605 if constexpr (WS::is_soft) {
606 file << std::fixed << std::setprecision(2);
607 if constexpr (std::is_same_v<WS, BCJR_Workspace>) {
608 file << ws->alpha[s][i] << "\\mid " << ws->beta[s][i];
609 } else {
611 }
612 } else {
614 }
615 }
616 file << "$};";
617 }
618 }
619
620 for (size_t s = 0; s < E.size(); ++s) {
621 for (size_t j = 0; j < E[s].size(); ++j) {
622 const auto& e = E[s][j];
623 const auto label = e.value.get_label();
624 if (label == 0) {
625 file << "\n \\path[trellisarrowzero]";
626 } else if (label == T::get_size() - 1) {
627 file << "\n \\path[trellisarrowone]";
628 } else {
629 const size_t a = 63 - 63 * static_cast<double>(label) / (T::get_size() - 1);
630 const uint8_t r = details::colormap[a][0];
631 const uint8_t g = details::colormap[a][1];
632 const uint8_t b = details::colormap[a][2];
633 file << "\n \\definecolor{color}{RGB}{" << static_cast<int>(r) << ", " << static_cast<int>(g) << ", "
634 << static_cast<int>(b) << "}\\path[trellisarrow, draw=color, fill=color]";
635 }
636 file << " (" << s << "_" << e.from_id << ")";
637 if (ws) {
638 file << " edge[trellisedgelabel] node[black] {\\tiny$";
639 if constexpr (WS::is_soft) file << std::fixed << std::setprecision(2);
640 file << ws->edge_costs[s][j] << "$}";
641 } else {
642 file << " --";
643 }
644 file << " (" << s + 1 << "_" << e.to_id << ");";
645 }
646 }
647
648 file << "\n \\begin{scope}[on background layer]";
650 for (size_t s = 0; s < V.size(); ++s) {
651 file << "\n \\draw [dotted, shorten >=-5mm] (" << s << "_0) to (" << s << ", "
652 << -static_cast<int>(maxdepth) + 1 << ");";
653 }
654
655 if constexpr (!std::is_same_v<WS, BCJR_Workspace>) {
656 if (ws && frontier > 0) {
658 path.reserve(frontier + 1);
659 for (size_t i = 0; i < V[frontier].size(); ++i) {
660 path.clear();
661 size_t v = i;
663 for (size_t s = frontier; s > 0; --s) {
664 v = ws->backptrs[s][v]->from_id;
666 }
667 file << "\n \\draw[trellispath, green, double=green!15] (" << frontier - 1 << "_" << path[1]
668 << ") -- (" << frontier << "_" << path[0] << ");";
669 if (path.size() > 2) {
670 file << "\n \\draw[trellispath, blue, double=blue!15]";
671 for (size_t k = 1; k < path.size(); ++k) {
672 file << " (" << frontier - k << "_" << path[k] << ")";
673 if (k + 1 < path.size()) file << " --";
674 }
675 file << ";";
676 }
677 }
678 }
679 }
680
681 file << "\n \\end{scope}"
682 << "\n \\node[node distance=.0cm, below left=of 0_0] {$\\mathfrak{s}$};"
683 << "\n \\node[node distance=.0cm, below right=of " << E.size() << "_0] {$\\mathfrak{t}$};";
684
685 if constexpr (requires { ws->v; }) {
686 if (ws && ws->v.has_value()) {
687 file << "\n \\node[anchor=east] at ($(0_0)+(0,.5)$) {\\small$\\bm{v}=$};";
688 for (size_t s = 0; s < E.size(); ++s) {
689 file << "\n \\node at ($(" << s << "_0)!0.5!(" << s + 1 << "_0)+(0,.5)$) {\\small$";
690 if (s == 0) file << "(";
691 std::visit([&](const auto& w) { file << w[s]; }, *(ws->v));
692 file << (s + 1 == E.size() ? ")" : ",") << "$};";
693 }
694 }
695 }
696
697 file << "\n\\end{tikzpicture}\n";
698}
699
700template <FieldType T>
701template <typename WS>
702void Trellis<T>::export_as_tikz(const std::string& filename, const WS* ws) const
703 requires FiniteFieldType<T> && (T::get_size() <= 64)
704{
708 tikz_picture(file, ws, V.size() - 1);
709 file.close();
710}
711
712template <FieldType T>
713void Trellis<T>::export_as_tikz(const std::string& filename) const
714 requires FiniteFieldType<T> && (T::get_size() <= 64)
715{
717}
718
719template <FieldType T>
720template <FiniteFieldType U>
722Trellis<U> Trellis<T>::merge_segments() const {
724 const size_t n = E.size();
725 const size_t full_groups = n / m;
726
728 size_t seg = 0;
729
730 for (size_t g = 0; g < full_groups; ++g) {
731 std::vector<std::vector<const details::Edge<T>*>> paths;
732
733 for (const auto& e : E[g * m]) paths.push_back({&e});
734
735 for (size_t step = 1; step < m; ++step) {
736 std::vector<std::vector<const details::Edge<T>*>> next;
737 for (const auto& path : paths)
738 for (const auto& e : E[g * m + step])
739 if (e.from_id == path.back()->to_id) {
740 auto ext = path;
741 ext.push_back(&e);
743 }
744 paths = std::move(next);
745 }
746
747 for (const auto& path : paths) {
748 Vector<T> v(m);
749 for (size_t i = 0; i < m; ++i) v.set_component(i, path[i]->value);
751 }
752 ++seg;
753 }
754
755 for (size_t s = full_groups * m; s < n; ++s) {
756 for (const auto& e : E[s]) {
757 Vector<T> v(m, T(0));
760 }
761 ++seg;
762 }
763
764 return result;
765}
766
767} // namespace CECCO
768
769#endif
Contains implementation details not to be exposed to the user. Functions and classes here may change ...
Definition blocks.hpp:59
Provides a framework for error correcting codes.
Definition blocks.hpp:57
Workspace for binary BCJR forward-backward decoding.
std::vector< std::vector< double > > alpha
Forward metrics by layer and vertex id.
std::vector< std::vector< double > > beta
Backward metrics by layer and vertex id.
BCJR_Workspace(const Trellis &tr)
Allocate metric arrays for tr.
void calculate_edge_costs(const Trellis &tr, const Vector< double > &llrs)
Set BCJR edge costs from binary LLRs.
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.
Trellis with field-labelled edges between consecutive layers.
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 between two adjacent trellis layers.
Definition trellises.hpp:79
uint32_t from_id
Source vertex id.
Definition trellises.hpp:90
Edge(uint32_t from_id, uint32_t to_id, T value)
Construct an edge from_id --value--> to_id.
T value
Edge label.
Definition trellises.hpp:94
uint32_t to_id
Target vertex id.
Definition trellises.hpp:92
Vertex in one trellis layer.
Definition trellises.hpp:65
uint32_t id
Layer-local vertex id.
Definition trellises.hpp:70
Vertex(uint32_t id) noexcept
Construct a vertex with layer-local id id.