C(++)ECCO
C++ Error Control COding: a header-only library for ECC simulations and experiments, modeling complete coding systems across arbitrary finite fields and complex inter-field relationships - Christian Senger <senger@inue.uni-stuttgart.de>
Loading...
Searching...
No Matches
matrices.hpp
Go to the documentation of this file.
1/**
2 * @file matrices.hpp
3 * @brief Matrix arithmetic library
4 * @author Christian Senger <senger@inue.uni-stuttgart.de>
5 * @version 2.3.4
6 * @date 2026
7 *
8 * @copyright
9 * Copyright (c) 2026, Christian Senger <senger@inue.uni-stuttgart.de>
10 *
11 * Licensed for noncommercial use only, including academic teaching, research, and personal non-profit purposes.
12 * Commercial use is prohibited without a separate commercial license. See the [LICENSE](../../LICENSE) file in the
13 * repository root for full terms and how to request a commercial license.
14 *
15 * @section Description
16 *
17 * Dense and structured matrices over any @ref CECCO::ComponentType (finite fields, floating-point,
18 * complex, signed integers). Provides REF/RREF (with binary-field fast paths), cached rank,
19 * determinant, nullspace, characteristic polynomial, and matrix inversion. Cross-field
20 * operations bridge through @ref CECCO::largest_common_subfield_t.
21 *
22 * The class template `Matrix<T>` automatically tracks structural type
23 * (@ref CECCO::details::matrix_type_t: Zero, Identity, Diagonal, Vandermonde, Toeplitz)
24 * to enable specialized fast paths. The type tag is transparent to callers; factories
25 * (`IdentityMatrix`, `DiagonalMatrix`, `VandermondeMatrix`, …) all return `Matrix<T>`.
26 *
27 * @code{.cpp}
28 * // Factory + arithmetic
29 * auto I = IdentityMatrix<double>(3);
30 * auto D = DiagonalMatrix(Vector<double>{1, 2, 3});
31 * auto M = I + D; // 3×3, type tag stays Diagonal
32 *
33 * // Finite-field linear algebra
34 * Matrix<Fp<7>> P = {{1, 2}, {3, 4}};
35 * size_t r = P.rank(); // Cached
36 * auto null_basis = P.basis_of_nullspace();
37 * @endcode
38 *
39 * @see @ref fields.hpp, @ref vectors.hpp, @ref field_concepts_traits.hpp
40 */
41
42#ifndef MATRICES_HPP
43#define MATRICES_HPP
44
45#include <cassert>
46#include <fstream>
47#include <iomanip>
48#include <iostream>
49#include <ranges>
50#include <set>
51#include <sstream>
52#include <unordered_map>
53
55
56/*
57// transitive
58#include <algorithm>
59#include <complex>
60#include <concepts>
61#include <cstdint>
62#include <functional>
63#include <initializer_list>
64#include <iterator>
65#include <limits>
66#include <random>
67#include <stdexcept>
68#include <string>
69#include <type_traits>
70#include <utility>
71#include <vector>
72
73#include "InfInt.hpp"
74#include "helpers.hpp"
75*/
76
77namespace CECCO {
78
79namespace details {
80
81/**
82 * @brief Matrix structural type tag for optimization (internal)
83 *
84 * Maintained automatically by `Matrix<T>` operations; mutations that break a structure
85 * demote the tag to `Generic`. Specialized algorithms dispatch on this tag to avoid
86 * full O(mn) traversals when possible.
87 */
89 /**
90 * @brief Generic matrix with arbitrary elements
91 */
93
94 /**
95 * @brief Zero matrix with all elements equal to zero
96 */
98
99 /**
100 * @brief Diagonal matrix (square) with non-zero elements only on the main diagonal
101 */
103
104 /**
105 * @brief Identity matrix with ones on diagonal and zeros elsewhere
106 */
108
109 /**
110 * @brief Vandermonde matrix with arithmetic progressions (of pairwise distinct elements) in columns
111 */
113
114 /**
115 * @brief Toeplitz matrix T_{i,j} = t_{i-j} with constant diagonals
116 */
118};
119
120} // namespace details
121
122template <ComponentType T>
123class Vector;
124template <CoefficientType T>
125class Polynomial;
126template <ComponentType T>
127class Matrix;
128
129template <ComponentType T>
130constexpr Matrix<T> ZeroMatrix(size_t m, size_t n);
131template <ComponentType T>
132constexpr Matrix<T> IdentityMatrix(size_t m);
133template <ComponentType T>
134constexpr Matrix<T> DiagonalMatrix(const Vector<T>& v);
135template <ComponentType T>
136constexpr Matrix<T> ToeplitzMatrix(const Vector<T>& v, size_t m, size_t n);
137template <ReliablyComparableType T>
138constexpr Matrix<T> VandermondeMatrix(const Vector<T>& v, size_t m);
139template <ComponentType T>
140std::ostream& operator<<(std::ostream& os, const Matrix<T>& rhs);
141
142/**
143 * @brief Dense m × n matrix over a @ref CECCO::ComponentType
144 *
145 * @tparam T Component type satisfying @ref CECCO::ComponentType (finite field, `double`,
146 * `std::complex<double>`, or signed integer including `InfInt`)
147 *
148 * Components are stored row-major in a contiguous buffer. A structural tag
149 * (@ref details::matrix_type_t) tracks `Zero` / `Identity` / `Diagonal` / `Vandermonde` /
150 * `Toeplitz` structures and dispatches to specialised fast paths; mutating operations that
151 * break a tracked structure demote the tag to `Generic`. Structure is re-detected after
152 * additive and multiplicative composition, but not after component-level mutations.
153 * Dimension mismatches in arithmetic throw `std::invalid_argument`.
154 *
155 * Methods that need division (REF/RREF, inversion, nullspace, determinant, characteristic
156 * polynomial, …) are gated by `requires FieldType<T>`; eigenvalue computation by
157 * `requires FiniteFieldType<T>`. Methods that compare against zero (Hamming weight, rank,
158 * structural tests, …) are gated by `requires ReliablyComparableType<T>`.
159 *
160 * Cross-field constructors and assignment operators between two finite fields of the same
161 * characteristic route through @ref CECCO::details::largest_common_subfield_t, so matrices
162 * over fields from disjoint construction towers can interoperate.
163 *
164 * @section Usage_Example
165 *
166 * @code{.cpp}
167 * using F4 = Ext<Fp<2>, MOD{1, 1, 1}>;
168 * Matrix<F4> M = {{1, 0}, {1, 1}};
169 * size_t r = M.rank(); // cached
170 * auto null_basis = M.basis_of_nullspace();
171 * auto chi = M.characteristic_polynomial();
172 * @endcode
173 */
174template <ComponentType T>
175class Matrix {
176 friend constexpr Matrix<T> IdentityMatrix<>(size_t m);
177 friend constexpr Matrix<T> DiagonalMatrix<>(const Vector<T>& v);
178 friend constexpr Matrix<T> ToeplitzMatrix<>(const Vector<T>& v, size_t m, size_t n);
179 template <ReliablyComparableType U>
180 friend constexpr Matrix<U> VandermondeMatrix(const Vector<U>& v, size_t m);
181 template <ReliablyComparableType U>
182 friend constexpr bool operator==(const Matrix<U>& lhs, const Matrix<U>& rhs);
183 friend std::ostream& operator<< <>(std::ostream& os, const Matrix& rhs);
184 template <ComponentType>
185 friend class Matrix;
186 friend class Vector<T>;
187
188 public:
189 /** @name Constructors
190 * @{
191 */
192
193 /// @brief Default constructor: empty matrix
194 constexpr Matrix() noexcept = default;
195
196 /// @brief m × n zero matrix (tag @ref details::Zero)
197 constexpr Matrix(size_t m, size_t n) : data(m * n, T(0)), m(m), n(n), type(details::Zero) {}
198
199 /**
200 * @brief m × n matrix with every component equal to `l`
201 *
202 * Type tag is determined based on m, n, and l.
203 */
204 Matrix(size_t m, size_t n, const T& l);
205
206 /**
207 * @brief m × n matrix from a flat initializer list (row-major)
208 *
209 * @throws std::invalid_argument if `l.size() != m * n`
210 */
211 constexpr Matrix(size_t m, size_t n, std::initializer_list<T> l);
212
213 /**
214 * @brief From nested initializer lists, e.g. `{{1, 2, 3}, {4, 5, 6}}`
215 *
216 * Rows of unequal length are zero-padded to the longest row.
217 */
218 Matrix(std::initializer_list<std::initializer_list<T>> l);
219
220 /// @brief 1 × n row matrix from a `Vector<T>`
221 Matrix(const Vector<T>& v);
222
223 constexpr Matrix(const Matrix& other)
224 : data(other.data),
225 m(other.m),
226 n(other.n),
227 transposed(other.transposed),
228 type(other.type),
229 cache(other.cache) {}
230
231 constexpr Matrix(Matrix&& other) noexcept
232 : data(std::move(other.data)),
233 m(other.m),
234 n(other.n),
235 transposed(other.transposed),
236 type(other.type),
238 other.m = 0;
239 other.n = 0;
240 other.transposed = false;
241 other.type = details::Zero;
242 other.cache.invalidate();
243 }
244
245 /**
246 * @brief Cross-field conversion between two finite fields of the same characteristic
247 *
248 * @tparam S Source field type (`Matrix<S>`); must share characteristic with T
249 *
250 * Converts component by component via T's cross-field constructor, which routes through
251 * @ref CECCO::details::largest_common_subfield_t and so handles disjoint construction towers.
252 * Propagates `std::invalid_argument` if any component is not representable in T.
253 */
254 template <FiniteFieldType S>
255 constexpr Matrix(const Matrix<S>& other)
257
258 /**
259 * @brief Read from a PPM (P3) image file using the 64-entry colormap of @ref export_as_ppm
260 *
261 * Pixels whose RGB does not match any colormap entry are erased (under
262 * @ref CECCO_ERASURE_SUPPORT) or replaced by a random field element.
263 *
264 * @note Convert other formats with ImageMagick:
265 * @code{.sh}
266 * magick input.png -alpha remove -background black +dither -remap palette.ppm -compress none output.ppm
267 * @endcode
268 */
269 Matrix(const std::string& filename)
270 requires FiniteFieldType<T> && (T::get_size() <= 64);
271
272 /** @} */
273
274 /** @name Assignment Operators
275 * @{
276 */
277
278 constexpr Matrix& operator=(const Matrix& rhs);
279 constexpr Matrix& operator=(Matrix&& rhs) noexcept;
280
281 /// @brief Cross-field assignment (same semantics as the cross-field constructor)
282 template <FiniteFieldType S>
284 constexpr Matrix& operator=(const Matrix<S>& other);
285
286 /** @} */
287
288 /** @name Unary Arithmetic Operations
289 * @{
290 */
291
292 /// @brief Unary `+` (lvalue): returns a copy
293 constexpr Matrix operator+() const& { return *this; }
294 /// @brief Unary `+` (rvalue): returns the rvalue itself
295 constexpr Matrix operator+() && noexcept { return std::move(*this); }
296
297 /// @brief Unary `−` (lvalue): returns a new matrix with each component negated
298 constexpr Matrix operator-() const&;
299 /// @brief Unary `−` (rvalue): negates components in place
300 constexpr Matrix operator-() &&;
301
302 /** @} */
303
304 /** @name Compound Assignment Operations
305 * @{
306 */
307
308 /**
309 * @brief Component-wise addition `this(i, j) += rhs(i, j)`
310 *
311 * @throws std::invalid_argument if dimensions differ
312 */
313 Matrix& operator+=(const Matrix& rhs);
314
315 /**
316 * @brief Component-wise subtraction `this(i, j) -= rhs(i, j)`
317 *
318 * @throws std::invalid_argument if dimensions differ
319 */
320 Matrix& operator-=(const Matrix& rhs);
321
322 /**
323 * @brief Matrix multiplication `*this = *this · rhs`
324 *
325 * @throws std::invalid_argument if `this->get_n() != rhs.get_m()`
326 */
327 Matrix& operator*=(const Matrix& rhs);
328
329 /// @brief Multiply every component by the scalar `s`
330 constexpr Matrix& operator*=(const T& s);
331
332 /**
333 * @brief Divide every component by the scalar `s`
334 *
335 * @throws std::invalid_argument if `s == T(0)`
336 * @note Round-trip `(M / s) * s == M` is only guaranteed when T satisfies @ref CECCO::FieldType
337 * (otherwise integer rounding may corrupt components).
338 */
339 Matrix& operator/=(const T& s)
341
342 /** @} */
343
344 /** @name Randomization
345 * @{
346 */
347
348 /**
349 * @brief Fill matrix with random values
350 *
351 * @return Reference to this matrix after randomization
352 *
353 * Distribution depends on the component type: finite-field types draw uniformly from the
354 * field; signed integers from [−100, 100]; `double` and the real/imaginary parts of
355 * `std::complex<double>` from [−1.0, 1.0].
356 *
357 * @note The type tag is re-detected afterwards, so incidental structure (e.g. an all-zero
358 * draw over a small field) is recognized.
359 *
360 * @note Unavailable for polynomial components: randomizing a polynomial needs a degree,
361 * see @ref CECCO::Polynomial::randomize.
362 */
365
366 /** @} */
367
368 /** @name Information and Properties
369 * @{
370 */
371
372 /**
373 * @brief Get the number of rows
374 *
375 * @return Number of rows in the matrix
376 */
377 constexpr size_t get_m() const noexcept { return m; }
378
379 /**
380 * @brief Get the number of columns
381 *
382 * @return Number of columns in the matrix
383 */
384 constexpr size_t get_n() const noexcept { return n; }
385
386 /**
387 * @brief Check if matrix is empty
388 *
389 * @return true if matrix has zero rows or columns, false otherwise
390 */
391 constexpr bool is_empty() const noexcept { return m == 0 || n == 0; }
392
393 /**
394 * @brief Check if the matrix is zero, caching the result via the type tag
395 *
396 * @return true iff every component equals T(0)
397 *
398 * On a positive result the tag is updated to @ref details::Zero, so subsequent calls and
399 * other tag-aware fast paths short-circuit.
400 */
401 constexpr bool is_zero() {
402 if (type == details::Zero) return true;
403 const T zero(0);
404 const bool b = std::ranges::all_of(data, [&zero](const T& v) { return v == zero; });
405 if (b) type = details::Zero;
406 return b;
407 }
408
409 /**
410 * @brief Check if the matrix is zero (no caching)
411 *
412 * @return true iff every component equals T(0)
413 */
414 constexpr bool is_zero() const {
415 if (type == details::Zero) return true;
416 const T zero(0);
417 return std::ranges::all_of(data, [&zero](const T& v) { return v == zero; });
418 }
419
420 /// @brief Hamming weight: number of non-zero, non-erased components; cached on first call
421 size_t wH() const
423 {
424 return cache.template get_or_compute<Weight>([this] { return calculate_weight(); });
425 }
426
427 /**
428 * @brief Matrix rank, computed once and cached
429 *
430 * @return Dimension of the row space (equivalently, of the column space)
431 *
432 * Uses row reduction to echelon form. Subsequent calls return the cached value until a
433 * mutating operation invalidates it.
434 */
435 size_t rank() const
437
438 /**
439 * @brief Check if matrix is invertible
440 *
441 * @return true iff the matrix is square and has full rank
442 */
443 bool is_invertible() const
445 {
446 return m == n && rank() == m;
447 }
448
449 /**
450 * @brief Main diagonal as a vector
451 *
452 * @return Vector containing entries (i, i) for i = 0, …, m−1
453 *
454 * @throws std::invalid_argument if the matrix is not square
455 */
456 Vector<T> diagonal() const;
457
458 /**
459 * @brief Characteristic polynomial det(λI − A)
460 *
461 * @return Polynomial of degree m
462 *
463 * Computed via the Samuelson–Berkowitz algorithm in the general case; the
464 * @ref details::Diagonal and @ref details::Vandermonde tags trigger closed-form shortcuts.
465 *
466 * @note Member template with U fixed to T, so that instantiating a matrix over polynomial
467 * components does not form the unsupported type `Polynomial<Polynomial<...>>`.
468 *
469 * @throws std::invalid_argument if the matrix is not square or is empty
470 */
471 template <CoefficientType U = T>
474
475 /**
476 * @brief Basis of the nullspace (right kernel)
477 *
478 * @return Matrix whose rows span { x : A xᵀ = 0 }; empty matrix if the nullspace is trivial
479 *
480 * Computed by row reduction.
481 */
484
485 /**
486 * @brief Alias for @ref basis_of_nullspace
487 *
488 * @return Matrix whose rows form a basis of the kernel
489 */
492 {
493 return basis_of_nullspace();
494 }
495
496 /**
497 * @brief Matrix determinant
498 *
499 * @return det(A); T(0) for singular matrices
500 *
501 * Algorithm depends on the type tag: closed-form for @ref details::Identity,
502 * @ref details::Zero, and @ref details::Diagonal; Samuelson–Berkowitz otherwise.
503 *
504 * @throws std::invalid_argument if the matrix is not square or is empty
505 */
508
509 /**
510 * @brief Eigenvalues lying in the underlying finite field
511 *
512 * @return Roots of the characteristic polynomial that lie in T
513 *
514 * Eigenvalues that exist only in an extension of T are omitted.
515 *
516 * @throws std::invalid_argument if the matrix is not square
517 */
520
521 /**
522 * @brief Enumerate every vector in the row space
523 *
524 * @return All q^rank vectors in span(rows), where q = |T|
525 *
526 * @warning Size grows as q^rank, only practical for small fields and small rank.
527 */
530
531 /**
532 * @brief Alias for @ref rowspace
533 *
534 * @return Every vector in the span of the rows
535 */
536 std::vector<Vector<T>> span() const
538 {
539 return rowspace();
540 }
541
542 /** @} */
543
544 /** @name Component Access and Manipulation
545 * @{
546 */
547
548 /**
549 * @brief Set component (i, j) by perfect forwarding
550 *
551 * @param i Row index (0-based)
552 * @param j Column index (0-based)
553 * @param c Value to assign; bound by lvalue or rvalue reference
554 * @return Reference to this matrix after modification
555 *
556 * @throws std::invalid_argument if either index is out of bounds
557 */
558 template <typename U>
561
562 /**
563 * @brief Access component (i, j) (read-only)
564 *
565 * @param i Row index (0-based)
566 * @param j Column index (0-based)
567 * @return Const reference to the component at (i, j)
568 *
569 * @throws std::invalid_argument if either index is out of bounds
570 */
571 const T& operator()(size_t i, size_t j) const;
572
573 /**
574 * @brief Extract row i as a vector
575 *
576 * @param i Row index
577 * @return Vector containing the components of row i
578 *
579 * @throws std::invalid_argument if i is out of bounds
580 */
581 Vector<T> get_row(size_t i) const;
582
583 /**
584 * @brief Extract column j as a (row) vector
585 *
586 * @param j Column index
587 * @return Vector containing the components of column j; transposed, since @ref Vector models row vectors
588 *
589 * @throws std::invalid_argument if j is out of bounds
590 */
591 Vector<T> get_col(size_t j) const;
592
593 /**
594 * @brief Extract submatrix from region [i, i+h) × [j, j+w)
595 *
596 * @param i Starting row index
597 * @param j Starting column index
598 * @param h Height (number of rows)
599 * @param w Width (number of columns)
600 * @return Submatrix of shape h × w
601 *
602 * @throws std::invalid_argument if the region extends beyond the matrix
603 */
604 Matrix<T> get_submatrix(size_t i, size_t j, size_t h, size_t w) const;
605
606 /**
607 * @brief Overwrite the region starting at (i, j) with N
608 *
609 * @param i Starting row index
610 * @param j Starting column index
611 * @param N Source matrix; its shape must fit within this matrix from (i, j)
612 * @return Reference to this matrix after the assignment
613 *
614 * @throws std::invalid_argument if the region would extend beyond bounds
615 */
616 Matrix<T>& set_submatrix(size_t i, size_t j, const Matrix& N);
617
618 /**
619 * @brief Concatenate other to the right (column-wise)
620 *
621 * @param other Matrix to append on the right; must have the same number of rows as this
622 * @return Reference to this matrix after the join
623 *
624 * @throws std::invalid_argument if row counts differ
625 */
626 Matrix<T>& horizontal_join(const Matrix& other);
627
628 /**
629 * @brief Concatenate other below (row-wise)
630 *
631 * @param other Matrix to append below; must have the same number of columns as this
632 * @return Reference to this matrix after the join
633 *
634 * @throws std::invalid_argument if column counts differ
635 */
636 Matrix<T>& vertical_join(const Matrix& other);
637
638 /**
639 * @brief Block-diagonal join: this in the upper-left, other in the lower-right
640 *
641 * @param other Matrix placed in the lower-right block
642 * @return Reference to this matrix after the join
643 *
644 * Off-diagonal blocks are filled with zeros.
645 */
646 Matrix<T>& diagonal_join(const Matrix& other);
647
648 /**
649 * @brief Kronecker (tensor) product with other
650 *
651 * @param other Right operand
652 * @return Reference to this matrix after the product
653 *
654 * If this is m × n and other is p × q, the result is mp × nq.
655 */
656 Matrix<T>& Kronecker_product(const Matrix& other);
657
658 /**
659 * @brief Swap rows i and j
660 *
661 * @param i First row index
662 * @param j Second row index
663 * @return Reference to this matrix after the swap
664 *
665 * @throws std::invalid_argument if either index is out of bounds
666 */
667 Matrix<T>& swap_rows(size_t i, size_t j);
668
669 /**
670 * @brief Swap columns i and j
671 *
672 * @param i First column index
673 * @param j Second column index
674 * @return Reference to this matrix after the swap
675 *
676 * @throws std::invalid_argument if either index is out of bounds
677 */
678 Matrix<T>& swap_columns(size_t i, size_t j);
679
680 /**
681 * @brief row[i] ← s · row[i]
682 *
683 * @param s Scalar multiplier
684 * @param i Row index
685 * @return Reference to this matrix after the scaling
686 *
687 * @throws std::invalid_argument if i is out of bounds
688 */
689 Matrix<T>& scale_row(const T& s, size_t i);
690
691 /**
692 * @brief col[i] ← s · col[i]
693 *
694 * @param s Scalar multiplier
695 * @param i Column index
696 * @return Reference to this matrix after the scaling
697 *
698 * @throws std::invalid_argument if i is out of bounds
699 */
700 Matrix<T>& scale_column(const T& s, size_t i);
701
702 /**
703 * @brief row[j] ← row[j] + s · row[i]
704 *
705 * @param s Scalar multiplier on the source row
706 * @param i Source row index
707 * @param j Destination row index
708 * @return Reference to this matrix after the update
709 *
710 * @throws std::invalid_argument if either index is out of bounds
711 */
712 Matrix<T>& add_scaled_row(const T& s, size_t i, size_t j);
713
714 /**
715 * @brief col[j] ← col[j] + s · col[i]
716 *
717 * @param s Scalar multiplier on the source column
718 * @param i Source column index
719 * @param j Destination column index
720 * @return Reference to this matrix after the update
721 *
722 * @throws std::invalid_argument if either index is out of bounds
723 */
724 Matrix<T>& add_scaled_column(const T& s, size_t i, size_t j);
725
726 /**
727 * @brief row[j] ← row[j] + row[i]
728 *
729 * @param i Source row index
730 * @param j Destination row index
731 * @return Reference to this matrix after the update
732 *
733 * @throws std::invalid_argument if either index is out of bounds
734 */
735 Matrix<T>& add_row(size_t i, size_t j) {
736 if (i >= m || j >= m)
737 throw std::invalid_argument("trying to add row to other row, at least one of them is non-existent");
738 return add_scaled_row(T(1), i, j);
739 }
740
741 /**
742 * @brief col[j] ← col[j] + col[i]
743 *
744 * @param i Source column index
745 * @param j Destination column index
746 * @return Reference to this matrix after the update
747 *
748 * @throws std::invalid_argument if either index is out of bounds
749 */
750 Matrix<T>& add_column(size_t i, size_t j) {
751 if (i >= n || j >= n)
752 throw std::invalid_argument("trying to add column to other column, at least one of them is non-existent");
754 add_scaled_row(T(1), i, j);
756 return *this;
757 }
758
759 /**
760 * @brief Delete the columns whose indices appear in v
761 *
762 * @param v Column indices (deduplicated internally)
763 * @return Reference to this matrix after deletion
764 *
765 * @throws std::invalid_argument if any index in v is out of bounds
766 */
767 Matrix<T>& delete_columns(const std::vector<size_t>& v);
768
769 /**
770 * @brief Delete column i (single-index convenience for @ref delete_columns)
771 *
772 * @param i Column index
773 * @return Reference to this matrix after deletion
774 *
775 * @throws std::invalid_argument if i is out of bounds
776 */
777 Matrix<T>& delete_column(size_t i) { return delete_columns({i}); }
778
779 /**
780 * @brief Delete the rows whose indices appear in v
781 *
782 * @param v Row indices (deduplicated internally)
783 * @return Reference to this matrix after deletion
784 *
785 * @throws std::invalid_argument if any index in v is out of bounds
786 */
787 Matrix<T>& delete_rows(const std::vector<size_t>& v);
788
789 /**
790 * @brief Delete row i (single-index convenience for @ref delete_rows)
791 *
792 * @param i Row index
793 * @return Reference to this matrix after deletion
794 *
795 * @throws std::invalid_argument if i is out of bounds
796 */
797 Matrix<T>& delete_row(size_t i) { return delete_rows({i}); }
798
799#ifdef CECCO_ERASURE_SUPPORT
800
801 /**
802 * @brief Flag component (i, j) as erased
803 *
804 * @param i Row index
805 * @param j Column index
806 * @return Reference to this matrix after erasing
807 *
808 * Marks the component as erased; field arithmetic on erased elements is undefined, see
809 * @ref CECCO_ERASURE_SUPPORT and the erase()/unerase() interface in fields.hpp.
810 *
811 * @warning An erased element can no longer participate in field operations or property
812 * queries. Correct handling of erased elements is the caller's responsibility.
813 *
814 * @throws std::invalid_argument if (i, j) is out of bounds
815 */
818
819 /**
820 * @brief Clear the erasure flag on component (i, j)
821 *
822 * @param i Row index
823 * @param j Column index
824 * @return Reference to this matrix after un-erasing
825 *
826 * @throws std::invalid_argument if (i, j) is out of bounds
827 */
830
831 /**
832 * @brief Flag every component of the specified columns as erased
833 *
834 * @param v Column indices (deduplicated internally)
835 * @return Reference to this matrix after erasing
836 *
837 * @warning See @ref erase_component for the semantics and caller obligations.
838 *
839 * @throws std::invalid_argument if any index in v is out of bounds
840 */
843
844 /**
845 * @brief Erase column i (single-index convenience for @ref erase_columns)
846 *
847 * @param i Column index
848 * @return Reference to this matrix after erasing
849 *
850 * @throws std::invalid_argument if i is out of bounds
851 */
854 {
855 return erase_columns({i});
856 }
857
858 /**
859 * @brief Clear erasure flags on every component of the specified columns
860 *
861 * @param v Column indices (deduplicated internally)
862 * @return Reference to this matrix after un-erasing
863 *
864 * @throws std::invalid_argument if any index in v is out of bounds
865 */
868
869 /**
870 * @brief Un-erase column i (single-index convenience for @ref unerase_columns)
871 *
872 * @param i Column index
873 * @return Reference to this matrix after un-erasing
874 *
875 * @throws std::invalid_argument if i is out of bounds
876 */
879 {
880 return unerase_columns({i});
881 }
882
883 /**
884 * @brief Flag every component of the specified rows as erased
885 *
886 * @param v Row indices (deduplicated internally)
887 * @return Reference to this matrix after erasing
888 *
889 * @warning See @ref erase_component for the semantics and caller obligations.
890 *
891 * @throws std::invalid_argument if any index in v is out of bounds
892 */
895
896 /**
897 * @brief Erase row i (single-index convenience for @ref erase_rows)
898 *
899 * @param i Row index
900 * @return Reference to this matrix after erasing
901 *
902 * @throws std::invalid_argument if i is out of bounds
903 */
906 {
907 return erase_rows({i});
908 }
909
910 /**
911 * @brief Clear erasure flags on every component of the specified rows
912 *
913 * @param v Row indices (deduplicated internally)
914 * @return Reference to this matrix after un-erasing
915 *
916 * @throws std::invalid_argument if any index in v is out of bounds
917 */
920
921 /**
922 * @brief Un-erase row i (single-index convenience for @ref unerase_rows)
923 *
924 * @param i Row index
925 * @return Reference to this matrix after un-erasing
926 *
927 * @throws std::invalid_argument if i is out of bounds
928 */
931 {
932 return unerase_rows({i});
933 }
934
935#endif
936
937 /** @} */
938
939 /** @name Transformations
940 * @{
941 */
942
943 /**
944 * @brief Reverse the row order
945 *
946 * @return Reference to this matrix after the reversal
947 */
949
950 /**
951 * @brief Reverse the column order
952 *
953 * @return Reference to this matrix after the reversal
954 */
956
957 /**
958 * @brief Set every component to l
959 *
960 * @param l Value assigned to every component
961 * @return Reference to this matrix after filling
962 *
963 * Type tag is determined based on m, n, and l.
964 */
965 constexpr Matrix<T>& fill(const T& l);
966
967 /**
968 * @brief Transpose in place
969 *
970 * @return Reference to this matrix after transposition
971 *
972 * For an m × n matrix the result is n × m, with component (i, j) moved to (j, i).
973 */
974 constexpr Matrix<T>& transpose();
975
976 /**
977 * @brief Row echelon form (REF), with a binary-field fast path
978 *
979 * @param rank_ptr Optional out-parameter; if non-null, receives the rank
980 * @return Reference to this matrix after the reduction
981 *
982 * Forward Gaussian elimination only, cheaper than RREF when only the rank or a
983 * triangularised form is needed. For Fp<2>, pivot scaling is skipped at compile time. Rank
984 * is cached when @p rank_ptr is non-null.
985 */
986 Matrix<T>& ref(size_t* rank_ptr = nullptr)
988
989 /**
990 * @brief Reduced row echelon form (RREF)
991 *
992 * @param rank_ptr Optional out-parameter; if non-null, receives the rank
993 * @return Reference to this matrix after the reduction
994 *
995 * Two phases: forward elimination (REF) followed by backward elimination. Rank is always
996 * cached.
997 */
998 Matrix<T>& rref(size_t* rank_ptr = nullptr)
1000
1001 /**
1002 * @brief Invert in place
1003 *
1004 * @return Reference to this matrix after inversion
1005 *
1006 * Gaussian elimination with partial pivoting. Requires a square non-singular matrix.
1007 *
1008 * @note Square Vandermonde matrices are inverted in O(m²) by Lagrange interpolation through a
1009 * shared master polynomial rather than by elimination; the inverse is a general matrix.
1010 *
1011 * @throws std::invalid_argument if the matrix is not square or is singular
1012 */
1015
1016 /** @} */
1017
1018 /** @name Finite Field Specific Operations
1019 * @{
1020 */
1021
1022 /**
1023 * @brief Reinterpret the columns as elements of a superfield
1024 *
1025 * @tparam S Superfield of T (same construction tower)
1026 * @return Vector over S, one component per column
1027 *
1028 * Each column is read as the coordinate representation of an element of S over T.
1029 */
1030 template <FiniteFieldType S>
1031 constexpr Vector<S> as_vector() const
1033
1034 /**
1035 * @brief Flatten in row-major order
1036 *
1037 * @return Vector of length m·n; component (i, j) maps to index i·n + j
1038 */
1039 Vector<T> to_vector() const;
1040
1041 /**
1042 * @brief Export the matrix as a PPM (P3) image
1043 *
1044 * @param filename Output path
1045 *
1046 * Each component is mapped through a built-in 64-entry colormap (black → blue → green →
1047 * yellow → white). The image is n wide and m tall.
1048 *
1049 * @note With @ref CECCO_ERASURE_SUPPORT, erased components render in red.
1050 */
1051 void export_as_ppm(const std::string& filename) const
1052 requires FiniteFieldType<T> && (T::get_size() <= 64);
1053
1054 /** @} */
1055
1056 private:
1057 /// @brief Component storage, row-major
1058 std::vector<T> data;
1059
1060 /// @brief Number of rows
1061 size_t m = 0;
1062 /// @brief Number of columns
1063 size_t n = 0;
1064
1065 /// @brief When true, accesses interpret storage as transposed (no data movement)
1066 bool transposed = false;
1067
1068 /// @brief Structural tag, see @ref details::matrix_type_t
1070
1071 /// @brief Cache for matrix rank and Hamming weight (invalidated by mutating operations)
1072 enum CacheIds { Rank = 0, Weight = 1 };
1074
1075 size_t calculate_weight() const
1077
1078 /**
1079 * @brief Upgrade the structural tag if the contents allow it
1080 *
1081 * One pass over the components: detects Zero and Toeplitz for any shape and Diagonal/Identity
1082 * for square matrices, with early exit once no upgrade remains possible. Vandermonde structure
1083 * is not re-detected since its tag also asserts pairwise distinct evaluation points.
1084 *
1085 * This is a pure update tag functionality, no full re-scan of the matrix. The assumption is that
1086 * the tag before the function call is valid (so in many cases it makes sense to reset to Generic first).
1087 */
1088 constexpr void determine_type_tag() {
1089 if constexpr (!ReliablyComparableType<T>) return;
1090 if (type == details::Zero || type == details::Identity) return;
1091 const auto at = [this](size_t i, size_t j) -> const T& {
1092 return transposed ? data[i + j * m] : data[i * n + j];
1093 };
1094 const T zero(0);
1095 const T one(1);
1096 bool all_zero = true;
1097 bool diagonal = (m == n);
1098 bool all_identity = (m == n);
1099 bool toeplitz = true;
1100 for (size_t i = 0; i < m; ++i) {
1101 for (size_t j = 0; j < n; ++j) {
1102 const T& x = at(i, j);
1103 if (i == j) {
1104 if (x != zero) all_zero = false;
1105 if (x != one) all_identity = false;
1106 } else if (x != zero) {
1107 all_zero = false;
1108 diagonal = false;
1109 all_identity = false;
1110 }
1111 if (i > 0 && j > 0 && x != at(i - 1, j - 1)) toeplitz = false;
1112 }
1113 if (!all_zero && !diagonal && !toeplitz) return;
1114 }
1115 if (all_zero)
1116 type = details::Zero;
1117 else if (all_identity)
1118 type = details::Identity;
1119 else if (diagonal)
1120 type = details::Diagonal;
1121 else if (toeplitz)
1122 type = details::Toeplitz;
1123 }
1124
1125 /**
1126 * @brief Matrix multiplication kernel with compile-time transpose dispatch
1127 * @tparam this_transposed Compile-time flag: true if left matrix (*this) is transposed
1128 * @tparam rhs_transposed Compile-time flag: true if right matrix (rhs) is transposed
1129 * @param this_data Raw pointer to left matrix data
1130 * @param rhs_data Raw pointer to right matrix data
1131 * @param res_data Raw pointer to result matrix data
1132 * @param M Number of rows in result
1133 * @param K Inner dimension (cols of this, rows of rhs)
1134 * @param N Number of columns in result
1135 * @param BS Block size for cache tiling
1136 */
1137 template <bool this_transposed, bool rhs_transposed>
1138 static void multiply_kernel(const T* this_data, const T* rhs_data, T* res_data, size_t M, size_t K, size_t N,
1139 size_t BS) {
1140 for (size_t ii = 0; ii < M; ii += BS) {
1141 const size_t imax = std::min(ii + BS, M);
1142 for (size_t kk = 0; kk < K; kk += BS) {
1143 const size_t kmax = std::min(kk + BS, K);
1144 for (size_t jj = 0; jj < N; jj += BS) {
1145 const size_t jmax = std::min(jj + BS, N);
1146 for (size_t i = ii; i < imax; ++i) {
1147 for (size_t k = kk; k < kmax; ++k) {
1148 const size_t this_idx = this_transposed ? (i + k * M) : (i * K + k);
1149 const T aik = this_data[this_idx];
1150 const size_t res_row_offset = i * N;
1151 for (size_t j = jj; j < jmax; ++j) {
1152 const size_t rhs_idx = rhs_transposed ? (k + j * K) : (k * N + j);
1153 res_data[res_row_offset + j] += aik * rhs_data[rhs_idx];
1154 }
1155 }
1156 }
1157 }
1158 }
1159 }
1160 }
1161
1162 /**
1163 * @brief Elimination helper for a single row operation
1164 * @tparam transposed True if matrix is transposed, false otherwise
1165 * @param data Raw matrix data
1166 * @param m Number of rows
1167 * @param n Number of columns
1168 * @param target_row Row to be eliminated
1169 * @param pivot_row Row containing the pivot
1170 * @param start_col Starting column for elimination
1171 * @param f Scaling factor
1172 */
1173 template <bool transposed>
1174 static void eliminate_row_kernel(T* data, size_t m, size_t n, size_t target_row, size_t pivot_row, size_t start_col,
1175 const T& f) {
1176 if constexpr (!transposed) {
1177 // Row-major access
1178 T* target_data = data + target_row * n;
1179 const T* pivot_data = data + pivot_row * n;
1180 for (size_t j = start_col; j < n; ++j) {
1181 target_data[j] -= f * pivot_data[j];
1182 }
1183 } else {
1184 // Column-major access
1185 for (size_t j = start_col; j < n; ++j) {
1186 data[target_row + j * m] -= f * data[pivot_row + j * m];
1187 }
1188 }
1189 }
1190
1191 /**
1192 * @brief REF elimination kernel with compile-time transpose dispatch
1193 * @tparam transposed True if matrix is transposed, false otherwise
1194 * @param data Raw matrix data
1195 * @param m Number of rows
1196 * @param n Number of columns
1197 * @param h Current pivot row
1198 * @param k Current pivot column
1199 * @return Number of rows processed (new h value)
1200 */
1201 template <bool transposed>
1202 static size_t ref_elimination_kernel(T* data, size_t m, size_t n, size_t h, size_t k) {
1203 const T zero(0);
1204 while (h < m && k < n) {
1205 // find pivot
1206 size_t p = m; // Default: no pivot found
1207
1208 for (size_t row = h; row < m; ++row) {
1209 const size_t idx = transposed ? (row + k * m) : (row * n + k);
1210 if (data[idx] != zero) {
1211 p = row;
1212 break;
1213 }
1214 }
1215
1216 if (p == m) { // no pivot in column -> proceed to next column
1217 ++k;
1218 } else {
1219 // this->swap_rows(h, p);
1220 if constexpr (!transposed)
1221 std::swap_ranges(data + h * n, data + (h + 1) * n, data + p * n);
1222 else
1223 for (size_t j = 0; j < n; ++j) std::swap(data[h + j * m], data[p + j * m]);
1224
1225 if constexpr (!std::is_same_v<T, Fp<2>>) {
1226 const size_t pivot_idx = transposed ? (h + k * m) : (h * n + k);
1227 const T inv = T(1) / data[pivot_idx];
1228 if constexpr (!transposed) {
1229 for (size_t j = k; j < n; ++j) data[h * n + j] *= inv;
1230 } else {
1231 for (size_t j = k; j < n; ++j) data[h + j * m] *= inv;
1232 }
1233 }
1234
1235 // Forward elimination only - eliminate entries BELOW pivot
1236 for (size_t i = h + 1; i < m; ++i) {
1237 const size_t f_idx = transposed ? (i + k * m) : (i * n + k);
1238 const T f = data[f_idx];
1239
1240 if (f != zero) eliminate_row_kernel<transposed>(data, m, n, i, h, k, f);
1241 }
1242
1243 ++h;
1244 ++k;
1245 }
1246 }
1247 return h;
1248 }
1249
1250 /**
1251 * @brief RREF backward elimination kernel with compile-time transpose dispatch
1252 * @tparam transposed True if matrix is transposed, false otherwise
1253 * @param data Raw matrix data
1254 * @param m Number of rows
1255 * @param n Number of columns
1256 * @param r Matrix rank (number of pivots to process)
1257 */
1258 template <bool transposed>
1259 void rref_backward_elimination_kernel(T* data, size_t m, size_t n, size_t r) {
1260 const T zero(0);
1261 for (size_t i = 0; i < r; ++i) {
1262 const size_t pivot_row = r - 1 - i; // Process from last to first
1263
1264 // find pivot
1265 size_t pivot_col = 0;
1266 while (pivot_col < n) {
1267 const size_t pivot_idx = transposed ? (pivot_row + pivot_col * m) : (pivot_row * n + pivot_col);
1268 if (data[pivot_idx] != zero) {
1269 break;
1270 }
1271 ++pivot_col;
1272 }
1273
1274 if (pivot_col < n) {
1275 // Backward elimination only - eliminate entries ABOVE pivot
1276 for (size_t row = 0; row < pivot_row; ++row) {
1277 const size_t f_idx = transposed ? (row + pivot_col * m) : (row * n + pivot_col);
1278 const T f = data[f_idx];
1279
1280 if (f != zero) eliminate_row_kernel<transposed>(data, m, n, row, pivot_row, pivot_col, f);
1281 }
1282 }
1283 }
1284 }
1285
1286 /**
1287 * @brief Calculate matrix rank (private implementation for caching)
1288 */
1289 size_t calculate_rank() const
1291};
1292
1293/* Matrix member function implementations */
1294
1295template <ComponentType T>
1296Matrix<T>::Matrix(size_t m, size_t n, const T& l) : data(m * n), m(m), n(n) {
1297 fill(l);
1298}
1299
1300template <ComponentType T>
1301constexpr Matrix<T>::Matrix(size_t m, size_t n, std::initializer_list<T> l)
1302 : data(l), m(m), n(n), type(details::Generic) {
1303 if (l.size() != m * n) {
1304 throw std::invalid_argument(
1305 "number of elements in initializer list does not correspond to number of rows and columns specified");
1306 }
1307 determine_type_tag();
1308}
1309
1310template <ComponentType T>
1311Matrix<T>::Matrix(std::initializer_list<std::initializer_list<T>> l) : m(l.size()), n(0), type(details::Generic) {
1312 if (m == 0) return;
1313 for (auto it = l.begin(); it != l.end(); ++it) {
1314 if (it->size() > n) n = it->size();
1315 }
1316 if (n == 0) return;
1317 data.resize(m * n, T(0));
1318
1319 size_t i = 0;
1320 for (auto row = l.begin(); row != l.end(); ++row) {
1321 size_t j = 0;
1322 for (auto val = row->begin(); val != row->end(); ++val) set_component(i, j++, *val);
1323 ++i;
1324 }
1325 determine_type_tag();
1326}
1327
1328template <ComponentType T>
1329Matrix<T>::Matrix(const Vector<T>& v) : data(v.get_n()), m(1), n(v.get_n()), type(details::Toeplitz) {
1330 std::copy(v.data.begin(), v.data.end(), data.begin());
1331 if (n == 0) {
1332 type = details::Zero;
1333 cache.template set<Rank>(0);
1334 cache.template set<Weight>(0);
1335 } else {
1336 if constexpr (ReliablyComparableType<T>) {
1337 if (n == 1 && data[0] != T(0)) {
1338 if (data[0] == T(1))
1339 type = details::Identity;
1340 else
1341 type = details::Diagonal;
1342 cache.template set<Rank>(1);
1343 cache.template set<Weight>(1);
1344 } else if (v.is_zero()) {
1345 type = details::Zero;
1346 cache.template set<Rank>(0);
1347 cache.template set<Weight>(0);
1348 } else {
1349 cache.template set<Rank>(1);
1350 }
1351 }
1352 }
1353}
1354
1355template <ComponentType T>
1356template <FiniteFieldType S>
1357constexpr Matrix<T>::Matrix(const Matrix<S>& other)
1359 : data(other.get_m() * other.get_n()), m(other.get_m()), n(other.get_n()), type(other.type) {
1360 if (!other.transposed) {
1361 std::transform(other.data.begin(), other.data.end(), data.begin(), [](const S& elem) { return T(elem); });
1362 } else {
1363 for (size_t i = 0; i < m; ++i)
1364 for (size_t j = 0; j < n; ++j) data[i * n + j] = T(other(i, j));
1365 }
1366 cache.invalidate();
1367}
1368
1369template <ComponentType T>
1370Matrix<T>::Matrix(const std::string& filename)
1371 requires FiniteFieldType<T> && (T::get_size() <= 64)
1372{
1374 if (!in) throw std::invalid_argument("Cannot open PPM file: " + filename);
1375
1376 auto next_token = [&](std::string& tok) -> void {
1377 for (;;) {
1378 if (!(in >> tok)) throw std::invalid_argument("Unexpected end of PPM file: " + filename);
1379 if (tok[0] == '#') {
1381 continue;
1382 }
1383 return;
1384 }
1385 };
1386
1387 std::string tok;
1388 next_token(tok); // "P3"
1389 next_token(tok);
1390 size_t w = std::stoul(tok);
1391 next_token(tok);
1392 size_t h = std::stoul(tok);
1393 next_token(tok); /* maxval */ // assumed 255
1394
1395 *this = Matrix(h, w);
1396
1397 // "RGB to a" map
1398 static const auto rgb_to_a = [] {
1400 m.reserve(64);
1401 for (uint8_t a = 0; a < 64; ++a) {
1402 const uint32_t key = (uint32_t(details::colormap[a][0]) << 16) | (uint32_t(details::colormap[a][1]) << 8) |
1403 uint32_t(details::colormap[a][2]);
1404 m.emplace(key, a);
1405 }
1406 return m;
1407 }();
1408
1409 auto label_from_a = [&](uint8_t a) -> size_t { return ((T::get_size() - 1) * (63 - static_cast<size_t>(a))) / 63; };
1410
1411 for (size_t i = 0; i < h; ++i) {
1412 for (size_t j = 0; j < w; ++j) {
1413 next_token(tok);
1414 uint8_t r = static_cast<uint8_t>(std::stoi(tok));
1415 next_token(tok);
1416 uint8_t g = static_cast<uint8_t>(std::stoi(tok));
1417 next_token(tok);
1418 uint8_t b = static_cast<uint8_t>(std::stoi(tok));
1419
1420 const uint32_t key = (uint32_t(r) << 16) | (uint32_t(g) << 8) | uint32_t(b);
1421
1422 const auto it = rgb_to_a.find(key);
1423 if (it != rgb_to_a.end()) {
1425 } else {
1426#ifdef CECCO_ERASURE_SUPPORT
1428#else
1429 set_component(i, j, T(0).randomize());
1430#endif
1431 }
1432 }
1433 }
1434
1436}
1437
1438template <ComponentType T>
1439constexpr Matrix<T>& Matrix<T>::operator=(const Matrix& rhs) {
1440 if (this == &rhs) return *this;
1441 data = rhs.data;
1442 m = rhs.m;
1443 n = rhs.n;
1444 transposed = rhs.transposed;
1445 type = rhs.type;
1446 cache = rhs.cache;
1447 return *this;
1448}
1449
1450template <ComponentType T>
1451constexpr Matrix<T>& Matrix<T>::operator=(Matrix&& rhs) noexcept {
1452 if (this == &rhs) return *this;
1453 data = std::move(rhs.data);
1454 m = rhs.m;
1455 n = rhs.n;
1456 transposed = rhs.transposed;
1457 type = rhs.type;
1458 cache = std::move(rhs.cache);
1459 rhs.m = 0;
1460 rhs.n = 0;
1461 rhs.transposed = false;
1462 rhs.type = details::Zero;
1463 rhs.cache.invalidate();
1464 return *this;
1465}
1466
1467template <ComponentType T>
1468template <FiniteFieldType S>
1470constexpr Matrix<T>& Matrix<T>::operator=(const Matrix<S>& other) {
1471 cache.invalidate();
1473 std::transform(other.data.cbegin(), other.data.cend(), data.begin(), [&](const S& e) { return T(e); });
1474 m = other.get_m();
1475 n = other.get_n();
1477 type = other.type;
1478 return *this;
1479}
1480
1481template <ComponentType T>
1482constexpr Matrix<T> Matrix<T>::operator-() const& {
1483 auto res = *this;
1484 if constexpr (FiniteFieldType<T>) {
1485 if constexpr (T::get_characteristic() == 2) return res; // -M == M in characteristic 2
1486 }
1487 if (type == details::Generic || type == details::Vandermonde || type == details::Toeplitz) {
1488 std::ranges::for_each(res.data, [](T& x) { x = -x; });
1489 if (type == details::Vandermonde) res.type = details::Generic;
1490
1491 } else if (type == details::Zero) {
1492 /* no-op */
1493 } else if (type == details::Diagonal || type == details::Identity) {
1494 for (size_t mu = 0; mu < m; ++mu) res.data[mu * n + mu] = -res.data[mu * n + mu];
1495 if (type == details::Identity) res.type = details::Diagonal;
1496 }
1497 return res;
1498}
1499
1500template <ComponentType T>
1501constexpr Matrix<T> Matrix<T>::operator-() && {
1502 if constexpr (FiniteFieldType<T>) {
1503 if constexpr (T::get_characteristic() == 2) return std::move(*this); // -M == M in characteristic 2
1504 }
1505 if (type == details::Generic || type == details::Vandermonde || type == details::Toeplitz) {
1506 std::ranges::for_each(data, [](T& x) { x = -x; });
1507 if (type == details::Vandermonde) {
1508 type = details::Generic;
1509 }
1510 } else if (type == details::Zero) {
1511 /* no-op */
1512 } else if (type == details::Diagonal || type == details::Identity) {
1513 for (size_t mu = 0; mu < m; ++mu) data[mu * n + mu] = -data[mu * n + mu];
1514 if (type == details::Identity) type = details::Diagonal;
1515 }
1516 return std::move(*this);
1517}
1518
1519template <ComponentType T>
1520Matrix<T>& Matrix<T>::operator+=(const Matrix& rhs) {
1521 if (m != rhs.m || n != rhs.n)
1522 throw std::invalid_argument(
1523 "trying to add two matrices of different "
1524 "dimensions");
1525 if (type == details::Zero) {
1526 *this = rhs;
1527 return *this;
1528 } else if (rhs.type == details::Zero) {
1529 return *this;
1530 } else if ((type == details::Diagonal || type == details::Identity) &&
1531 (rhs.type == details::Diagonal || rhs.type == details::Identity)) {
1532 cache.invalidate();
1533 for (size_t mu = 0; mu < m; ++mu) data[mu * n + mu] += rhs.data[mu * n + mu];
1534 if constexpr (ReliablyComparableType<T>) {
1535 bool zero = true;
1536 bool identity = true;
1537 for (size_t mu = 0; mu < m && (zero || identity); ++mu) {
1538 const T& x = data[mu * n + mu];
1539 if (x != T(0)) zero = false;
1540 if (x != T(1)) identity = false;
1541 }
1542 if (zero)
1543 type = details::Zero;
1544 else if (identity)
1545 type = details::Identity;
1546 else
1547 type = details::Diagonal;
1548 } else {
1549 type = details::Diagonal; // without reliable comparison the sum is only known to be diagonal
1550 }
1551 return *this;
1552 } else {
1553 cache.invalidate();
1554 if (transposed == rhs.transposed) {
1555 std::transform(data.begin(), data.end(), rhs.data.begin(), data.begin(), std::plus<T>{});
1556 } else {
1557 T* a = data.data();
1558 const T* b = rhs.data.data();
1559 for (size_t mu = 0; mu < m; ++mu)
1560 for (size_t nu = 0; nu < n; ++nu)
1561 a[transposed ? (mu + nu * m) : (mu * n + nu)] += b[rhs.transposed ? (mu + nu * m) : (mu * n + nu)];
1562 }
1563 }
1564 type = details::Generic;
1565 determine_type_tag();
1566 return *this;
1567}
1568
1569template <ComponentType T>
1570Matrix<T>& Matrix<T>::operator-=(const Matrix& rhs) {
1571 if (m != rhs.m || n != rhs.n)
1572 throw std::invalid_argument(
1573 "trying to subtract two matrices of different "
1574 "dimensions");
1575 if constexpr (FiniteFieldType<T>) {
1576 if constexpr (T::get_characteristic() == 2) return operator+=(rhs); // -M == M in characteristic 2
1577 }
1578 if (type == details::Zero) {
1579 *this = -rhs;
1580 return *this;
1581 } else if (rhs.type == details::Zero) {
1582 return *this;
1583 } else if ((type == details::Diagonal || type == details::Identity) &&
1584 (rhs.type == details::Diagonal || rhs.type == details::Identity)) {
1585 cache.invalidate();
1586 for (size_t mu = 0; mu < m; ++mu) data[mu * n + mu] -= rhs.data[mu * n + mu];
1587 if constexpr (ReliablyComparableType<T>) {
1588 bool zero = true;
1589 bool identity = true;
1590 for (size_t mu = 0; mu < m && (zero || identity); ++mu) {
1591 const T& x = data[mu * n + mu];
1592 if (x != T(0)) zero = false;
1593 if (x != T(1)) identity = false;
1594 }
1595 if (zero)
1596 type = details::Zero;
1597 else if (identity)
1598 type = details::Identity;
1599 else
1600 type = details::Diagonal;
1601 } else {
1602 type = details::Diagonal; // without reliable comparison the difference is only known to be diagonal
1603 }
1604 return *this;
1605 } else {
1606 cache.invalidate();
1607 if (transposed == rhs.transposed) {
1608 std::transform(data.begin(), data.end(), rhs.data.begin(), data.begin(), std::minus<T>{});
1609 } else {
1610 T* a = data.data();
1611 const T* b = rhs.data.data();
1612 for (size_t mu = 0; mu < m; ++mu)
1613 for (size_t nu = 0; nu < n; ++nu)
1614 a[transposed ? (mu + nu * m) : (mu * n + nu)] -= b[rhs.transposed ? (mu + nu * m) : (mu * n + nu)];
1615 }
1616 }
1617 type = details::Generic;
1618 determine_type_tag();
1619 return *this;
1620}
1621
1622template <ComponentType T>
1623Matrix<T>& Matrix<T>::operator*=(const Matrix& rhs) {
1624 if (n != rhs.m)
1625 throw std::invalid_argument(
1626 "trying to multiply two matrices "
1627 "with incompatible dimensions");
1628 if (type == details::Zero || rhs.type == details::Zero) {
1629 *this = Matrix(m, rhs.n);
1630 return *this;
1631 } else if (type == details::Identity) {
1632 *this = rhs;
1633 return *this;
1634 } else if (rhs.type == details::Identity) {
1635 return *this;
1636 } else if (type == details::Diagonal && rhs.type == details::Diagonal) {
1637 cache.invalidate();
1638 for (size_t mu = 0; mu < m; ++mu) data[mu * n + mu] *= rhs.data[mu * n + mu];
1639 if constexpr (ReliablyComparableType<T>) {
1640 bool zero = true;
1641 bool identity = true;
1642 for (size_t mu = 0; mu < m && (zero || identity); ++mu) {
1643 const T& x = data[mu * n + mu];
1644 if (x != T(0)) zero = false;
1645 if (x != T(1)) identity = false;
1646 }
1647 if (zero)
1648 type = details::Zero;
1649 else if (identity)
1650 type = details::Identity;
1651 else
1652 type = details::Diagonal;
1653 }
1654 return *this;
1655 } else if (type == details::Diagonal) {
1656 auto res = rhs;
1657 for (size_t mu = 0; mu < m; ++mu) res.scale_row((*this)(mu, mu), mu);
1658 *this = std::move(res);
1659 } else if (rhs.type == details::Diagonal) {
1660 // one layout-order pass instead of n stride-m scale_column walks; the diagonal of the
1661 // square rhs sits at the same flat index whether or not rhs is transposed
1662 cache.invalidate();
1663 if (!transposed) {
1664 for (size_t mu = 0; mu < m; ++mu)
1665 for (size_t nu = 0; nu < n; ++nu) data[mu * n + nu] *= rhs.data[nu * rhs.n + nu];
1666 } else {
1667 for (size_t nu = 0; nu < n; ++nu) {
1668 const T& s = rhs.data[nu * rhs.n + nu];
1669 for (size_t mu = 0; mu < m; ++mu) data[mu + nu * m] *= s;
1670 }
1671 }
1672 type = details::Generic; // raw writes bypass scale_column's tag demotion
1673 } else {
1674 const size_t M = get_m();
1675 const size_t K = get_n();
1676 const size_t N = rhs.get_n();
1677 Matrix<T> res(M, N, T(0));
1678
1679 const T* this_data = this->data.data();
1680 const T* rhs_data = rhs.data.data();
1681 T* res_data = res.data.data();
1682
1683 // Tune this block/tile size for architecture. 48, 64, 96, 128 are reasonable.
1684 constexpr size_t BS = 64;
1685
1686 if (!this->transposed && !rhs.transposed) {
1687 multiply_kernel<false, false>(this_data, rhs_data, res_data, M, K, N, BS);
1688 } else if (this->transposed && !rhs.transposed) {
1689 multiply_kernel<true, false>(this_data, rhs_data, res_data, M, K, N, BS);
1690 } else if (!this->transposed && rhs.transposed) {
1691 multiply_kernel<false, true>(this_data, rhs_data, res_data, M, K, N, BS);
1692 } else {
1693 multiply_kernel<true, true>(this_data, rhs_data, res_data, M, K, N, BS);
1694 }
1695 res.type = details::Generic;
1696 *this = std::move(res);
1697 }
1698 determine_type_tag();
1699 cache.invalidate();
1700 return *this;
1701}
1702
1703template <ComponentType T>
1704constexpr Matrix<T>& Matrix<T>::operator*=(const T& s) {
1705 if (s == T(0)) {
1706 *this = Matrix(m, n);
1707 } else if (s == T(1) || type == details::Zero) {
1708 /* no-op */
1709 } else if (type == details::Generic || type == details::Vandermonde || type == details::Toeplitz) {
1710 std::ranges::for_each(data, [&s](T& x) { x *= s; });
1711 if (type == details::Vandermonde) type = details::Generic;
1712 } else if (type == details::Diagonal || type == details::Identity) {
1713 for (size_t mu = 0; mu < m; ++mu) data[mu * n + mu] *= s;
1714 if (type == details::Identity) type = details::Diagonal;
1715 }
1716 return *this;
1717}
1718
1719template <ComponentType T>
1720Matrix<T>& Matrix<T>::operator/=(const T& s)
1722{
1723 if (s == T(0)) throw std::invalid_argument("trying to divide components of matrix by zero");
1724 if constexpr (FieldType<T>) {
1725 operator*=(T(1) / s);
1726 } else if (s == T(1) || type == details::Zero) {
1727 /* no-op */
1728 } else if (type == details::Generic || type == details::Vandermonde || type == details::Toeplitz) {
1729 cache.invalidate();
1730 std::ranges::for_each(data, [&s](T& x) { x /= s; });
1732 } else if (type == details::Diagonal || type == details::Identity) {
1733 cache.invalidate();
1734 for (size_t mu = 0; mu < m; ++mu) data[mu * n + mu] /= s;
1736 }
1737 return *this;
1738}
1739
1740template <ComponentType T>
1743{
1744 cache.invalidate();
1745 if constexpr (FieldType<T>) {
1747 } else if constexpr (std::same_as<T, double>) {
1748 std::uniform_real_distribution<double> dist(-1.0, 1.0);
1749 std::ranges::for_each(data, [&](double& val) { val = dist(gen()); });
1750 } else if constexpr (std::same_as<T, std::complex<double>>) {
1751 std::uniform_real_distribution<double> dist(-1.0, 1.0);
1753 [&](std::complex<double>& val) { val = std::complex<double>(dist(gen()), dist(gen())); });
1754 } else if constexpr (SignedIntType<T>) {
1755 std::uniform_int_distribution<long long> dist(-100, 100);
1756 std::ranges::for_each(data, [&](T& val) { val = T(dist(gen())); });
1757 }
1758 type = details::Generic;
1760 return *this;
1761}
1762
1763template <ComponentType T>
1764size_t Matrix<T>::calculate_weight() const
1766{
1767 if (m == 0 || n == 0) return 0;
1768
1769 if (type == details::Zero) {
1770 return 0;
1771 } else {
1772 size_t res = data.size() - std::count(data.cbegin(), data.cend(), T(0));
1773#ifdef CECCO_ERASURE_SUPPORT
1774 if constexpr (FieldType<T>) res -= std::count_if(data.cbegin(), data.cend(), [](T x) { return x.is_erased(); });
1775#endif
1776 return res;
1777 }
1778}
1779
1780template <ComponentType T>
1781size_t Matrix<T>::rank() const
1783{
1784 return cache.template get_or_compute<Rank>([this] { return calculate_rank(); });
1785}
1786
1787template <ComponentType T>
1788Vector<T> Matrix<T>::diagonal() const {
1789 if (m != n) throw std::invalid_argument("trying to extract diagonal of non-square matrix");
1790 Vector<T> res(n);
1791 for (size_t i = 0; i < n; ++i) res.data[i] = (*this)(i, i);
1792 res.cache.invalidate(); // raw writes bypass set_component's cache handling
1793 return res;
1794}
1795
1796template <ComponentType T>
1797template <CoefficientType U>
1798 requires FieldType<T> && std::same_as<U, T>
1800 if (m != n) throw std::invalid_argument("trying to calculate characteristic polynomial of non-square matrix");
1801 if (m == 0) throw std::invalid_argument("trying to calculate characteristic polynomial of empty matrix");
1802 if (m == 1) return Polynomial<T>({-(*this)(0, 0), 1});
1804 // Samuelson–Berkowitz algorithm
1805
1806 // calculate Toeplitz matrices
1807 std::vector<Matrix<T>> TM; // Toeplitz matrices
1808 TM.reserve(m);
1809 for (size_t i = 0; i < m; ++i) {
1810 // partition matrix
1811 T a = (*this)(i, i);
1812 if (m - i == 1) {
1813 Vector<T> v(2);
1814 v.set_component(0, -a);
1815 v.set_component(1, T(1));
1816 TM.push_back(ToeplitzMatrix(v, 2, 1));
1817 } else if (m - i == 2) {
1818 const Matrix C = get_submatrix(i + 1, i, m - i - 1, 1);
1819 const Matrix R = get_submatrix(i, i + 1, 1, m - i - 1);
1820 Vector<T> v(4);
1821 v.set_component(0, -(R * C)(0, 0));
1822 v.set_component(1, -a);
1823 v.set_component(2, T(1));
1824 TM.push_back(ToeplitzMatrix(v, 3, 2));
1825 } else {
1826 const Matrix C = get_submatrix(i + 1, i, m - i - 1, 1);
1827 const Matrix R = get_submatrix(i, i + 1, 1, m - i - 1);
1828 Matrix A = get_submatrix(i + 1, i + 1, m - i - 1, m - i - 1);
1829 const Matrix X = A;
1830 Vector<T> v(2 * (m - i));
1831 for (size_t j = 0; j < m - i - 2; ++j) {
1832 v.set_component(m - i - 3 - j, -((R * A * C)(0, 0)));
1833 A *= X;
1834 }
1835 v.set_component(m - i - 2, -(R * C)(0, 0));
1836 v.set_component(m - i - 1, -a);
1837 v.set_component(m - i, T(1));
1838 TM.push_back(ToeplitzMatrix(v, m - i + 1, m - i));
1839 }
1840 }
1841
1842 // multiply Toeplitz matrices together
1843 Matrix P = IdentityMatrix<T>(m + 1);
1844 for (auto it = TM.cbegin(); it != TM.cend(); ++it) {
1845 P *= *it;
1846 }
1847
1848 // extract polynomial from solution/column vector
1849 Polynomial<T> res;
1850 for (size_t i = 0; i <= m; ++i) res.set_coefficient(m - i, P(i, 0));
1851 return res;
1852 } else if (type == details::Zero) {
1853 return Monomial<T>(m, T(1));
1854 } else if (type == details::Diagonal) {
1855 Polynomial<T> res({1});
1856 for (size_t mu = 0; mu < m; ++mu) res *= Polynomial<T>({-(*this)(mu, mu), T(1)});
1857 return res;
1858 } else if (type == details::Identity) {
1859 Polynomial<T> res({-1, 1});
1860 return res ^ m;
1861 }
1862 throw std::logic_error("characteristic_polynomial(): unhandled matrix type");
1863}
1864
1865template <ComponentType T>
1868{
1869 const T zero(0);
1870 const T one(1);
1871
1872 if (type == details::Zero) return -IdentityMatrix<T>(n);
1873 if (type == details::Identity) return Matrix<T>(0, n);
1874 if (type == details::Diagonal) {
1876 for (size_t i = 0; i < m; ++i)
1877 if ((*this)(i, i) == zero) zero_positions.push_back(i);
1878 if (zero_positions.empty()) return Matrix<T>(0, n);
1880 const T minus_one(-one);
1882 return B;
1883 }
1884
1885 Matrix<T> temp(*this);
1886 size_t r = 0;
1887 temp.rref(&r);
1888
1889 if (n - r == 0) return Matrix<T>(0, n);
1890
1891 Matrix B(n - r, n);
1892
1893 std::vector<size_t> mocols; // "minus one columns"
1894 mocols.reserve(n - r);
1895 size_t i = 0;
1896 for (size_t j = 0; j < n; ++j) {
1897 if (i < m) {
1898 if (temp(i, j) == one)
1899 ++i;
1900 else
1902 } else {
1904 }
1905 }
1906
1907 const T minus_one(-one);
1908 size_t offset = 0;
1909 for (size_t i = 0; i < m + offset + 1; ++i) {
1910 for (size_t k = offset; k < n - r; ++k) {
1911 if (i + offset == mocols[k]) {
1913 ++offset;
1914 } else {
1915 B.set_component(k, i + offset, temp(i, mocols[k]));
1916 }
1917 }
1918 }
1919
1920 return B;
1921}
1922
1923template <ComponentType T>
1926{
1927 if (m != n) throw std::invalid_argument("trying to calculate determinant of non-square matrix");
1928 if (m == 0) throw std::invalid_argument("trying to calculate determinant of empty matrix");
1929 if (m == 1) return ((*this))(0, 0);
1930 if (type == details::Generic || type == details::Toeplitz) {
1931 // char_poly is monic det(λI - A), so [0] = (-1)^m · det(A).
1932 const auto c = characteristic_polynomial()[0];
1933 return (m % 2 == 0) ? c : -c;
1934 } else if (type == details::Vandermonde) {
1935 T s(1);
1936 for (size_t mu = 1; mu < m; ++mu)
1937 for (size_t i = 0; i < mu; ++i) s *= (*this)(1, mu) - (*this)(1, i);
1938 return s;
1939 } else if (type == details::Zero) {
1940 return T(0);
1941 } else if (type == details::Diagonal) {
1942 const T zero(0);
1943 T s(1);
1944 for (size_t i = 0; i < m; ++i) {
1945 if ((*this)(i, i) == zero) return zero;
1946 s *= (*this)(i, i);
1947 }
1948 return s;
1949 } else if (type == details::Identity) {
1950 return T(1);
1951 }
1952 throw std::logic_error("determinant(): unhandled matrix type");
1953}
1954
1955template <ComponentType T>
1958{
1959 const auto p = characteristic_polynomial();
1960 const T zero(0);
1961 std::vector<T> res;
1962 for (size_t j = 0; j < T::get_size(); ++j) {
1963 T element = T(j);
1964 if (p(element) == zero) res.push_back(element);
1965 }
1966 return res;
1967}
1968
1969template <ComponentType T>
1972{
1973 auto basis = *this;
1974 size_t r;
1975 basis.ref(&r);
1976
1977 constexpr size_t q = T::get_size();
1978 if (sqm<InfInt>(q, r) > sqm<InfInt>(10, 10)) {
1979 throw std::out_of_range("row space too big (more than 10^10 elements) to compute all elements");
1980 }
1981 const size_t size = sqm<size_t>(q, r);
1982 std::vector<size_t> qpow(r);
1983 for (size_t i = 0; i < r; ++i) qpow[i] = (i == 0) ? 1 : qpow[i - 1] * q;
1984 const T zero(0);
1985 std::vector<Vector<T>> res;
1986 res.reserve(size);
1987 for (size_t counter = 0; counter < size; ++counter) {
1988 Vector<T> temp(n);
1989 for (size_t i = 0; i < r; ++i) {
1990 T scalar(counter / qpow[i] % q);
1991 if (scalar != zero) {
1992 temp += scalar * basis.get_row(r - i - 1);
1993 }
1994 }
1996 }
1997 // sort vectors so that two spans consisting of the same vectors compare (==) to equal
1998 std::sort(res.begin(), res.end(), [](Vector<T>& a, Vector<T>& b) { return a.as_integer() > b.as_integer(); });
1999 return res;
2000}
2001
2002template <ComponentType T>
2003template <typename U>
2006{
2007 if (i >= m || j >= n) throw std::invalid_argument("trying to access non-existent component of matrix");
2008
2009 T& old_value = (!transposed) ? data[i * n + j] : data[i + j * m];
2010
2011 T new_value(std::forward<U>(c));
2012 if constexpr (ReliablyComparableType<T>) {
2013 if (old_value == new_value) return *this;
2014 }
2015
2016 switch (type) {
2017 case details::Generic:
2018 break;
2019
2020 case details::Diagonal:
2021 if (i != j) type = details::Generic;
2022 break;
2023
2024 case details::Zero:
2025 if (i == j && m == n)
2027 else
2028 type = details::Generic;
2029 break;
2030
2031 case details::Identity:
2032 if (i == j)
2034 else
2035 type = details::Generic;
2036 break;
2037
2038 case details::Vandermonde:
2039 case details::Toeplitz:
2040 type = details::Generic;
2041 break;
2042
2043 default:
2044 type = details::Generic;
2045 break;
2046 }
2047
2048 if (!transposed)
2049 data[i * n + j] = new_value;
2050 else
2051 data[i + j * m] = new_value;
2052
2053 if constexpr (ReliablyComparableType<T>) {
2054 if (m == 1 && n == 1) {
2055 if (new_value == T(0))
2056 type = details::Zero;
2057 else if (new_value == T(1))
2059 else
2061 }
2062 }
2063
2064 cache.invalidate();
2065
2066 return *this;
2067}
2068
2069template <ComponentType T>
2070const T& Matrix<T>::operator()(size_t i, size_t j) const {
2071 if (i >= m || j >= n) throw std::invalid_argument("trying to access non-existent component of matrix");
2072 if (!transposed) return data[i * n + j];
2073 return data[i + j * m];
2074}
2075
2076template <ComponentType T>
2078 if (i >= m) throw std::invalid_argument("trying to access non-existent row");
2079 Vector<T> res(n);
2080 if (!transposed) {
2081 std::copy(data.begin() + i * n, data.begin() + (i + 1) * n, res.data.begin());
2082 } else {
2083 for (size_t j = 0; j < n; ++j) res.data[j] = (*this)(i, j);
2084 }
2085 res.cache.invalidate(); // raw writes bypass set_component's cache handling
2086 return res;
2087}
2088
2089template <ComponentType T>
2091 if (j >= n) throw std::invalid_argument("trying to access non-existent column");
2092 Vector<T> res(m);
2093 if (transposed) {
2094 std::copy(data.begin() + j * m, data.begin() + (j + 1) * m, res.data.begin());
2095 } else {
2096 for (size_t i = 0; i < m; ++i) res.data[i] = (*this)(i, j);
2097 }
2098 res.cache.invalidate(); // raw writes bypass set_component's cache handling
2099 return res;
2100}
2101
2102template <ComponentType T>
2104 if (i + h > m || j + w > n)
2105 throw std::invalid_argument(
2106 "trying to extract a submatrix with incompatible "
2107 "dimensions");
2108 Matrix res(h, w);
2109 if (res.data.empty()) return res;
2110
2112 if (!transposed && !res.transposed) {
2113 for (size_t mu = 0; mu < h; ++mu)
2114 std::copy(data.begin() + (i + mu) * n + j, data.begin() + (i + mu) * n + j + w,
2115 res.data.begin() + mu * w);
2116 res.type = details::Generic; // raw copy bypasses set_component's tag tracking
2117 } else {
2118 for (size_t mu = 0; mu < h; ++mu)
2119 for (size_t nu = 0; nu < w; ++nu) res.data[mu * w + nu] = (*this)(i + mu, j + nu);
2120 res.type = details::Generic; // raw writes bypass set_component's tag tracking
2121 }
2122 if (type == details::Vandermonde && i == 0) {
2124 } else if (type == details::Toeplitz) {
2126 }
2127 } else if (type == details::Zero) {
2128 /* no-op */
2129 } else if (type == details::Diagonal || type == details::Identity) {
2130 for (size_t mu = 0; mu < h; ++mu)
2131 for (size_t nu = 0; nu < w; ++nu)
2132 if (i + mu == j + nu) res.set_component(mu, nu, (*this)(i + mu, j + nu));
2133 if (type == details::Identity) {
2134 res.type = (i == j && h == w) ? details::Identity : details::Toeplitz;
2135 } else if (i == j && h == w) {
2137 }
2138 }
2139 return res;
2140}
2141
2142template <ComponentType T>
2144 if (m < i + N.m || n < j + N.n)
2145 throw std::invalid_argument(
2146 "trying to replace submatrix with "
2147 "matrix of incompatible dimensions");
2148
2149 cache.invalidate();
2150 if (!transposed && !N.transposed) {
2151 for (size_t mu = 0; mu < N.m; ++mu)
2152 std::copy(N.data.begin() + mu * N.n, N.data.begin() + (mu + 1) * N.n, data.begin() + (i + mu) * n + j);
2153 } else {
2154 for (size_t mu = 0; mu < N.m; ++mu)
2155 for (size_t nu = 0; nu < N.n; ++nu)
2156 data[transposed ? (i + mu) + (j + nu) * m : (i + mu) * n + (j + nu)] = N(mu, nu);
2157 }
2158 type = details::Generic;
2160 return *this;
2161}
2162
2163template <ComponentType T>
2165 if (m != other.m)
2166 throw std::invalid_argument(
2167 "trying to horizontally join two "
2168 "matrices of incompatible dimensions");
2169 if (type == details::Zero && other.type == details::Zero) {
2170 *this = ZeroMatrix<T>(m, n + other.n);
2171 } else {
2172 Matrix temp(m, n + other.n);
2173 if (!transposed && !other.transposed) {
2174 for (size_t mu = 0; mu < m; ++mu) {
2175 std::copy(data.begin() + mu * n, data.begin() + (mu + 1) * n, temp.data.begin() + mu * temp.n);
2176 std::copy(other.data.begin() + mu * other.n, other.data.begin() + (mu + 1) * other.n,
2177 temp.data.begin() + mu * temp.n + n);
2178 }
2179 } else {
2180 for (size_t mu = 0; mu < m; ++mu) {
2181 for (size_t nu = 0; nu < n; ++nu) temp.data[mu * temp.n + nu] = (*this)(mu, nu);
2182 for (size_t nu = 0; nu < other.n; ++nu) temp.data[mu * temp.n + n + nu] = other(mu, nu);
2183 }
2184 }
2185 temp.type = details::Generic; // raw writes bypass set_component's tag tracking
2187 *this = std::move(temp);
2188 }
2189 return *this;
2190}
2191
2192template <ComponentType T>
2194 if (n != other.n)
2195 throw std::invalid_argument(
2196 "trying to vertically join two "
2197 "matrices of incompatible dimensions");
2198 if (type == details::Zero && other.type == details::Zero) {
2199 *this = ZeroMatrix<T>(m + other.m, n);
2200 } else {
2201 Matrix temp(m + other.m, n);
2202 if (!transposed) {
2203 std::copy(data.begin(), data.end(), temp.data.begin());
2204 } else {
2205 for (size_t mu = 0; mu < m; ++mu)
2206 for (size_t nu = 0; nu < n; ++nu) temp.data[mu * n + nu] = (*this)(mu, nu);
2207 }
2208 if (!other.transposed) {
2209 std::copy(other.data.begin(), other.data.end(), temp.data.begin() + m * n);
2210 } else {
2211 for (size_t mu = 0; mu < other.m; ++mu)
2212 for (size_t nu = 0; nu < n; ++nu) temp.data[(m + mu) * n + nu] = other(mu, nu);
2213 }
2214 temp.type = details::Generic; // raw writes bypass set_component's tag tracking
2216 *this = std::move(temp);
2217 }
2218 return *this;
2219}
2220
2221template <ComponentType T>
2223 if (type == details::Zero && other.type == details::Zero) {
2224 *this = ZeroMatrix<T>(m + other.m, n + other.n);
2225 } else if (type == details::Identity && other.type == details::Identity) {
2226 *this = IdentityMatrix<T>(m + other.m);
2227 } else if ((type == details::Diagonal || type == details::Identity || (type == details::Zero && m == n)) &&
2229 (other.type == details::Zero && other.m == other.n))) {
2231 } else {
2232 Matrix temp(m + other.m, n + other.n);
2233 for (size_t mu = 0; mu < m; ++mu) {
2234 if (!transposed)
2235 std::copy(data.begin() + mu * n, data.begin() + (mu + 1) * n, temp.data.begin() + mu * temp.n);
2236 else
2237 for (size_t nu = 0; nu < n; ++nu) temp.data[mu * temp.n + nu] = (*this)(mu, nu);
2238 }
2239 for (size_t mu = 0; mu < other.m; ++mu) {
2240 if (!other.transposed)
2241 std::copy(other.data.begin() + mu * other.n, other.data.begin() + (mu + 1) * other.n,
2242 temp.data.begin() + (m + mu) * temp.n + n);
2243 else
2244 for (size_t nu = 0; nu < other.n; ++nu) temp.data[(m + mu) * temp.n + n + nu] = other(mu, nu);
2245 }
2246 temp.type = details::Generic; // raw writes bypass set_component's tag tracking
2248 *this = std::move(temp);
2249 }
2250 return *this;
2251}
2252
2253template <ComponentType T>
2255 if (type == details::Zero || other.type == details::Zero) {
2256 *this = ZeroMatrix<T>(m * other.m, n * other.n);
2257 } else if (type == details::Identity && other.type == details::Identity) {
2258 *this = IdentityMatrix<T>(m * other.m);
2259 } else if (type == details::Diagonal || type == details::Identity) {
2261 auto d1 = diagonal();
2262 auto d2 = other.diagonal();
2263 Vector<T> d(m * other.m);
2264 for (size_t idx = 0; idx < m * other.m; ++idx) d.set_component(idx, d1[idx / other.m] * d2[idx % other.m]);
2265 *this = DiagonalMatrix<T>(std::move(d));
2266 } else {
2267 Matrix temp(m * other.m, n * other.n);
2268 for (size_t mu = 0; mu < m; ++mu) {
2269 for (size_t i = 0; i < other.m; ++i) {
2270 T* dst = temp.data.data() + (mu * other.m + i) * temp.n + mu * other.n;
2271 if (type == details::Identity) {
2272 if (!other.transposed)
2273 std::copy(other.data.begin() + i * other.n, other.data.begin() + (i + 1) * other.n, dst);
2274 else
2275 for (size_t j = 0; j < other.n; ++j) dst[j] = other(i, j);
2276 } else {
2277 const T& s = (*this)(mu, mu);
2278 if (!other.transposed) {
2279 const T* src = other.data.data() + i * other.n;
2280 for (size_t j = 0; j < other.n; ++j) dst[j] = src[j] * s;
2281 } else {
2282 for (size_t j = 0; j < other.n; ++j) dst[j] = other(i, j) * s;
2283 }
2284 }
2285 }
2286 }
2287 temp.type = details::Generic; // raw writes bypass set_component's tag tracking
2289 *this = std::move(temp);
2290 }
2291 } else {
2292 Matrix temp(m * other.m, n * other.n);
2293 for (size_t mu = 0; mu < m; ++mu)
2294 for (size_t nu = 0; nu < n; ++nu) {
2295 const T& s = (*this)(mu, nu);
2296 for (size_t i = 0; i < other.m; ++i) {
2297 T* dst = temp.data.data() + (mu * other.m + i) * temp.n + nu * other.n;
2298 if (!other.transposed) {
2299 const T* src = other.data.data() + i * other.n;
2300 for (size_t j = 0; j < other.n; ++j) dst[j] = src[j] * s;
2301 } else {
2302 for (size_t j = 0; j < other.n; ++j) dst[j] = other(i, j) * s;
2303 }
2304 }
2305 }
2306 temp.type = details::Generic; // raw writes bypass set_component's tag tracking
2308 *this = std::move(temp);
2309 }
2310
2311 return *this;
2312}
2313
2314template <ComponentType T>
2316 if (i >= m || j >= m) throw std::invalid_argument("trying to swap non-existent row(s)");
2317 if (i == j) return *this;
2318 if (type != details::Zero) {
2319 if (!transposed) {
2320 std::swap_ranges(data.begin() + i * n, data.begin() + (i + 1) * n, data.begin() + j * n);
2321 } else {
2322 for (size_t nu = 0; nu < n; ++nu) std::swap(data[i + nu * m], data[j + nu * m]);
2323 }
2324 type = details::Generic;
2325 }
2326 return *this;
2327}
2328
2329template <ComponentType T>
2331 if (i >= n || j >= n) throw std::invalid_argument("trying to swap non-existent column(s)");
2332 if (i == j) return *this;
2333 if (type == details::Vandermonde) {
2334 // swapping two columns of a Vandermonde matrix only permutes its evaluation points
2335 for (size_t mu = 0; mu < m; ++mu)
2336 std::swap(data[transposed ? mu + i * m : mu * n + i], data[transposed ? mu + j * m : mu * n + j]);
2337 return *this;
2338 }
2339 transpose();
2340 swap_rows(i, j);
2341 transpose();
2342 return *this;
2343}
2344
2345template <ComponentType T>
2347 if (i >= m) throw std::invalid_argument("trying to scale non-existent row");
2348 if (s == T(1)) return *this;
2349 if (s == T(0)) cache.invalidate();
2351 if (!transposed) {
2352 std::for_each(data.begin() + i * n, data.begin() + (i + 1) * n, [&s](T& x) { x *= s; });
2354
2355 } else {
2356 for (size_t nu = 0; nu < n; ++nu) data[i + nu * m] *= s;
2358 }
2359 } else if (type == details::Zero) {
2360 /* no-op */
2361 } else if (type == details::Diagonal || type == details::Identity) {
2362 if ((*this)(i, i) == T(0)) return *this; // diagonal entry already zero: no-op
2363 data[i * n + i] *= s;
2365 if (s == T(0)) {
2366 const T zero(0);
2367 bool all_zero = true;
2368 for (size_t mu = 0; mu < m; ++mu) {
2369 if ((*this)(mu, mu) != zero) {
2370 all_zero = false;
2371 break;
2372 }
2373 }
2374 if (all_zero) type = details::Zero;
2375 }
2376 }
2377 return *this;
2378}
2379
2380template <ComponentType T>
2382 if (i >= n) throw std::invalid_argument("trying to scale non-existent column");
2383 if (s == T(1)) return *this;
2384 transpose();
2385 scale_row(s, i);
2386 transpose();
2387 return *this;
2388}
2389
2390template <ComponentType T>
2392 if (i >= m || j >= m)
2393 throw std::invalid_argument("trying to add scaled row to other row, at least one of them is non-existent");
2394 if (s == T(0)) return *this;
2395 if (i == j) {
2396 // row j is scaled by 1+s: rank and weight survive unless 1+s == 0
2397 if (T(1) + s == T(0)) cache.invalidate();
2398 } else {
2399 cache.template invalidate<Weight>();
2400 }
2402 if (!transposed) {
2403 std::transform(data.begin() + j * n, data.begin() + (j + 1) * n, data.begin() + i * n, data.begin() + j * n,
2404 [&s](const T& target, const T& source) { return target + s * source; });
2406 } else {
2407 for (size_t nu = 0; nu < n; ++nu) data[j + nu * m] += s * data[i + nu * m];
2409 }
2410 } else if (type == details::Zero) {
2411 /* no-op */
2412 } else if (type == details::Diagonal || type == details::Identity) {
2413 const T pivot = (*this)(i, i);
2414 if (pivot == T(0)) return *this; // row i is zero: no-op
2415 data[transposed ? j + i * m : j * n + i] += pivot * s;
2416 if (i != j)
2417 type = details::Generic;
2418 else if (type == details::Identity)
2420 }
2421 return *this;
2422}
2423
2424template <ComponentType T>
2426 if (i >= n || j >= n)
2427 throw std::invalid_argument(
2428 "trying to add scaled column to other column, at least one of them is non-existent");
2429 if (s == T(0)) return *this;
2430 transpose();
2431 add_scaled_row(s, i, j);
2432 transpose();
2433 return *this;
2434}
2435
2436template <ComponentType T>
2438 if (v.empty()) return *this;
2439
2440 std::set<size_t> indices(v.begin(), v.end());
2441 for (auto it = indices.cbegin(); it != indices.cend(); ++it) {
2442 if (*it >= n) throw std::invalid_argument("trying to delete non-existent column");
2443 }
2444
2445 Matrix res(m, n - indices.size());
2446 if (!transposed) {
2447 std::vector<std::pair<size_t, size_t>> runs; // maximal [begin, end) ranges of kept columns
2448 for (size_t j = 0; j < n;) {
2449 if (indices.contains(j)) {
2450 ++j;
2451 continue;
2452 }
2453 const size_t begin = j;
2454 while (j < n && !indices.contains(j)) ++j;
2456 }
2457 for (size_t i = 0; i < m; ++i) {
2458 size_t out = 0;
2459 for (auto run = runs.cbegin(); run != runs.cend(); ++run) {
2460 std::copy(data.begin() + i * n + run->first, data.begin() + i * n + run->second,
2461 res.data.begin() + i * res.n + out);
2462 out += run->second - run->first;
2463 }
2464 }
2465 } else {
2466 size_t out = 0;
2467 for (size_t j = 0; j < n; ++j) {
2468 if (indices.contains(j)) continue;
2469 for (size_t i = 0; i < m; ++i) res.data[i * res.n + out] = (*this)(i, j);
2470 ++out;
2471 }
2472 }
2473 if (type == details::Vandermonde && res.n > 0) {
2474 // deleting columns of a Vandermonde matrix only removes (pairwise distinct) evaluation points
2475 res.type = (res.m == 1 && res.n == 1) ? details::Identity : details::Vandermonde;
2476 } else {
2477 res.type = details::Generic; // raw copy bypasses set_component's tag tracking
2479 }
2480 *this = std::move(res);
2481 return *this;
2482}
2483
2484template <ComponentType T>
2486 if (v.empty()) return *this;
2487
2488 std::set<size_t> indices(v.begin(), v.end());
2489 for (auto it = indices.cbegin(); it != indices.cend(); ++it) {
2490 if (*it >= m) throw std::invalid_argument("trying to delete non-existent row");
2491 }
2492
2493 Matrix res(m - indices.size(), n);
2494 size_t out = 0;
2495 for (size_t i = 0; i < m; ++i) {
2496 if (indices.contains(i)) continue;
2497 if (!transposed)
2498 std::copy(data.begin() + i * n, data.begin() + (i + 1) * n, res.data.begin() + out * n);
2499 else
2500 for (size_t j = 0; j < n; ++j) res.data[out * n + j] = (*this)(i, j);
2501 ++out;
2502 }
2503 if (type == details::Vandermonde && res.m > 0 && *indices.begin() == m - indices.size()) {
2504 // deleting trailing rows of a Vandermonde matrix only removes its highest powers
2505 res.type = (res.m == 1 && res.n == 1) ? details::Identity : details::Vandermonde;
2506 } else {
2507 res.type = details::Generic; // raw copy bypasses set_component's tag tracking
2509 }
2510 *this = std::move(res);
2511 return *this;
2512}
2513
2514#ifdef CECCO_ERASURE_SUPPORT
2515
2516template <ComponentType T>
2519{
2520 if (i >= m || j >= n) throw std::invalid_argument("trying to erase component at invalid index");
2521
2522 if (!transposed)
2523 data[i * n + j].erase();
2524 else
2525 data[i + j * m].erase();
2526
2527 type = details::Generic;
2528 cache.invalidate();
2529 return *this;
2530}
2531
2532template <ComponentType T>
2535{
2536 if (i >= m || j >= n) throw std::invalid_argument("trying to un-erase component at invalid index");
2537
2538 if (!transposed)
2539 data[i * n + j].unerase();
2540 else
2541 data[i + j * m].unerase();
2542
2543 type = details::Generic;
2544 cache.invalidate();
2545 return *this;
2546}
2547
2548template <ComponentType T>
2551{
2552 if (v.empty()) return *this;
2553
2554 std::set<size_t> indices(v.begin(), v.end());
2555 for (auto it = indices.cbegin(); it != indices.cend(); ++it) {
2556 if (*it >= n) throw std::invalid_argument("trying to erase non-existent column");
2557 }
2558
2559 for (auto it = indices.cbegin(); it != indices.cend(); ++it) {
2560 for (size_t row = 0; row < m; ++row) erase_component(row, *it);
2561 }
2562
2563 type = details::Generic;
2564 cache.invalidate();
2565 return *this;
2566}
2567
2568template <ComponentType T>
2571{
2572 if (v.empty()) return *this;
2573
2574 std::set<size_t> indices(v.begin(), v.end());
2575 for (auto it = indices.cbegin(); it != indices.cend(); ++it) {
2576 if (*it >= m) throw std::invalid_argument("trying to erase non-existent row");
2577 }
2578
2579 for (auto it = indices.cbegin(); it != indices.cend(); ++it) {
2580 for (size_t col = 0; col < n; ++col) erase_component(*it, col);
2581 }
2582
2583 type = details::Generic;
2584 cache.invalidate();
2585 return *this;
2586}
2587
2588template <ComponentType T>
2591{
2592 std::set<size_t> indices(v.begin(), v.end());
2593 for (auto it = indices.cbegin(); it != indices.cend(); ++it) {
2594 if (*it >= n) throw std::invalid_argument("trying to un-erase non-existent column");
2595 }
2596
2597 for (auto it = indices.cbegin(); it != indices.cend(); ++it) {
2598 for (size_t row = 0; row < m; ++row) unerase_component(row, *it);
2599 }
2600
2601 type = details::Generic;
2602 cache.invalidate();
2603 return *this;
2604}
2605
2606template <ComponentType T>
2609{
2610 std::set<size_t> indices(v.begin(), v.end());
2611 for (auto it = indices.cbegin(); it != indices.cend(); ++it) {
2612 if (*it >= m) throw std::invalid_argument("trying to un-erase non-existent row");
2613 }
2614
2615 for (auto it = indices.cbegin(); it != indices.cend(); ++it) {
2616 for (size_t col = 0; col < n; ++col) unerase_component(*it, col);
2617 }
2618
2619 type = details::Generic;
2620 cache.invalidate();
2621 return *this;
2622}
2623
2624#endif
2625
2626template <ComponentType T>
2628 if (type != details::Zero) {
2629 if (!transposed) {
2630 for (size_t mu = 0; mu < m / 2; ++mu)
2631 std::swap_ranges(data.begin() + mu * n, data.begin() + (mu + 1) * n, data.begin() + (m - 1 - mu) * n);
2632 } else {
2633 for (size_t mu = 0; mu < m / 2; ++mu)
2634 for (size_t nu = 0; nu < n; ++nu) std::swap(data[mu + nu * m], data[m - 1 - mu + nu * m]);
2635 }
2636 type = details::Generic;
2637 }
2638 return *this;
2639}
2640
2641template <ComponentType T>
2643 if (type != details::Zero) {
2644 if (!transposed) {
2645 for (size_t mu = 0; mu < m; ++mu) std::reverse(data.begin() + mu * n, data.begin() + (mu + 1) * n);
2646 } else {
2647 for (size_t mu = 0; mu < m; ++mu)
2648 for (size_t nu = 0; nu < n / 2; ++nu) std::swap(data[mu + nu * m], data[mu + (n - 1 - nu) * m]);
2649 }
2650 if (type != details::Vandermonde) {
2651 type = details::Generic;
2652 }
2653 }
2654 return *this;
2655}
2656
2657template <ComponentType T>
2658constexpr Matrix<T>& Matrix<T>::fill(const T& l) {
2659 cache.invalidate();
2660 std::fill(data.begin(), data.end(), l);
2661 if (data.empty()) {
2662 type = details::Zero;
2663 cache.template set<Rank>(0);
2664 cache.template set<Weight>(0);
2665 } else {
2666 if constexpr (ReliablyComparableType<T>) {
2667 if (m == 1 && n == 1 && l != T(0)) {
2668 if (l == T(1))
2670 else
2672 cache.template set<Rank>(1);
2673 cache.template set<Weight>(1);
2674 } else if (l == T(0)) {
2675 type = details::Zero;
2676 cache.template set<Rank>(0);
2677 cache.template set<Weight>(0);
2678 } else {
2680 cache.template set<Rank>(1);
2681 cache.template set<Weight>(m * n);
2682 }
2683 } else {
2685 }
2686 }
2687 return *this;
2688}
2689
2690template <ComponentType T>
2691constexpr Matrix<T>& Matrix<T>::transpose() {
2694 std::swap(m, n);
2696 } else if (type == details::Zero) {
2697 std::swap(m, n);
2698 } else if (type == details::Diagonal || type == details::Identity) {
2699 /* no-op */
2700 }
2701 return *this;
2702}
2703
2704template <ComponentType T>
2707{
2708 if (type == details::Generic || type == details::Toeplitz) {
2709 size_t h = 0;
2710 size_t k = 0;
2711
2712 if (!this->transposed)
2713 h = ref_elimination_kernel<false>(this->data.data(), m, n, h, k);
2714 else
2715 h = ref_elimination_kernel<true>(this->data.data(), m, n, h, k);
2716
2717 cache.template invalidate<Weight>();
2718 cache.template set<Rank>(h);
2719 if (rank_ptr != nullptr) *rank_ptr = h;
2721
2722 } else if (type == details::Vandermonde) {
2723 // For Vandermonde matrices, calculate RREF (as special case of REF)
2724 return rref(rank_ptr);
2725 } else if (type == details::Diagonal) {
2727 size_t r = 0;
2728 for (size_t i = 0; i < std::min(m, n); ++i) {
2729 if ((*this)(i, i) == T(0)) {
2731 } else {
2732 set_component(i, i, T(1)); // Normalize to 1 for REF
2733 ++r;
2734 }
2735 }
2736
2737 if (!zero_rows.empty()) {
2738 this->delete_rows(zero_rows);
2740 type = details::Generic; // No longer purely diagonal
2741 } else {
2743 }
2744
2745 cache.template set<Rank>(r);
2746 if (rank_ptr != nullptr) *rank_ptr = r;
2747 } else if (type == details::Identity) {
2748 cache.template set<Rank>(m);
2749 if (rank_ptr != nullptr) *rank_ptr = m;
2750 } else if (type == details::Zero) {
2751 cache.template set<Rank>(0);
2752 if (rank_ptr != nullptr) *rank_ptr = 0;
2753 }
2754 return *this;
2755}
2756
2757template <ComponentType T>
2760{
2761 if (type == details::Generic || type == details::Toeplitz) {
2762 size_t r = 0;
2763 this->ref(&r);
2764
2765 if (!this->transposed)
2766 rref_backward_elimination_kernel<false>(this->data.data(), m, n, r);
2767 else
2768 rref_backward_elimination_kernel<true>(this->data.data(), m, n, r);
2769
2770 cache.template set<Rank>(r);
2771 if (rank_ptr != nullptr) *rank_ptr = r;
2772 if (r == m && m == n)
2774 else if (r == n && n > 0)
2775 type = details::Toeplitz; // full column rank: RREF is the identity above zero rows
2776 else if (type == details::Toeplitz)
2777 type = details::Generic;
2778 } else if (type == details::Vandermonde) {
2779 if (m == n) {
2780 // Case 1: Square Vandermonde -> I
2781 *this = IdentityMatrix<T>(m);
2782 if (rank_ptr != nullptr) *rank_ptr = m;
2784
2785 } else if (m < n) {
2786 // Case 2: Wide Vandermonde [W | A] with W, A Vandermonde -> [I | W^-1 A]
2787 auto W = this->get_submatrix(0, 0, m, m);
2788 auto A = this->get_submatrix(0, m, m, n - m);
2789
2790 W.invert(); // Use known Vandermonde inverse of W
2791 auto M = W * A;
2792
2793 *this = IdentityMatrix<T>(m);
2794 this->horizontal_join(std::move(M));
2795
2796 cache.template set<Rank>(m);
2797 if (rank_ptr != nullptr) *rank_ptr = m;
2798 type = details::Generic;
2799
2800 } else {
2801 // Case 3: Tall Vandermonde (more rows than columns) -> I above of Z
2802 *this = IdentityMatrix<T>(n);
2803 this->vertical_join(ZeroMatrix<T>(m - n, n));
2804
2805 cache.template set<Rank>(n);
2806 if (rank_ptr != nullptr) *rank_ptr = n;
2807 type = details::Toeplitz; // very special case of Toeplitz...
2808 }
2809 } else if (type == details::Diagonal) {
2811 size_t r = 0;
2812 for (size_t i = 0; i < std::min(m, n); ++i) {
2813 if ((*this)(i, i) == T(0)) {
2815 } else {
2816 set_component(i, i, T(1)); // Normalize to 1 for RREF
2817 ++r;
2818 }
2819 }
2820
2821 if (!zero_rows.empty()) {
2822 this->delete_rows(zero_rows);
2824 type = details::Generic;
2825 } else {
2827 }
2828
2829 cache.template set<Rank>(r);
2830 if (rank_ptr != nullptr) *rank_ptr = r;
2831 } else if (type == details::Zero) {
2832 cache.template set<Rank>(0);
2833 if (rank_ptr != nullptr) *rank_ptr = 0;
2834 } else if (type == details::Identity) {
2835 cache.template set<Rank>(m);
2836 if (rank_ptr != nullptr) *rank_ptr = m;
2837 }
2838 return *this;
2839}
2840
2841template <ComponentType T>
2844{
2845 if (type == details::Generic || type == details::Toeplitz) {
2846 if (m != n) throw std::invalid_argument("trying to invert a non-square matrix");
2847 const auto I = IdentityMatrix<T>(m);
2848 Matrix temp(m, 2 * m);
2849 temp.set_submatrix(0, 0, *this);
2850 temp.set_submatrix(0, m, I);
2851 temp.rref();
2852 if (temp.get_submatrix(0, 0, m, m) != I)
2853 throw std::invalid_argument("trying to invert a non-invertible matrix");
2854 *this = temp.get_submatrix(0, m, m, m);
2855
2856 } else if (type == details::Vandermonde) {
2857 if (m != n) throw std::invalid_argument("trying to invert a non-square Vandermonde matrix");
2858 if (m > 1) {
2859 Vector<T> v(m);
2860 for (size_t i = 0; i < m; ++i) v.set_component(i, (*this)(1, i));
2861
2862 Polynomial<T> P(1);
2863 for (size_t j = 0; j < m; ++j) P *= Polynomial<T>({-v[j], T(1)});
2864
2865 for (size_t i = 0; i < m; ++i) {
2866 const auto Pi = poly_long_div(P, Polynomial<T>({-v[i], T(1)})).first;
2867 const T scale = T(1) / Pi(v[i]);
2868 for (size_t j = 0; j < m; ++j) set_component(i, j, Pi[j] * scale);
2869 }
2870 type = details::Generic;
2871 }
2872 } else if (type == details::Zero) {
2873 throw std::invalid_argument("trying to invert a non-invertible matrix/a zero matrix");
2874 } else if (type == details::Diagonal) {
2875 // Check for zero diagonal elements and invert
2876 for (size_t mu = 0; mu < m; ++mu) {
2877 if ((*this)(mu, mu) == T(0))
2878 throw std::invalid_argument(
2879 "trying to invert a non-invertible matrix/a diagonal matrix with at least one zero on the "
2880 "diagonal");
2881 data[mu * n + mu] = T(1) / data[mu * n + mu];
2882 }
2883 } else if (type == details::Identity) {
2884 /* no-op */
2885 }
2886 return *this;
2887}
2888
2889template <ComponentType T>
2890template <FiniteFieldType S>
2891constexpr Vector<S> Matrix<T>::as_vector() const
2893{
2894 const size_t rows = S().template as_vector<T>().get_n();
2895 if (rows != get_m())
2896 throw std::invalid_argument("trying to create superfield vector from subfield matrix, wrong number of rows");
2897
2898 Vector<S> res(get_n());
2899 for (size_t i = 0; i < get_n(); ++i) res.set_component(i, S(get_col(i)));
2900 return res;
2901}
2902
2903template <ComponentType T>
2905 Vector<T> v(m * n);
2906 if (!transposed) {
2907 std::copy(data.begin(), data.end(), v.data.begin());
2908 } else {
2909 size_t k = 0;
2910 for (size_t i = 0; i < m; ++i)
2911 for (size_t j = 0; j < n; ++j) v.data[k++] = (*this)(i, j);
2912 }
2913 v.cache.invalidate(); // raw writes bypass set_component's cache handling
2914 return v;
2915}
2916
2917template <ComponentType T>
2919 requires FiniteFieldType<T> && (T::get_size() <= 64)
2920{
2921 std::ofstream file;
2923
2924 file << "P3" << std::endl; // PPM with P3 format
2925 file << get_n() << " " << get_m() << std::endl;
2926 file << 255 << std::endl;
2927
2928 for (size_t i = 0; i < get_m(); ++i) {
2929 for (size_t j = 0; j < get_n(); ++j) {
2930#ifdef CECCO_ERASURE_SUPPORT
2931 if ((*this)(i, j).is_erased()) {
2932 file << std::setw(3) << 255 << " " << std::setw(3) << 0 << " " << std::setw(3) << 0 << " ";
2933 if (j == get_n() - 1) file << std::endl;
2934 continue;
2935 }
2936#endif
2937
2938 const auto label = (*this)(i, j).get_label();
2939
2940 const size_t a = 63 - 63 * static_cast<double>(label) / (T::get_size() - 1);
2941
2942 const uint8_t r = details::colormap[a][0];
2943 const uint8_t g = details::colormap[a][1];
2944 const uint8_t b = details::colormap[a][2];
2945
2946 file << std::setw(3) << static_cast<int>(r) << " " << std::setw(3) << static_cast<int>(g) << " "
2947 << std::setw(3) << static_cast<int>(b) << " ";
2948 }
2949 file << std::endl;
2950 }
2951
2952 file.close();
2953}
2954
2955template <ComponentType T>
2956size_t Matrix<T>::calculate_rank() const
2958{
2959 Matrix<T> temp(*this);
2960 size_t r = 0;
2961 temp.ref(&r); // Use REF instead of RREF for better performance
2962 return r;
2963}
2964
2965/* free functions wrt. Matrix */
2966
2967/*
2968 * matrix + matrix
2969 */
2970
2971template <ComponentType T>
2972constexpr Matrix<T> operator+(const Matrix<T>& lhs, const Matrix<T>& rhs) {
2973 Matrix<T> res(lhs);
2974 res += rhs;
2975 return res;
2976}
2977
2978template <ComponentType T>
2979constexpr Matrix<T> operator+(Matrix<T>&& lhs, const Matrix<T>& rhs) {
2980 Matrix<T> res(std::move(lhs));
2981 res += rhs;
2982 return res;
2983}
2984
2985template <ComponentType T>
2986constexpr Matrix<T> operator+(const Matrix<T>& lhs, Matrix<T>&& rhs) {
2987 Matrix<T> res(std::move(rhs));
2988 res += lhs;
2989 return res;
2990}
2991
2992template <ComponentType T>
2993constexpr Matrix<T> operator+(Matrix<T>&& lhs, Matrix<T>&& rhs) {
2994 Matrix<T> res(std::move(lhs));
2995 res += rhs;
2996 return res;
2997}
2998
2999/*
3000 * matrix - matrix
3001 */
3002
3003template <ComponentType T>
3004constexpr Matrix<T> operator-(const Matrix<T>& lhs, const Matrix<T>& rhs) {
3005 Matrix<T> res(lhs);
3006 res -= rhs;
3007 return res;
3008}
3009
3010template <ComponentType T>
3011constexpr Matrix<T> operator-(Matrix<T>&& lhs, const Matrix<T>& rhs) {
3012 Matrix<T> res(std::move(lhs));
3013 res -= rhs;
3014 return res;
3015}
3016
3017template <ComponentType T>
3018constexpr Matrix<T> operator-(const Matrix<T>& lhs, Matrix<T>&& rhs) {
3019 Matrix<T> res(-std::move(rhs));
3020 res += lhs;
3021 return res;
3022}
3023
3024template <ComponentType T>
3025constexpr Matrix<T> operator-(Matrix<T>&& lhs, Matrix<T>&& rhs) {
3026 Matrix<T> res(std::move(lhs));
3027 res -= rhs;
3028 return res;
3029}
3030
3031/*
3032 * matrix * matrix
3033 */
3034
3035template <ComponentType T>
3036constexpr Matrix<T> operator*(const Matrix<T>& lhs, const Matrix<T>& rhs) {
3037 Matrix<T> res(lhs);
3038 res *= rhs;
3039 return res;
3040}
3041
3042template <ComponentType T>
3043constexpr Matrix<T> operator*(Matrix<T>&& lhs, const Matrix<T>& rhs) {
3044 Matrix<T> res(std::move(lhs));
3045 res *= rhs;
3046 return res;
3047}
3048
3049/*
3050 * vector * matrix
3051 */
3052
3053template <ComponentType T>
3054constexpr Vector<T> operator*(const Vector<T>& lhs, const Matrix<T>& rhs) {
3055 return (Matrix<T>(lhs) * rhs).get_row(0);
3056}
3057
3058template <ComponentType T>
3059constexpr Vector<T> operator*(Vector<T>&& lhs, const Matrix<T>& rhs) {
3060 return (Matrix<T>(std::move(lhs)) * rhs).get_row(0);
3061}
3062
3063/*
3064 * matrix * T
3065 */
3066
3067template <ComponentType T>
3068constexpr Matrix<T> operator*(const Matrix<T>& lhs, const T& rhs) {
3069 Matrix<T> res(lhs);
3070 res *= rhs;
3071 return res;
3072}
3073
3074template <ComponentType T>
3075constexpr Matrix<T> operator*(Matrix<T>&& lhs, const T& rhs) {
3076 Matrix<T> res(std::move(lhs));
3077 res *= rhs;
3078 return res;
3079}
3080
3081/*
3082 * T * matrix
3083 */
3084
3085template <ComponentType T>
3086constexpr Matrix<T> operator*(const T& lhs, const Matrix<T>& rhs) {
3087 Matrix<T> res(rhs);
3088 res *= lhs;
3089 return res;
3090}
3091
3092template <ComponentType T>
3093constexpr Matrix<T> operator*(const T& lhs, Matrix<T>&& rhs) {
3094 Matrix<T> res(std::move(rhs));
3095 res *= lhs;
3096 return res;
3097}
3098
3099/*
3100 * matrix / T
3101 */
3102
3103template <CoefficientType T>
3104constexpr Matrix<T> operator/(const Matrix<T>& lhs, const T& rhs) {
3105 Matrix<T> res(lhs);
3106 res /= rhs;
3107 return res;
3108}
3109
3110template <CoefficientType T>
3111constexpr Matrix<T> operator/(Matrix<T>&& lhs, const T& rhs) {
3112 Matrix<T> res(std::move(lhs));
3113 res /= rhs;
3114 return res;
3115}
3116
3117template <CoefficientType T>
3119 Matrix<T> res(M);
3120 res.randomize();
3121 return res;
3122}
3123
3124template <CoefficientType T>
3126 Matrix<T> res(std::move(M));
3127 res.randomize();
3128 return res;
3129}
3130
3131template <ReliablyComparableType T>
3132constexpr size_t wH(const Matrix<T>& M) {
3133 return M.wH();
3134}
3135
3136/**
3137 * @brief Hamming distance dₕ(lhs, rhs) = wₕ(lhs − rhs)
3138 *
3139 * Number of matrix components in which `lhs` and `rhs` differ. Under
3140 * @ref CECCO_ERASURE_SUPPORT, positions where either side is erased are not counted.
3141 *
3142 * @throws std::invalid_argument if dimensions differ
3143 */
3144template <ReliablyComparableType T>
3145size_t dH(const Matrix<T>& lhs, const Matrix<T>& rhs) {
3146 if (lhs.get_m() != rhs.get_m() || lhs.get_n() != rhs.get_n())
3147 throw std::invalid_argument(
3148 "trying to calculate Hamming distance between matrices of different "
3149 "dimensions");
3150 return (lhs - rhs).wH();
3151}
3152
3153// rvalue overloads of dH share semantics with the const&,const& version above.
3154template <ReliablyComparableType T>
3155size_t dH(Matrix<T>&& lhs, const Matrix<T>& rhs) {
3156 if (lhs.get_m() != rhs.get_m() || lhs.get_n() != rhs.get_n())
3157 throw std::invalid_argument(
3158 "trying to calculate Hamming distance between matrices of different "
3159 "dimensions");
3160 return (std::move(lhs) - rhs).wH();
3161}
3162
3163template <ReliablyComparableType T>
3164size_t dH(const Matrix<T>& lhs, Matrix<T>&& rhs) {
3165 if (lhs.get_m() != rhs.get_m() || lhs.get_n() != rhs.get_n())
3166 throw std::invalid_argument(
3167 "trying to calculate Hamming distance between matrices of different "
3168 "dimensions");
3169 return (lhs - std::move(rhs)).wH();
3170}
3171
3172template <ReliablyComparableType T>
3174 if (lhs.get_m() != rhs.get_m() || lhs.get_n() != rhs.get_n())
3175 throw std::invalid_argument(
3176 "trying to calculate Hamming distance between matrices of different "
3177 "dimensions");
3178 return (std::move(lhs) - std::move(rhs)).wH();
3179}
3180
3181template <ComponentType T>
3182Matrix<T> set_component(const Matrix<T>& M, size_t i, size_t j, const T& c) {
3183 Matrix<T> res(M);
3184 res.set_component(i, j, c);
3185 return res;
3186}
3187
3188template <ComponentType T>
3190 Matrix<T> res(std::move(M));
3191 res.set_component(i, j, c);
3192 return res;
3193}
3194
3195template <ComponentType T>
3197 return M.get_submatrix(i, j, h, w);
3198}
3199
3200template <ComponentType T>
3202 Matrix<T> res(M);
3203 res.set_submatrix(i, j, N);
3204 return res;
3205}
3206
3207template <ComponentType T>
3209 Matrix<T> res(std::move(M));
3210 res.set_submatrix(i, j, N);
3211 return res;
3212}
3213
3214template <ComponentType T>
3216 Matrix<T> res(lhs);
3218 return res;
3219}
3220
3221template <ComponentType T>
3223 Matrix<T> res(std::move(lhs));
3225 return res;
3226}
3227
3228template <ComponentType T>
3230 Matrix<T> res(std::move(lhs));
3232 return res;
3233}
3234
3235template <ComponentType T>
3237 Matrix<T> res(lhs);
3239 return res;
3240}
3241
3242template <ComponentType T>
3244 Matrix<T> res(std::move(lhs));
3246 return res;
3247}
3248
3249template <ComponentType T>
3251 Matrix<T> res(std::move(lhs));
3253 return res;
3254}
3255
3256template <ComponentType T>
3258 Matrix<T> res(lhs);
3260 return res;
3261}
3262
3263template <ComponentType T>
3265 Matrix<T> res(std::move(lhs));
3267 return res;
3268}
3269
3270template <ComponentType T>
3272 Matrix<T> res(std::move(lhs));
3274 return res;
3275}
3276
3277template <ComponentType T>
3279 Matrix<T> res(lhs);
3281 return res;
3282}
3283
3284template <ComponentType T>
3286 Matrix<T> res(std::move(lhs));
3288 return res;
3289}
3290
3291template <ComponentType T>
3293 Matrix<T> res(std::move(lhs));
3295 return res;
3296}
3297
3298template <ComponentType T>
3300 Matrix<T> res(M);
3301 res.swap_rows(i, j);
3302 return res;
3303}
3304
3305template <ComponentType T>
3307 Matrix<T> res(M);
3308 res.swap_columns(i, j);
3309 return res;
3310}
3311
3312template <ComponentType T>
3314 Matrix<T> res(std::move(M));
3315 res.swap_rows(i, j);
3316 return res;
3317}
3318
3319template <ComponentType T>
3321 Matrix<T> res(std::move(M));
3322 res.swap_columns(i, j);
3323 return res;
3324}
3325
3326template <ComponentType T>
3327Matrix<T> scale_row(const Matrix<T>& M, const T& s, size_t i) {
3328 Matrix<T> res(M);
3329 res.scale_row(s, i);
3330 return res;
3331}
3332
3333template <ComponentType T>
3334Matrix<T> scale_column(const Matrix<T>& M, const T& s, size_t i) {
3335 Matrix<T> res(M);
3336 res.scale_column(s, i);
3337 return res;
3338}
3339
3340template <ComponentType T>
3342 Matrix<T> res(std::move(M));
3343 res.scale_row(s, i);
3344 return res;
3345}
3346
3347template <ComponentType T>
3349 Matrix<T> res(std::move(M));
3350 res.scale_column(s, i);
3351 return res;
3352}
3353
3354template <ComponentType T>
3356 Matrix<T> res(M);
3357 res.add_scaled_row(s, i, j);
3358 return res;
3359}
3360
3361template <ComponentType T>
3363 Matrix<T> res(M);
3365 return res;
3366}
3367
3368template <ComponentType T>
3370 Matrix<T> res(std::move(M));
3371 res.add_scaled_row(s, i, j);
3372 return res;
3373}
3374
3375template <ComponentType T>
3377 Matrix<T> res(std::move(M));
3379 return res;
3380}
3381
3382template <ComponentType T>
3384 Matrix<T> res(M);
3385 res.add_row(i, j);
3386 return res;
3387}
3388
3389template <ComponentType T>
3391 Matrix<T> res(M);
3392 res.add_column(i, j);
3393 return res;
3394}
3395
3396template <ComponentType T>
3398 Matrix<T> res(std::move(M));
3399 res.add_row(i, j);
3400 return res;
3401}
3402
3403template <ComponentType T>
3405 Matrix<T> res(std::move(M));
3406 res.add_column(i, j);
3407 return res;
3408}
3409
3410template <ComponentType T>
3411constexpr Matrix<T> delete_columns(const Matrix<T>& lhs, const std::vector<size_t>& v) {
3412 Matrix<T> res(lhs);
3414 return res;
3415}
3416
3417template <ComponentType T>
3418constexpr Matrix<T> delete_columns(Matrix<T>&& lhs, const std::vector<size_t>& v) {
3419 Matrix<T> res(std::move(lhs));
3421 return res;
3422}
3423
3424template <ComponentType T>
3425constexpr Matrix<T> delete_column(const Matrix<T>& lhs, size_t i) {
3426 Matrix<T> res(lhs);
3428 return res;
3429}
3430
3431template <ComponentType T>
3433 Matrix<T> res(std::move(lhs));
3435 return res;
3436}
3437
3438template <ComponentType T>
3439constexpr Matrix<T> delete_rows(const Matrix<T>& lhs, const std::vector<size_t>& v) {
3440 Matrix<T> res(lhs);
3441 res.delete_rows(v);
3442 return res;
3443}
3444
3445template <ComponentType T>
3446constexpr Matrix<T> delete_rows(Matrix<T>&& lhs, const std::vector<size_t>& v) {
3447 Matrix<T> res(std::move(lhs));
3448 res.delete_rows(v);
3449 return res;
3450}
3451
3452template <ComponentType T>
3453constexpr Matrix<T> delete_row(const Matrix<T>& lhs, size_t i) {
3454 Matrix<T> res(lhs);
3455 res.delete_row(i);
3456 return res;
3457}
3458
3459template <ComponentType T>
3461 Matrix<T> res(std::move(lhs));
3462 res.delete_row(i);
3463 return res;
3464}
3465
3466#ifdef CECCO_ERASURE_SUPPORT
3467
3468template <ComponentType T>
3470 Matrix<T> res(lhs);
3472 return res;
3473}
3474
3475template <ComponentType T>
3477 Matrix<T> res(std::move(lhs));
3479 return res;
3480}
3481
3482template <ComponentType T>
3484 Matrix<T> res(lhs);
3486 return res;
3487}
3488
3489template <ComponentType T>
3491 Matrix<T> res(std::move(lhs));
3493 return res;
3494}
3495
3496template <ComponentType T>
3497constexpr Matrix<T> erase_columns(const Matrix<T>& lhs, const std::vector<size_t>& v) {
3498 Matrix<T> res(lhs);
3500 return res;
3501}
3502
3503template <ComponentType T>
3504constexpr Matrix<T> erase_columns(Matrix<T>&& lhs, const std::vector<size_t>& v) {
3505 Matrix<T> res(std::move(lhs));
3507 return res;
3508}
3509
3510template <ComponentType T>
3511constexpr Matrix<T> erase_column(const Matrix<T>& lhs, size_t i) {
3512 Matrix<T> res(lhs);
3514 return res;
3515}
3516
3517template <ComponentType T>
3519 Matrix<T> res(std::move(lhs));
3521 return res;
3522}
3523
3524template <ComponentType T>
3525constexpr Matrix<T> unerase_columns(const Matrix<T>& lhs, const std::vector<size_t>& v) {
3526 Matrix<T> res(lhs);
3528 return res;
3529}
3530
3531template <ComponentType T>
3532constexpr Matrix<T> unerase_columns(Matrix<T>&& lhs, const std::vector<size_t>& v) {
3533 Matrix<T> res(std::move(lhs));
3535 return res;
3536}
3537
3538template <ComponentType T>
3539constexpr Matrix<T> unerase_column(const Matrix<T>& lhs, size_t i) {
3540 Matrix<T> res(lhs);
3542 return res;
3543}
3544
3545template <ComponentType T>
3547 Matrix<T> res(std::move(lhs));
3549 return res;
3550}
3551
3552template <ComponentType T>
3553constexpr Matrix<T> erase_rows(const Matrix<T>& lhs, const std::vector<size_t>& v) {
3554 Matrix<T> res(lhs);
3555 res.erase_rows(v);
3556 return res;
3557}
3558
3559template <ComponentType T>
3560constexpr Matrix<T> erase_rows(Matrix<T>&& lhs, const std::vector<size_t>& v) {
3561 Matrix<T> res(std::move(lhs));
3562 res.erase_rows(v);
3563 return res;
3564}
3565
3566template <ComponentType T>
3567constexpr Matrix<T> erase_row(const Matrix<T>& lhs, size_t i) {
3568 Matrix<T> res(lhs);
3569 res.erase_row(i);
3570 return res;
3571}
3572
3573template <ComponentType T>
3575 Matrix<T> res(std::move(lhs));
3576 res.erase_row(i);
3577 return res;
3578}
3579
3580template <ComponentType T>
3581constexpr Matrix<T> unerase_rows(const Matrix<T>& lhs, const std::vector<size_t>& v) {
3582 Matrix<T> res(lhs);
3584 return res;
3585}
3586
3587template <ComponentType T>
3588constexpr Matrix<T> unerase_rows(Matrix<T>&& lhs, const std::vector<size_t>& v) {
3589 Matrix<T> res(std::move(lhs));
3591 return res;
3592}
3593
3594template <ComponentType T>
3595constexpr Matrix<T> unerase_row(const Matrix<T>& lhs, size_t i) {
3596 Matrix<T> res(lhs);
3597 res.unerase_row(i);
3598 return res;
3599}
3600
3601template <ComponentType T>
3603 Matrix<T> res(std::move(lhs));
3604 res.unerase_row(i);
3605 return res;
3606}
3607
3608#endif
3609
3610template <ComponentType T>
3612 Matrix<T> res(M);
3613 res.reverse_rows();
3614 return res;
3615}
3616
3617template <ComponentType T>
3619 Matrix<T> res(std::move(M));
3620 res.reverse_rows();
3621 return res;
3622}
3623
3624template <ComponentType T>
3626 Matrix<T> res(M);
3628 return res;
3629}
3630
3631template <ComponentType T>
3633 Matrix<T> res(std::move(M));
3635 return res;
3636}
3637
3638template <ComponentType T>
3639constexpr Matrix<T> fill(const Matrix<T>& m, const T& value) {
3640 Matrix<T> res(m);
3641 res.fill(value);
3642 return res;
3643}
3644
3645template <ComponentType T>
3646constexpr Matrix<T> fill(Matrix<T>&& m, const T& value) {
3647 Matrix<T> res(std::move(m));
3648 res.fill(value);
3649 return res;
3650}
3651
3652template <ComponentType T>
3653constexpr Matrix<T> transpose(const Matrix<T>& M) {
3654 Matrix<T> res(M);
3655 res.transpose();
3656 return res;
3657}
3658
3659template <ComponentType T>
3660constexpr Matrix<T> transpose(Matrix<T>&& M) {
3661 Matrix<T> res(std::move(M));
3662 res.transpose();
3663 return res;
3664}
3665
3666template <FieldType T>
3667Matrix<T> rref(const Matrix<T>& M) {
3668 Matrix<T> res(M);
3669 res.rref();
3670 return res;
3671}
3672
3673template <FieldType T>
3675 Matrix<T> res(std::move(M));
3676 res.rref();
3677 return res;
3678}
3679
3680template <FieldType T>
3682 Matrix<T> res(M);
3683 res.invert();
3684 return res;
3685}
3686
3687template <FieldType T>
3689 Matrix<T> res(std::move(M));
3690 res.invert();
3691 return res;
3692}
3693
3694/**
3695 * @brief Equality of two matrices
3696 *
3697 * @return true iff dimensions match and all components are equal
3698 *
3699 * Tag-aware fast paths handle structured operands (e.g. two Toeplitz matrices compare only
3700 * their first row and column).
3701 */
3702template <ReliablyComparableType T>
3703constexpr bool operator==(const Matrix<T>& lhs, const Matrix<T>& rhs) {
3704 if (lhs.m != rhs.m || lhs.n != rhs.n) return false;
3705
3706 if (lhs.m == 0 || lhs.n == 0) {
3707 return true;
3708 } else if (lhs.m == 1) {
3709 return lhs.get_row(0) == rhs.get_row(0);
3710 } else if (lhs.n == 1) {
3711 return lhs.get_col(0) == rhs.get_col(0);
3712 } else if (lhs.type == details::Toeplitz && rhs.type == details::Toeplitz) {
3713 // Compare left column (in reverse order for details::Toeplitz structure)
3714 for (size_t mu = 0; mu < lhs.m; ++mu)
3715 if (lhs(lhs.m - 1 - mu, 0) != rhs(lhs.m - 1 - mu, 0)) return false;
3716 // Compare top row (excluding first element)
3717 for (size_t nu = 1; nu < lhs.n; ++nu)
3718 if (lhs(0, nu) != rhs(0, nu)) return false;
3719 } else if (lhs.type == details::Vandermonde && rhs.type == details::Vandermonde) {
3720 return lhs.get_row(1) == rhs.get_row(1);
3721 } else if ((lhs.type == details::Diagonal && rhs.type == details::Diagonal) ||
3724 return lhs.diagonal() == rhs.diagonal();
3725 } else if (lhs.type == details::Identity && rhs.type == details::Identity) {
3726 /* no-op */
3727 } else if (lhs.type == details::Zero) {
3728 return rhs.is_zero();
3729 } else if (rhs.type == details::Zero) {
3730 return lhs.is_zero();
3731 } else {
3732 if (!lhs.transposed && !rhs.transposed) {
3733 return std::equal(lhs.data.begin(), lhs.data.end(), rhs.data.begin());
3734 } else {
3735 for (size_t mu = 0; mu < lhs.m; ++mu)
3736 for (size_t nu = 0; nu < lhs.n; ++nu)
3737 if (lhs(mu, nu) != rhs(mu, nu)) return false;
3738 return true;
3739 }
3740 }
3741 return true;
3742}
3743
3744/**
3745 * @brief Negation of @ref operator==
3746 *
3747 * @return true iff dimensions or any component differ
3748 */
3749template <ReliablyComparableType T>
3750constexpr bool operator!=(const Matrix<T>& lhs, const Matrix<T>& rhs) {
3751 return !(lhs == rhs);
3752}
3753
3754/**
3755 * @brief Pretty-print the matrix with column alignment and bracket borders
3756 *
3757 * @return Reference to @p os for chaining
3758 *
3759 * Columns are separated by one blank, by three for polynomial components, whose own rendering
3760 * contains blanks. An empty matrix is printed as "(empty matrix)".
3761 */
3762template <ComponentType T>
3764 if (rhs.m == 0 || rhs.n == 0) {
3765 os << " (empty matrix)";
3766 return os;
3767 }
3769 std::vector<size_t> widths(rhs.n, 0);
3770 for (size_t i = 0; i < rhs.m; ++i)
3771 for (size_t j = 0; j < rhs.n; ++j) {
3773 ss << rhs(i, j);
3774 entries[i][j] = ss.str();
3775 widths[j] = std::max(widths[j], entries[i][j].length());
3776 }
3777 auto print_entry = [&](size_t i, size_t j) {
3778 const auto flags = os.flags();
3779 if constexpr (PolynomialType<T>) os << std::left;
3780 os << std::setw(widths[j]) << entries[i][j];
3781 os.flags(flags);
3782 };
3783 constexpr const char* sep = PolynomialType<T> ? " " : " ";
3784 os << " " << (rhs.m == 1 ? "(" : "⌈");
3785 for (size_t j = 0; j + 1 < rhs.n; ++j) {
3786 print_entry(0, j);
3787 os << sep;
3788 }
3789 print_entry(0, rhs.n - 1);
3790 os << (rhs.m == 1 ? ")" : "⌉");
3791 if (rhs.m > 1) os << std::endl;
3792 if (rhs.m > 2) {
3793 for (size_t i = 1; i + 1 < rhs.m; ++i) {
3794 os << " |";
3795 for (size_t j = 0; j + 1 < rhs.n; ++j) {
3796 print_entry(i, j);
3797 os << sep;
3798 }
3799 print_entry(i, rhs.n - 1);
3800 os << "|" << std::endl;
3801 }
3802 }
3803 if (rhs.m > 1) {
3804 os << " ⌊";
3805 for (size_t j = 0; j + 1 < rhs.n; ++j) {
3806 print_entry(rhs.m - 1, j);
3807 os << sep;
3808 }
3809 print_entry(rhs.m - 1, rhs.n - 1);
3810 os << "⌋";
3811 }
3812 return os;
3813}
3814
3815/*
3816 * factories
3817 */
3818
3819/**
3820 * @brief m × n matrix of zeros (tag @ref details::Zero)
3821 *
3822 * @param m Number of rows
3823 * @param n Number of columns
3824 * @return Newly constructed zero matrix
3825 */
3826template <ComponentType T>
3828 return Matrix<T>(m, n);
3829}
3830
3831/**
3832 * @brief m × m identity matrix I_m
3833 *
3834 * @param m Side length
3835 * @return Newly constructed identity matrix; for m = 0 the tag is @ref details::Zero
3836 */
3837template <ComponentType T>
3839 auto res = Matrix<T>(m, m);
3840 for (size_t i = 0; i < m; ++i) res.set_component(i, i, T(1));
3841 if (m != 0) res.type = details::Identity;
3842 return res;
3843}
3844
3845/**
3846 * @brief Permutation matrix P with P_{i, perm[i]} = 1
3847 *
3848 * @param perm Permutation of {0, …, m-1}; m is inferred from `perm.size()`
3849 * @return m × m permutation matrix; the identity permutation returns @ref IdentityMatrix
3850 *
3851 * @throws std::invalid_argument if perm contains out-of-range or duplicate indices
3852 */
3853template <ComponentType T>
3855 const size_t m = perm.size();
3856
3857 std::vector<bool> seen(m, false);
3858 bool is_identity_perm = true;
3859 for (size_t i = 0; i < m; ++i) {
3860 if (perm[i] >= m || seen[perm[i]])
3861 throw std::invalid_argument("Trying to construct permutation matrix from a list that is not a permutation");
3862 seen[perm[i]] = true;
3863 if (perm[i] != i) is_identity_perm = false;
3864 }
3865
3866 if (is_identity_perm) return IdentityMatrix<T>(m);
3867
3868 auto res = Matrix<T>(m, m);
3869 for (size_t i = 0; i < m; ++i) res.set_component(i, perm[i], T(1));
3870
3871 return res;
3872}
3873
3874/**
3875 * @brief m × m exchange matrix (ones on the anti-diagonal)
3876 *
3877 * @param m Side length
3878 * @return Matrix with E_{i, j} = 1 iff i + j = m − 1
3879 */
3880template <ComponentType T>
3882 auto res = IdentityMatrix<T>(m);
3884 return res;
3885}
3886
3887/**
3888 * @brief Diagonal matrix with diagonal v (strongest tag of @ref details::Zero,
3889 * @ref details::Identity, @ref details::Diagonal)
3890 *
3891 * @param v Vector of diagonal entries; the result is `v.length()` × `v.length()`
3892 * @return Diagonal matrix with v[i] on position (i, i), zeros elsewhere
3893 */
3894template <ComponentType T>
3895constexpr Matrix<T> DiagonalMatrix(const Vector<T>& v) {
3896 const size_t m = v.get_n();
3897 Matrix<T> res(m, m);
3898 for (size_t i = 0; i < m; ++i) {
3899 res.set_component(i, i, v[i]);
3900 }
3901 if constexpr (ReliablyComparableType<T>) {
3902 const T zero(0);
3903 const T one(1);
3904 bool all_zero = true;
3905 bool all_identity = true;
3906 for (size_t i = 0; i < m && (all_zero || all_identity); ++i) {
3907 if (v[i] != zero) all_zero = false;
3908 if (v[i] != one) all_identity = false;
3909 }
3910 if (all_zero)
3911 res.type = details::Zero;
3912 else if (all_identity)
3914 else
3916 } else if (m != 0) {
3918 }
3919 return res;
3920}
3921
3922/**
3923 * @brief m × n Toeplitz matrix from its diagonal entries
3924 *
3925 * @param v Diagonal values, ordered so that v[i − j + n − 1] sits on entry (i, j); length m + n − 1
3926 * @param m Number of rows
3927 * @param n Number of columns
3928 * @return Toeplitz matrix; tagged by the strongest detected structural type
3929 *
3930 * @throws std::invalid_argument if `m == 0` or `n == 0`
3931 * @throws std::invalid_argument if `v.length() != m + n - 1`
3932 */
3933template <ComponentType T>
3934constexpr Matrix<T> ToeplitzMatrix(const Vector<T>& v, size_t m, size_t n) {
3935 if (m == 0 || n == 0) throw std::invalid_argument("Toeplitz matrix dimensions must be positive");
3936 if (v.get_n() != m + n - 1)
3937 throw std::invalid_argument(
3938 "vector for constructing m x n Toeplitz matrix must have "
3939 "length m+n-1");
3940 Matrix<T> res(m, n);
3941
3942 // Fill first column: v[0] to v[m-1] in reverse order
3943 for (size_t i = 0; i < m; ++i) res.data[(m - 1 - i) * n] = v[i];
3944
3945 // Fill first row: v[m-1] to v[m+n-2]
3946 for (size_t j = 1; j < n; ++j) res.data[j] = v[m - 1 + j];
3947
3948 // Fill remaining elements using diagonal copy pattern
3949 for (size_t i = 1; i < m; ++i)
3950 for (size_t j = 1; j < n; ++j) res.data[i * n + j] = res.data[(i - 1) * n + (j - 1)];
3951 if constexpr (ReliablyComparableType<T>) {
3952 res.type = details::Generic; // raw writes bypass set_component's tag tracking
3954 } else {
3956 }
3957 return res;
3958}
3959
3960/**
3961 * @brief m × n Hankel matrix from its anti-diagonal entries
3962 *
3963 * @param v Anti-diagonal values; length m + n − 1
3964 * @param m Number of rows
3965 * @param n Number of columns
3966 * @return Hankel matrix; each ascending anti-diagonal is constant
3967 *
3968 * Constructed by reversing the columns of `ToeplitzMatrix(reverse(v), m, n)`.
3969 *
3970 * @throws std::invalid_argument if `v.length() != m + n - 1`
3971 */
3972template <ComponentType T>
3973constexpr Matrix<T> HankelMatrix(const Vector<T>& v, size_t m, size_t n) {
3974 auto res = ToeplitzMatrix<T>(reverse(v), m, n);
3976 return res;
3977}
3978
3979/**
3980 * @brief Vandermonde matrix V_{i, j} = v[j]^i
3981 *
3982 * @param v Evaluation points; must be pairwise distinct
3983 * @param m Number of rows (i.e. the highest power is m − 1)
3984 * @return m × `v.length()` Vandermonde matrix; the 1 × 1 case is tagged as @ref details::Identity
3985 *
3986 * @throws std::invalid_argument if v is empty, has duplicates, or m is zero
3987 */
3988template <ReliablyComparableType T>
3989constexpr Matrix<T> VandermondeMatrix(const Vector<T>& v, size_t m) {
3990 const size_t n = v.get_n();
3991 if (n == 0)
3992 throw std::invalid_argument(
3993 "vector for constructing Vandermonde matrix must have "
3994 "at least one element");
3995 if (m == 0) throw std::invalid_argument("trying to construct Vandermonde matrix with zero rows");
3996 if (!v.is_pairwise_distinct())
3997 throw std::invalid_argument("vector for constructing Vandermonde matrix must have pairwise distinct elements");
3998
3999 Matrix<T> res(m, n);
4000
4001 const T one(1);
4002 for (size_t i = 0; i < n; ++i) res.data[i] = one;
4003
4004 if (m > 1) {
4005 for (size_t i = 0; i < n; ++i) res.data[n + i] = v[i];
4006
4007 for (size_t j = 2; j < m; ++j)
4008 for (size_t i = 0; i < n; ++i) res.data[j * n + i] = res.data[(j - 1) * n + i] * v[i];
4009 }
4010 res.type = (m == 1 && n == 1) ? details::Identity : details::Vandermonde;
4011 return res;
4012}
4013
4014/**
4015 * @brief m × m upper shift matrix (ones on the superdiagonal)
4016 *
4017 * @param m Side length
4018 * @return Matrix with U_{i, i+1} = 1, all other entries 0
4019 */
4020template <ComponentType T>
4022 if (m < 2) return Matrix<T>(m, m);
4023 Vector<T> v(2 * m - 1);
4024 v.set_component(m, T(1));
4025 return ToeplitzMatrix<T>(v, m, m);
4026}
4027
4028/**
4029 * @brief m × m lower shift matrix (ones on the subdiagonal); transpose of @ref UpperShiftMatrix
4030 *
4031 * @param m Side length
4032 * @return Matrix with L_{i+1, i} = 1, all other entries 0
4033 */
4034template <ComponentType T>
4036 return transpose(UpperShiftMatrix<T>(m));
4037}
4038
4039/**
4040 * @brief Companion matrix of a monic polynomial p(x) = x^n + a_{n−1} x^{n−1} + … + a_0
4041 *
4042 * @param poly Monic polynomial of degree n
4043 * @return n × n matrix whose characteristic polynomial is `poly`
4044 *
4045 * Built from a lower shift matrix; the last column holds the negated coefficients of `poly`.
4046 *
4047 * @throws std::invalid_argument if `poly` is not monic
4048 */
4049template <CoefficientType T>
4051 if (!poly.is_monic()) throw std::invalid_argument("companion matrices only defined for monic polynomials");
4052 const size_t d = poly.degree();
4054
4055 for (size_t i = 0; i < d; ++i) res.set_component(i, d - 1, -poly[i]);
4056
4057 return res;
4058}
4059
4060} // namespace CECCO
4061
4062#endif
Dense m × n matrix over a CECCO::ComponentType.
Definition matrices.hpp:175
Matrix(std::initializer_list< std::initializer_list< T > > l)
From nested initializer lists, e.g. {{1, 2, 3}, {4, 5, 6}}.
Matrix(size_t m, size_t n, const T &l)
m × n matrix with every component equal to l
constexpr bool is_empty() const noexcept
Check if matrix is empty.
Definition matrices.hpp:391
constexpr bool is_zero()
Check if the matrix is zero, caching the result via the type tag.
Definition matrices.hpp:401
Matrix< T > & swap_columns(size_t i, size_t j)
Swap columns i and j.
constexpr Matrix operator+() const &
Unary + (lvalue): returns a copy.
Definition matrices.hpp:293
Matrix & randomize()
Fill matrix with random values.
Matrix< T > & diagonal_join(const Matrix &other)
Block-diagonal join: this in the upper-left, other in the lower-right.
size_t wH() const
Hamming weight: number of non-zero, non-erased components; cached on first call.
Definition matrices.hpp:421
Matrix< T > get_submatrix(size_t i, size_t j, size_t h, size_t w) const
Extract submatrix from region [i, i+h) × [j, j+w).
constexpr Matrix & operator=(const Matrix &rhs)
constexpr Matrix & operator=(Matrix &&rhs) noexcept
friend constexpr Matrix< T > ToeplitzMatrix(const Vector< T > &v, size_t m, size_t n)
m × n Toeplitz matrix from its diagonal entries
bool is_invertible() const
Check if matrix is invertible.
Definition matrices.hpp:443
Matrix(const std::string &filename)
Read from a PPM (P3) image file using the 64-entry colormap of export_as_ppm.
Vector< T > to_vector() const
Flatten in row-major order.
Matrix< T > basis_of_kernel() const
Alias for basis_of_nullspace.
Definition matrices.hpp:490
Matrix< T > & add_scaled_column(const T &s, size_t i, size_t j)
col[j] ← col[j] + s · col[i]
const T & operator()(size_t i, size_t j) const
Access component (i, j) (read-only).
Matrix & operator-=(const Matrix &rhs)
Component-wise subtraction this(i, j) -= rhs(i, j).
constexpr size_t get_m() const noexcept
Get the number of rows.
Definition matrices.hpp:377
constexpr Matrix operator-() const &
Unary − (lvalue): returns a new matrix with each component negated.
constexpr Matrix() noexcept=default
Default constructor: empty matrix.
constexpr bool is_zero() const
Check if the matrix is zero (no caching).
Definition matrices.hpp:414
Matrix< T > & reverse_columns()
Reverse the column order.
constexpr Matrix(const Matrix &other)
Definition matrices.hpp:223
void export_as_ppm(const std::string &filename) const
Export the matrix as a PPM (P3) image.
Matrix< T > & set_submatrix(size_t i, size_t j, const Matrix &N)
Overwrite the region starting at (i, j) with N.
Matrix & operator*=(const Matrix &rhs)
Matrix multiplication *this = *this · rhs.
Matrix< T > & horizontal_join(const Matrix &other)
Concatenate other to the right (column-wise).
constexpr size_t get_n() const noexcept
Get the number of columns.
Definition matrices.hpp:384
friend constexpr bool operator==(const Matrix< U > &lhs, const Matrix< U > &rhs)
constexpr Matrix(size_t m, size_t n, std::initializer_list< T > l)
m × n matrix from a flat initializer list (row-major)
constexpr Matrix operator-() &&
Unary − (rvalue): negates components in place.
Vector< T > get_col(size_t j) const
Extract column j as a (row) vector.
Matrix(const Vector< T > &v)
1 × n row matrix from a Vector<T>
constexpr Matrix(size_t m, size_t n)
m × n zero matrix (tag details::Zero)
Definition matrices.hpp:197
Matrix< T > & scale_row(const T &s, size_t i)
row[i] ← s · row[i]
friend constexpr Matrix< T > DiagonalMatrix(const Vector< T > &v)
Diagonal matrix with diagonal v (strongest tag of details::Zero, details::Identity,...
Matrix & operator/=(const T &s)
Divide every component by the scalar s.
friend constexpr Matrix< T > IdentityMatrix(size_t m)
m × m identity matrix I_m
constexpr Matrix< T > & transpose()
Transpose in place.
constexpr Matrix(Matrix &&other) noexcept
Definition matrices.hpp:231
Matrix< T > & delete_rows(const std::vector< size_t > &v)
Delete the rows whose indices appear in v.
constexpr Matrix(const Matrix< S > &other)
Cross-field conversion between two finite fields of the same characteristic.
Matrix< T > & add_scaled_row(const T &s, size_t i, size_t j)
row[j] ← row[j] + s · row[i]
Matrix< T > & add_row(size_t i, size_t j)
row[j] ← row[j] + row[i]
Definition matrices.hpp:735
constexpr Matrix operator+() &&noexcept
Unary + (rvalue): returns the rvalue itself.
Definition matrices.hpp:295
Matrix< T > & rref(size_t *rank_ptr=nullptr)
Reduced row echelon form (RREF).
Matrix< T > & reverse_rows()
Reverse the row order.
Matrix< T > & scale_column(const T &s, size_t i)
col[i] ← s · col[i]
Matrix< T > basis_of_nullspace() const
Basis of the nullspace (right kernel).
constexpr Matrix< T > & fill(const T &l)
Set every component to l.
Matrix & operator+=(const Matrix &rhs)
Component-wise addition this(i, j) += rhs(i, j).
Matrix< T > & Kronecker_product(const Matrix &other)
Kronecker (tensor) product with other.
Matrix< T > & swap_rows(size_t i, size_t j)
Swap rows i and j.
Vector< T > get_row(size_t i) const
Extract row i as a vector.
constexpr Vector< S > as_vector() const
Reinterpret the columns as elements of a superfield.
constexpr Matrix & operator*=(const T &s)
Multiply every component by the scalar s.
Matrix< T > & delete_column(size_t i)
Delete column i (single-index convenience for delete_columns).
Definition matrices.hpp:777
std::vector< Vector< T > > rowspace() const
Enumerate every vector in the row space.
Matrix< T > & add_column(size_t i, size_t j)
col[j] ← col[j] + col[i]
Definition matrices.hpp:750
Matrix< T > & vertical_join(const Matrix &other)
Concatenate other below (row-wise).
Matrix< T > & invert()
Invert in place.
Matrix< T > & delete_columns(const std::vector< size_t > &v)
Delete the columns whose indices appear in v.
std::vector< T > eigenvalues() const
Eigenvalues lying in the underlying finite field.
std::vector< Vector< T > > span() const
Alias for rowspace.
Definition matrices.hpp:536
Matrix< T > & delete_row(size_t i)
Delete row i (single-index convenience for delete_rows).
Definition matrices.hpp:797
Matrix< T > & ref(size_t *rank_ptr=nullptr)
Row echelon form (REF), with a binary-field fast path.
Univariate polynomial p(x) = a₀ + a₁x + … + aₙxⁿ over a CECCO::CoefficientType.
Vector v = (v₀, v₁, …, vₙ₋₁) over a CECCO::ComponentType.
Definition vectors.hpp:124
Contains implementation details not to be exposed to the user. Functions and classes here may change ...
Definition blocks.hpp:59
matrix_type_t
Matrix structural type tag for optimization (internal).
Definition matrices.hpp:88
@ Generic
Generic matrix with arbitrary elements.
Definition matrices.hpp:92
@ Identity
Identity matrix with ones on diagonal and zeros elsewhere.
Definition matrices.hpp:107
@ Toeplitz
Toeplitz matrix T_{i,j} = t_{i-j} with constant diagonals.
Definition matrices.hpp:117
@ Zero
Zero matrix with all elements equal to zero.
Definition matrices.hpp:97
@ Vandermonde
Vandermonde matrix with arithmetic progressions (of pairwise distinct elements) in columns.
Definition matrices.hpp:112
@ Diagonal
Diagonal matrix (square) with non-zero elements only on the main diagonal.
Definition matrices.hpp:102
Provides a framework for error correcting codes.
Definition blocks.hpp:57
constexpr Matrix< T > ToeplitzMatrix(const Vector< T > &v, size_t m, size_t n)
m × n Toeplitz matrix from its diagonal entries
constexpr Matrix< T > VandermondeMatrix(const Vector< T > &v, size_t m)
Vandermonde matrix V_{i, j} = v[j]^i.
constexpr Matrix< T > DiagonalMatrix(const Vector< T > &v)
Diagonal matrix with diagonal v (strongest tag of details::Zero, details::Identity,...
constexpr Matrix< T > IdentityMatrix(size_t m)
m × m identity matrix I_m
constexpr Matrix< T > ZeroMatrix(size_t m, size_t n)
m × n matrix of zeros (tag details::Zero)