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 Word> class BitGauss;
22template<unsigned_word Word> class BitLU;
23// clang-format on
24
31template<unsigned_word 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(const std::vector<row_type>& 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 static constexpr BitMat outer_product(const BitVec<Word>& u, const BitVec<Word>& v) {
189 BitMat result;
190 for (auto i = 0uz; i < u.size(); ++i) {
191 row_type row = u.get(i) ? row_type(v) : row_type(v.size());
192 result.m_rows.push_back(std::move(row));
193 }
194 return result;
195 }
196
206 static constexpr BitMat outer_sum(const BitVec<Word>& u, const BitVec<Word>& v) {
207 BitMat result;
208 for (auto i = 0uz; i < u.size(); ++i) {
209 result.m_rows.push_back(v);
210 if (u.get(i)) result.m_rows.back().flip_all();
211 }
212 return result;
213 }
214
222 static constexpr BitMat from(usize m, usize n, std::invocable<usize, usize> auto f) {
223 BitMat result{m, n};
224 for (auto i = 0uz; i < m; ++i)
225 for (auto j = 0uz; j < n; ++j)
226 if (f(i, j)) result.m_rows[i].set(j);
227 return result;
228 }
229
233
257 static BitMat random(usize m, usize n, double p = 0.5, std::uint64_t seed = 0) {
258 // Keep a single static RNG per thread for all calls to this method, seeded with entropy on the first call.
259 thread_local RNG rng;
260
261 // Edge case handling ...
262 if (p < 0) return BitMat::zeros(m, n);
263
264 // Scale p by 2^64 to remove floating point arithmetic from the main loop below.
265 // If we determine p rounds to 1 then we can just set all elements to 1 and return early.
266 p = p * 0x1p64 + 0.5;
267 if (p >= 0x1p64) return BitMat::ones(m, n);
268
269 // p does not round to 1 so we use a 64-bit URNG and check each draw against the 64-bit scaled p.
270 auto scaled_p = static_cast<std::uint64_t>(p);
271
272 // If a seed was provided, set the RNG's seed to it. Otherwise, we carry on from where we left off.
273 std::uint64_t old_seed = rng.seed();
274 if (seed != 0) rng.set_seed(seed);
275
276 auto result = BitMat::zeros(m, n);
277 for (auto i = 0uz; i < m; ++i) {
278 for (auto j = 0uz; j < n; ++j) {
279 if (rng.u64() < scaled_p) result.m_rows[i].set(j);
280 }
281 }
282
283 // Restore the old seed if necessary.
284 if (seed != 0) rng.set_seed(old_seed);
285
286 return result;
287 }
288
307 static BitMat seeded_random(usize m, usize n, std::uint64_t seed) { return random(m, n, 0.5, seed); }
308
326 static BitMat seeded_random(usize m, std::uint64_t seed) { return random(m, m, 0.5, seed); }
327
341 static BitMat biased_random(usize m, usize n, double p) { return random(m, n, p, 0); }
342
354 static BitMat biased_random(usize m, double p) { return random(m, m, p, 0); }
355
359
367 static constexpr BitMat zero(usize m) { return BitMat{m, m}; }
368
376 static constexpr BitMat identity(usize m) {
377 BitMat result{m, m};
378 for (auto i = 0uz; i < m; ++i) result.m_rows[i].set(i);
379 return result;
380 }
381
393 template<typename Rhs>
394
395 static constexpr BitMat companion(const BitStore<Rhs>& top_row) {
396 // Edge case:
397 if (top_row.size() == 0) return BitMat{};
398 auto result = BitMat::zero(top_row.size());
399 result.m_rows[0].copy(top_row);
400 result.set_sub_diagonal(1);
401 return result;
402 }
403
414 static constexpr BitMat left_shift(usize n, usize p) {
415 auto result = BitMat::zeros(n, n);
416 result.set_super_diagonal(p);
417 return result;
418 }
419
431 auto result = BitMat::zeros(n, n);
432 result.set_sub_diagonal(p);
433 return result;
434 }
435
447 auto result = BitMat::zeros(n, n);
448 for (auto i = 0uz; i < n; ++i) {
449 auto j = (i + n - p) % n;
450 result.set(i, j);
451 }
452 return result;
453 }
454
466 auto result = BitMat::zeros(n, n);
467 for (auto i = 0uz; i < n; ++i) {
468 auto j = (i + p) % n;
469 result.set(i, j);
470 }
471 return result;
472 }
473
477
479 //
490 static std::optional<BitMat> from_vector_of_rows(const BitVec<Word>& v, usize r) {
491 // Edge case?
492 if (v.size() == 0) return BitMat{};
493
494 // Error handling ...
495 if (r == 0 || v.size() % r != 0) return std::nullopt;
496
497 // Number of columns (for sure this is an even division)
498 usize c = v.size() / r;
499
500 // Create the bit-matrix and copy the rows.
501 BitMat result{r, c};
502 for (auto i = 0uz; i < r; ++i) {
503 auto begin = i * c;
504 auto end = begin + c;
505 result.m_rows[i].copy(v.span(begin, end));
506 }
507 return result;
508 }
509
525 static std::optional<BitMat> from_vector_of_cols(const BitVec<Word>& v, usize c) {
526 // Edge case?
527 if (v.size() == 0) return BitMat{};
528
529 // Error handling ...
530 if (c == 0 || v.size() % c != 0) return std::nullopt;
531
532 // Number of columns (for sure this is an even division)
533 usize r = v.size() / c;
534
535 // Create the bit-matrix and copy the rows.
536 BitMat result{r, c};
537 usize iv = 0;
538 for (auto j = 0uz; j < c; ++j) {
539 for (auto i = 0uz; i < r; ++i) {
540 if (v.get(iv)) result.m_rows[i].set(j);
541 iv++;
542 }
543 }
544 return result;
545 }
546
550
571 static std::optional<BitMat> from_string(std::string_view s) {
572 // Edge case
573 if (s.empty()) return BitMat{};
574
575 // We split the string into tokens using the standard regex library.
576 std::string src(s);
577 auto delims = std::regex(R"([\s|,|;]+)");
578 std::sregex_token_iterator iter{src.cbegin(), src.cend(), delims, -1};
579 std::vector<std::string> tokens{iter, {}};
580
581 // Zap any empty tokens & check there is something to do
582 tokens.erase(std::remove_if(tokens.begin(), tokens.end(), [](std::string_view x) { return x.empty(); }),
583 tokens.end());
584 if (tokens.empty()) return BitMat{};
585
586 // We hope to fill a BitMat.
587 BitMat result;
588
589 // Iterate through the possible rows
590 usize n_rows = tokens.size();
591 usize n_cols = 0;
592 for (auto i = 0uz; i < n_rows; ++i) {
593 // Attempt to parse the current token as a row for the bit-matrix.
594 auto r = row_type::from_string(tokens[i]);
595
596 // Parse failure?
597 if (!r) return std::nullopt;
598
599 // We've read a potentially valid BitMat row.
600 if (i == 0) {
601 // First row sets the number of columns
602 n_cols = r->size();
603 result.resize(n_rows, n_cols);
604 } else {
605 // Subsequent rows better have the same number of elements!
606 if (r->size() != n_cols) return std::nullopt;
607 }
608 result.m_rows[i] = *r;
609 }
610 return result;
611 }
612
616
618 constexpr usize rows() const { return m_rows.size(); }
619
621 constexpr usize cols() const { return m_rows.size() > 0 ? m_rows[0].size() : 0; }
622
624 constexpr usize size() const { return rows() * cols(); }
625
627 constexpr bool is_empty() const { return rows() == 0; }
628
632
644 constexpr bool is_square() const { return rows() != 0 && rows() == cols(); }
645
657 constexpr bool is_zero() const { return is_square() && none(); }
658
667 constexpr bool is_identity() const {
668 if (!is_square()) return false;
669 for (auto i = 0uz; i < rows(); ++i) {
670 row_type r = m_rows[i];
671 r.flip(i);
672 if (r.any()) return false;
673 }
674 return true;
675 }
676
686 constexpr bool is_symmetric() const {
687 if (!is_square()) return false;
688 for (auto i = 0uz; i < rows(); ++i) {
689 for (auto j = 0uz; j < cols(); ++j) {
690 if (get(i, j) != get(j, i)) return false;
691 }
692 }
693 return true;
694 }
695
699
709 constexpr usize count_ones() const {
710 usize count = 0;
711 for (const auto& row : m_rows) count += row.count_ones();
712 return count;
713 }
714
722 constexpr usize count_zeros() const { return size() - count_ones(); }
723
733 constexpr usize count_ones_on_diagonal() const {
734 gf2_debug_assert(is_square(), "BitMat is {} x {} but it should be square!", rows(), cols());
735 usize result = 0;
736 for (auto i = 0uz; i < rows(); ++i)
737 if (get(i, i)) ++result;
738 return result;
739 }
740
754 constexpr bool trace() const { return count_ones_on_diagonal() % 2 == 1; }
755
759
773 constexpr bool any() const {
774 for (const auto& row : m_rows)
775 if (row.any()) return true;
776 return false;
777 }
778
792 constexpr bool all() const {
793 for (const auto& row : m_rows)
794 if (!row.all()) return false;
795 return true;
796 }
797
811 constexpr bool none() const {
812 for (const auto& row : m_rows)
813 if (!row.none()) return false;
814 return true;
815 }
816
820
832 constexpr bool get(usize r, usize c) const {
833 gf2_debug_assert(r < rows(), "Row index {} out of bounds [0,{})", r, rows());
834 gf2_debug_assert(c < cols(), "Column index {} out of bounds [0,{})", c, cols());
835 return m_rows[r].get(c);
836 }
837
849 constexpr bool operator()(usize r, usize c) const {
850 gf2_debug_assert(r < rows(), "Row index {} out of bounds [0,{})", r, rows());
851 gf2_debug_assert(c < cols(), "Column index {} out of bounds [0,{})", c, cols());
852 return m_rows[r].get(c);
853 }
854
866 constexpr void set(usize r, usize c, bool val = true) {
867 gf2_debug_assert(r < rows(), "Row index {} out of bounds [0,{})", r, rows());
868 gf2_debug_assert(c < cols(), "Column index {} out of bounds [0,{})", c, cols());
869 m_rows[r].set(c, val);
870 }
871
884 gf2_debug_assert(r < rows(), "Row index {} out of bounds [0,{})", r, rows());
885 gf2_debug_assert(c < cols(), "Column index {} out of bounds [0,{})", c, cols());
886 return m_rows[r][c];
887 }
888
899 constexpr void flip(usize r, usize c) {
900 gf2_debug_assert(r < rows(), "Row index {} out of bounds [0,{})", r, rows());
901 gf2_debug_assert(c < cols(), "Column index {} out of bounds [0,{})", c, cols());
902 m_rows[r].flip(c);
903 }
904
908
920 constexpr const row_type& row(usize r) const {
921 gf2_debug_assert(r < rows(), "Row index {} out of bounds [0,{})", r, rows());
922 return m_rows[r];
923 }
924
937 constexpr row_type& row(usize r) {
938 gf2_debug_assert(r < rows(), "Row index {} out of bounds [0,{})", r, rows());
939 return m_rows[r];
940 }
941
953 constexpr const row_type& operator[](usize r) const {
954 gf2_debug_assert(r < rows(), "Row index {} out of bounds [0,{})", r, rows());
955 return m_rows[r];
956 }
957
970 constexpr row_type& operator[](usize r) {
971 gf2_debug_assert(r < rows(), "Row index {} out of bounds [0,{})", r, rows());
972 return m_rows[r];
973 }
974
978
995 constexpr BitVec<Word> col(usize c) const {
996 gf2_debug_assert(c < cols(), "Column {} out of bounds [0, {})", c, cols());
997 auto result = BitVec<Word>::zeros(rows());
998 for (auto r = 0uz; r < rows(); ++r) {
999 if (get(r, c)) { result.set(r); }
1000 }
1001 return result;
1002 }
1003
1007
1018 constexpr void set_all(bool value = true) {
1019 for (auto& row : m_rows) row.set_all(value);
1020 }
1021
1030 constexpr void flip_all() {
1031 for (auto& row : m_rows) row.flip_all();
1032 }
1033
1037
1048 constexpr void set_diagonal(bool val = true) {
1049 gf2_debug_assert(is_square(), "BitMat is {} x {} but it should be square!", rows(), cols());
1050 for (auto i = 0uz; i < rows(); ++i) { set(i, i, val); }
1051 }
1052
1063 constexpr void flip_diagonal() {
1064 gf2_debug_assert(is_square(), "BitMat is {} x {} but it should be square!", rows(), cols());
1065 for (auto i = 0uz; i < rows(); ++i) { flip(i, i); }
1066 }
1067
1080 constexpr void set_super_diagonal(usize d, bool val = true) {
1081 gf2_debug_assert(is_square(), "BitMat is {} x {} but it should be square!", rows(), cols());
1082 for (auto i = 0uz; i < rows() - d; ++i) { set(i, i + d, val); }
1083 }
1084
1097 constexpr void flip_super_diagonal(usize d) {
1098 gf2_debug_assert(is_square(), "BitMat is {} x {} but it should be square!", rows(), cols());
1099 for (auto i = 0uz; i < rows() - d; ++i) { flip(i, i + d); }
1100 }
1101
1114 constexpr void set_sub_diagonal(usize d, bool val = true) {
1115 gf2_debug_assert(is_square(), "BitMat is {} x {} but it should be square!", rows(), cols());
1116 for (auto i = 0uz; i < rows() - d; ++i) { set(i + d, i, val); }
1117 }
1118
1131 constexpr void flip_sub_diagonal(usize d) {
1132 gf2_debug_assert(is_square(), "BitMat is {} x {} but it should be square!", rows(), cols());
1133 for (auto i = 0uz; i < rows() - d; ++i) { flip(i + d, i); }
1134 }
1135
1139
1160 constexpr void resize(usize r, usize c) {
1161 // Edge case ...
1162 if (rows() == r && cols() == c) return;
1163
1164 // Resizes to zero in either dimension is taken as a zap the lot instruction
1165 if (r == 0 || c == 0) r = c = 0;
1166
1167 // Rows, then columns
1168 m_rows.resize(r);
1169 for (auto& row : m_rows) row.resize(c);
1170 }
1171
1181 constexpr void clear() { resize(0, 0); }
1182
1193 constexpr void make_square(usize n) { resize(n, n); }
1194
1198
1211 constexpr BitMat& append_row(const BitVec<Word>& row) {
1212 gf2_assert_eq(row.size(), cols(), "Row has {} elements but bit-matrix has {} columns!", row.size(), cols());
1213 m_rows.push_back(row);
1214 return *this;
1215 }
1216
1232 gf2_assert_eq(row.size(), cols(), "Row has {} elements but bit-matrix has {} columns!", row.size(), cols());
1233 m_rows.push_back(std::move(row));
1234 return *this;
1235 }
1236
1249 constexpr BitMat& append_rows(const BitMat<Word>& src) {
1250 gf2_assert_eq(src.cols(), cols(), "Source has {} columns but bit-matrix has {} columns!", src.cols(), cols());
1251 m_rows.insert(m_rows.end(), src.m_rows.begin(), src.m_rows.end());
1252 return *this;
1253 }
1254
1269 constexpr BitMat& append_rows(BitMat<Word>&& src) {
1270 gf2_assert_eq(src.cols(), cols(), "Source has {} columns but bit-matrix has {} columns!", src.cols(), cols());
1271 m_rows.insert(m_rows.end(), std::make_move_iterator(src.m_rows.begin()),
1272 std::make_move_iterator(src.m_rows.end()));
1273 return *this;
1274 }
1275
1288 template<typename Store>
1290 requires store_word_is<Store, Word>
1291 {
1292 gf2_assert_eq(col.size(), rows(), "Column has {} elements but bit-matrix has {} rows!", col.size(), rows());
1293 for (auto i = 0uz; i < rows(); ++i) m_rows[i].push(col.get(i));
1294 return *this;
1295 }
1296
1309 constexpr BitMat& append_cols(const BitMat<Word>& src) {
1310 gf2_assert_eq(src.rows(), rows(), "Source has {} rows but bit-matrix has {} rows!", src.rows(), rows());
1311 for (auto i = 0uz; i < rows(); ++i) m_rows[i].append(src.m_rows[i]);
1312 return *this;
1313 }
1314
1318
1328 constexpr std::optional<BitVec<Word>> remove_row() {
1329 if (rows() == 0) return std::nullopt;
1330 auto row = std::move(m_rows.back());
1331 m_rows.pop_back();
1332 return row;
1333 }
1334
1345 constexpr std::optional<BitMat<Word>> remove_rows(usize k) {
1346 if (rows() < k) return std::nullopt;
1347 auto begin = m_rows.end() - static_cast<std::ptrdiff_t>(k);
1348 auto end = m_rows.end();
1349 auto result = BitMat<Word>(std::vector<row_type>(begin, end));
1350 m_rows.erase(begin, end);
1351 return result;
1352 }
1353
1364 constexpr std::optional<BitVec<Word>> remove_col() {
1365 if (cols() == 0) return std::nullopt;
1366 auto result = col(cols() - 1);
1367 for (auto i = 0uz; i < rows(); ++i) m_rows[i].pop();
1368 return result;
1369 }
1370
1374
1389 constexpr BitMat sub(usize r_start, usize r_end, usize c_start, usize c_end) const {
1390
1391 // Check that the row range is valid.
1392 gf2_assert(r_start <= r_end, "Invalid row range");
1393 gf2_assert(r_end <= rows(), "Row range extends beyond the end of the bit-matrix");
1394
1395 // Check that the column range is valid.
1396 gf2_assert(c_start <= c_end, "Invalid column range");
1397 gf2_assert(c_end <= cols(), "Column range extends beyond the right edge of the bit-matrix");
1398
1399 // Get the number of rows and columns in the sub-matrix.
1400 auto r = r_end - r_start;
1401 auto c = c_end - c_start;
1402
1403 // Create the sub-matrix.
1404 BitMat result{r, c};
1405 for (auto i = 0uz; i < r; ++i) result.m_rows[i].copy(m_rows[i + r_start].span(c_start, c_end));
1406
1407 return result;
1408 }
1409
1420 constexpr void replace(usize top, usize left, const BitMat<Word>& src) {
1421 auto r = src.rows();
1422 auto c = src.cols();
1423 gf2_assert(top + r <= rows(), "Too many rows for the replacement sub-matrix to fit");
1424 gf2_assert(left + c <= cols(), "Too many columns for the replacement sub-matrix to fit");
1425 for (auto i = 0uz; i < r; ++i) m_rows[top + i].span(left, left + c).copy(src.m_rows[i]);
1426 }
1427
1431
1440 constexpr BitMat lower() const {
1441 // Edge case:
1442 if (is_empty()) return BitMat{};
1443
1444 // Start with a copy of the bit-matrix.
1445 BitMat result = *this;
1446
1447 // Set the upper triangular part to zero.
1448 auto nc = cols();
1449 for (auto i = 0uz; i < rows(); ++i) {
1450 auto first = i + 1;
1451 if (first < nc) result.m_rows[i].span(first, nc).set_all(false);
1452 }
1453 return result;
1454 }
1455
1464 constexpr BitMat upper() const {
1465 // Edge case:
1466 if (is_empty()) return BitMat{};
1467
1468 // Start with a copy of the bit-matrix.
1469 auto result = *this;
1470
1471 // Set the upper triangular part to zero.
1472 auto c = cols();
1473 for (auto i = 0uz; i < rows(); ++i) {
1474 auto len = std::min(i, c);
1475 if (len > 0) result.m_rows[i].span(0, len).set_all(false);
1476 }
1477 return result;
1478 }
1479
1490 constexpr BitMat strictly_lower() const {
1491 auto result = lower();
1492 result.set_diagonal(false);
1493 return result;
1494 }
1495
1506 constexpr BitMat strictly_upper() const {
1507 auto result = upper();
1508 result.set_diagonal(false);
1509 return result;
1510 }
1511
1522 constexpr BitMat unit_lower() const {
1523 auto result = lower();
1524 result.set_diagonal(true);
1525 return result;
1526 }
1527
1538 constexpr BitMat unit_upper() const {
1539 auto result = upper();
1540 result.set_diagonal(true);
1541 return result;
1542 }
1543
1547
1549 //
1558 constexpr void swap_rows(usize i, usize j) {
1559 gf2_debug_assert(i < rows(), "Row index {} out of bounds [0,{})", i, rows());
1560 gf2_debug_assert(j < rows(), "Row index {} out of bounds [0,{})", j, rows());
1561 std::swap(m_rows[i], m_rows[j]);
1562 }
1563
1574 constexpr void swap_cols(usize i, usize j) {
1575 gf2_debug_assert(i < cols(), "Column index {} out of bounds [0,{})", i, cols());
1576 gf2_debug_assert(j < cols(), "Column index {} out of bounds [0,{})", j, cols());
1577 for (auto k = 0uz; k < rows(); ++k) m_rows[k].swap(i, j);
1578 }
1579
1590 constexpr void add_identity() {
1591 gf2_debug_assert(is_square(), "This bit-matrix is {} x {} but it should be square!", rows(), cols());
1592 for (auto i = 0uz; i < rows(); ++i) flip(i, i);
1593 }
1594
1598
1611 constexpr BitMat transposed() const {
1612 auto r = rows();
1613 auto c = cols();
1614 BitMat result{c, r};
1615 for (auto i = 0uz; i < r; ++i) {
1616 for (auto j = 0uz; j < c; ++j) {
1617 if (get(i, j)) { result.set(j, i); }
1618 }
1619 }
1620 return result;
1621 }
1622
1635 constexpr void transpose() {
1636 gf2_assert(is_square(), "`transpose()` requires a square matrix");
1637 for (auto i = 0uz; i < rows(); ++i) {
1638 for (auto j = 0uz; j < i; ++j) {
1639 if (get(i, j) != get(j, i)) {
1640 flip(i, j);
1641 flip(j, i);
1642 }
1643 }
1644 }
1645 }
1646
1650
1668 constexpr BitMat to_the(usize n, bool n_is_log2 = false) const {
1669 gf2_assert(is_square(), "Bit-matrix is {} x {} but it should be square!", rows(), cols());
1670
1671 // Perhaps we just need lots of square steps? Note that 2^0 = 1 so M^(2^0) = M.
1672 if (n_is_log2) {
1673 auto result = *this;
1674 for (auto i = 0uz; i < n; ++i) result = dot(result, result);
1675 return result;
1676 }
1677
1678 // Otherwise we need square & multiply steps but we first handle the edge case:
1679 if (n == 0) return BitMat::identity(rows());
1680
1681 // OK n != 0: Note that if e.g. n = 0b00010111 then std::bit_floor(n) = 0b00010000.
1682 usize n_bit = std::bit_floor(n);
1683
1684 // Start with a copy of this bit-matrix which takes care of the most significant binary digit in n.
1685 auto result = *this;
1686 n_bit >>= 1;
1687
1688 // More to go?
1689 while (n_bit) {
1690 // Always square and then multiply if necessary (i.e. if current bit in n is set).
1691 result = dot(result, result);
1692 if (n & n_bit) result = dot(result, *this);
1693
1694 // On to the next bit position in n.
1695 n_bit >>= 1;
1696 }
1697 return result;
1698 }
1699
1703
1727 gf2_assert(!is_empty(), "Bit-matrix must not be empty");
1728
1729 // We return a bit-vector that shows which columns have a pivot -- start by assuming none.
1730 auto has_pivot = BitVec<Word>::zeros(cols());
1731
1732 // The current row of the echelon form we are working on.
1733 auto r = 0uz;
1734 auto nr = rows();
1735
1736 // Iterate over each column.
1737 for (auto j = 0uz; j < cols(); ++j) {
1738 // Find a non-zero entry in this column below the diagonal (a "pivot").
1739 auto p = r;
1740 while (p < nr && !get(p, j)) p++;
1741
1742 // Did we find a pivot in this column?
1743 if (p < nr) {
1744 // Mark this column as having a pivot.
1745 has_pivot.set(j);
1746
1747 // If necessary, swap the current row with the row that has the pivot.
1748 if (p != r) swap_rows(p, r);
1749
1750 // Below the working row make sure column j is zero by elimination if necessary.
1751 auto row_r = m_rows[r];
1752 for (auto i = r + 1; i < nr; ++i)
1753 if (get(i, j)) m_rows[i] ^= row_r;
1754
1755 // Move to the next row. If we've reached the end of the matrix, we're done.
1756 r += 1;
1757 if (r == nr) break;
1758 }
1759 }
1760
1761 // Return the bit-vector that shows which columns have a pivot.
1762 return has_pivot;
1763 }
1764
1784 // Start with the echelon form.
1785 auto has_pivot = to_echelon_form();
1786
1787 // Iterate over each row from the bottom up - Gauss Jordan elimination.
1788 for (auto r = rows(); r--;) {
1789 // Find the first set bit in the current row if there is one.
1790 if (auto p = m_rows[r].first_set()) {
1791 // Clear out everything in column p *above* row r (already cleared out below the pivot).
1792 auto row_r = m_rows[r];
1793 for (auto i = 0uz; i < r; ++i)
1794 if (get(i, *p)) m_rows[i] ^= row_r;
1795 }
1796 }
1797
1798 // Return the bit-vector that shows which columns have a pivot.
1799 return has_pivot;
1800 }
1801
1805
1813 std::optional<BitMat> inverse() const {
1814 // The bit-matrix must be square & non-empty.
1815 if (is_empty() || !is_square()) return std::nullopt;
1816
1817 // Create a copy of the bit-matrix & augment it with the identity matrix on the right.
1818 auto matrix = *this;
1819 auto nr = rows();
1820 auto nc = cols();
1821 matrix.append_cols(BitMat::identity(nr));
1822
1823 // Transform the augmented matrix to reduced row-echelon form.
1824 matrix.to_reduced_echelon_form();
1825
1826 // If all went well the left half is the identity matrix and the right half is the inverse.
1827 if (matrix.sub(0, nr, 0, nc).is_identity()) return matrix.sub(0, nr, nc, nc * 2);
1828
1829 return std::nullopt;
1830 }
1831
1845 static constexpr double probability_invertible(usize n) {
1846 // A 0x0 matrix is not well defined throw an error!
1847 if (n == 0) throw std::invalid_argument("Matrix should not be 0x0--likely an error somewhere upstream!");
1848
1849 // Formula is p(n) = \prod_{k = 1}^{n} (1 - 2^{-k}) which runs out of juice once n hits any size at all!
1850 usize n_prod = std::min(n, static_cast<size_t>(std::numeric_limits<double>::digits));
1851
1852 // Compute the product over the range that matters
1853 double product = 1;
1854 double pow2 = 1;
1855 while (n_prod--) {
1856 pow2 *= 0.5;
1857 product *= 1 - pow2;
1858 }
1859 return product;
1860 }
1861
1875 static constexpr double probability_singular(usize n) { return 1 - probability_invertible(n); }
1876
1880
1889 BitLU<Word> LU() const { return BitLU<Word>{*this}; }
1890
1894
1908 template<typename Store>
1909 auto solver_for(const BitStore<Store>& b) const
1910 requires store_word_is<Store, Word>
1911 {
1912 return BitGauss<Word>{*this, b};
1913 }
1914
1925 template<typename Store>
1926 auto x_for(const BitStore<Store>& b) const
1927 requires store_word_is<Store, Word>
1928 {
1929 return solver_for(b)();
1930 }
1931
1935
1955 gf2_assert(is_square(), "Bit-matrix must be square not {} x {}", rows(), cols());
1957 }
1958
1969 auto n_companions = top_rows.size();
1970 if (n_companions == 0) return BitPoly<Word>::zero();
1971
1972 // Compute the product of the characteristic polynomials of the companion matrices.
1973 auto result = companion_matrix_characteristic_polynomial(top_rows[0]);
1974 for (auto i = 1uz; i < n_companions; ++i) { result *= companion_matrix_characteristic_polynomial(top_rows[i]); }
1975 return result;
1976 }
1977
1993 auto n = top_row.size();
1994
1995 // The characteristic polynomial is degree n with n + 1 coefficients (leading coefficient is 1).
1996 auto coeffs = BitVec<Word>::ones(n + 1);
1997
1998 // The lower order coefficients are the top row of the companion matrix in reverse order.
1999 for (auto j = 0uz; j < n; ++j) { coeffs.set(n - j - 1, top_row.get(j)); }
2000 return BitPoly<Word>{std::move(coeffs)};
2001 }
2002
2016 std::vector<BitVec<Word>> frobenius_form() const {
2017 // The bit-matrix must be square.
2018 gf2_assert(is_square(), "Bit-matrix must be square not {} x {}", rows(), cols());
2019
2020 // Space for the top rows of the companion matrices which we will return.
2021 auto nr = rows();
2022 auto top_rows = std::vector<BitVec<Word>>{};
2023 top_rows.reserve(nr);
2024
2025 // Make a working copy of the bit-matrix to work through using Danilevsky's algorithm.
2026 auto copy = *this;
2027 while (nr > 0) {
2028 auto companion = copy.danilevsky_step(nr);
2029 nr -= companion.size();
2030 top_rows.push_back(companion);
2031 }
2032 return top_rows;
2033 }
2034
2038
2051 constexpr void operator^=(const BitMat<Word>& rhs) {
2052 gf2_assert(rows() == rhs.rows(), "Row dimensions do not match: {} != {}.", rows(), rhs.rows());
2053 gf2_assert(cols() == rhs.cols(), "Column dimensions do not match: {} != {}.", cols(), rhs.cols());
2054 for (auto i = 0uz; i < rows(); ++i) row(i) ^= rhs.row(i);
2055 }
2056
2069 constexpr void operator&=(const BitMat<Word>& rhs) {
2070 gf2_assert(rows() == rhs.rows(), "Row dimensions do not match: {} != {}.", rows(), rhs.rows());
2071 gf2_assert(cols() == rhs.cols(), "Column dimensions do not match: {} != {}.", cols(), rhs.cols());
2072 for (auto i = 0uz; i < rows(); ++i) row(i) &= rhs.row(i);
2073 }
2074
2087 constexpr void operator|=(const BitMat<Word>& rhs) {
2088 gf2_assert(rows() == rhs.rows(), "Row dimensions do not match: {} != {}.", rows(), rhs.rows());
2089 gf2_assert(cols() == rhs.cols(), "Column dimensions do not match: {} != {}.", cols(), rhs.cols());
2090 for (auto i = 0uz; i < rows(); ++i) row(i) |= rhs.row(i);
2091 }
2092
2105 constexpr auto operator^(const BitMat<Word>& rhs) const {
2106 gf2_assert(rows() == rhs.rows(), "Row dimensions do not match: {} != {}.", rows(), rhs.rows());
2107 gf2_assert(cols() == rhs.cols(), "Column dimensions do not match: {} != {}.", cols(), rhs.cols());
2108 auto result = *this;
2109 result ^= rhs;
2110 return result;
2111 }
2112
2125 constexpr auto operator&(const BitMat<Word>& rhs) const {
2126 gf2_assert(rows() == rhs.rows(), "Row dimensions do not match: {} != {}.", rows(), rhs.rows());
2127 gf2_assert(cols() == rhs.cols(), "Column dimensions do not match: {} != {}.", cols(), rhs.cols());
2128 auto result = *this;
2129 result &= rhs;
2130 return result;
2131 }
2132
2145 constexpr auto operator|(const BitMat<Word>& rhs) const {
2146 gf2_assert(rows() == rhs.rows(), "Row dimensions do not match: {} != {}.", rows(), rhs.rows());
2147 gf2_assert(cols() == rhs.cols(), "Column dimensions do not match: {} != {}.", cols(), rhs.cols());
2148 auto result = *this;
2149 result |= rhs;
2150 return result;
2151 }
2152
2161 constexpr auto operator~() {
2162 auto result = *this;
2163 result.flip_all();
2164 return result;
2165 }
2166
2170
2183 constexpr void operator+=(const BitMat<Word>& rhs) { operator^=(rhs); }
2184
2197 constexpr void operator-=(const BitMat<Word>& rhs) { operator^=(rhs); }
2198
2211 constexpr auto operator+(const BitMat<Word>& rhs) const {
2212 auto result = *this;
2213 result += rhs;
2214 return result;
2215 }
2216
2229 constexpr auto operator-(const BitMat<Word>& rhs) const {
2230 auto result = *this;
2231 result -= rhs;
2232 return result;
2233 }
2234
2238
2253 std::string to_binary_string(std::string_view row_sep = "\n", std::string_view bit_sep = "",
2254 std::string_view row_prefix = "", std::string_view row_suffix = "") const {
2255 usize nr = rows();
2256
2257 // Edge case?
2258 if (nr == 0) return std::string{};
2259
2260 usize nc = cols();
2261 usize per_row = nc + (nc - 1) * bit_sep.size() + row_prefix.size() + row_suffix.size();
2262 std::string result;
2263 result.reserve(nr * per_row);
2264
2265 for (auto i = 0uz; i < nr; ++i) {
2266 result += m_rows[i].to_binary_string(bit_sep, row_prefix, row_suffix);
2267 if (i + 1 < nr) result += row_sep;
2268 }
2269 return result;
2270 }
2271
2281 std::string to_compact_binary_string() const { return to_binary_string(" "); }
2282
2292 std::string to_string() const { return to_binary_string("\n"); }
2293
2307 std::string to_pretty_string() const { return to_binary_string("\n", " ", "\u2502", "\u2502"); }
2308
2336 std::string to_hex_string(std::string_view row_sep = "\n") const {
2337 // Edge case.
2338 usize nr = rows();
2339 if (nr == 0) return std::string{};
2340
2341 // Set up the return string with enough space for the entire matrix.
2342 usize nc = cols();
2343 std::string result;
2344 result.reserve(nr * (nc / 4 + row_sep.size()));
2345
2346 // Append each row to the result string.
2347 for (auto i = 0uz; i < nr; ++i) {
2348 result += m_rows[i].to_hex_string();
2349 if (i + 1 < nr) result += row_sep;
2350 }
2351 return result;
2352 }
2353
2381 std::string to_compact_hex_string() const { return to_hex_string(" "); }
2382
2383 // The usual output stream operator for a bit-store
2384 constexpr std::ostream& operator<<(std::ostream& s) const { return s << to_string(); }
2385
2389
2398 friend constexpr bool operator==(const BitMat& lhs, const BitMat& rhs) {
2399 // Edge case.
2400 if (&lhs == &rhs) return true;
2401
2402 // Check the active words for equality.
2403 auto row_count = lhs.rows();
2404 if (rhs.rows() != row_count) return false;
2405 for (auto i = 0uz; i < row_count; ++i)
2406 if (lhs.m_rows[i] != rhs.m_rows[i]) return false;
2407
2408 // Made it
2409 return true;
2410 }
2411
2412private:
2413 // Runs a check to see that all the rows in a vector of rows have the same number of columns.
2414 constexpr bool check_rows(const std::vector<row_type>& rows) const {
2415 if (rows.empty()) return true;
2416 auto nc = rows[0].size();
2417 for (auto i = 1uz; i < rows.size(); ++i) {
2418 if (rows[i].size() != nc) return false;
2419 }
2420 return true;
2421 }
2422
2423 // Performs a single step of Danilevsky's algorithm to reduce a bit-matrix to Frobenius form.
2424 //
2425 // Frobenius form is block diagonal with one or more companion matrices along the diagonal. All matrices can be
2426 // reduced to this form via a sequence of similarity transformations. This methods performs a single one of those
2427 // transformations.
2428 //
2429 // This `frobenius_form` function calls here with an N x N bit-matrix. In each call the method concentrates on just
2430 // 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
2431 // companion matrix that is the transformation of the bottom-right (n-k) x (n-k) sub-matrix. The caller can store
2432 // that result, decrement n, and call again on the smaller top-left sub-matrix. It may be that the whole matrix
2433 // gets reduced to a single companion matrix in one step and then there will be no need to call again.
2434 //
2435 // The method tries to transform the n x n top-left sub-matrix to a companion matrix working from its bottom-right
2436 // 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
2437 // and returns the top row of that sub-matrix. The caller can store that result, decrement n, and call again on
2438 // the smaller top-left sub-matrix.
2439 //
2440 // NOTE: This method panics if the bit-matrix is not square.
2441 BitVec<Word> danilevsky_step(usize n) {
2442 gf2_assert(n <= rows(), "No top-left {} x {} sub-matrix in a matrix with {} rows", n, n, rows());
2443
2444 // Edge case: A 1 x 1 matrix is already in companion form.
2445 if (n == 1) return BitVec<Word>::constant(1, get(0, 0));
2446
2447 // Step k of algorithm attempts to reduce row k to companion form.
2448 // By construction, rows k+1 or later are already in companion form.
2449 auto k = n - 1;
2450 while (k > 0) {
2451 // If row k's sub-diagonal is all zeros we look for an earlier column with a 1.
2452 // If found, we swap that column here & then swap the equivalent rows to preserve similarity.
2453 if (!get(k, k - 1)) {
2454 for (auto j = 0uz; j < k - 1; ++j) {
2455 if (get(k, j)) {
2456 swap_rows(j, k - 1);
2457 swap_cols(j, k - 1);
2458 break;
2459 }
2460 }
2461 }
2462
2463 // No joy? Perhaps we have a companion matrix in the lower left corner and can return its top row?
2464 if (!get(k, k - 1)) break;
2465
2466 // Still no joy? The sub-diagonal is not all zeros so apply transform to make it so: self <- M^-1 * self *
2467 // M, where M is the identity matrix with the (k-1)'st row replaced by the k'th row of `self`. We can
2468 // sparsely represent M as just a clone of that k'th row of `self`.
2469 auto m = m_rows[k];
2470
2471 // Note the M^-1 is the same as M and self <- M^-1 * self just alters a few of our elements.
2472 for (auto j = 0uz; j < n; ++j) set(k - 1, j, dot(m, col(j)));
2473
2474 // We also use the sparsity of M when computing self <- self * M.
2475 for (auto i = 0uz; i < k; ++i) {
2476 for (auto j = 0uz; j < n; ++j) {
2477 auto tmp = get(i, k - 1) && m.get(j);
2478 if (j == k - 1) {
2479 set(i, j, tmp);
2480 } else {
2481 set(i, j, get(i, j) ^ tmp);
2482 }
2483 }
2484 }
2485
2486 // Now put row k into companion form of all zeros with one on the sub-diagonal.
2487 // All the rows below k are already in companion form.
2488 m_rows[k].set_all(false);
2489 set(k, k - 1);
2490
2491 // Done with row k
2492 k -= 1;
2493 }
2494
2495 // At this point, k == 0 OR the bit-matrix has non-removable zero on the sub-diagonal of row k.
2496 // Either way, the bottom-right (n-k) x (n-k) sub-matrix, starting at self[k][k], is in companion form.
2497 // We return the top row of that companion sub-matrix.
2498 auto top_row = BitVec<Word>::zeros(n - k);
2499 for (auto j = 0uz; j < n - k; ++j) top_row.set(j, get(k, k + j));
2500
2501 // Done
2502 return top_row;
2503 }
2504};
2505
2506// --------------------------------------------------------------------------------------------------------------------
2507// Matrix multiplication ...
2508// -------------------------------------------------------------------------------------------------------------------
2509
2511template<unsigned_word Word, typename Rhs>
2512 requires store_word_is<Rhs, Word>
2513constexpr auto
2514dot(const BitMat<Word>& lhs, const BitStore<Rhs>& rhs) {
2515 gf2_assert_eq(lhs.cols(), rhs.size(), "Incompatible dimensions: {} != {}", lhs.cols(), rhs.size());
2516 usize r = lhs.rows();
2517 BitVec<Word> retval{r};
2518 for (auto i = 0uz; i < r; ++i) retval[i] = dot(lhs.row(i), rhs);
2519 return retval;
2520}
2521
2523template<unsigned_word Word, typename Rhs>
2524 requires store_word_is<Rhs, Word>
2525constexpr auto
2526operator*(const BitMat<Word>& lhs, const BitStore<Rhs>& rhs) {
2527 return dot(lhs, rhs);
2528}
2529
2533template<unsigned_word Word, typename Lhs>
2534 requires store_word_is<Lhs, Word>
2535constexpr auto
2536dot(const BitStore<Word>& lhs, const BitMat<Word>& rhs) {
2537 gf2_assert_eq(lhs.size(), rhs.rows(), "Incompatible dimensions: {} != {}", lhs.size(), rhs.rows());
2538 usize c = rhs.cols();
2539 BitVec<Word> retval{c};
2540 for (auto j = 0uz; j < c; ++j) retval[j] = dot(lhs, rhs.col(j));
2541 return retval;
2542}
2543
2547template<unsigned_word Word, typename Lhs>
2548 requires store_word_is<Lhs, Word>
2549constexpr auto
2550operator*(const BitStore<Lhs>& lhs, const BitMat<Word>& rhs) {
2551 return dot(lhs, rhs);
2552}
2553
2555template<unsigned_word Word>
2556constexpr auto
2557dot(const BitMat<Word>& lhs, const BitMat<Word>& rhs) {
2558 gf2_assert_eq(lhs.cols(), rhs.rows(), "Incompatible dimensions: {} != {}", lhs.cols(), rhs.rows());
2559 usize r = lhs.rows();
2560 usize c = rhs.cols();
2561 BitMat<Word> retval{r, c};
2562 for (auto j = 0uz; j < c; ++j) {
2563 auto rhs_col = rhs.col(j);
2564 for (auto i = 0uz; i < r; ++i) retval.set(i, j, dot(lhs.row(i), rhs_col));
2565 }
2566 return retval;
2567}
2568
2570template<unsigned_word Word>
2571constexpr auto
2572operator*(const BitMat<Word>& lhs, const BitMat<Word>& rhs) {
2573 return dot(lhs, rhs);
2574}
2575
2576// --------------------------------------------------------------------------------------------------------------------
2577// Some utility methods to print multiple matrices & vectors side-by-side ...
2578// -------------------------------------------------------------------------------------------------------------------
2579
2581template<unsigned_word Word, typename Rhs>
2582 requires store_word_is<Rhs, Word>
2583std::string
2585
2586 // If either is empty there was likely a bug somewhere!
2587 gf2_assert(!A.is_empty(), "Matrix A is empty which is likely an error!");
2588 gf2_assert(!b.is_empty(), "Vector b is empty which is likely an error!");
2589
2590 // Most rows we ever print
2591 auto nr = std::max(A.rows(), b.size());
2592
2593 // Space filler might be needed if the matrix has fewer rows than the vector
2594 std::string A_fill(A.cols(), ' ');
2595
2596 // Little lambda that turns a bool into a string
2597 auto b2s = [](bool x) { return x ? "1" : "0"; };
2598
2599 // Build the result string
2600 std::string result;
2601 for (auto r = 0uz; r < nr; ++r) {
2602 auto A_str = r < A.rows() ? A[r].to_string() : A_fill;
2603 auto b_str = r < b.size() ? b2s(b[r]) : " ";
2604 result += A_str + "\t" + b_str;
2605 if (r + 1 < nr) result += "\n";
2606 }
2607
2608 return result;
2609}
2610
2612template<unsigned_word Word, typename Rhs>
2613 requires store_word_is<Rhs, Word>
2614std::string
2616
2617 // If either is empty there was likely a bug somewhere!
2618 gf2_assert(!A.is_empty(), "Matrix A is empty which is likely an error!");
2619 gf2_assert(!b.is_empty(), "Vector b is empty which is likely an error!");
2620 gf2_assert(!c.is_empty(), "Vector c is empty which is likely an error!");
2621
2622 // Most rows we ever print
2623 auto nr = std::max({A.rows(), b.size(), c.size()});
2624
2625 // Space filler might be needed if the matrix has fewer rows than the vector
2626 std::string A_fill(A.cols(), ' ');
2627
2628 // Little lambda that turns a bool into a string
2629 auto b2s = [](bool x) { return x ? "1" : "0"; };
2630
2631 // Build the result string
2632 std::string result;
2633 for (auto r = 0uz; r < nr; ++r) {
2634 auto A_str = r < A.rows() ? A[r].to_string() : A_fill;
2635 auto b_str = r < b.size() ? b2s(b[r]) : " ";
2636 auto c_str = r < c.size() ? b2s(c[r]) : " ";
2637 result += A_str + "\t" + b_str + "\t" + c_str;
2638 if (r + 1 < nr) result += "\n";
2639 }
2640
2641 return result;
2642}
2643
2645template<unsigned_word Word, typename Rhs>
2646 requires store_word_is<Rhs, Word>
2647std::string
2648string_for(const BitMat<Word>& A, const BitStore<Rhs>& b, const BitStore<Rhs>& c, const BitStore<Rhs>& d) {
2649
2650 // If either is empty there was likely a bug somewhere!
2651 gf2_assert(!A.is_empty(), "Matrix A is empty which is likely an error!");
2652 gf2_assert(!b.is_empty(), "Vector b is empty which is likely an error!");
2653 gf2_assert(!c.is_empty(), "Vector c is empty which is likely an error!");
2654 gf2_assert(!d.is_empty(), "Vector d is empty which is likely an error!");
2655
2656 // Most rows we ever print
2657 auto nr = std::max({A.rows(), b.size(), c.size(), d.size()});
2658
2659 // Space filler might be needed if the matrix has fewer rows than the vector
2660 std::string A_fill(A.cols(), ' ');
2661
2662 // Little lambda that turns a bool into a string
2663 auto b2s = [](bool x) { return x ? "1" : "0"; };
2664
2665 // Build the result string
2666 std::string result;
2667 for (auto r = 0uz; r < nr; ++r) {
2668 auto A_str = r < A.rows() ? A[r].to_string() : A_fill;
2669 auto b_str = r < b.size() ? b2s(b[r]) : " ";
2670 auto c_str = r < c.size() ? b2s(c[r]) : " ";
2671 auto d_str = r < d.size() ? b2s(d[r]) : " ";
2672 result += A_str + "\t" + b_str + "\t" + c_str + "\t" + d_str;
2673 if (r + 1 < nr) result += "\n";
2674 }
2675
2676 return result;
2677}
2678
2680template<unsigned_word Word>
2681std::string
2683
2684 // If either is empty there was likely a bug somewhere!
2685 gf2_assert(!A.is_empty(), "Matrix A is empty which is likely an error!");
2686 gf2_assert(!B.is_empty(), "Matrix B is empty which is likely an error!");
2687
2688 // Most rows we ever print
2689 auto nr = std::max(A.rows(), B.rows());
2690
2691 // Space filler might be needed if the matrix has fewer rows than the vector
2692 std::string A_fill(A.cols(), ' ');
2693 std::string B_fill(B.cols(), ' ');
2694
2695 // Build the result string
2696 std::string result;
2697 for (auto r = 0uz; r < nr; ++r) {
2698 auto A_str = r < A.rows() ? A[r].to_string() : A_fill;
2699 auto B_str = r < B.rows() ? B[r].to_string() : B_fill;
2700 result += A_str + "\t" + B_str;
2701 if (r + 1 < nr) result += "\n";
2702 }
2703
2704 return result;
2705}
2706
2708template<unsigned_word Word>
2709std::string
2710string_for(const BitMat<Word>& A, const BitMat<Word>& B, const BitMat<Word>& C) {
2711
2712 // If either is empty there was likely a bug somewhere!
2713 gf2_assert(!A.is_empty(), "Matrix A is empty which is likely an error!");
2714 gf2_assert(!B.is_empty(), "Matrix B is empty which is likely an error!");
2715 gf2_assert(!C.is_empty(), "Matrix C is empty which is likely an error!");
2716
2717 // Most rows we ever print
2718 auto nr = std::max({A.rows(), B.rows(), C.rows()});
2719
2720 // Space filler might be needed if the matrix has fewer rows than the vector
2721 std::string A_fill(A.cols(), ' ');
2722 std::string B_fill(B.cols(), ' ');
2723 std::string C_fill(C.cols(), ' ');
2724
2725 // Build the result string
2726 std::string result;
2727 for (auto r = 0uz; r < nr; ++r) {
2728 auto A_str = r < A.rows() ? A[r].to_string() : A_fill;
2729 auto B_str = r < B.rows() ? B[r].to_string() : B_fill;
2730 auto C_str = r < C.rows() ? C[r].to_string() : C_fill;
2731 result += A_str + "\t" + B_str + "\t" + C_str;
2732 if (r + 1 < nr) result += "\n";
2733 }
2734
2735 return result;
2736}
2737
2738} // namespace gf2
2739
2740// --------------------------------------------------------------------------------------------------------------------
2741// Specialises `std::formatter` to handle bit-matrices ...
2742// -------------------------------------------------------------------------------------------------------------------
2743
2762template<gf2::unsigned_word Word>
2763struct std::formatter<gf2::BitMat<Word>> {
2764
2766 constexpr auto parse(const std::format_parse_context& ctx) {
2767 auto it = ctx.begin();
2768 while (it != ctx.end() && *it != '}') {
2769 switch (*it) {
2770 case 'p': m_pretty = true; break;
2771 case 'x': m_hex = true; break;
2772 default: m_error = true;
2773 }
2774 ++it;
2775 }
2776 return it;
2777 }
2778
2780 template<class FormatContext>
2781 auto format(const gf2::BitMat<Word>& rhs, FormatContext& ctx) const {
2782 // Was there a format specification error?
2783 if (m_error) return std::format_to(ctx.out(), "'UNRECOGNIZED FORMAT SPECIFIER FOR BIT-MATRIX'");
2784
2785 // Special handling requested?
2786 if (m_hex) return std::format_to(ctx.out(), "{}", rhs.to_hex_string());
2787 if (m_pretty) return std::format_to(ctx.out(), "{}", rhs.to_pretty_string());
2788
2789 // Default
2790 return std::format_to(ctx.out(), "{}", rhs.to_string());
2791 }
2792
2793 bool m_hex = false;
2794 bool m_pretty = false;
2795 bool m_error = false;
2796};
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:45
The BitLU class provides the LU decomposition for bit-matrices.
Definition BitLU.h:30
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:2307
constexpr BitMat & append_rows(const BitMat< Word > &src)
Appends all the rows from the src bit-matrix onto the end of this bit-matrix by copying them.
Definition BitMat.h:1249
constexpr bool trace() const
Returns the "sum" of the main diagonal elements of the bit-matrix.
Definition BitMat.h:754
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:326
static BitPoly< Word > frobenius_matrix_characteristic_polynomial(const std::vector< BitVec< Word > > &top_rows)
Class method that returns the characteristic polynomial of a Frobenius matrix as a gf2::BitPoly.
Definition BitMat.h:1968
constexpr void transpose()
Transposes a square bit-matrix in place.
Definition BitMat.h:1635
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:1490
constexpr void operator-=(const BitMat< Word > &rhs)
In-place difference with a bit-matrix rhs – in GF(2) subtraction is the same as XOR.
Definition BitMat.h:2197
std::string to_compact_hex_string() const
Returns a compact "hex" string representation of the bit-matrix.
Definition BitMat.h:2381
constexpr BitMat upper() const
Returns an independent clone of the upper triangular part of the bit-matrix.
Definition BitMat.h:1464
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:222
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:644
constexpr auto operator&(const BitMat< Word > &rhs) const
TheAND of this with a bit-matrix rhs returning a new bit-matrix.
Definition BitMat.h:2125
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 BitMat & append_cols(const BitMat< Word > &src)
Appends all the columns from the src bit-matrix onto the right of this bit-matrix so M -> M|src.
Definition BitMat.h:1309
constexpr bool get(usize r, usize c) const
Returns true if the element at row r and column c is set.
Definition BitMat.h:832
constexpr usize count_ones_on_diagonal() const
Returns the number of ones on the main diagonal of the bit-matrix.
Definition BitMat.h:733
constexpr row_type & row(usize r)
Returns a read-write reference to the row at index r.
Definition BitMat.h:937
constexpr void flip(usize r, usize c)
Flips the bit at row r and column c.
Definition BitMat.h:899
static BitMat right_rotation(usize n, usize p)
Constructs the n x n rotate-right by p places matrix.
Definition BitMat.h:465
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:866
static std::optional< BitMat > from_vector_of_cols(const BitVec< Word > &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:525
std::string to_string() const
Returns the default string representation for this bit-matrix.
Definition BitMat.h:2292
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
constexpr BitMat & append_row(BitVec< Word > &&row)
Appends a single row onto the end of the bit-matrix by moving it.
Definition BitMat.h:1231
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:1845
static BitPoly< Word > companion_matrix_characteristic_polynomial(const BitVec< Word > &top_row)
Class method to return the characteristic polynomial of a companion matrix as a gf2::BitPoly.
Definition BitMat.h:1992
constexpr void operator|=(const BitMat< Word > &rhs)
In-place OR with a bit-matrix rhs.
Definition BitMat.h:2087
std::vector< BitVec< Word > > frobenius_form() const
Returns the Frobenius form of this bit-matrix in compact top-row only form.
Definition BitMat.h:2016
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:1875
static constexpr BitMat zero(usize m)
Factory method to create the m x m square "zero" bit-matrix.
Definition BitMat.h:367
constexpr bool is_identity() const
Returns true if this is the identity bit-matrix.
Definition BitMat.h:667
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:341
constexpr void flip_diagonal()
Flips all the elements on the main diagonal of a square bit-matrix.
Definition BitMat.h:1063
constexpr usize size() const
Returns the totalnumber of elements in the bit-matrix.
Definition BitMat.h:624
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:1131
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:1080
constexpr BitMat unit_lower() const
Returns an independent clone of the unit lower triangular part of the bit-matrix.
Definition BitMat.h:1522
auto x_for(const BitStore< Store > &b) const
Returns a solution to the system of linear equations A.x_ = b or std::nullopt if there are none....
Definition BitMat.h:1926
constexpr void set_all(bool value=true)
Sets all the elements of the bit-matrix to the specified boolean value.
Definition BitMat.h:1018
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:307
constexpr usize cols() const
Returns the number of columns in the bit-matrix.
Definition BitMat.h:621
constexpr BitMat(const std::vector< row_type > &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_super_diagonal(usize d)
Flips all the elements on the super-diagonal d of a square bit-matrix.
Definition BitMat.h:1097
BitVec< Word > to_reduced_echelon_form()
Transforms the bit-matrix to reduced row-echelon form (in-place).
Definition BitMat.h:1783
constexpr const row_type & row(usize r) const
Returns a read-only reference to the row at index r.
Definition BitMat.h:920
constexpr void swap_cols(usize i, usize j)
Swaps columns i and j of the bit-matrix.
Definition BitMat.h:1574
static constexpr BitMat outer_product(const BitVec< Word > &u, const BitVec< Word > &v)
Factory method to create the m x n bit-matrix from the outer product of the given bit-vectors.
Definition BitMat.h:188
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 swap_rows(usize i, usize j)
Swaps rows i and j of the bit-matrix.
Definition BitMat.h:1558
constexpr void operator+=(const BitMat< Word > &rhs)
In-place addition with a bit-matrix rhs – in GF(2) addition is the same as XOR.
Definition BitMat.h:2183
constexpr const row_type & operator[](usize r) const
Returns a read-only reference to the row at index r.
Definition BitMat.h:953
static constexpr BitMat identity(usize m)
Factory method to create the m x m identity bit-matrix.
Definition BitMat.h:376
constexpr void resize(usize r, usize c)
Resize the bit-matrix to r rows and c columns.
Definition BitMat.h:1160
constexpr void flip_all()
Flips all the elements of the bit-matrix.
Definition BitMat.h:1030
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:995
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:1389
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:1328
constexpr usize count_ones() const
Returns the number of one elements in the bit-matrix.
Definition BitMat.h:709
constexpr BitMat & append_col(const BitStore< Store > &col)
Appends a single column col onto the right of the bit-matrix so M -> M|col.
Definition BitMat.h:1289
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:1590
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:849
constexpr bool is_symmetric() const
Returns true if this is a symmetric square bit-matrix.
Definition BitMat.h:686
constexpr auto operator+(const BitMat< Word > &rhs) const
Returns a new bit-matrix that is *this + rhs which is *this ^ rhs in GF(2).
Definition BitMat.h:2211
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:1269
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:1726
constexpr bool is_zero() const
Returns true if this a square zero bit-matrix?
Definition BitMat.h:657
BitLU< Word > LU() const
Returns the LU decomposition of the bit-matrix.
Definition BitMat.h:1889
constexpr usize rows() const
Returns the number of rows in the bit-matrix.
Definition BitMat.h:618
constexpr void operator&=(const BitMat< Word > &rhs)
In-place AND with a bit-matrix rhs.
Definition BitMat.h:2069
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:354
std::optional< BitMat > inverse() const
Returns the inverse of a square bit-matrix or std::nullopt if the matrix is singular.
Definition BitMat.h:1813
std::string to_compact_binary_string() const
Returns a one-line minimal binary string representation for this bit-matrix.
Definition BitMat.h:2281
constexpr void clear()
Removes all the elements from the bit-matrix.
Definition BitMat.h:1181
constexpr bool all() const
Returns true if all elements of the bit-matrix are set.
Definition BitMat.h:792
static constexpr BitMat companion(const BitStore< Rhs > &top_row)
Constructs a square companion BitMat with a copy of the given top row and a sub-diagonal of 1s.
Definition BitMat.h:395
constexpr void operator^=(const BitMat< Word > &rhs)
In-place XOR with a bit-matrix rhs.
Definition BitMat.h:2051
constexpr auto operator|(const BitMat< Word > &rhs) const
TheOR of this with a bit-matrix rhs returning a new bit-matrix.
Definition BitMat.h:2145
static constexpr BitMat outer_sum(const BitVec< Word > &u, const BitVec< Word > &v)
Factory method to create the m x n bit-matrix from the outer sum of the given bit-vectors.
Definition BitMat.h:206
constexpr auto operator^(const BitMat< Word > &rhs) const
TheXOR of this with a bit-matrix rhs returning a new bit-matrix.
Definition BitMat.h:2105
constexpr bool is_empty() const
Is this an empty bit-matrix?
Definition BitMat.h:627
constexpr void replace(usize top, usize left, const BitMat< Word > &src)
Replaces the sub-matrix starting at row top and column left with a copy of the sub-matrix src.
Definition BitMat.h:1420
constexpr auto operator~()
Returns a copy of this bit-matrix with all the elements flipped.
Definition BitMat.h:2161
constexpr BitMat transposed() const
Returns a new bit-matrix that is the transpose of this one.
Definition BitMat.h:1611
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:571
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:883
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:2253
static BitMat left_rotation(usize n, usize p)
Constructs the n x n rotate-left by p places matrix.
Definition BitMat.h:446
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:1193
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:257
constexpr BitMat unit_upper() const
Returns an independent clone of the unit upper triangular part of the bit-matrix.
Definition BitMat.h:1538
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:970
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:1364
auto solver_for(const BitStore< Store > &b) const
Returns the Gaussian elimination solver for this bit-matrix and the passed r.h.s. vector b.
Definition BitMat.h:1909
constexpr void set_diagonal(bool val=true)
Sets the main diagonal of a square bit-matrix to the boolean value val.
Definition BitMat.h:1048
constexpr BitMat strictly_upper() const
Returns an independent clone of the strictly upper triangular part of the bit-matrix.
Definition BitMat.h:1506
static BitMat right_shift(usize n, usize p)
Constructs the n x n shift-right by p places BitMat.
Definition BitMat.h:430
friend constexpr bool operator==(const BitMat &lhs, const BitMat &rhs)
Equality operator checks that any pair of bit-matrices are equal in content.
Definition BitMat.h:2398
BitPoly< Word > characteristic_polynomial() const
Returns the characteristic polynomial of any square bit-matrix as a gf2::BitPoly.
Definition BitMat.h:1954
constexpr auto operator-(const BitMat< Word > &rhs) const
Returns a new bit-matrix that is *this - rhs which is *this ^ rhs in GF(2).
Definition BitMat.h:2229
std::string to_hex_string(std::string_view row_sep="\n") const
"Returns the "hex" string representation of the bit-matrix
Definition BitMat.h:2336
constexpr bool none() const
Returns true if no elements of the bit-matrix are set.
Definition BitMat.h:811
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:773
constexpr usize count_zeros() const
Returns the number of zero elements in the bit-matrix.
Definition BitMat.h:722
static constexpr BitMat left_shift(usize n, usize p)
Constructs the n x n shift-left by p places BitMat.
Definition BitMat.h:414
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:1668
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:1345
constexpr BitMat lower() const
Returns an independent clone of the lower triangular part of the bit-matrix.
Definition BitMat.h:1440
constexpr BitMat & append_row(const BitVec< Word > &row)
Appends a single row onto the end of the bit-matrix by copying it.
Definition BitMat.h:1211
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:1114
static std::optional< BitMat > from_vector_of_rows(const BitVec< Word > &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:490
A BitPoly represents a polynomial over GF(2) where we store the polynomial coefficients in a bit-vect...
Definition BitPoly.h:31
static constexpr BitPoly zero()
Factory method to return the "zero" bit-polynomial p(x) := 0.
Definition BitPoly.h:91
A BitRef is a proxy class to reference a single bit in a bit-store.
Definition BitRef.h:38
The base class for the vector-like types in the gf2 library: BitSet, BitVec, and BitSpan The templat...
Definition BitStore.h:35
constexpr bool is_empty() const
Returns true if the store is empty, false otherwise.
Definition BitStore.h:295
constexpr auto span(usize begin, usize end) const
Returns an immutable bit-span encompassing the bits in the half-open range [begin,...
Definition BitStore.h:1035
constexpr bool get(usize i) const
Returns true if the bit at the given index i is set, false otherwise.
Definition BitStore.h:123
constexpr usize size() const
Returns the number of bit elements in the store.
Definition BitStore.h:63
auto flip(usize index)
Definition BitStore.h:233
constexpr bool any() const
Definition BitStore.h:308
A dynamically-sized vector over GF(2) with bit elements compactly stored in a standard vector of prim...
Definition BitVec.h:37
static std::optional< BitVec > from_string(std::string_view sv)
Definition BitVec.h:402
constexpr usize size() const
Returns the number of bit-elements in the bit-vector.
Definition BitVec.h:64
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:231
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:212
static constexpr BitVec alternating(usize n)
Factory method to generate a bit-vector of length n looking like 101010....
Definition BitVec.h:255
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:220
The namespace for the gf2 library.
Definition assert.h:119
constexpr auto dot(const BitMat< Word > &lhs, const BitStore< Rhs > &rhs)
Bit-matrix, bit-store multiplication, M * v, returning a new bit-vector.
Definition BitMat.h:2514
constexpr auto operator*(const BitMat< Word > &lhs, const BitStore< Rhs > &rhs)
Operator form for bit-matrix, bit-vector multiplication, M * v, returning a new bit-vector.
Definition BitMat.h:2526
std::string string_for(const BitMat< Word > &A, const BitStore< Rhs > &b)
Returns a string that shows a bit-matrix and a bit-vector side-by-side.
Definition BitMat.h:2584
std::size_t usize
Word type alias for the platform's "native"-sized unsigned integer.
Definition unsigned_word.h:41
constexpr auto parse(const std::format_parse_context &ctx)
Parse a bit-store format specifier where where we recognize {:p} and {:x}.
Definition BitMat.h:2766
auto format(const gf2::BitMat< Word > &rhs, FormatContext &ctx) const
Push out a formatted bit-matrix using the various to_string(...) methods in the class.
Definition BitMat.h:2781