GF2++
Loading...
Searching...
No Matches
BitMat.h
Go to the documentation of this file.
1#pragma once
2// SPDX-FileCopyrightText: 2025 Nessan Fitzmaurice <nzznfitz+gh@icloud.com>
3// SPDX-License-Identifier: MIT
4
8
9#include <gf2/BitPoly.h>
10#include <gf2/BitVec.h>
11#include <gf2/RNG.h>
12
13#include <string>
14#include <optional>
15#include <regex>
16
17namespace gf2 {
18
19// Forward declarations to avoid recursive inclusion issues
20// clang-format off
21template<Unsigned Word> class BitGauss;
22template<Unsigned Word> class BitLU;
23// clang-format on
24
31template<Unsigned Word = usize>
32class BitMat {
33private:
35 std::vector<BitVec<Word>> m_rows;
36
37public:
38 // The row type is a `BitVec` ...
39 using row_type = BitVec<Word>;
40
42 using word_type = Word;
43
46
54 constexpr BitMat() : m_rows{} {}
55
65 constexpr BitMat(usize n) : m_rows{} {
66 if (n > 0) resize(n, n);
67 }
68
78 constexpr BitMat(usize m, usize n) : m_rows{} {
79 if (m > 0 && n > 0) resize(m, n);
80 }
81
92 constexpr BitMat(std::vector<row_type> const& rows) : m_rows{rows} {
93 gf2_assert(check_rows(m_rows), "Not all rows have the same size!");
94 }
95
109 constexpr BitMat(std::vector<BitVec<Word>>&& rows) : m_rows{std::move(rows)} {
110 gf2_assert(check_rows(m_rows), "Not all rows have the same size!");
111 }
112
116
124 static constexpr BitMat zeros(usize m, usize n) { return BitMat{m, n}; }
125
133 static constexpr BitMat zeros(usize m) { return BitMat{m, m}; }
134
142 static constexpr BitMat ones(usize m, usize n) {
143 auto rows = std::vector{m, BitVec<Word>::ones(n)};
144 return BitMat{std::move(rows)};
145 }
146
154 static constexpr BitMat ones(usize m) { return BitMat::ones(m, m); }
155
163 static constexpr BitMat alternating(usize m, usize n) {
164 auto rows = std::vector{m, BitVec<Word>::alternating(n)};
165 // Flip every other row.
166 for (auto i = 1uz; i < m; i += 2) rows[i].flip_all();
167 return BitMat{std::move(rows)};
168 }
169
177 static constexpr BitMat alternating(usize m) { return BitMat::alternating(m, m); }
178
188 template<BitStore Lhs, BitStore Rhs>
189 requires std::same_as<typename Lhs::word_type, Word> && std::same_as<typename Rhs::word_type, Word>
190 static constexpr BitMat outer_product(Lhs const& u, Rhs const& v) {
191 BitMat result;
192 for (auto i = 0uz; i < u.size(); ++i) {
193 row_type row = u.get(i) ? row_type(v) : row_type(v.size());
194 result.m_rows.push_back(std::move(row));
195 }
196 return result;
197 }
198
208 template<BitStore Lhs, BitStore Rhs>
209 requires std::same_as<typename Lhs::word_type, Word> && std::same_as<typename Rhs::word_type, Word>
210 static constexpr BitMat outer_sum(Lhs const& u, Rhs const& v) {
211 BitMat result;
212 for (auto i = 0uz; i < u.size(); ++i) {
213 result.m_rows.push_back(v);
214 if (u.get(i)) result.m_rows.back().flip_all();
215 }
216 return result;
217 }
218
226 static constexpr BitMat from(usize m, usize n, std::invocable<usize, usize> auto f) {
227 BitMat result{m, n};
228 for (auto i = 0uz; i < m; ++i)
229 for (auto j = 0uz; j < n; ++j)
230 if (f(i, j)) result.m_rows[i].set(j);
231 return result;
232 }
233
237
261 static BitMat random(usize m, usize n, double p = 0.5, std::uint64_t seed = 0) {
262 // Keep a single static RNG per thread for all calls to this method, seeded with entropy on the first call.
263 thread_local RNG rng;
264
265 // Edge case handling ...
266 if (p < 0) return BitMat::zeros(m, n);
267
268 // Scale p by 2^64 to remove floating point arithmetic from the main loop below.
269 // If we determine p rounds to 1 then we can just set all elements to 1 and return early.
270 p = p * 0x1p64 + 0.5;
271 if (p >= 0x1p64) return BitMat::ones(m, n);
272
273 // p does not round to 1 so we use a 64-bit URNG and check each draw against the 64-bit scaled p.
274 auto scaled_p = static_cast<std::uint64_t>(p);
275
276 // If a seed was provided, set the RNG's seed to it. Otherwise, we carry on from where we left off.
277 std::uint64_t old_seed = rng.seed();
278 if (seed != 0) rng.set_seed(seed);
279
280 auto result = BitMat::zeros(m, n);
281 for (auto i = 0uz; i < m; ++i) {
282 for (auto j = 0uz; j < n; ++j) {
283 if (rng.u64() < scaled_p) result.m_rows[i].set(j);
284 }
285 }
286
287 // Restore the old seed if necessary.
288 if (seed != 0) rng.set_seed(old_seed);
289
290 return result;
291 }
292
311 static BitMat seeded_random(usize m, usize n, std::uint64_t seed) { return random(m, n, 0.5, seed); }
312
330 static BitMat seeded_random(usize m, std::uint64_t seed) { return random(m, m, 0.5, seed); }
331
345 static BitMat biased_random(usize m, usize n, double p) { return random(m, n, p, 0); }
346
358 static BitMat biased_random(usize m, double p) { return random(m, m, p, 0); }
359
363
371 static constexpr BitMat zero(usize m) { return BitMat{m, m}; }
372
380 static constexpr BitMat identity(usize m) {
381 BitMat result{m, m};
382 for (auto i = 0uz; i < m; ++i) result.m_rows[i].set(i);
383 return result;
384 }
385
397 template<BitStore Store>
398 requires std::same_as<typename Store::word_type, Word>
399 static constexpr BitMat companion(Store const& top_row) {
400 // Edge case:
401 if (top_row.size() == 0) return BitMat{};
402 auto result = BitMat::zero(top_row.size());
403 result.m_rows[0].copy(top_row);
404 result.set_sub_diagonal(1);
405 return result;
406 }
407
418 static constexpr BitMat left_shift(usize n, usize p) {
419 auto result = BitMat::zeros(n, n);
420 result.set_super_diagonal(p);
421 return result;
422 }
423
435 auto result = BitMat::zeros(n, n);
436 result.set_sub_diagonal(p);
437 return result;
438 }
439
451 auto result = BitMat::zeros(n, n);
452 for (auto i = 0uz; i < n; ++i) {
453 auto j = (i + n - p) % n;
454 result.set(i, j);
455 }
456 return result;
457 }
458
470 auto result = BitMat::zeros(n, n);
471 for (auto i = 0uz; i < n; ++i) {
472 auto j = (i + p) % n;
473 result.set(i, j);
474 }
475 return result;
476 }
477
481
483 //
494 template<BitStore Store>
495 requires std::same_as<typename Store::word_type, Word>
496 static std::optional<BitMat> from_row_store(Store const& v, usize r) {
497 // Edge case?
498 if (v.size() == 0) return BitMat{};
499
500 // Error handling ...
501 if (r == 0 || v.size() % r != 0) return std::nullopt;
502
503 // Number of columns (for sure this is an even division)
504 usize c = v.size() / r;
505
506 // Create the bit-matrix and copy the rows.
507 BitMat result{r, c};
508 for (auto i = 0uz; i < r; ++i) {
509 auto begin = i * c;
510 auto end = begin + c;
511 result.m_rows[i].copy(v.span(begin, end));
512 }
513 return result;
514 }
515
531 template<BitStore Store>
532 requires std::same_as<typename Store::word_type, Word>
533 static std::optional<BitMat> from_col_store(Store const& v, usize c) {
534 // Edge case?
535 if (v.size() == 0) return BitMat{};
536
537 // Error handling ...
538 if (c == 0 || v.size() % c != 0) return std::nullopt;
539
540 // Number of columns (for sure this is an even division)
541 usize r = v.size() / c;
542
543 // Create the bit-matrix and copy the rows.
544 BitMat result{r, c};
545 usize iv = 0;
546 for (auto j = 0uz; j < c; ++j) {
547 for (auto i = 0uz; i < r; ++i) {
548 if (v.get(iv)) result.m_rows[i].set(j);
549 iv++;
550 }
551 }
552 return result;
553 }
554
558
579 static std::optional<BitMat> from_string(std::string_view s) {
580 // Edge case
581 if (s.empty()) return BitMat{};
582
583 // We split the string into tokens using the standard regex library.
584 std::string src(s);
585 auto delims = std::regex(R"([\s|,|;]+)");
586 std::sregex_token_iterator iter{src.cbegin(), src.cend(), delims, -1};
587 std::vector<std::string> tokens{iter, {}};
588
589 // Zap any empty tokens & check there is something to do
590 tokens.erase(std::remove_if(tokens.begin(), tokens.end(), [](std::string_view x) { return x.empty(); }),
591 tokens.end());
592 if (tokens.empty()) return BitMat{};
593
594 // We hope to fill a BitMat.
595 BitMat result;
596
597 // Iterate through the possible rows
598 usize n_rows = tokens.size();
599 usize n_cols = 0;
600 for (auto i = 0uz; i < n_rows; ++i) {
601 // Attempt to parse the current token as a row for the bit-matrix.
602 auto r = row_type::from_string(tokens[i]);
603
604 // Parse failure?
605 if (!r) return std::nullopt;
606
607 // We've read a potentially valid BitMat row.
608 if (i == 0) {
609 // First row sets the number of columns
610 n_cols = r->size();
611 result.resize(n_rows, n_cols);
612 } else {
613 // Subsequent rows better have the same number of elements!
614 if (r->size() != n_cols) return std::nullopt;
615 }
616 result.m_rows[i] = *r;
617 }
618 return result;
619 }
620
624
626 constexpr usize rows() const { return m_rows.size(); }
627
629 constexpr usize cols() const { return m_rows.size() > 0 ? m_rows[0].size() : 0; }
630
632 constexpr usize size() const { return rows() * cols(); }
633
635 constexpr bool is_empty() const { return rows() == 0; }
636
640
652 constexpr bool is_square() const { return rows() != 0 && rows() == cols(); }
653
665 constexpr bool is_zero() const { return is_square() && none(); }
666
675 constexpr bool is_identity() const {
676 if (!is_square()) return false;
677 for (auto i = 0uz; i < rows(); ++i) {
678 row_type r = m_rows[i];
679 r.flip(i);
680 if (r.any()) return false;
681 }
682 return true;
683 }
684
694 constexpr bool is_symmetric() const {
695 if (!is_square()) return false;
696 for (auto i = 0uz; i < rows(); ++i) {
697 for (auto j = 0uz; j < cols(); ++j) {
698 if (get(i, j) != get(j, i)) return false;
699 }
700 }
701 return true;
702 }
703
707
717 constexpr usize count_ones() const {
718 usize count = 0;
719 for (const auto& row : m_rows) count += row.count_ones();
720 return count;
721 }
722
730 constexpr usize count_zeros() const { return size() - count_ones(); }
731
741 constexpr usize count_ones_on_diagonal() const {
742 gf2_debug_assert(is_square(), "BitMat is {} x {} but it should be square!", rows(), cols());
743 usize result = 0;
744 for (auto i = 0uz; i < rows(); ++i)
745 if (get(i, i)) ++result;
746 return result;
747 }
748
762 constexpr bool trace() const { return count_ones_on_diagonal() % 2 == 1; }
763
767
781 constexpr bool any() const {
782 for (const auto& row : m_rows)
783 if (row.any()) return true;
784 return false;
785 }
786
800 constexpr bool all() const {
801 for (const auto& row : m_rows)
802 if (!row.all()) return false;
803 return true;
804 }
805
819 constexpr bool none() const {
820 for (const auto& row : m_rows)
821 if (!row.none()) return false;
822 return true;
823 }
824
828
840 constexpr bool get(usize r, usize c) const {
841 gf2_debug_assert(r < rows(), "Row index {} out of bounds [0,{})", r, rows());
842 gf2_debug_assert(c < cols(), "Column index {} out of bounds [0,{})", c, cols());
843 return m_rows[r].get(c);
844 }
845
857 constexpr bool operator()(usize r, usize c) const {
858 gf2_debug_assert(r < rows(), "Row index {} out of bounds [0,{})", r, rows());
859 gf2_debug_assert(c < cols(), "Column index {} out of bounds [0,{})", c, cols());
860 return m_rows[r].get(c);
861 }
862
874 constexpr void set(usize r, usize c, bool val = true) {
875 gf2_debug_assert(r < rows(), "Row index {} out of bounds [0,{})", r, rows());
876 gf2_debug_assert(c < cols(), "Column index {} out of bounds [0,{})", c, cols());
877 m_rows[r].set(c, val);
878 }
879
892 gf2_debug_assert(r < rows(), "Row index {} out of bounds [0,{})", r, rows());
893 gf2_debug_assert(c < cols(), "Column index {} out of bounds [0,{})", c, cols());
894 return m_rows[r][c];
895 }
896
907 constexpr void flip(usize r, usize c) {
908 gf2_debug_assert(r < rows(), "Row index {} out of bounds [0,{})", r, rows());
909 gf2_debug_assert(c < cols(), "Column index {} out of bounds [0,{})", c, cols());
910 m_rows[r].flip(c);
911 }
912
916
928 constexpr const row_type& row(usize r) const {
929 gf2_debug_assert(r < rows(), "Row index {} out of bounds [0,{})", r, rows());
930 return m_rows[r];
931 }
932
945 constexpr row_type& row(usize r) {
946 gf2_debug_assert(r < rows(), "Row index {} out of bounds [0,{})", r, rows());
947 return m_rows[r];
948 }
949
961 constexpr const row_type& operator[](usize r) const {
962 gf2_debug_assert(r < rows(), "Row index {} out of bounds [0,{})", r, rows());
963 return m_rows[r];
964 }
965
978 constexpr row_type& operator[](usize r) {
979 gf2_debug_assert(r < rows(), "Row index {} out of bounds [0,{})", r, rows());
980 return m_rows[r];
981 }
982
986
1003 constexpr BitVec<Word> col(usize c) const {
1004 gf2_debug_assert(c < cols(), "Column {} out of bounds [0, {})", c, cols());
1005 auto result = BitVec<Word>::zeros(rows());
1006 for (auto r = 0uz; r < rows(); ++r) {
1007 if (get(r, c)) { result.set(r); }
1008 }
1009 return result;
1010 }
1011
1015
1026 constexpr void set_all(bool value = true) {
1027 for (auto& row : m_rows) row.set_all(value);
1028 }
1029
1038 constexpr void flip_all() {
1039 for (auto& row : m_rows) row.flip_all();
1040 }
1041
1045
1056 constexpr void set_diagonal(bool val = true) {
1057 gf2_debug_assert(is_square(), "BitMat is {} x {} but it should be square!", rows(), cols());
1058 for (auto i = 0uz; i < rows(); ++i) { set(i, i, val); }
1059 }
1060
1071 constexpr void flip_diagonal() {
1072 gf2_debug_assert(is_square(), "BitMat is {} x {} but it should be square!", rows(), cols());
1073 for (auto i = 0uz; i < rows(); ++i) { flip(i, i); }
1074 }
1075
1088 constexpr void set_super_diagonal(usize d, bool val = true) {
1089 gf2_debug_assert(is_square(), "BitMat is {} x {} but it should be square!", rows(), cols());
1090 for (auto i = 0uz; i < rows() - d; ++i) { set(i, i + d, val); }
1091 }
1092
1105 constexpr void flip_super_diagonal(usize d) {
1106 gf2_debug_assert(is_square(), "BitMat is {} x {} but it should be square!", rows(), cols());
1107 for (auto i = 0uz; i < rows() - d; ++i) { flip(i, i + d); }
1108 }
1109
1122 constexpr void set_sub_diagonal(usize d, bool val = true) {
1123 gf2_debug_assert(is_square(), "BitMat is {} x {} but it should be square!", rows(), cols());
1124 for (auto i = 0uz; i < rows() - d; ++i) { set(i + d, i, val); }
1125 }
1126
1139 constexpr void flip_sub_diagonal(usize d) {
1140 gf2_debug_assert(is_square(), "BitMat is {} x {} but it should be square!", rows(), cols());
1141 for (auto i = 0uz; i < rows() - d; ++i) { flip(i + d, i); }
1142 }
1143
1147
1168 constexpr void resize(usize r, usize c) {
1169 // Edge case ...
1170 if (rows() == r && cols() == c) return;
1171
1172 // Resizes to zero in either dimension is taken as a zap the lot instruction
1173 if (r == 0 || c == 0) r = c = 0;
1174
1175 // Rows, then columns
1176 m_rows.resize(r);
1177 for (auto& row : m_rows) row.resize(c);
1178 }
1179
1189 constexpr void clear() { resize(0, 0); }
1190
1201 constexpr void make_square(usize n) { resize(n, n); }
1202
1206
1219 template<BitStore Store>
1220 requires std::same_as<typename Store::word_type, Word>
1221 constexpr BitMat& append_row(Store const& row) {
1222 gf2_assert_eq(row.size(), cols(), "Row has {} elements but bit-matrix has {} columns!", row.size(), cols());
1223 m_rows.push_back(row);
1224 return *this;
1225 }
1226
1241 template<BitStore Store>
1242 requires std::same_as<typename Store::word_type, Word>
1243 constexpr BitMat& append_row(Store&& row) {
1244 gf2_assert_eq(row.size(), cols(), "Row has {} elements but bit-matrix has {} columns!", row.size(), cols());
1245 m_rows.push_back(std::move(row));
1246 return *this;
1247 }
1248
1261 constexpr BitMat& append_rows(BitMat<Word> const& src) {
1262 gf2_assert_eq(src.cols(), cols(), "Source has {} columns but bit-matrix has {} columns!", src.cols(), cols());
1263 m_rows.insert(m_rows.end(), src.m_rows.begin(), src.m_rows.end());
1264 return *this;
1265 }
1266
1281 constexpr BitMat& append_rows(BitMat<Word>&& src) {
1282 gf2_assert_eq(src.cols(), cols(), "Source has {} columns but bit-matrix has {} columns!", src.cols(), cols());
1283 m_rows.insert(m_rows.end(), std::make_move_iterator(src.m_rows.begin()),
1284 std::make_move_iterator(src.m_rows.end()));
1285 return *this;
1286 }
1287
1300 template<BitStore Store>
1301 requires std::same_as<typename Store::word_type, Word>
1302 constexpr BitMat& append_col(Store const& col) {
1303 gf2_assert_eq(col.size(), rows(), "Column has {} elements but bit-matrix has {} rows!", col.size(), rows());
1304 for (auto i = 0uz; i < rows(); ++i) m_rows[i].push(col.get(i));
1305 return *this;
1306 }
1307
1320 constexpr BitMat& append_cols(BitMat<Word> const& src) {
1321 gf2_assert_eq(src.rows(), rows(), "Source has {} rows but bit-matrix has {} rows!", src.rows(), rows());
1322 for (auto i = 0uz; i < rows(); ++i) m_rows[i].append(src.m_rows[i]);
1323 return *this;
1324 }
1325
1329
1339 constexpr std::optional<BitVec<Word>> remove_row() {
1340 if (rows() == 0) return std::nullopt;
1341 auto row = std::move(m_rows.back());
1342 m_rows.pop_back();
1343 return row;
1344 }
1345
1356 constexpr std::optional<BitMat<Word>> remove_rows(usize k) {
1357 if (rows() < k) return std::nullopt;
1358 auto begin = m_rows.end() - static_cast<std::ptrdiff_t>(k);
1359 auto end = m_rows.end();
1360 auto result = BitMat<Word>(std::vector<row_type>(begin, end));
1361 m_rows.erase(begin, end);
1362 return result;
1363 }
1364
1375 constexpr std::optional<BitVec<Word>> remove_col() {
1376 if (cols() == 0) return std::nullopt;
1377 auto result = col(cols() - 1);
1378 for (auto i = 0uz; i < rows(); ++i) m_rows[i].pop();
1379 return result;
1380 }
1381
1385
1400 constexpr BitMat sub(usize r_start, usize r_end, usize c_start, usize c_end) const {
1401
1402 // Check that the row range is valid.
1403 gf2_assert(r_start <= r_end, "Invalid row range");
1404 gf2_assert(r_end <= rows(), "Row range extends beyond the end of the bit-matrix");
1405
1406 // Check that the column range is valid.
1407 gf2_assert(c_start <= c_end, "Invalid column range");
1408 gf2_assert(c_end <= cols(), "Column range extends beyond the right edge of the bit-matrix");
1409
1410 // Get the number of rows and columns in the sub-matrix.
1411 auto r = r_end - r_start;
1412 auto c = c_end - c_start;
1413
1414 // Create the sub-matrix.
1415 BitMat result{r, c};
1416 for (auto i = 0uz; i < r; ++i) result.m_rows[i].copy(m_rows[i + r_start].span(c_start, c_end));
1417
1418 return result;
1419 }
1420
1431 constexpr void replace(usize top, usize left, BitMat<Word> const& src) {
1432 auto r = src.rows();
1433 auto c = src.cols();
1434 gf2_assert(top + r <= rows(), "Too many rows for the replacement sub-matrix to fit");
1435 gf2_assert(left + c <= cols(), "Too many columns for the replacement sub-matrix to fit");
1436 for (auto i = 0uz; i < r; ++i) m_rows[top + i].span(left, left + c).copy(src.m_rows[i]);
1437 }
1438
1442
1451 constexpr BitMat lower() const {
1452 // Edge case:
1453 if (is_empty()) return BitMat{};
1454
1455 // Start with a copy of the bit-matrix.
1456 BitMat result = *this;
1457
1458 // Set the upper triangular part to zero.
1459 auto nc = cols();
1460 for (auto i = 0uz; i < rows(); ++i) {
1461 auto first = i + 1;
1462 if (first < nc) result.m_rows[i].span(first, nc).set_all(false);
1463 }
1464 return result;
1465 }
1466
1475 constexpr BitMat upper() const {
1476 // Edge case:
1477 if (is_empty()) return BitMat{};
1478
1479 // Start with a copy of the bit-matrix.
1480 auto result = *this;
1481
1482 // Set the upper triangular part to zero.
1483 auto c = cols();
1484 for (auto i = 0uz; i < rows(); ++i) {
1485 auto len = std::min(i, c);
1486 if (len > 0) result.m_rows[i].span(0, len).set_all(false);
1487 }
1488 return result;
1489 }
1490
1501 constexpr BitMat strictly_lower() const {
1502 auto result = lower();
1503 result.set_diagonal(false);
1504 return result;
1505 }
1506
1517 constexpr BitMat strictly_upper() const {
1518 auto result = upper();
1519 result.set_diagonal(false);
1520 return result;
1521 }
1522
1533 constexpr BitMat unit_lower() const {
1534 auto result = lower();
1535 result.set_diagonal(true);
1536 return result;
1537 }
1538
1549 constexpr BitMat unit_upper() const {
1550 auto result = upper();
1551 result.set_diagonal(true);
1552 return result;
1553 }
1554
1558
1560 //
1569 constexpr void swap_rows(usize i, usize j) {
1570 gf2_debug_assert(i < rows(), "Row index {} out of bounds [0,{})", i, rows());
1571 gf2_debug_assert(j < rows(), "Row index {} out of bounds [0,{})", j, rows());
1572 std::swap(m_rows[i], m_rows[j]);
1573 }
1574
1585 constexpr void swap_cols(usize i, usize j) {
1586 gf2_debug_assert(i < cols(), "Column index {} out of bounds [0,{})", i, cols());
1587 gf2_debug_assert(j < cols(), "Column index {} out of bounds [0,{})", j, cols());
1588 for (auto k = 0uz; k < rows(); ++k) m_rows[k].swap(i, j);
1589 }
1590
1601 constexpr void add_identity() {
1602 gf2_debug_assert(is_square(), "This bit-matrix is {} x {} but it should be square!", rows(), cols());
1603 for (auto i = 0uz; i < rows(); ++i) flip(i, i);
1604 }
1605
1609
1622 constexpr BitMat transposed() const {
1623 auto r = rows();
1624 auto c = cols();
1625 BitMat result{c, r};
1626 for (auto i = 0uz; i < r; ++i) {
1627 for (auto j = 0uz; j < c; ++j) {
1628 if (get(i, j)) { result.set(j, i); }
1629 }
1630 }
1631 return result;
1632 }
1633
1646 constexpr void transpose() {
1647 gf2_assert(is_square(), "`transpose()` requires a square matrix");
1648 for (auto i = 0uz; i < rows(); ++i) {
1649 for (auto j = 0uz; j < i; ++j) {
1650 if (get(i, j) != get(j, i)) {
1651 flip(i, j);
1652 flip(j, i);
1653 }
1654 }
1655 }
1656 }
1657
1661
1679 constexpr BitMat to_the(usize n, bool n_is_log2 = false) const {
1680 gf2_assert(is_square(), "Bit-matrix is {} x {} but it should be square!", rows(), cols());
1681
1682 // Perhaps we just need lots of square steps? Note that 2^0 = 1 so M^(2^0) = M.
1683 if (n_is_log2) {
1684 auto result = *this;
1685 for (auto i = 0uz; i < n; ++i) result = dot(result, result);
1686 return result;
1687 }
1688
1689 // Otherwise we need square & multiply steps but we first handle the edge case:
1690 if (n == 0) return BitMat::identity(rows());
1691
1692 // OK n != 0: Note that if e.g. n = 0b00010111 then std::bit_floor(n) = 0b00010000.
1693 usize n_bit = std::bit_floor(n);
1694
1695 // Start with a copy of this bit-matrix which takes care of the most significant binary digit in n.
1696 auto result = *this;
1697 n_bit >>= 1;
1698
1699 // More to go?
1700 while (n_bit) {
1701 // Always square and then multiply if necessary (i.e. if current bit in n is set).
1702 result = dot(result, result);
1703 if (n & n_bit) result = dot(result, *this);
1704
1705 // On to the next bit position in n.
1706 n_bit >>= 1;
1707 }
1708 return result;
1709 }
1710
1714
1738 gf2_assert(!is_empty(), "Bit-matrix must not be empty");
1739
1740 // We return a bit-vector that shows which columns have a pivot -- start by assuming none.
1741 auto has_pivot = BitVec<Word>::zeros(cols());
1742
1743 // The current row of the echelon form we are working on.
1744 auto r = 0uz;
1745 auto nr = rows();
1746
1747 // Iterate over each column.
1748 for (auto j = 0uz; j < cols(); ++j) {
1749 // Find a non-zero entry in this column below the diagonal (a "pivot").
1750 auto p = r;
1751 while (p < nr && !get(p, j)) p++;
1752
1753 // Did we find a pivot in this column?
1754 if (p < nr) {
1755 // Mark this column as having a pivot.
1756 has_pivot.set(j);
1757
1758 // If necessary, swap the current row with the row that has the pivot.
1759 if (p != r) swap_rows(p, r);
1760
1761 // Below the working row make sure column j is zero by elimination if necessary.
1762 auto row_r = m_rows[r];
1763 for (auto i = r + 1; i < nr; ++i)
1764 if (get(i, j)) m_rows[i] ^= row_r;
1765
1766 // Move to the next row. If we've reached the end of the matrix, we're done.
1767 r += 1;
1768 if (r == nr) break;
1769 }
1770 }
1771
1772 // Return the bit-vector that shows which columns have a pivot.
1773 return has_pivot;
1774 }
1775
1795 // Start with the echelon form.
1796 auto has_pivot = to_echelon_form();
1797
1798 // Iterate over each row from the bottom up - Gauss Jordan elimination.
1799 for (auto r = rows(); r--;) {
1800 // Find the first set bit in the current row if there is one.
1801 if (auto p = m_rows[r].first_set()) {
1802 // Clear out everything in column p *above* row r (already cleared out below the pivot).
1803 auto row_r = m_rows[r];
1804 for (auto i = 0uz; i < r; ++i)
1805 if (get(i, *p)) m_rows[i] ^= row_r;
1806 }
1807 }
1808
1809 // Return the bit-vector that shows which columns have a pivot.
1810 return has_pivot;
1811 }
1812
1816
1824 std::optional<BitMat> inverse() const {
1825 // The bit-matrix must be square & non-empty.
1826 if (is_empty() || !is_square()) return std::nullopt;
1827
1828 // Create a copy of the bit-matrix & augment it with the identity matrix on the right.
1829 auto matrix = *this;
1830 auto nr = rows();
1831 auto nc = cols();
1832 matrix.append_cols(BitMat::identity(nr));
1833
1834 // Transform the augmented matrix to reduced row-echelon form.
1835 matrix.to_reduced_echelon_form();
1836
1837 // If all went well the left half is the identity matrix and the right half is the inverse.
1838 if (matrix.sub(0, nr, 0, nc).is_identity()) return matrix.sub(0, nr, nc, nc * 2);
1839
1840 return std::nullopt;
1841 }
1842
1856 static constexpr double probability_invertible(usize n) {
1857 // A 0x0 matrix is not well defined throw an error!
1858 if (n == 0) throw std::invalid_argument("Matrix should not be 0x0--likely an error somewhere upstream!");
1859
1860 // Formula is p(n) = \prod_{k = 1}^{n} (1 - 2^{-k}) which runs out of juice once n hits any size at all!
1861 usize n_prod = std::min(n, static_cast<size_t>(std::numeric_limits<double>::digits));
1862
1863 // Compute the product over the range that matters
1864 double product = 1;
1865 double pow2 = 1;
1866 while (n_prod--) {
1867 pow2 *= 0.5;
1868 product *= 1 - pow2;
1869 }
1870 return product;
1871 }
1872
1886 static constexpr double probability_singular(usize n) { return 1 - probability_invertible(n); }
1887
1891
1900 BitLU<Word> LU() const { return BitLU<Word>{*this}; }
1901
1905
1919 template<BitStore Rhs>
1920 requires std::same_as<typename Rhs::word_type, Word>
1921 auto solver_for(Rhs const& b) const {
1922 return BitGauss<Word>{*this, b};
1923 }
1924
1935 template<BitStore Rhs>
1936 requires std::same_as<typename Rhs::word_type, Word>
1937 auto x_for(Rhs const& b) const {
1938 return solver_for(b)();
1939 }
1940
1944
1964 gf2_assert(is_square(), "Bit-matrix must be square not {} x {}", rows(), cols());
1966 }
1967
1978 auto n_companions = top_rows.size();
1979 if (n_companions == 0) return BitPoly<Word>::zero();
1980
1981 // Compute the product of the characteristic polynomials of the companion matrices.
1982 auto result = companion_matrix_characteristic_polynomial(top_rows[0]);
1983 for (auto i = 1uz; i < n_companions; ++i) { result *= companion_matrix_characteristic_polynomial(top_rows[i]); }
1984 return result;
1985 }
1986
2002 auto n = top_row.size();
2003
2004 // The characteristic polynomial is degree n with n + 1 coefficients (leading coefficient is 1).
2005 auto coeffs = BitVec<Word>::ones(n + 1);
2006
2007 // The lower order coefficients are the top row of the companion matrix in reverse order.
2008 for (auto j = 0uz; j < n; ++j) { coeffs.set(n - j - 1, top_row.get(j)); }
2009 return BitPoly<Word>{std::move(coeffs)};
2010 }
2011
2025 std::vector<BitVec<Word>> frobenius_form() const {
2026 // The bit-matrix must be square.
2027 gf2_assert(is_square(), "Bit-matrix must be square not {} x {}", rows(), cols());
2028
2029 // Space for the top rows of the companion matrices which we will return.
2030 auto nr = rows();
2031 auto top_rows = std::vector<BitVec<Word>>{};
2032 top_rows.reserve(nr);
2033
2034 // Make a working copy of the bit-matrix to work through using Danilevsky's algorithm.
2035 auto copy = *this;
2036 while (nr > 0) {
2037 auto companion = copy.danilevsky_step(nr);
2038 nr -= companion.size();
2039 top_rows.push_back(companion);
2040 }
2041 return top_rows;
2042 }
2043
2047
2060 constexpr void operator^=(BitMat<Word> const& rhs) {
2061 gf2_assert(rows() == rhs.rows(), "Row dimensions do not match: {} != {}.", rows(), rhs.rows());
2062 gf2_assert(cols() == rhs.cols(), "Column dimensions do not match: {} != {}.", cols(), rhs.cols());
2063 for (auto i = 0uz; i < rows(); ++i) row(i) ^= rhs.row(i);
2064 }
2065
2078 constexpr void operator&=(BitMat<Word> const& rhs) {
2079 gf2_assert(rows() == rhs.rows(), "Row dimensions do not match: {} != {}.", rows(), rhs.rows());
2080 gf2_assert(cols() == rhs.cols(), "Column dimensions do not match: {} != {}.", cols(), rhs.cols());
2081 for (auto i = 0uz; i < rows(); ++i) row(i) &= rhs.row(i);
2082 }
2083
2096 constexpr void operator|=(BitMat<Word> const& rhs) {
2097 gf2_assert(rows() == rhs.rows(), "Row dimensions do not match: {} != {}.", rows(), rhs.rows());
2098 gf2_assert(cols() == rhs.cols(), "Column dimensions do not match: {} != {}.", cols(), rhs.cols());
2099 for (auto i = 0uz; i < rows(); ++i) row(i) |= rhs.row(i);
2100 }
2101
2114 constexpr auto operator^(BitMat<Word> const& rhs) const {
2115 gf2_assert(rows() == rhs.rows(), "Row dimensions do not match: {} != {}.", rows(), rhs.rows());
2116 gf2_assert(cols() == rhs.cols(), "Column dimensions do not match: {} != {}.", cols(), rhs.cols());
2117 auto result = *this;
2118 result ^= rhs;
2119 return result;
2120 }
2121
2134 constexpr auto operator&(BitMat<Word> const& rhs) const {
2135 gf2_assert(rows() == rhs.rows(), "Row dimensions do not match: {} != {}.", rows(), rhs.rows());
2136 gf2_assert(cols() == rhs.cols(), "Column dimensions do not match: {} != {}.", cols(), rhs.cols());
2137 auto result = *this;
2138 result &= rhs;
2139 return result;
2140 }
2141
2154 constexpr auto operator|(BitMat<Word> const& rhs) const {
2155 gf2_assert(rows() == rhs.rows(), "Row dimensions do not match: {} != {}.", rows(), rhs.rows());
2156 gf2_assert(cols() == rhs.cols(), "Column dimensions do not match: {} != {}.", cols(), rhs.cols());
2157 auto result = *this;
2158 result |= rhs;
2159 return result;
2160 }
2161
2170 constexpr auto operator~() {
2171 auto result = *this;
2172 result.flip_all();
2173 return result;
2174 }
2175
2179
2192 constexpr void operator+=(BitMat<Word> const& rhs) { operator^=(rhs); }
2193
2206 constexpr void operator-=(BitMat<Word> const& rhs) { operator^=(rhs); }
2207
2220 constexpr auto operator+(BitMat<Word> const& rhs) const {
2221 auto result = *this;
2222 result += rhs;
2223 return result;
2224 }
2225
2238 constexpr auto operator-(BitMat<Word> const& rhs) const {
2239 auto result = *this;
2240 result -= rhs;
2241 return result;
2242 }
2243
2247
2262 std::string to_binary_string(std::string_view row_sep = "\n", std::string_view bit_sep = "",
2263 std::string_view row_prefix = "", std::string_view row_suffix = "") const {
2264 usize nr = rows();
2265
2266 // Edge case?
2267 if (nr == 0) return std::string{};
2268
2269 usize nc = cols();
2270 usize per_row = nc + (nc - 1) * bit_sep.size() + row_prefix.size() + row_suffix.size();
2271 std::string result;
2272 result.reserve(nr * per_row);
2273
2274 for (auto i = 0uz; i < nr; ++i) {
2275 result += m_rows[i].to_binary_string(bit_sep, row_prefix, row_suffix);
2276 if (i + 1 < nr) result += row_sep;
2277 }
2278 return result;
2279 }
2280
2290 std::string to_compact_binary_string() const { return to_binary_string(" "); }
2291
2301 std::string to_string() const { return to_binary_string("\n"); }
2302
2316 std::string to_pretty_string() const { return to_binary_string("\n", " ", "\u2502", "\u2502"); }
2317
2345 std::string to_hex_string(std::string_view row_sep = "\n") const {
2346 // Edge case.
2347 usize nr = rows();
2348 if (nr == 0) return std::string{};
2349
2350 // Set up the return string with enough space for the entire matrix.
2351 usize nc = cols();
2352 std::string result;
2353 result.reserve(nr * (nc / 4 + row_sep.size()));
2354
2355 // Append each row to the result string.
2356 for (auto i = 0uz; i < nr; ++i) {
2357 result += m_rows[i].to_hex_string();
2358 if (i + 1 < nr) result += row_sep;
2359 }
2360 return result;
2361 }
2362
2390 std::string to_compact_hex_string() const { return to_hex_string(" "); }
2391
2392 // The usual output stream operator for a bit-store
2393 constexpr std::ostream& operator<<(std::ostream& s) const { return s << to_string(); }
2394
2398
2407 friend constexpr bool operator==(BitMat const& lhs, BitMat const& rhs) {
2408 // Edge case.
2409 if (&lhs == &rhs) return true;
2410
2411 // Check the active words for equality.
2412 auto row_count = lhs.rows();
2413 if (rhs.rows() != row_count) return false;
2414 for (auto i = 0uz; i < row_count; ++i)
2415 if (lhs.m_rows[i] != rhs.m_rows[i]) return false;
2416
2417 // Made it
2418 return true;
2419 }
2420
2421private:
2422 // Runs a check to see that all the rows in a vector of rows have the same number of columns.
2423 constexpr bool check_rows(std::vector<row_type> const& rows) const {
2424 if (rows.empty()) return true;
2425 auto nc = rows[0].size();
2426 for (auto i = 1uz; i < rows.size(); ++i) {
2427 if (rows[i].size() != nc) return false;
2428 }
2429 return true;
2430 }
2431
2432 // Performs a single step of Danilevsky's algorithm to reduce a bit-matrix to Frobenius form.
2433 //
2434 // Frobenius form is block diagonal with one or more companion matrices along the diagonal. All matrices can be
2435 // reduced to this form via a sequence of similarity transformations. This methods performs a single one of those
2436 // transformations.
2437 //
2438 // This `frobenius_form` function calls here with an N x N bit-matrix. In each call the method concentrates on just
2439 // the top-left n x n sub-matrix. On the first call, n should be set to N. The method returns the top row of the
2440 // companion matrix that is the transformation of the bottom-right (n-k) x (n-k) sub-matrix. The caller can store
2441 // that result, decrement n, and call again on the smaller top-left sub-matrix. It may be that the whole matrix
2442 // gets reduced to a single companion matrix in one step and then there will be no need to call again.
2443 //
2444 // The method tries to transform the n x n top-left sub-matrix to a companion matrix working from its bottom-right
2445 // corner up. It stops when it gets to a point where the bottom-right (n-k) x (n-k) sub-matrix is in companion form
2446 // and returns the top row of that sub-matrix. The caller can store that result, decrement n, and call again on
2447 // the smaller top-left sub-matrix.
2448 //
2449 // NOTE: This method panics if the bit-matrix is not square.
2450 BitVec<Word> danilevsky_step(usize n) {
2451 gf2_assert(n <= rows(), "No top-left {} x {} sub-matrix in a matrix with {} rows", n, n, rows());
2452
2453 // Edge case: A 1 x 1 matrix is already in companion form.
2454 if (n == 1) return BitVec<Word>::constant(1, get(0, 0));
2455
2456 // Step k of algorithm attempts to reduce row k to companion form.
2457 // By construction, rows k+1 or later are already in companion form.
2458 auto k = n - 1;
2459 while (k > 0) {
2460 // If row k's sub-diagonal is all zeros we look for an earlier column with a 1.
2461 // If found, we swap that column here & then swap the equivalent rows to preserve similarity.
2462 if (!get(k, k - 1)) {
2463 for (auto j = 0uz; j < k - 1; ++j) {
2464 if (get(k, j)) {
2465 swap_rows(j, k - 1);
2466 swap_cols(j, k - 1);
2467 break;
2468 }
2469 }
2470 }
2471
2472 // No joy? Perhaps we have a companion matrix in the lower left corner and can return its top row?
2473 if (!get(k, k - 1)) break;
2474
2475 // Still no joy? The sub-diagonal is not all zeros so apply transform to make it so: self <- M^-1 * self *
2476 // M, where M is the identity matrix with the (k-1)'st row replaced by the k'th row of `self`. We can
2477 // sparsely represent M as just a clone of that k'th row of `self`.
2478 auto m = m_rows[k];
2479
2480 // Note the M^-1 is the same as M and self <- M^-1 * self just alters a few of our elements.
2481 for (auto j = 0uz; j < n; ++j) set(k - 1, j, dot(m, col(j)));
2482
2483 // We also use the sparsity of M when computing self <- self * M.
2484 for (auto i = 0uz; i < k; ++i) {
2485 for (auto j = 0uz; j < n; ++j) {
2486 auto tmp = get(i, k - 1) && m.get(j);
2487 if (j == k - 1) {
2488 set(i, j, tmp);
2489 } else {
2490 set(i, j, get(i, j) ^ tmp);
2491 }
2492 }
2493 }
2494
2495 // Now put row k into companion form of all zeros with one on the sub-diagonal.
2496 // All the rows below k are already in companion form.
2497 m_rows[k].set_all(false);
2498 set(k, k - 1);
2499
2500 // Done with row k
2501 k -= 1;
2502 }
2503
2504 // At this point, k == 0 OR the bit-matrix has non-removable zero on the sub-diagonal of row k.
2505 // Either way, the bottom-right (n-k) x (n-k) sub-matrix, starting at self[k][k], is in companion form.
2506 // We return the top row of that companion sub-matrix.
2507 auto top_row = BitVec<Word>::zeros(n - k);
2508 for (auto j = 0uz; j < n - k; ++j) top_row.set(j, get(k, k + j));
2509
2510 // Done
2511 return top_row;
2512 }
2513};
2514
2515// --------------------------------------------------------------------------------------------------------------------
2516// Matrix multiplication ...
2517// -------------------------------------------------------------------------------------------------------------------
2518
2520template<Unsigned Word, BitStore Rhs>
2521 requires std::same_as<typename Rhs::word_type, Word>
2522constexpr auto
2523dot(BitMat<Word> const& lhs, Rhs const& rhs) {
2524 gf2_assert_eq(lhs.cols(), rhs.size(), "Incompatible dimensions: {} != {}", lhs.cols(), rhs.size());
2525 usize r = lhs.rows();
2526 BitVec<Word> retval{r};
2527 for (auto i = 0uz; i < r; ++i) retval[i] = dot(lhs.row(i), rhs);
2528 return retval;
2529}
2530
2532template<Unsigned Word, BitStore Rhs>
2533 requires std::same_as<typename Rhs::word_type, Word>
2534constexpr auto
2535operator*(BitMat<Word> const& lhs, Rhs const& rhs) {
2536 return dot(lhs, rhs);
2537}
2538
2542template<Unsigned Word, BitStore Lhs>
2543 requires std::same_as<typename Lhs::word_type, Word>
2544constexpr auto
2545dot(Lhs const& lhs, BitMat<Word> const& rhs) {
2546 gf2_assert_eq(lhs.size(), rhs.rows(), "Incompatible dimensions: {} != {}", lhs.size(), rhs.rows());
2547 usize c = rhs.cols();
2548 BitVec<Word> retval{c};
2549 for (auto j = 0uz; j < c; ++j) retval[j] = dot(lhs, rhs.col(j));
2550 return retval;
2551}
2552
2556template<Unsigned Word, BitStore Lhs>
2557 requires std::same_as<typename Lhs::word_type, Word>
2558constexpr auto
2559operator*(Lhs const& lhs, BitMat<Word> const& rhs) {
2560 return dot(lhs, rhs);
2561}
2562
2564template<Unsigned Word>
2565constexpr auto
2566dot(BitMat<Word> const& lhs, BitMat<Word> const& rhs) {
2567 gf2_assert_eq(lhs.cols(), rhs.rows(), "Incompatible dimensions: {} != {}", lhs.cols(), rhs.rows());
2568 usize r = lhs.rows();
2569 usize c = rhs.cols();
2570 BitMat<Word> retval{r, c};
2571 for (auto j = 0uz; j < c; ++j) {
2572 auto rhs_col = rhs.col(j);
2573 for (auto i = 0uz; i < r; ++i) retval.set(i, j, dot(lhs.row(i), rhs_col));
2574 }
2575 return retval;
2576}
2577
2579template<Unsigned Word>
2580constexpr auto
2581operator*(BitMat<Word> const& lhs, BitMat<Word> const& rhs) {
2582 return dot(lhs, rhs);
2583}
2584
2585// --------------------------------------------------------------------------------------------------------------------
2586// Some utility methods to print multiple matrices & vectors side-by-side ...
2587// -------------------------------------------------------------------------------------------------------------------
2588
2590template<Unsigned Word, BitStore Rhs>
2591 requires std::same_as<typename Rhs::word_type, Word>
2592std::string
2593string_for(BitMat<Word> const& A, Rhs const& b) {
2594
2595 // If either is empty there was likely a bug somewhere!
2596 gf2_assert(!A.is_empty(), "Matrix A is empty which is likely an error!");
2597 gf2_assert(!b.is_empty(), "Vector b is empty which is likely an error!");
2598
2599 // Most rows we ever print
2600 auto nr = std::max(A.rows(), b.size());
2601
2602 // Space filler might be needed if the matrix has fewer rows than the vector
2603 std::string A_fill(A.cols(), ' ');
2604
2605 // Little lambda that turns a bool into a string
2606 auto b2s = [](bool x) { return x ? "1" : "0"; };
2607
2608 // Build the result string
2609 std::string result;
2610 for (auto r = 0uz; r < nr; ++r) {
2611 auto A_str = r < A.rows() ? A[r].to_string() : A_fill;
2612 auto b_str = r < b.size() ? b2s(b[r]) : " ";
2613 result += A_str + "\t" + b_str;
2614 if (r + 1 < nr) result += "\n";
2615 }
2616
2617 return result;
2618}
2619
2621template<Unsigned Word, BitStore Rhs>
2622 requires std::same_as<typename Rhs::word_type, Word>
2623std::string
2624string_for(BitMat<Word> const& A, Rhs const& b, Rhs const& c) {
2625
2626 // If either is empty there was likely a bug somewhere!
2627 gf2_assert(!A.is_empty(), "Matrix A is empty which is likely an error!");
2628 gf2_assert(!b.is_empty(), "Vector b is empty which is likely an error!");
2629 gf2_assert(!c.is_empty(), "Vector c is empty which is likely an error!");
2630
2631 // Most rows we ever print
2632 auto nr = std::max({A.rows(), b.size(), c.size()});
2633
2634 // Space filler might be needed if the matrix has fewer rows than the vector
2635 std::string A_fill(A.cols(), ' ');
2636
2637 // Little lambda that turns a bool into a string
2638 auto b2s = [](bool x) { return x ? "1" : "0"; };
2639
2640 // Build the result string
2641 std::string result;
2642 for (auto r = 0uz; r < nr; ++r) {
2643 auto A_str = r < A.rows() ? A[r].to_string() : A_fill;
2644 auto b_str = r < b.size() ? b2s(b[r]) : " ";
2645 auto c_str = r < c.size() ? b2s(c[r]) : " ";
2646 result += A_str + "\t" + b_str + "\t" + c_str;
2647 if (r + 1 < nr) result += "\n";
2648 }
2649
2650 return result;
2651}
2652
2654template<Unsigned Word, BitStore Rhs>
2655 requires std::same_as<typename Rhs::word_type, Word>
2656std::string
2657string_for(BitMat<Word> const& A, Rhs const& b, Rhs const& c, Rhs const& d) {
2658
2659 // If either is empty there was likely a bug somewhere!
2660 gf2_assert(!A.is_empty(), "Matrix A is empty which is likely an error!");
2661 gf2_assert(!b.is_empty(), "Vector b is empty which is likely an error!");
2662 gf2_assert(!c.is_empty(), "Vector c is empty which is likely an error!");
2663 gf2_assert(!d.is_empty(), "Vector d is empty which is likely an error!");
2664
2665 // Most rows we ever print
2666 auto nr = std::max({A.rows(), b.size(), c.size(), d.size()});
2667
2668 // Space filler might be needed if the matrix has fewer rows than the vector
2669 std::string A_fill(A.cols(), ' ');
2670
2671 // Little lambda that turns a bool into a string
2672 auto b2s = [](bool x) { return x ? "1" : "0"; };
2673
2674 // Build the result string
2675 std::string result;
2676 for (auto r = 0uz; r < nr; ++r) {
2677 auto A_str = r < A.rows() ? A[r].to_string() : A_fill;
2678 auto b_str = r < b.size() ? b2s(b[r]) : " ";
2679 auto c_str = r < c.size() ? b2s(c[r]) : " ";
2680 auto d_str = r < d.size() ? b2s(d[r]) : " ";
2681 result += A_str + "\t" + b_str + "\t" + c_str + "\t" + d_str;
2682 if (r + 1 < nr) result += "\n";
2683 }
2684
2685 return result;
2686}
2687
2689template<Unsigned Word>
2690std::string
2692
2693 // If either is empty there was likely a bug somewhere!
2694 gf2_assert(!A.is_empty(), "Matrix A is empty which is likely an error!");
2695 gf2_assert(!B.is_empty(), "Matrix B is empty which is likely an error!");
2696
2697 // Most rows we ever print
2698 auto nr = std::max(A.rows(), B.rows());
2699
2700 // Space filler might be needed if the matrix has fewer rows than the vector
2701 std::string A_fill(A.cols(), ' ');
2702 std::string B_fill(B.cols(), ' ');
2703
2704 // Build the result string
2705 std::string result;
2706 for (auto r = 0uz; r < nr; ++r) {
2707 auto A_str = r < A.rows() ? A[r].to_string() : A_fill;
2708 auto B_str = r < B.rows() ? B[r].to_string() : B_fill;
2709 result += A_str + "\t" + B_str;
2710 if (r + 1 < nr) result += "\n";
2711 }
2712
2713 return result;
2714}
2715
2717template<Unsigned Word>
2718std::string
2719string_for(BitMat<Word> const& A, BitMat<Word> const& B, BitMat<Word> const& C) {
2720
2721 // If either is empty there was likely a bug somewhere!
2722 gf2_assert(!A.is_empty(), "Matrix A is empty which is likely an error!");
2723 gf2_assert(!B.is_empty(), "Matrix B is empty which is likely an error!");
2724 gf2_assert(!C.is_empty(), "Matrix C is empty which is likely an error!");
2725
2726 // Most rows we ever print
2727 auto nr = std::max({A.rows(), B.rows(), C.rows()});
2728
2729 // Space filler might be needed if the matrix has fewer rows than the vector
2730 std::string A_fill(A.cols(), ' ');
2731 std::string B_fill(B.cols(), ' ');
2732 std::string C_fill(C.cols(), ' ');
2733
2734 // Build the result string
2735 std::string result;
2736 for (auto r = 0uz; r < nr; ++r) {
2737 auto A_str = r < A.rows() ? A[r].to_string() : A_fill;
2738 auto B_str = r < B.rows() ? B[r].to_string() : B_fill;
2739 auto C_str = r < C.rows() ? C[r].to_string() : C_fill;
2740 result += A_str + "\t" + B_str + "\t" + C_str;
2741 if (r + 1 < nr) result += "\n";
2742 }
2743
2744 return result;
2745}
2746
2747} // namespace gf2
2748
2749// --------------------------------------------------------------------------------------------------------------------
2750// Specialises `std::formatter` to handle bit-matrices ...
2751// -------------------------------------------------------------------------------------------------------------------
2752
2771template<gf2::Unsigned Word>
2772struct std::formatter<gf2::BitMat<Word>> {
2773
2775 constexpr auto parse(std::format_parse_context const& ctx) {
2776 auto it = ctx.begin();
2777 while (it != ctx.end() && *it != '}') {
2778 switch (*it) {
2779 case 'p': m_pretty = true; break;
2780 case 'x': m_hex = true; break;
2781 default: m_error = true;
2782 }
2783 ++it;
2784 }
2785 return it;
2786 }
2787
2789 template<class FormatContext>
2790 auto format(gf2::BitMat<Word> const& rhs, FormatContext& ctx) const {
2791 // Was there a format specification error?
2792 if (m_error) return std::format_to(ctx.out(), "'UNRECOGNIZED FORMAT SPECIFIER FOR BIT-MATRIX'");
2793
2794 // Special handling requested?
2795 if (m_hex) return std::format_to(ctx.out(), "{}", rhs.to_hex_string());
2796 if (m_pretty) return std::format_to(ctx.out(), "{}", rhs.to_pretty_string());
2797
2798 // Default
2799 return std::format_to(ctx.out(), "{}", rhs.to_string());
2800 }
2801
2802 bool m_hex = false;
2803 bool m_pretty = false;
2804 bool m_error = false;
2805};
Polynomials over GF(2). See the BitPoly page for more details.
Vectors over GF(2) with compactly stored bit-elements in a standard vector of primitive unsigned word...
#define gf2_assert_eq(lhs, rhs,...)
A confirmation macro that checks the equality of two values lhs and rhs. On failure,...
Definition assert.h:113
#define gf2_debug_assert(cond,...)
A confirmation macro that checks a boolean condition cond. On failure, the confirmation prints an e...
Definition assert.h:68
#define gf2_assert(cond,...)
A confirmation macro that checks a boolean condition cond. On failure, the confirmation prints an e...
Definition assert.h:98
The BitGauss class is a Gaussian elimination solver for systems of linear equations over GF(2)....
Definition BitGauss.h:44
The BitLU class provides the LU decomposition for bit-matrices.
Definition BitLU.h:29
A dynamically-sized matrix over GF(2) stored as a vector of bit-vectors representing the rows of the ...
Definition BitMat.h:32
std::string to_pretty_string() const
Returns the default pretty string representation for this bit-matrix.
Definition BitMat.h:2316
constexpr bool trace() const
Returns the "sum" of the main diagonal elements of the bit-matrix.
Definition BitMat.h:762
static BitMat seeded_random(usize m, std::uint64_t seed)
Factory method to generate a square bit-matrix of size m x m where the elements are from independent ...
Definition BitMat.h:330
constexpr void transpose()
Transposes a square bit-matrix in place.
Definition BitMat.h:1646
static constexpr BitMat zeros(usize m, usize n)
Factory method to create the m x n zero bit-matrix with all the elements set to 0.
Definition BitMat.h:124
constexpr BitMat strictly_lower() const
Returns an independent clone of the strictly lower triangular part of the bit-matrix.
Definition BitMat.h:1501
constexpr auto operator|(BitMat< Word > const &rhs) const
TheOR of this with a bit-matrix rhs returning a new bit-matrix.
Definition BitMat.h:2154
std::string to_compact_hex_string() const
Returns a compact "hex" string representation of the bit-matrix.
Definition BitMat.h:2390
constexpr BitMat upper() const
Returns an independent clone of the upper triangular part of the bit-matrix.
Definition BitMat.h:1475
static constexpr BitMat from(usize m, usize n, std::invocable< usize, usize > auto f)
Factory method to construct an bit-matrix by repeatedly calling f(i, j) for each index pair.
Definition BitMat.h:226
constexpr bool is_square() const
Returns true if this a square bit-matrix? Note that empty bit-matrices are NOT considered square.
Definition BitMat.h:652
static constexpr BitMat ones(usize m)
Factory method to create the m x m square bit-matrix with all the elements set to 1.
Definition BitMat.h:154
constexpr bool get(usize r, usize c) const
Returns true if the element at row r and column c is set.
Definition BitMat.h:840
constexpr usize count_ones_on_diagonal() const
Returns the number of ones on the main diagonal of the bit-matrix.
Definition BitMat.h:741
constexpr void operator+=(BitMat< Word > const &rhs)
In-place addition with a bit-matrix rhs – in GF(2) addition is the same as XOR.
Definition BitMat.h:2192
constexpr row_type & row(usize r)
Returns a read-write reference to the row at index r.
Definition BitMat.h:945
constexpr void flip(usize r, usize c)
Flips the bit at row r and column c.
Definition BitMat.h:907
friend constexpr bool operator==(BitMat const &lhs, BitMat const &rhs)
Equality operator checks that any pair of bit-matrices are equal in content.
Definition BitMat.h:2407
static BitMat right_rotation(usize n, usize p)
Constructs the n x n rotate-right by p places matrix.
Definition BitMat.h:469
constexpr BitMat & append_col(Store const &col)
Appends a single column col onto the right of the bit-matrix so M -> M|col.
Definition BitMat.h:1302
constexpr void set(usize r, usize c, bool val=true)
Sets the bit at row r and column c to the bool value val. The default is to set the bit to true.
Definition BitMat.h:874
std::string to_string() const
Returns the default string representation for this bit-matrix.
Definition BitMat.h:2301
constexpr auto operator-(BitMat< Word > const &rhs) const
Returns a new bit-matrix that is *this - rhs which is *this ^ rhs in GF(2).
Definition BitMat.h:2238
Word word_type
The underlying unsigned word type used to store the bits.
Definition BitMat.h:42
static constexpr BitMat zeros(usize m)
Factory method to create the m x m square bit-matrix with all the elements set to 0.
Definition BitMat.h:133
static constexpr double probability_invertible(usize n)
Class method that returns the probability that a square n x n bit-matrix is invertible if each elemen...
Definition BitMat.h:1856
std::vector< BitVec< Word > > frobenius_form() const
Returns the Frobenius form of this bit-matrix in compact top-row only form.
Definition BitMat.h:2025
static BitPoly< Word > companion_matrix_characteristic_polynomial(BitVec< Word > const &top_row)
Class method to return the characteristic polynomial of a companion matrix as a gf2::BitPoly.
Definition BitMat.h:2001
static constexpr double probability_singular(usize n)
Class method that returns the probability that a square n x n bit-matrix is singular if each element ...
Definition BitMat.h:1886
static constexpr BitMat zero(usize m)
Factory method to create the m x m square "zero" bit-matrix.
Definition BitMat.h:371
constexpr bool is_identity() const
Returns true if this is the identity bit-matrix.
Definition BitMat.h:675
static BitMat biased_random(usize m, usize n, double p)
Factory method to generate a bit-matrix of size m x n where the elements are from independent fair co...
Definition BitMat.h:345
constexpr auto operator+(BitMat< Word > const &rhs) const
Returns a new bit-matrix that is *this + rhs which is *this ^ rhs in GF(2).
Definition BitMat.h:2220
constexpr BitMat(std::vector< row_type > const &rows)
Construct a bit-matrix by copying a given set of rows which can be any BitStore subclass.
Definition BitMat.h:92
constexpr void flip_diagonal()
Flips all the elements on the main diagonal of a square bit-matrix.
Definition BitMat.h:1071
constexpr usize size() const
Returns the totalnumber of elements in the bit-matrix.
Definition BitMat.h:632
constexpr BitMat(std::vector< BitVec< Word > > &&rows)
Construct a bit-matrix by moving the given rows.
Definition BitMat.h:109
constexpr void flip_sub_diagonal(usize d)
Flips all the elements on the sub-diagonal d of a square bit-matrix.
Definition BitMat.h:1139
constexpr void set_super_diagonal(usize d, bool val=true)
Sets the elements on super-diagonal d of a square bit-matrix to the boolean value val.
Definition BitMat.h:1088
constexpr BitMat unit_lower() const
Returns an independent clone of the unit lower triangular part of the bit-matrix.
Definition BitMat.h:1533
constexpr void set_all(bool value=true)
Sets all the elements of the bit-matrix to the specified boolean value.
Definition BitMat.h:1026
static constexpr BitMat outer_sum(Lhs const &u, Rhs const &v)
Factory method to create the m x n bit-matrix from the outer sum of the given bit-stores.
Definition BitMat.h:210
static BitMat seeded_random(usize m, usize n, std::uint64_t seed)
Factory method to generate a bit-matrix of size m x n where the elements are from independent fair co...
Definition BitMat.h:311
constexpr usize cols() const
Returns the number of columns in the bit-matrix.
Definition BitMat.h:629
static BitPoly< Word > frobenius_matrix_characteristic_polynomial(std::vector< BitVec< Word > > const &top_rows)
Class method that returns the characteristic polynomial of a Frobenius matrix as a gf2::BitPoly.
Definition BitMat.h:1977
constexpr void flip_super_diagonal(usize d)
Flips all the elements on the super-diagonal d of a square bit-matrix.
Definition BitMat.h:1105
BitVec< Word > to_reduced_echelon_form()
Transforms the bit-matrix to reduced row-echelon form (in-place).
Definition BitMat.h:1794
constexpr const row_type & row(usize r) const
Returns a read-only reference to the row at index r.
Definition BitMat.h:928
constexpr void swap_cols(usize i, usize j)
Swaps columns i and j of the bit-matrix.
Definition BitMat.h:1585
static constexpr BitMat alternating(usize m)
Factory method to create the m x m square bit-matrix with alternating elements.
Definition BitMat.h:177
constexpr void operator|=(BitMat< Word > const &rhs)
In-place OR with a bit-matrix rhs.
Definition BitMat.h:2096
constexpr void swap_rows(usize i, usize j)
Swaps rows i and j of the bit-matrix.
Definition BitMat.h:1569
constexpr const row_type & operator[](usize r) const
Returns a read-only reference to the row at index r.
Definition BitMat.h:961
static constexpr BitMat identity(usize m)
Factory method to create the m x m identity bit-matrix.
Definition BitMat.h:380
constexpr void resize(usize r, usize c)
Resize the bit-matrix to r rows and c columns.
Definition BitMat.h:1168
constexpr void flip_all()
Flips all the elements of the bit-matrix.
Definition BitMat.h:1038
constexpr BitVec< Word > col(usize c) const
Returns a clone of the elements in column c from the bit-matrix as an independent bit-vector.
Definition BitMat.h:1003
constexpr BitMat sub(usize r_start, usize r_end, usize c_start, usize c_end) const
Returns an independent clone of the sub-matrix delimited by the given row and column ranges.
Definition BitMat.h:1400
constexpr std::optional< BitVec< Word > > remove_row()
Removes a row off the end of the bit-matrix and returns it or std::nullopt if the bit-matrix is empty...
Definition BitMat.h:1339
constexpr usize count_ones() const
Returns the number of one elements in the bit-matrix.
Definition BitMat.h:717
static constexpr BitMat alternating(usize m, usize n)
Factory method to create the m x n bit-matrix with alternating elements.
Definition BitMat.h:163
constexpr void add_identity()
Adds the identity bit-matrix to the bit-matrix.
Definition BitMat.h:1601
constexpr bool operator()(usize r, usize c) const
Returns the value of the bit at row r and column c as a bool.
Definition BitMat.h:857
constexpr bool is_symmetric() const
Returns true if this is a symmetric square bit-matrix.
Definition BitMat.h:694
constexpr BitMat & append_rows(BitMat< Word > &&src)
Appends all the rows from the src bit-matrix onto the end of this bit-matrix by moving them.
Definition BitMat.h:1281
static constexpr BitMat ones(usize m, usize n)
Factory method to create the m x n bit-matrix with all the elements set to 1.
Definition BitMat.h:142
BitVec< Word > to_echelon_form()
Transforms an arbitrary shaped, non-empty, bit-matrix to row-echelon form (in-place).
Definition BitMat.h:1737
static std::optional< BitMat > from_row_store(Store const &v, usize r)
Factory method to reshape a bit-vector v that is assumed to a sequence of r rows into a bit-matrix.
Definition BitMat.h:496
constexpr bool is_zero() const
Returns true if this a square zero bit-matrix?
Definition BitMat.h:665
BitLU< Word > LU() const
Returns the LU decomposition of the bit-matrix.
Definition BitMat.h:1900
constexpr usize rows() const
Returns the number of rows in the bit-matrix.
Definition BitMat.h:626
static BitMat biased_random(usize m, double p)
Factory method to generate a square bit-matrix of size m x m where the elements are from independent ...
Definition BitMat.h:358
constexpr void operator-=(BitMat< Word > const &rhs)
In-place difference with a bit-matrix rhs – in GF(2) subtraction is the same as XOR.
Definition BitMat.h:2206
std::optional< BitMat > inverse() const
Returns the inverse of a square bit-matrix or std::nullopt if the matrix is singular.
Definition BitMat.h:1824
std::string to_compact_binary_string() const
Returns a one-line minimal binary string representation for this bit-matrix.
Definition BitMat.h:2290
constexpr void clear()
Removes all the elements from the bit-matrix.
Definition BitMat.h:1189
constexpr BitMat & append_rows(BitMat< Word > const &src)
Appends all the rows from the src bit-matrix onto the end of this bit-matrix by copying them.
Definition BitMat.h:1261
constexpr bool all() const
Returns true if all elements of the bit-matrix are set.
Definition BitMat.h:800
static constexpr BitMat outer_product(Lhs const &u, Rhs const &v)
Factory method to create the m x n bit-matrix from the outer product of the given bit-stores.
Definition BitMat.h:190
constexpr auto operator&(BitMat< Word > const &rhs) const
TheAND of this with a bit-matrix rhs returning a new bit-matrix.
Definition BitMat.h:2134
constexpr bool is_empty() const
Is this an empty bit-matrix?
Definition BitMat.h:635
constexpr auto operator~()
Returns a copy of this bit-matrix with all the elements flipped.
Definition BitMat.h:2170
auto x_for(Rhs const &b) const
Returns a solution to the system of linear equations A.x_ = b or std::nullopt if there are none....
Definition BitMat.h:1937
constexpr BitMat transposed() const
Returns a new bit-matrix that is the transpose of this one.
Definition BitMat.h:1622
static std::optional< BitMat > from_string(std::string_view s)
Factory method to construct a bit-matrix from a string that is assumed to be a sequence of rows.
Definition BitMat.h:579
constexpr BitRef< row_type > operator()(usize r, usize c)
Returns the bit at row r and column c as a BitRef reference which can be used to set the bit.
Definition BitMat.h:891
constexpr void operator^=(BitMat< Word > const &rhs)
In-place XOR with a bit-matrix rhs.
Definition BitMat.h:2060
std::string to_binary_string(std::string_view row_sep="\n", std::string_view bit_sep="", std::string_view row_prefix="", std::string_view row_suffix="") const
Returns a configurable binary string representation for this bit-matrix.
Definition BitMat.h:2262
static BitMat left_rotation(usize n, usize p)
Constructs the n x n rotate-left by p places matrix.
Definition BitMat.h:450
constexpr BitMat(usize m, usize n)
Constructs the m x n bit-matrix with all the elements set to 0.
Definition BitMat.h:78
constexpr void make_square(usize n)
Makes an arbitrary rectangular bit-matrix into a square BitMat.
Definition BitMat.h:1201
static BitMat random(usize m, usize n, double p=0.5, std::uint64_t seed=0)
Factory method to generate a bit-matrix of size m x n where the elements are picked at random.
Definition BitMat.h:261
constexpr BitMat unit_upper() const
Returns an independent clone of the unit upper triangular part of the bit-matrix.
Definition BitMat.h:1549
constexpr BitMat(usize n)
Constructs the n x n square bit-matrix with all the elements set to 0.
Definition BitMat.h:65
constexpr row_type & operator[](usize r)
Returns a read-write reference to the row at index r.
Definition BitMat.h:978
constexpr std::optional< BitVec< Word > > remove_col()
Removes a column off the right of the bit-matrix and returns it or std::nullopt if the bit-matrix is ...
Definition BitMat.h:1375
constexpr BitMat & append_row(Store &&row)
Appends a single row onto the end of the bit-matrix by moving it.
Definition BitMat.h:1243
constexpr void replace(usize top, usize left, BitMat< Word > const &src)
Replaces the sub-matrix starting at row top and column left with a copy of the sub-matrix src.
Definition BitMat.h:1431
static constexpr BitMat companion(Store const &top_row)
Constructs a square companion BitMat with a copy of the given top row and a sub-diagonal of 1s.
Definition BitMat.h:399
constexpr void set_diagonal(bool val=true)
Sets the main diagonal of a square bit-matrix to the boolean value val.
Definition BitMat.h:1056
constexpr BitMat strictly_upper() const
Returns an independent clone of the strictly upper triangular part of the bit-matrix.
Definition BitMat.h:1517
static BitMat right_shift(usize n, usize p)
Constructs the n x n shift-right by p places BitMat.
Definition BitMat.h:434
static std::optional< BitMat > from_col_store(Store const &v, usize c)
Factory method to reshape a bit-vector that is assumed to a sequence of c columns into a bit-matrix.
Definition BitMat.h:533
constexpr void operator&=(BitMat< Word > const &rhs)
In-place AND with a bit-matrix rhs.
Definition BitMat.h:2078
BitPoly< Word > characteristic_polynomial() const
Returns the characteristic polynomial of any square bit-matrix as a gf2::BitPoly.
Definition BitMat.h:1963
std::string to_hex_string(std::string_view row_sep="\n") const
"Returns the "hex" string representation of the bit-matrix
Definition BitMat.h:2345
constexpr bool none() const
Returns true if no elements of the bit-matrix are set.
Definition BitMat.h:819
constexpr BitMat & append_row(Store const &row)
Appends a single row onto the end of the bit-matrix by copying it.
Definition BitMat.h:1221
constexpr BitMat()
The default constructor creates an empty bit-matrix with no rows or columns.
Definition BitMat.h:54
constexpr bool any() const
Returns true if any element of the bit-matrix is set.
Definition BitMat.h:781
auto solver_for(Rhs const &b) const
Returns the Gaussian elimination solver for this bit-matrix and the passed r.h.s. vector b.
Definition BitMat.h:1921
constexpr usize count_zeros() const
Returns the number of zero elements in the bit-matrix.
Definition BitMat.h:730
static constexpr BitMat left_shift(usize n, usize p)
Constructs the n x n shift-left by p places BitMat.
Definition BitMat.h:418
constexpr BitMat to_the(usize n, bool n_is_log2=false) const
Returns a new bit-matrix that is this one raised to some power n or 2^n.
Definition BitMat.h:1679
constexpr std::optional< BitMat< Word > > remove_rows(usize k)
Removes k rows off the end of the bit-matrix and returns them as a new bit-matrix or std::nullopt if ...
Definition BitMat.h:1356
constexpr BitMat lower() const
Returns an independent clone of the lower triangular part of the bit-matrix.
Definition BitMat.h:1451
constexpr auto operator^(BitMat< Word > const &rhs) const
TheXOR of this with a bit-matrix rhs returning a new bit-matrix.
Definition BitMat.h:2114
constexpr void set_sub_diagonal(usize d, bool val=true)
Sets the elements on sub-diagonal d of a square bit-matrix to the boolean value val.
Definition BitMat.h:1122
constexpr BitMat & append_cols(BitMat< Word > const &src)
Appends all the columns from the src bit-matrix onto the right of this bit-matrix so M -> M|src.
Definition BitMat.h:1320
A BitPoly represents a polynomial over GF(2) where we store the polynomial coefficients in a bit-vect...
Definition BitPoly.h:30
static constexpr BitPoly zero()
Factory method to return the "zero" bit-polynomial p(x) := 0.
Definition BitPoly.h:95
A BitRef is a proxy class to reference a single bit in a bit-store.
Definition BitRef.h:33
A dynamically-sized vector over GF(2) with bit elements compactly stored in a standard vector of prim...
Definition BitVec.h:23
static std::optional< BitVec > from_string(std::string_view sv)
Definition BitVec.h:408
constexpr usize size() const
Returns the number of bit-elements in the bit-vector.
Definition BitVec.h:50
static constexpr BitVec constant(usize n, bool value)
Factory method to generate a bit-vector of length n where the elements are set to value.
Definition BitVec.h:213
constexpr bool any() const
Returns true if at least one bit in the store is set, false otherwise.
Definition BitVec.h:1049
static constexpr BitVec zeros(usize n)
Factory method to generate a bit-vector of length n where the elements are all 0.
Definition BitVec.h:194
static constexpr BitVec alternating(usize n)
Factory method to generate a bit-vector of length n looking like 101010....
Definition BitVec.h:237
static constexpr BitVec ones(usize n)
Factory method to generate a bit-vector of length n where the elements are all 1.
Definition BitVec.h:202
auto flip(usize i)
Flips the value of the bit-element i and returns this for chaining.
Definition BitVec.h:1001
constexpr bool get(usize i) const
Returns true if the bit at the given index i is set, false otherwise.
Definition BitVec.h:907
The namespace for the gf2 library.
Definition assert.h:119
std::string string_for(BitMat< Word > const &A, Rhs const &b)
Returns a string that shows a bit-matrix and a bit-vector side-by-side.
Definition BitMat.h:2593
constexpr std::optional< usize > first_set(Store const &store)
Returns the index of the first set bit in the bit-store or {} if no bits are set.
Definition BitStore.h:770
std::ostream & operator<<(Store const &store, std::ostream &s)
The usual output stream operator for a bit-store.
Definition BitStore.h:1605
constexpr auto operator*(BitMat< Word > const &lhs, Rhs const &rhs)
Operator form for bit-matrix, bit-store multiplication, M * v, returning a new bit-vector.
Definition BitMat.h:2535
constexpr auto dot(BitMat< Word > const &lhs, Rhs const &rhs)
Bit-matrix, bit-store multiplication, M * v, returning a new bit-vector.
Definition BitMat.h:2523
static constexpr auto span(Store const &store, usize begin, usize end)
Constructs a read-only bit-span over the const bit-store store for its bits in the range [begin,...
Definition BitStore.h:1176
constexpr auto & copy(Src src, Store &store)
Copies the bits from an unsigned integral src value and returns the store for chaining.
Definition BitStore.h:474
std::size_t usize
Word type alias for the platform's "native"-sized unsigned integer.
Definition Unsigned.h:42
constexpr auto & swap(Store &store, usize i0, usize i1)
Swaps the bits in the bit-store at indices i0 and i1 and returns the store for chaining.
Definition BitStore.h:297
STL namespace.
auto format(gf2::BitMat< Word > const &rhs, FormatContext &ctx) const
Push out a formatted bit-matrix using the various to_string(...) methods in the class.
Definition BitMat.h:2790
constexpr auto parse(std::format_parse_context const &ctx)
Parse a bit-store format specifier where where we recognize {:p} and {:x}.
Definition BitMat.h:2775