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.2.9
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 <ComponentType 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 <ComponentType 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` and structure is not re-detected.
152 * Dimension mismatches in arithmetic throw `std::invalid_argument`.
153 *
154 * Methods that need division (REF/RREF, inversion, nullspace, determinant, characteristic
155 * polynomial, …) are gated by `requires FieldType<T>`; eigenvalue computation by
156 * `requires FiniteFieldType<T>`. Methods that compare against zero (Hamming weight, rank,
157 * structural tests, …) are gated by `requires ReliablyComparableType<T>`.
158 *
159 * Cross-field constructors and assignment operators between two finite fields of the same
160 * characteristic route through @ref CECCO::details::largest_common_subfield_t, so matrices
161 * over fields from disjoint construction towers can interoperate.
162 *
163 * @section Usage_Example
164 *
165 * @code{.cpp}
166 * using F4 = Ext<Fp<2>, MOD{1, 1, 1}>;
167 * Matrix<F4> M = {{1, 0}, {1, 1}};
168 * size_t r = M.rank(); // cached
169 * auto null_basis = M.basis_of_nullspace();
170 * auto chi = M.characteristic_polynomial();
171 * @endcode
172 */
173template <ComponentType T>
174class Matrix {
175 friend constexpr Matrix<T> IdentityMatrix<>(size_t m);
176 friend constexpr Matrix<T> DiagonalMatrix<>(const Vector<T>& v);
177 friend constexpr Matrix<T> ToeplitzMatrix<>(const Vector<T>& v, size_t m, size_t n);
178 friend constexpr Matrix<T> VandermondeMatrix<>(const Vector<T>& v, size_t m);
179 template <ReliablyComparableType U>
180 friend constexpr bool operator==(const Matrix<U>& lhs, const Matrix<U>& rhs);
181 friend std::ostream& operator<< <>(std::ostream& os, const Matrix& rhs);
182 template <ComponentType>
183 friend class Matrix;
184
185 public:
186 /** @name Constructors
187 * @{
188 */
189
190 /// @brief Default constructor: empty matrix
191 constexpr Matrix() noexcept = default;
192
193 /// @brief m × n zero matrix (tag @ref details::Zero)
194 constexpr Matrix(size_t m, size_t n) : data(m * n), m(m), n(n), type(details::Zero) {}
195
196 /**
197 * @brief m × n matrix with every component equal to `l`
198 *
199 * Tag is @ref details::Zero if `l == T(0)`, otherwise @ref details::Generic.
200 */
201 Matrix(size_t m, size_t n, const T& l);
202
203 /**
204 * @brief m × n matrix from a flat initializer list (row-major)
205 *
206 * @throws std::invalid_argument if `l.size() != m * n`
207 */
208 constexpr Matrix(size_t m, size_t n, std::initializer_list<T> l);
209
210 /**
211 * @brief From nested initializer lists, e.g. `{{1, 2, 3}, {4, 5, 6}}`
212 *
213 * Rows of unequal length are zero-padded to the longest row.
214 */
215 Matrix(std::initializer_list<std::initializer_list<T>> l);
216
217 /// @brief 1 × n row matrix from a `Vector<T>`
218 Matrix(const Vector<T>& v);
219
220 constexpr Matrix(const Matrix& other)
221 : data(other.data),
222 m(other.m),
223 n(other.n),
224 transposed(other.transposed),
225 type(other.type),
226 cache(other.cache) {}
227
228 constexpr Matrix(Matrix&& other) noexcept
229 : data(std::move(other.data)),
230 m(other.m),
231 n(other.n),
232 transposed(other.transposed),
233 type(other.type),
234 cache(std::move(other.cache)) {}
235
236 /**
237 * @brief Cross-field conversion between two finite fields of the same characteristic
238 *
239 * @tparam S Source field type (`Matrix<S>`); must share characteristic with T
240 *
241 * Converts component by component via T's cross-field constructor, which routes through
242 * @ref CECCO::details::largest_common_subfield_t and so handles disjoint construction towers.
243 * Propagates `std::invalid_argument` if any component is not representable in T.
244 */
245 template <FiniteFieldType S>
246 constexpr Matrix(const Matrix<S>& other)
248
249 /**
250 * @brief Read from a PPM (P3) image file using the 64-entry colormap of @ref export_as_ppm
251 *
252 * Pixels whose RGB does not match any colormap entry are erased (under
253 * @ref CECCO_ERASURE_SUPPORT) or replaced by a random field element.
254 *
255 * @note Convert other formats with ImageMagick:
256 * @code{.sh}
257 * magick input.png -alpha remove -background black +dither -remap palette.ppm -compress none output.ppm
258 * @endcode
259 */
260 Matrix(const std::string& filename)
261 requires FiniteFieldType<T> && (T::get_size() <= 64);
262
263 /** @} */
264
265 /** @name Assignment Operators
266 * @{
267 */
268
269 constexpr Matrix& operator=(const Matrix& rhs);
270 constexpr Matrix& operator=(Matrix&& rhs) noexcept;
271
272 /// @brief Cross-field assignment (same semantics as the cross-field constructor)
273 template <FiniteFieldType S>
275 constexpr Matrix& operator=(const Matrix<S>& other);
276
277 /** @} */
278
279 /** @name Unary Arithmetic Operations
280 * @{
281 */
282
283 /// @brief Unary `+` (lvalue): returns a copy
284 constexpr Matrix operator+() const& { return *this; }
285 /// @brief Unary `+` (rvalue): returns the rvalue itself
286 constexpr Matrix operator+() && noexcept { return std::move(*this); }
287
288 /// @brief Unary `−` (lvalue): returns a new matrix with each component negated
289 constexpr Matrix operator-() const&;
290 /// @brief Unary `−` (rvalue): negates components in place
291 constexpr Matrix operator-() &&;
292
293 /** @} */
294
295 /** @name Compound Assignment Operations
296 * @{
297 */
298
299 /**
300 * @brief Component-wise addition `this(i, j) += rhs(i, j)`
301 *
302 * @throws std::invalid_argument if dimensions differ
303 */
304 Matrix& operator+=(const Matrix& rhs);
305
306 /**
307 * @brief Component-wise subtraction `this(i, j) -= rhs(i, j)`
308 *
309 * @throws std::invalid_argument if dimensions differ
310 */
311 Matrix& operator-=(const Matrix& rhs);
312
313 /**
314 * @brief Matrix multiplication `*this = *this · rhs`
315 *
316 * @throws std::invalid_argument if `this->get_n() != rhs.get_m()`
317 */
318 Matrix& operator*=(const Matrix& rhs);
319
320 /// @brief Multiply every component by the scalar `s`
321 constexpr Matrix& operator*=(const T& s);
322
323 /**
324 * @brief Divide every component by the scalar `s`
325 *
326 * @throws std::invalid_argument if `s == T(0)`
327 * @note Round-trip `(M / s) * s == M` is only guaranteed when T satisfies @ref CECCO::FieldType
328 * (otherwise integer rounding may corrupt components).
329 */
330 Matrix& operator/=(const T& s);
331
332 /** @} */
333
334 /** @name Randomization
335 * @{
336 */
337
338 /**
339 * @brief Fill matrix with random values
340 *
341 * @return Reference to this matrix after randomization
342 *
343 * Distribution depends on the component type: finite-field types draw uniformly from the
344 * field; signed integers from [−100, 100]; `double` and the real/imaginary parts of
345 * `std::complex<double>` from [−1.0, 1.0].
346 *
347 * @note Tag becomes @ref details::Generic — incidental structure (e.g. an accidental zero
348 * matrix) is not re-detected.
349 */
351
352 /** @} */
353
354 /** @name Information and Properties
355 * @{
356 */
357
358 /**
359 * @brief Get the number of rows
360 *
361 * @return Number of rows in the matrix
362 */
363 constexpr size_t get_m() const noexcept { return m; }
364
365 /**
366 * @brief Get the number of columns
367 *
368 * @return Number of columns in the matrix
369 */
370 constexpr size_t get_n() const noexcept { return n; }
371
372 /**
373 * @brief Check if matrix is empty
374 *
375 * @return true if matrix has zero rows or columns, false otherwise
376 */
377 constexpr bool is_empty() const noexcept { return m == 0 || n == 0; }
378
379 /**
380 * @brief Check if the matrix is zero, caching the result via the type tag
381 *
382 * @return true iff every component equals T(0)
383 *
384 * On a positive result the tag is updated to @ref details::Zero, so subsequent calls and
385 * other tag-aware fast paths short-circuit.
386 */
387 constexpr bool is_zero() {
388 if (type == details::Zero) return true;
389 const bool b = std::ranges::all_of(data, [](const T& v) { return v == T(0); });
390 if (b) type = details::Zero;
391 return b;
392 }
393
394 /**
395 * @brief Check if the matrix is zero (no caching)
396 *
397 * @return true iff every component equals T(0)
398 */
399 constexpr bool is_zero() const {
400 if (type == details::Zero) return true;
401 return std::ranges::all_of(data, [](const T& v) { return v == T(0); });
402 }
403
404 /// @brief Hamming weight: number of non-zero, non-erased components; cached on first call
405 size_t wH() const
407 {
408 return cache.template get_or_compute<Weight>([this] { return calculate_weight(); });
409 }
410
411 /**
412 * @brief Matrix rank, computed once and cached
413 *
414 * @return Dimension of the row space (equivalently, of the column space)
415 *
416 * Uses row reduction to echelon form. Subsequent calls return the cached value until a
417 * mutating operation invalidates it.
418 */
419 size_t rank() const
421
422 /**
423 * @brief Check if matrix is invertible
424 *
425 * @return true iff the matrix is square and has full rank
426 */
427 bool is_invertible() const
429 {
430 return m == n && rank() == m;
431 }
432
433 /**
434 * @brief Main diagonal as a vector
435 *
436 * @return Vector containing entries (i, i) for i = 0, …, m−1
437 *
438 * @throws std::invalid_argument if the matrix is not square
439 */
440 Vector<T> diagonal() const;
441
442 /**
443 * @brief Characteristic polynomial det(λI − A)
444 *
445 * @return Polynomial of degree m
446 *
447 * Computed via the Samuelson–Berkowitz algorithm in the general case; the
448 * @ref details::Diagonal and @ref details::Vandermonde tags trigger closed-form shortcuts.
449 *
450 * @throws std::invalid_argument if the matrix is not square or is empty
451 */
454
455 /**
456 * @brief Basis of the nullspace (right kernel)
457 *
458 * @return Matrix whose rows span { x : A xᵀ = 0 }; empty matrix if the nullspace is trivial
459 *
460 * Computed by row reduction.
461 */
464
465 /**
466 * @brief Alias for @ref basis_of_nullspace
467 *
468 * @return Matrix whose rows form a basis of the kernel
469 */
472 {
473 return basis_of_nullspace();
474 }
475
476 /**
477 * @brief Matrix determinant
478 *
479 * @return det(A); T(0) for singular matrices
480 *
481 * Algorithm depends on the type tag: closed-form for @ref details::Identity,
482 * @ref details::Zero, and @ref details::Diagonal; Samuelson–Berkowitz otherwise.
483 *
484 * @throws std::invalid_argument if the matrix is not square or is empty
485 */
488
489 /**
490 * @brief Eigenvalues lying in the underlying finite field
491 *
492 * @return Roots of the characteristic polynomial that lie in T
493 *
494 * Eigenvalues that exist only in an extension of T are omitted.
495 *
496 * @throws std::invalid_argument if the matrix is not square
497 */
500
501 /**
502 * @brief Enumerate every vector in the row space
503 *
504 * @return All q^rank vectors in span(rows), where q = |T|
505 *
506 * @warning Size grows as q^rank — only practical for small fields and small rank.
507 */
510
511 /**
512 * @brief Alias for @ref rowspace
513 *
514 * @return Every vector in the span of the rows
515 */
516 std::vector<Vector<T>> span() const
518 {
519 return rowspace();
520 }
521
522 /** @} */
523
524 /** @name Component Access and Manipulation
525 * @{
526 */
527
528 /**
529 * @brief Set component (i, j) by perfect forwarding
530 *
531 * @param i Row index (0-based)
532 * @param j Column index (0-based)
533 * @param c Value to assign; bound by lvalue or rvalue reference
534 * @return Reference to this matrix after modification
535 *
536 * @throws std::invalid_argument if either index is out of bounds
537 */
538 template <typename U>
541
542 /**
543 * @brief Access component (i, j) (read-only)
544 *
545 * @param i Row index (0-based)
546 * @param j Column index (0-based)
547 * @return Const reference to the component at (i, j)
548 *
549 * @throws std::invalid_argument if either index is out of bounds
550 */
551 const T& operator()(size_t i, size_t j) const;
552
553 /**
554 * @brief Extract row i as a vector
555 *
556 * @param i Row index
557 * @return Vector containing the components of row i
558 *
559 * @throws std::invalid_argument if i is out of bounds
560 */
561 Vector<T> get_row(size_t i) const;
562
563 /**
564 * @brief Extract column j as a (row) vector
565 *
566 * @param j Column index
567 * @return Vector containing the components of column j; transposed, since @ref Vector models row vectors
568 *
569 * @throws std::invalid_argument if j is out of bounds
570 */
571 Vector<T> get_col(size_t j) const;
572
573 /**
574 * @brief Extract submatrix from region [i, i+h) × [j, j+w)
575 *
576 * @param i Starting row index
577 * @param j Starting column index
578 * @param h Height (number of rows)
579 * @param w Width (number of columns)
580 * @return Submatrix of shape h × w
581 *
582 * @throws std::invalid_argument if the region extends beyond the matrix
583 */
584 Matrix<T> get_submatrix(size_t i, size_t j, size_t h, size_t w) const;
585
586 /**
587 * @brief Overwrite the region starting at (i, j) with N
588 *
589 * @param i Starting row index
590 * @param j Starting column index
591 * @param N Source matrix; its shape must fit within this matrix from (i, j)
592 * @return Reference to this matrix after the assignment
593 *
594 * @throws std::invalid_argument if the region would extend beyond bounds
595 */
596 Matrix<T>& set_submatrix(size_t i, size_t j, const Matrix& N);
597
598 /**
599 * @brief Concatenate other to the right (column-wise)
600 *
601 * @param other Matrix to append on the right; must have the same number of rows as this
602 * @return Reference to this matrix after the join
603 *
604 * @throws std::invalid_argument if row counts differ
605 */
606 Matrix<T>& horizontal_join(const Matrix& other);
607
608 /**
609 * @brief Concatenate other below (row-wise)
610 *
611 * @param other Matrix to append below; must have the same number of columns as this
612 * @return Reference to this matrix after the join
613 *
614 * @throws std::invalid_argument if column counts differ
615 */
616 Matrix<T>& vertical_join(const Matrix& other);
617
618 /**
619 * @brief Block-diagonal join: this in the upper-left, other in the lower-right
620 *
621 * @param other Matrix placed in the lower-right block
622 * @return Reference to this matrix after the join
623 *
624 * Off-diagonal blocks are filled with zeros.
625 */
626 Matrix<T>& diagonal_join(const Matrix& other);
627
628 /**
629 * @brief Kronecker (tensor) product with other
630 *
631 * @param other Right operand
632 * @return Reference to this matrix after the product
633 *
634 * If this is m × n and other is p × q, the result is mp × nq.
635 */
636 Matrix<T>& Kronecker_product(const Matrix& other);
637
638 /**
639 * @brief Swap rows i and j
640 *
641 * @param i First row index
642 * @param j Second row index
643 * @return Reference to this matrix after the swap
644 *
645 * @throws std::invalid_argument if either index is out of bounds
646 */
647 Matrix<T>& swap_rows(size_t i, size_t j);
648
649 /**
650 * @brief Swap columns i and j
651 *
652 * @param i First column index
653 * @param j Second column index
654 * @return Reference to this matrix after the swap
655 *
656 * @throws std::invalid_argument if either index is out of bounds
657 */
658 Matrix<T>& swap_columns(size_t i, size_t j);
659
660 /**
661 * @brief row[i] ← s · row[i]
662 *
663 * @param s Scalar multiplier
664 * @param i Row index
665 * @return Reference to this matrix after the scaling
666 *
667 * @throws std::invalid_argument if i is out of bounds
668 */
669 Matrix<T>& scale_row(const T& s, size_t i);
670
671 /**
672 * @brief col[i] ← s · col[i]
673 *
674 * @param s Scalar multiplier
675 * @param i Column index
676 * @return Reference to this matrix after the scaling
677 *
678 * @throws std::invalid_argument if i is out of bounds
679 */
680 Matrix<T>& scale_column(const T& s, size_t i);
681
682 /**
683 * @brief row[j] ← row[j] + s · row[i]
684 *
685 * @param s Scalar multiplier on the source row
686 * @param i Source row index
687 * @param j Destination row index
688 * @return Reference to this matrix after the update
689 *
690 * @throws std::invalid_argument if either index is out of bounds
691 */
692 Matrix<T>& add_scaled_row(const T& s, size_t i, size_t j);
693
694 /**
695 * @brief col[j] ← col[j] + s · col[i]
696 *
697 * @param s Scalar multiplier on the source column
698 * @param i Source column index
699 * @param j Destination column index
700 * @return Reference to this matrix after the update
701 *
702 * @throws std::invalid_argument if either index is out of bounds
703 */
704 Matrix<T>& add_scaled_column(const T& s, size_t i, size_t j);
705
706 /**
707 * @brief row[j] ← row[j] + row[i]
708 *
709 * @param i Source row index
710 * @param j Destination row index
711 * @return Reference to this matrix after the update
712 *
713 * @throws std::invalid_argument if either index is out of bounds
714 */
715 Matrix<T>& add_row(size_t i, size_t j) {
716 if (i >= m || j >= m)
717 throw std::invalid_argument("trying to add row to other row, at least one of them is non-existent");
718 return add_scaled_row(T(1), i, j);
719 }
720
721 /**
722 * @brief col[j] ← col[j] + col[i]
723 *
724 * @param i Source column index
725 * @param j Destination column index
726 * @return Reference to this matrix after the update
727 *
728 * @throws std::invalid_argument if either index is out of bounds
729 */
730 Matrix<T>& add_column(size_t i, size_t j) {
731 if (i >= n || j >= n)
732 throw std::invalid_argument("trying to add column to other column, at least one of them is non-existent");
734 add_scaled_row(T(1), i, j);
736 return *this;
737 }
738
739 /**
740 * @brief Delete the columns whose indices appear in v
741 *
742 * @param v Column indices (deduplicated internally)
743 * @return Reference to this matrix after deletion
744 *
745 * @throws std::invalid_argument if any index in v is out of bounds
746 */
747 Matrix<T>& delete_columns(const std::vector<size_t>& v);
748
749 /**
750 * @brief Delete column i (single-index convenience for @ref delete_columns)
751 *
752 * @param i Column index
753 * @return Reference to this matrix after deletion
754 *
755 * @throws std::invalid_argument if i is out of bounds
756 */
757 Matrix<T>& delete_column(size_t i) { return delete_columns({i}); }
758
759 /**
760 * @brief Delete the rows whose indices appear in v
761 *
762 * @param v Row 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_rows(const std::vector<size_t>& v);
768
769 /**
770 * @brief Delete row i (single-index convenience for @ref delete_rows)
771 *
772 * @param i Row 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_row(size_t i) { return delete_rows({i}); }
778
779#ifdef CECCO_ERASURE_SUPPORT
780
781 /**
782 * @brief Flag component (i, j) as erased
783 *
784 * @param i Row index
785 * @param j Column index
786 * @return Reference to this matrix after erasing
787 *
788 * Marks the component as erased; field arithmetic on erased elements is undefined — see
789 * @ref CECCO_ERASURE_SUPPORT and the erase()/unerase() interface in fields.hpp.
790 *
791 * @warning An erased element can no longer participate in field operations or property
792 * queries. Correct handling of erased elements is the caller's responsibility.
793 *
794 * @throws std::invalid_argument if (i, j) is out of bounds
795 */
798
799 /**
800 * @brief Clear the erasure flag on component (i, j)
801 *
802 * @param i Row index
803 * @param j Column index
804 * @return Reference to this matrix after un-erasing
805 *
806 * @throws std::invalid_argument if (i, j) is out of bounds
807 */
810
811 /**
812 * @brief Flag every component of the specified columns as erased
813 *
814 * @param v Column indices (deduplicated internally)
815 * @return Reference to this matrix after erasing
816 *
817 * @warning See @ref erase_component for the semantics and caller obligations.
818 *
819 * @throws std::invalid_argument if any index in v is out of bounds
820 */
823
824 /**
825 * @brief Erase column i (single-index convenience for @ref erase_columns)
826 *
827 * @param i Column index
828 * @return Reference to this matrix after erasing
829 *
830 * @throws std::invalid_argument if i is out of bounds
831 */
834 {
835 return erase_columns({i});
836 }
837
838 /**
839 * @brief Clear erasure flags on every component of the specified columns
840 *
841 * @param v Column indices (deduplicated internally)
842 * @return Reference to this matrix after un-erasing
843 *
844 * @throws std::invalid_argument if any index in v is out of bounds
845 */
848
849 /**
850 * @brief Un-erase column i (single-index convenience for @ref unerase_columns)
851 *
852 * @param i Column index
853 * @return Reference to this matrix after un-erasing
854 *
855 * @throws std::invalid_argument if i is out of bounds
856 */
859 {
860 return unerase_columns({i});
861 }
862
863 /**
864 * @brief Flag every component of the specified rows as erased
865 *
866 * @param v Row indices (deduplicated internally)
867 * @return Reference to this matrix after erasing
868 *
869 * @warning See @ref erase_component for the semantics and caller obligations.
870 *
871 * @throws std::invalid_argument if any index in v is out of bounds
872 */
875
876 /**
877 * @brief Erase row i (single-index convenience for @ref erase_rows)
878 *
879 * @param i Row index
880 * @return Reference to this matrix after erasing
881 *
882 * @throws std::invalid_argument if i is out of bounds
883 */
886 {
887 return erase_rows({i});
888 }
889
890 /**
891 * @brief Clear erasure flags on every component of the specified rows
892 *
893 * @param v Row indices (deduplicated internally)
894 * @return Reference to this matrix after un-erasing
895 *
896 * @throws std::invalid_argument if any index in v is out of bounds
897 */
900
901 /**
902 * @brief Un-erase row i (single-index convenience for @ref unerase_rows)
903 *
904 * @param i Row index
905 * @return Reference to this matrix after un-erasing
906 *
907 * @throws std::invalid_argument if i is out of bounds
908 */
911 {
912 return unerase_rows({i});
913 }
914
915#endif
916
917 /** @} */
918
919 /** @name Transformations
920 * @{
921 */
922
923 /**
924 * @brief Reverse the row order
925 *
926 * @return Reference to this matrix after the reversal
927 */
929
930 /**
931 * @brief Reverse the column order
932 *
933 * @return Reference to this matrix after the reversal
934 */
936
937 /**
938 * @brief Set every component to s
939 *
940 * @param s Value assigned to every component
941 * @return Reference to this matrix after filling
942 *
943 * @note Tag becomes @ref details::Zero if s == T(0), otherwise @ref details::Generic.
944 */
945 constexpr Matrix<T>& fill(const T& s);
946
947 /**
948 * @brief Transpose in place
949 *
950 * @return Reference to this matrix after transposition
951 *
952 * For an m × n matrix the result is n × m, with component (i, j) moved to (j, i).
953 */
954 constexpr Matrix<T>& transpose();
955
956 /**
957 * @brief Row echelon form (REF), with a binary-field fast path
958 *
959 * @param rank Optional out-parameter; if non-null, receives the rank
960 * @return Reference to this matrix after the reduction
961 *
962 * Forward Gaussian elimination only — cheaper than RREF when only the rank or a
963 * triangularised form is needed. For Fp<2>, pivot scaling is skipped at compile time. Rank
964 * is cached when @p rank is non-null.
965 */
966 Matrix<T>& ref(size_t* rank = nullptr)
968
969 /**
970 * @brief Reduced row echelon form (RREF)
971 *
972 * @param rank Optional out-parameter; if non-null, receives the rank
973 * @return Reference to this matrix after the reduction
974 *
975 * Two phases: forward elimination (REF) followed by backward elimination. Rank is always
976 * cached.
977 */
978 Matrix<T>& rref(size_t* rank = nullptr)
980
981 /**
982 * @brief Invert in place
983 *
984 * @return Reference to this matrix after inversion
985 *
986 * Gaussian elimination with partial pivoting. Requires a square non-singular matrix.
987 *
988 * @throws std::invalid_argument if the matrix is not square or is singular
989 */
992
993 /** @} */
994
995 /** @name Finite Field Specific Operations
996 * @{
997 */
998
999 /**
1000 * @brief Reinterpret the columns as elements of a superfield
1001 *
1002 * @tparam S Superfield of T (same construction tower)
1003 * @return Vector over S, one component per column
1004 *
1005 * Each column is read as the coordinate representation of an element of S over T.
1006 */
1007 template <FiniteFieldType S>
1008 constexpr Vector<S> as_vector() const
1010
1011 /**
1012 * @brief Flatten in row-major order
1013 *
1014 * @return Vector of length m·n; component (i, j) maps to index i·n + j
1015 */
1016 Vector<T> to_vector() const;
1017
1018 /**
1019 * @brief Export the matrix as a PPM (P3) image
1020 *
1021 * @param filename Output path
1022 *
1023 * Each component is mapped through a built-in 64-entry colormap (black → blue → green →
1024 * yellow → white). The image is n wide and m tall.
1025 *
1026 * @note With @ref CECCO_ERASURE_SUPPORT, erased components render in red.
1027 */
1028 void export_as_ppm(const std::string& filename) const
1029 requires FiniteFieldType<T> && (T::get_size() <= 64);
1030
1031 /** @} */
1032
1033 private:
1034 /// @brief Component storage, row-major
1035 std::vector<T> data;
1036
1037 /// @brief Number of rows
1038 size_t m = 0;
1039 /// @brief Number of columns
1040 size_t n = 0;
1041
1042 /// @brief When true, accesses interpret storage as transposed (no data movement)
1043 bool transposed = false;
1044
1045 /// @brief Structural tag — see @ref details::matrix_type_t
1047
1048 /// @brief Cache for matrix rank and Hamming weight (invalidated by mutating operations)
1049 enum CacheIds { Rank = 0, Weight = 1 };
1051
1052 size_t calculate_weight() const
1054
1055 /**
1056 * @brief Matrix multiplication kernel with compile-time transpose dispatch
1057 * @tparam this_transposed Compile-time flag: true if left matrix (*this) is transposed
1058 * @tparam rhs_transposed Compile-time flag: true if right matrix (rhs) is transposed
1059 * @param this_data Raw pointer to left matrix data
1060 * @param rhs_data Raw pointer to right matrix data
1061 * @param res_data Raw pointer to result matrix data
1062 * @param M Number of rows in result
1063 * @param K Inner dimension (cols of this, rows of rhs)
1064 * @param N Number of columns in result
1065 * @param BS Block size for cache tiling
1066 */
1067 template <bool this_transposed, bool rhs_transposed>
1068 static void multiply_kernel(const T* this_data, const T* rhs_data, T* res_data, size_t M, size_t K, size_t N,
1069 size_t BS) {
1070 for (size_t ii = 0; ii < M; ii += BS) {
1071 const size_t imax = std::min(ii + BS, M);
1072 for (size_t kk = 0; kk < K; kk += BS) {
1073 const size_t kmax = std::min(kk + BS, K);
1074 for (size_t jj = 0; jj < N; jj += BS) {
1075 const size_t jmax = std::min(jj + BS, N);
1076 for (size_t i = ii; i < imax; ++i) {
1077 for (size_t k = kk; k < kmax; ++k) {
1078 // Compile-time dispatch for optimal addressing
1079 const size_t this_idx = this_transposed ? (i + k * M) : (i * K + k);
1080 const T aik = this_data[this_idx];
1081 const size_t res_row_offset = i * N;
1082 for (size_t j = jj; j < jmax; ++j) {
1083 // Compile-time dispatch for optimal addressing
1084 const size_t rhs_idx = rhs_transposed ? (k + j * K) : (k * N + j);
1085 res_data[res_row_offset + j] += aik * rhs_data[rhs_idx];
1086 }
1087 }
1088 }
1089 }
1090 }
1091 }
1092 }
1093
1094 /**
1095 * @brief Elimination helper for a single row operation
1096 * @tparam transposed True if matrix is transposed, false otherwise
1097 * @param data Raw matrix data
1098 * @param m Number of rows
1099 * @param n Number of columns
1100 * @param target_row Row to be eliminated
1101 * @param pivot_row Row containing the pivot
1102 * @param start_col Starting column for elimination
1103 * @param f Scaling factor
1104 */
1105 template <bool transposed>
1106 static void eliminate_row_kernel(T* data, size_t m, size_t n, size_t target_row, size_t pivot_row,
1107 size_t start_col, const T& f) {
1108 if constexpr (!transposed) {
1109 // Row-major access
1110 T* target_data = data + target_row * n;
1111 const T* pivot_data = data + pivot_row * n;
1112 for (size_t j = start_col; j < n; ++j) {
1113 target_data[j] -= f * pivot_data[j];
1114 }
1115 } else {
1116 // Column-major access
1117 for (size_t j = start_col; j < n; ++j) {
1118 data[target_row + j * m] -= f * data[pivot_row + j * m];
1119 }
1120 }
1121 }
1122
1123 /**
1124 * @brief REF elimination kernel with compile-time transpose dispatch
1125 * @tparam transposed True if matrix is transposed, false otherwise
1126 * @param data Raw matrix data
1127 * @param m Number of rows
1128 * @param n Number of columns
1129 * @param h Current pivot row
1130 * @param k Current pivot column
1131 * @return Number of rows processed (new h value)
1132 */
1133 template <bool transposed>
1134 static size_t ref_elimination_kernel(T* data, size_t m, size_t n, size_t h, size_t k) {
1135 while (h < m && k < n) {
1136 // find pivot
1137 size_t p = m; // Default: no pivot found
1138
1139 for (size_t row = h; row < m; ++row) {
1140 const size_t idx = transposed ? (row + k * m) : (row * n + k);
1141 if (data[idx] != T(0)) {
1142 p = row;
1143 break;
1144 }
1145 }
1146
1147 if (p == m) { // no pivot in column -> proceed to next column
1148 ++k;
1149 } else {
1150 // this->swap_rows(h, p);
1151 if constexpr (!transposed)
1152 std::swap_ranges(data + h * n, data + (h + 1) * n, data + p * n);
1153 else
1154 for (size_t j = 0; j < n; ++j) std::swap(data[h + j * m], data[p + j * m]);
1155
1156 const size_t pivot_idx = transposed ? (h + k * m) : (h * n + k);
1157 const T pivot = data[pivot_idx];
1158
1159 // this->scale_row(T(1) / pivot, h);
1160 const T inv = T(1) / pivot;
1161 if constexpr (!transposed) {
1162 for (size_t j = k; j < n; ++j) data[h * n + j] *= inv;
1163 } else {
1164 for (size_t j = k; j < n; ++j) data[h + j * m] *= inv;
1165 }
1166
1167 // Forward elimination only - eliminate entries BELOW pivot
1168 for (size_t i = h + 1; i < m; ++i) {
1169 const size_t f_idx = transposed ? (i + k * m) : (i * n + k);
1170 const T f = data[f_idx];
1171
1172 if (f != T(0)) eliminate_row_kernel<transposed>(data, m, n, i, h, k, f);
1173 }
1174
1175 ++h;
1176 ++k;
1177 }
1178 }
1179 return h;
1180 }
1181
1182 /**
1183 * @brief RREF backward elimination kernel with compile-time transpose dispatch
1184 * @tparam transposed True if matrix is transposed, false otherwise
1185 * @param data Raw matrix data
1186 * @param m Number of rows
1187 * @param n Number of columns
1188 * @param r Matrix rank (number of pivots to process)
1189 */
1190 template <bool transposed>
1191 void rref_backward_elimination_kernel(T* data, size_t m, size_t n, size_t r) {
1192 for (size_t i = 0; i < r; ++i) {
1193 const size_t pivot_row = r - 1 - i; // Process from last to first
1194
1195 // find pivot
1196 size_t pivot_col = 0;
1197 while (pivot_col < n) {
1198 const size_t pivot_idx = transposed ? (pivot_row + pivot_col * m) : (pivot_row * n + pivot_col);
1199 if (data[pivot_idx] != T(0)) {
1200 break;
1201 }
1202 ++pivot_col;
1203 }
1204
1205 if (pivot_col < n) {
1206 // Backward elimination only - eliminate entries ABOVE pivot
1207 for (size_t row = 0; row < pivot_row; ++row) {
1208 const size_t f_idx = transposed ? (row + pivot_col * m) : (row * n + pivot_col);
1209 const T f = data[f_idx];
1210
1211 if (f != T(0)) eliminate_row_kernel<transposed>(data, m, n, row, pivot_row, pivot_col, f);
1212 }
1213 }
1214 }
1215 }
1216
1217 /**
1218 * @brief Calculate matrix rank (private implementation for caching)
1219 */
1220 size_t calculate_rank() const
1222};
1223
1224/* Matrix member function implementations */
1225
1226template <ComponentType T>
1227Matrix<T>::Matrix(size_t m, size_t n, const T& l)
1228 : data(m * n), m(m), n(n), type(l == T(0) ? details::Zero : details::Generic) {
1229 std::fill(data.begin(), data.end(), l);
1230 if (l != T(0))
1231 cache.template set<Rank>(1);
1232 else
1233 cache.template set<Rank>(0);
1234}
1235
1236template <ComponentType T>
1237constexpr Matrix<T>::Matrix(size_t m, size_t n, std::initializer_list<T> l)
1238 : data(l), m(m), n(n), type(details::Generic) {
1239 if (l.size() != m * n) {
1240 throw std::invalid_argument(
1241 "number of elements in initializer list does not correspond to number of rows and columns specified");
1242 }
1243}
1244
1245template <ComponentType T>
1246Matrix<T>::Matrix(std::initializer_list<std::initializer_list<T>> l) : m(l.size()), n(0), type(details::Generic) {
1247 if (m == 0) return;
1248 for (auto it = l.begin(); it != l.end(); ++it) {
1249 if (it->size() > n) n = it->size();
1250 }
1251 if (n == 0) return;
1252 data.resize(m * n);
1253
1254 size_t i = 0;
1255 for (const auto& row : l) {
1256 size_t j = 0;
1257 for (const auto& val : row) set_component(i, j++, val);
1258 ++i;
1259 }
1260}
1261
1262template <ComponentType T>
1263Matrix<T>::Matrix(const Vector<T>& v) : data(v.get_n()), m(1), n(v.get_n()), type(details::Toeplitz) {
1264 // Direct copy from vector data to matrix data for single row
1265 std::copy(v.data.begin(), v.data.end(), data.begin());
1266 cache.template set<Rank>(v.is_zero() ? 0 : 1);
1267}
1268
1269template <ComponentType T>
1270template <FiniteFieldType S>
1271constexpr Matrix<T>::Matrix(const Matrix<S>& other)
1273 : data(other.get_m() * other.get_n()),
1274 m(other.get_m()),
1275 n(other.get_n()),
1277 ? other.type
1278 : details::Generic) {
1279 if (!other.transposed) {
1281 [](const S& elem) { return T(elem); }); // Uses enhanced cross-field constructors
1282 } else {
1283 for (size_t i = 0; i < m; ++i)
1284 for (size_t j = 0; j < n; ++j) data[i * n + j] = T(other(i, j)); // Uses enhanced cross-field constructors
1285 }
1286 cache.invalidate();
1287}
1288
1289template <ComponentType T>
1290Matrix<T>::Matrix(const std::string& filename)
1291 requires FiniteFieldType<T> && (T::get_size() <= 64)
1292{
1294
1295 auto next_token = [&](std::string& tok) -> void {
1296 for (;;) {
1297 in >> tok;
1298 if (tok.empty()) continue;
1299 if (tok[0] == '#') {
1301 continue;
1302 }
1303 return;
1304 }
1305 };
1306
1307 std::string tok;
1308 next_token(tok); // "P3"
1309 next_token(tok);
1310 size_t w = std::stoul(tok);
1311 next_token(tok);
1312 size_t h = std::stoul(tok);
1313 next_token(tok); /* maxval */ // assumed 255
1314
1315 *this = Matrix(h, w);
1316
1317 // "RGB to a" map
1318 static const auto rgb_to_a = [] {
1320 m.reserve(64);
1321 for (uint8_t a = 0; a < 64; ++a) {
1322 const uint32_t key = (uint32_t(details::colormap[a][0]) << 16) | (uint32_t(details::colormap[a][1]) << 8) |
1323 uint32_t(details::colormap[a][2]);
1324 m.emplace(key, a);
1325 }
1326 return m;
1327 }();
1328
1329 auto label_from_a = [&](uint8_t a) -> size_t { return ((T::get_size() - 1) * (63 - static_cast<size_t>(a))) / 63; };
1330
1331 for (size_t i = 0; i < h; ++i) {
1332 for (size_t j = 0; j < w; ++j) {
1333 next_token(tok);
1334 uint8_t r = static_cast<uint8_t>(std::stoi(tok));
1335 next_token(tok);
1336 uint8_t g = static_cast<uint8_t>(std::stoi(tok));
1337 next_token(tok);
1338 uint8_t b = static_cast<uint8_t>(std::stoi(tok));
1339
1340 const uint32_t key = (uint32_t(r) << 16) | (uint32_t(g) << 8) | uint32_t(b);
1341
1342 const auto it = rgb_to_a.find(key);
1343 if (it != rgb_to_a.end()) {
1345 } else {
1346#ifdef CECCO_ERASURE_SUPPORT
1348#else
1349 set_component(i, j, T(0).randomize());
1350#endif
1351 }
1352 }
1353 }
1354}
1355
1356template <ComponentType T>
1357constexpr Matrix<T>& Matrix<T>::operator=(const Matrix& rhs) {
1358 if (this == &rhs) return *this;
1359 data = rhs.data;
1360 m = rhs.m;
1361 n = rhs.n;
1362 transposed = rhs.transposed;
1363 type = rhs.type;
1364 cache = rhs.cache;
1365 return *this;
1366}
1367
1368template <ComponentType T>
1369constexpr Matrix<T>& Matrix<T>::operator=(Matrix&& rhs) noexcept {
1370 data = std::move(rhs.data);
1371 m = rhs.m;
1372 n = rhs.n;
1373 transposed = rhs.transposed;
1374 type = rhs.type;
1375 cache = std::move(rhs.cache);
1376 return *this;
1377}
1378
1379template <ComponentType T>
1380template <FiniteFieldType S>
1382constexpr Matrix<T>& Matrix<T>::operator=(const Matrix<S>& other) {
1385 [&](const S& e) { return T(e); }); // Uses enhanced cross-field constructors
1386 m = other.get_m();
1387 n = other.get_n();
1389 type = other.type;
1390 cache.invalidate();
1391 return *this;
1392}
1393
1394template <ComponentType T>
1395constexpr Matrix<T> Matrix<T>::operator-() const& {
1396 auto res = *this;
1397 auto rank_backup = res.cache.template get<Rank>();
1398 if (type == details::Generic || type == details::Vandermonde || type == details::Toeplitz) {
1399 std::ranges::for_each(res.data, [](T& x) { x = -x; });
1400 if (type == details::Vandermonde) res.type = details::Generic;
1401
1402 } else if (type == details::Zero) {
1403 /* no-op */
1404 } else if (type == details::Diagonal || type == details::Identity) {
1405 for (size_t mu = 0; mu < m; ++mu) res.data[mu * n + mu] = -res.data[mu * n + mu];
1406 if constexpr (FiniteFieldType<T>) {
1407 if (T::get_characteristic() != 2 && type == details::Identity) res.type = details::Diagonal;
1408 } else {
1409 if (type == details::Identity) res.type = details::Diagonal;
1410 }
1411 }
1412 if (rank_backup) res.cache.template set<Rank>(*rank_backup);
1413 return res;
1414}
1415
1416template <ComponentType T>
1417constexpr Matrix<T> Matrix<T>::operator-() && {
1418 auto rank_backup = cache.template get<Rank>();
1419 if (type == details::Generic || type == details::Vandermonde || type == details::Toeplitz) {
1420 std::ranges::for_each(data, [](T& x) { x = -x; });
1421 if (type == details::Vandermonde) {
1422 type = details::Generic;
1423 }
1424 } else if (type == details::Zero) {
1425 /* no-op */
1426 } else if (type == details::Diagonal || type == details::Identity) {
1427 for (size_t mu = 0; mu < m; ++mu) data[mu * n + mu] = -data[mu * n + mu];
1428 if constexpr (FiniteFieldType<T>) {
1429 if (T::get_characteristic() != 2 && type == details::Identity) type = details::Diagonal;
1430 } else {
1431 if (type == details::Identity) type = details::Diagonal;
1432 }
1433 }
1434 if (rank_backup) cache.template set<Rank>(*rank_backup);
1435 return std::move(*this);
1436}
1437
1438template <ComponentType T>
1439Matrix<T>& Matrix<T>::operator+=(const Matrix& rhs) {
1440 if (m != rhs.m || n != rhs.n)
1441 throw std::invalid_argument(
1442 "trying to add two matrices of different "
1443 "dimensions");
1444 if (type == details::Zero) {
1445 *this = rhs;
1446 } else if (rhs.type == details::Zero) {
1447 /* no-op */
1448 } else if ((type == details::Diagonal && rhs.type == details::Diagonal) ||
1449 (type == details::Identity && rhs.type == details::Identity) ||
1450 (type == details::Diagonal && rhs.type == details::Identity) ||
1451 (type == details::Identity && rhs.type == details::Diagonal)) {
1452 for (size_t mu = 0; mu < m; ++mu) data[mu * n + mu] += rhs.data[mu * n + mu];
1453 if (type == details::Identity) type = details::Diagonal;
1454 } else {
1455 if (!transposed && !rhs.transposed) {
1456 std::transform(data.begin(), data.end(), rhs.data.begin(), data.begin(), std::plus<T>{});
1457 if (type != details::Generic && !(type == details::Toeplitz && rhs.type == details::Toeplitz))
1458 type = details::Generic;
1459 } else {
1460 for (size_t mu = 0; mu < m; ++mu)
1461 for (size_t nu = 0; nu < n; ++nu) set_component(mu, nu, (*this)(mu, nu) + rhs(mu, nu));
1462 }
1463 }
1464 if (std::all_of(data.cbegin(), data.cend(), [](const T& x) { return x == T(0); })) {
1465 this->type = details::Zero;
1466 }
1467 cache.invalidate();
1468 return *this;
1469}
1470
1471template <ComponentType T>
1472Matrix<T>& Matrix<T>::operator-=(const Matrix& rhs) {
1473 if (m != rhs.m || n != rhs.n)
1474 throw std::invalid_argument(
1475 "trying to subtract two matrices of different "
1476 "dimensions");
1477 operator+=(-rhs);
1478 cache.invalidate();
1479 return *this;
1480}
1481
1482template <ComponentType T>
1483Matrix<T>& Matrix<T>::operator*=(const Matrix& rhs) {
1484 if (n != rhs.m)
1485 throw std::invalid_argument(
1486 "trying to multiply two matrices "
1487 "with incompatible dimensions");
1488 if (type == details::Zero || rhs.type == details::Zero) {
1489 *this = Matrix(m, rhs.n);
1490 } else if (type == details::Identity) {
1491 *this = rhs;
1492 } else if (rhs.type == details::Identity) {
1493 /* no-op */
1494 } else if (type == details::Diagonal && rhs.type == details::Diagonal) {
1495 for (size_t mu = 0; mu < m; ++mu) data[mu * n + mu] *= rhs.data[mu * n + mu];
1496 } else if (type == details::Diagonal) {
1497 auto res = rhs;
1498 for (size_t mu = 0; mu < m; ++mu) {
1499 auto s = (*this)(mu, mu);
1500 for (size_t nu = 0; nu < rhs.n; ++nu) res.set_component(mu, nu, res(mu, nu) * s);
1501 }
1502 *this = std::move(res);
1503 } else if (rhs.type == details::Diagonal) {
1504 for (size_t nu = 0; nu < n; ++nu) {
1505 const auto& s = rhs(nu, nu);
1506 for (size_t mu = 0; mu < m; ++mu) set_component(mu, nu, (*this)(mu, nu) * s);
1507 }
1508 } else {
1509 const size_t M = get_m();
1510 const size_t K = get_n();
1511 const size_t N = rhs.get_n();
1512 Matrix<T> res(M, N, T(0));
1513
1514 const T* this_data = this->data.data();
1515 const T* rhs_data = rhs.data.data();
1516 T* res_data = res.data.data();
1517
1518 // Tune this block/tile size for architecture. 48, 64, 96, 128 are reasonable.
1519 constexpr size_t BS = 64;
1520
1521 // Branch ONCE on transpose flags - dispatch to optimized kernels with zero duplication
1522 if (!this->transposed && !rhs.transposed) {
1523 multiply_kernel<false, false>(this_data, rhs_data, res_data, M, K, N, BS);
1524 } else if (this->transposed && !rhs.transposed) {
1525 multiply_kernel<true, false>(this_data, rhs_data, res_data, M, K, N, BS);
1526 } else if (!this->transposed && rhs.transposed) {
1527 multiply_kernel<false, true>(this_data, rhs_data, res_data, M, K, N, BS);
1528 } else {
1529 multiply_kernel<true, true>(this_data, rhs_data, res_data, M, K, N, BS);
1530 }
1531 res.type = details::Generic;
1532 *this = std::move(res);
1533 }
1534 if (std::all_of(this->data.cbegin(), this->data.cend(), [](const T& elem) { return elem == T(0); })) {
1535 this->type = details::Zero;
1536 }
1537 cache.invalidate();
1538 return *this;
1539}
1540
1541template <ComponentType T>
1542constexpr Matrix<T>& Matrix<T>::operator*=(const T& s) {
1543 if (s == T(0)) {
1544 *this = Matrix(m, n);
1545 } else if (s == T(1) || type == details::Zero) {
1546 /* no-op */
1547 } else if (type == details::Generic || type == details::Vandermonde || type == details::Toeplitz) {
1548 std::ranges::for_each(data, [&s](T& x) { x *= s; });
1549 if (type == details::Vandermonde) type = details::Generic;
1550 } else if (type == details::Diagonal || type == details::Identity) {
1551 for (size_t mu = 0; mu < m; ++mu) data[mu * n + mu] *= s;
1552 if (type == details::Identity) type = details::Diagonal;
1553 }
1554 return *this;
1555}
1556
1557template <ComponentType T>
1558Matrix<T>& Matrix<T>::operator/=(const T& s) {
1559 if (s == T(0)) throw std::invalid_argument("trying to divide components of matrix by zero");
1560 operator*=(T(1) / s);
1561 return *this;
1562}
1563
1564template <ComponentType T>
1566 if constexpr (FieldType<T>) {
1567 std::ranges::for_each(data, std::mem_fn(&T::randomize));
1568 } else if constexpr (std::same_as<T, double>) {
1569 std::uniform_real_distribution<double> dist(-1.0, 1.0);
1570 std::ranges::for_each(data, [&](double& val) { val = dist(gen()); });
1571 } else if constexpr (std::same_as<T, std::complex<double>>) {
1572 std::uniform_real_distribution<double> dist(-1.0, 1.0);
1573 std::ranges::for_each(data,
1574 [&](std::complex<double>& val) { val = std::complex<double>(dist(gen()), dist(gen())); });
1575 } else if constexpr (SignedIntType<T>) {
1576 std::uniform_int_distribution<long long> dist(-100, 100);
1577 std::ranges::for_each(data, [&](T& val) { val = T(dist(gen())); });
1578 }
1579 type = details::Generic;
1580 cache.invalidate();
1581 return *this;
1582}
1583
1584template <ComponentType T>
1585size_t Matrix<T>::calculate_weight() const
1587{
1588 if (m == 0 || n == 0) return 0;
1589
1590 if (type == details::Zero) {
1591 return 0;
1592 } else {
1593 size_t res = data.size() - std::count(data.cbegin(), data.cend(), T(0));
1594#ifdef CECCO_ERASURE_SUPPORT
1595 if constexpr (FieldType<T>) res -= std::count_if(data.cbegin(), data.cend(), [](T x) { return x.is_erased(); });
1596#endif
1597 return res;
1598 }
1599 throw std::logic_error("calculate_weight(): unhandled matrix type");
1600}
1601
1602template <ComponentType T>
1603size_t Matrix<T>::rank() const
1605{
1606 return cache.template get_or_compute<Rank>([this] { return calculate_rank(); });
1607}
1608
1609template <ComponentType T>
1610Vector<T> Matrix<T>::diagonal() const {
1611 if (m != n) throw std::invalid_argument("trying to extract diagonal of non-square matrix");
1612 Vector<T> res(n);
1613 for (size_t i = 0; i < n; ++i) res.data[i] = (*this)(i, i);
1614 return res;
1615}
1616
1617template <ComponentType T>
1620{
1621 if (m != n) throw std::invalid_argument("trying to calculate characteristic polynomial of non-square matrix");
1622 if (m == 0) throw std::invalid_argument("trying to calculate characteristic polynomial of empty matrix");
1623 if (m == 1) return Polynomial<T>({-(*this)(0, 0), 1});
1625 // Samuelson–Berkowitz algorithm
1626
1627 // calculate details::Toeplitz matrices
1628 std::vector<Matrix<T>> TM; // details::Toeplitz matrices
1629 TM.reserve(m);
1630 for (size_t i = 0; i < m; ++i) {
1631 // partition matrix
1632 T a = (*this)(i, i);
1633 if (m - i == 1) {
1634 Vector<T> v(2);
1635 v.set_component(0, -a);
1636 v.set_component(1, T(1));
1637 TM.push_back(ToeplitzMatrix(v, 2, 1));
1638 } else if (m - i == 2) {
1639 const Matrix C = get_submatrix(i + 1, i, m - i - 1, 1);
1640 const Matrix R = get_submatrix(i, i + 1, 1, m - i - 1);
1641 Vector<T> v(4);
1642 v.set_component(0, -(R * C)(0, 0));
1643 v.set_component(1, -a);
1644 v.set_component(2, T(1));
1645 TM.push_back(ToeplitzMatrix(v, 3, 2));
1646 } else {
1647 const Matrix C = get_submatrix(i + 1, i, m - i - 1, 1);
1648 const Matrix R = get_submatrix(i, i + 1, 1, m - i - 1);
1649 Matrix A = get_submatrix(i + 1, i + 1, m - i - 1, m - i - 1);
1650 const Matrix X = A;
1651 Vector<T> v(2 * (m - i));
1652 for (size_t j = 0; j < m - i - 2; ++j) {
1653 v.set_component(m - i - 3 - j, -((R * A * C)(0, 0)));
1654 A *= X;
1655 }
1656 v.set_component(m - i - 2, -(R * C)(0, 0));
1657 v.set_component(m - i - 1, -a);
1658 v.set_component(m - i, T(1));
1659 TM.push_back(ToeplitzMatrix(v, m - i + 1, m - i));
1660 }
1661 }
1662
1663 // multiply details::Toeplitz matrices together
1664 Matrix P = IdentityMatrix<T>(m + 1);
1665 for (auto it = TM.cbegin(); it != TM.cend(); ++it) {
1666 P *= *it;
1667 }
1668
1669 // extract polynomial from solution/column vector
1670 Polynomial<T> res;
1671 for (size_t i = 0; i <= m; ++i) res.set_coefficient(m - i, P(i, 0));
1672
1673 // Samuelson-Berkowitz raw produces det(A - λI) = (-1)^m · det(λI - A);
1674 // negate for odd m to obtain the standard monic det(λI - A).
1675 if (m % 2) res *= T(-1);
1676
1677 return res;
1678 } else if (type == details::Zero) {
1679 Polynomial<T> res({0, 1});
1680 return res ^ m;
1681 } else if (type == details::Diagonal) {
1682 Polynomial<T> res({1});
1683 for (size_t mu = 0; mu < m; ++mu) res *= Polynomial<T>({-(*this)(mu, mu), T(1)});
1684 return res;
1685 } else if (type == details::Identity) {
1686 Polynomial<T> res({-1, 1});
1687 return res ^ m;
1688 }
1689 throw std::logic_error("characteristic_polynomial(): unhandled matrix type");
1690}
1691
1692template <ComponentType T>
1695{
1696 if (type == details::Zero) return IdentityMatrix<T>(n);
1697 if (type == details::Identity) return Matrix<T>(1, n, T(0));
1698 if (type == details::Diagonal) {
1700 for (size_t i = 0; i < m; ++i)
1701 if ((*this)(i, i) == T(0)) zero_positions.push_back(i);
1702 if (zero_positions.empty()) return Matrix<T>(1, n, T(0));
1704 for (size_t k = 0; k < zero_positions.size(); ++k) B.set_component(k, zero_positions[k], T(1));
1705 return B;
1706 }
1707
1708 Matrix<T> temp(*this);
1709 size_t r = 0;
1710 temp.rref(&r);
1711
1712 if (n - r == 0) return Matrix<T>(1, n, T(0));
1713
1714 Matrix B(n - r, n);
1715
1716 std::vector<size_t> mocols; // "minus one columns"
1717 mocols.reserve(n - r);
1718 size_t i = 0;
1719 for (size_t j = 0; j < n; ++j) {
1720 if (i < m) {
1721 if (temp(i, j) == T(1))
1722 ++i;
1723 else
1725 } else {
1727 }
1728 }
1729
1730 size_t offset = 0;
1731 for (size_t i = 0; i < m + offset + 1; ++i) {
1732 for (size_t k = offset; k < n - r; ++k) {
1733 if (i + offset == mocols[k]) {
1734 B.set_component(k, i + offset, -T(1));
1735 ++offset;
1736 } else {
1737 B.set_component(k, i + offset, temp(i, mocols[k]));
1738 }
1739 }
1740 }
1741
1742 return B;
1743}
1744
1745template <ComponentType T>
1746T Matrix<T>::determinant() const
1748{
1749 if (m != n) throw std::invalid_argument("trying to calculate determinant of non-square matrix");
1750 if (m == 0) throw std::invalid_argument("trying to calculate determinant of empty matrix");
1751 if (m == 1) return ((*this))(0, 0);
1752 if (type == details::Generic || type == details::Toeplitz) {
1753 // char_poly is monic det(λI - A), so [0] = (-1)^m · det(A).
1754 const auto c = characteristic_polynomial()[0];
1755 return (m % 2 == 0) ? c : -c;
1756 } else if (type == details::Vandermonde) {
1757 T s(1);
1758 for (size_t mu = 1; mu < m; ++mu)
1759 for (size_t i = 0; i < mu; ++i) s *= (*this)(1, mu) - (*this)(1, i);
1760 return s;
1761 } else if (type == details::Zero) {
1762 return T(0);
1763 } else if (type == details::Diagonal) {
1764 T s(1);
1765 for (size_t i = 0; i < m; ++i) {
1766 if ((*this)(i, i) == T(0)) return T(0);
1767 s *= (*this)(i, i);
1768 }
1769 return s;
1770 } else if (type == details::Identity) {
1771 return T(1);
1772 }
1773 throw std::logic_error("determinant(): unhandled matrix type");
1774}
1775
1776template <ComponentType T>
1779{
1780 const auto p = characteristic_polynomial();
1781 std::vector<T> res;
1782 for (size_t j = 0; j < T::get_size(); ++j) {
1783 T element = T(j);
1784 if (p(element) == T(0)) res.push_back(element);
1785 }
1786 return res;
1787}
1788
1789template <ComponentType T>
1792{
1793 const size_t r = rank();
1794 if (sqm<InfInt>(T::get_size(), r) > sqm<InfInt>(10, 10)) {
1795 throw std::out_of_range("row space too big (more than 10^10 elements) to compute all elements");
1796 }
1797 const auto size = sqm<size_t>(sqm<size_t>(T::get_p(), T::get_m()), r);
1798 std::vector<Vector<T>> res;
1799 res.reserve(size);
1800 for (size_t counter = 0; counter < size; ++counter) {
1801 Vector<T> temp(n);
1802 for (size_t i = 0; i < r; ++i) {
1803 T scalar(counter / sqm<size_t>(sqm<size_t>(T::get_p(), T::get_m()), i) %
1804 sqm<size_t>(T::get_p(), T::get_m()));
1805 if (scalar != T(0)) {
1806 temp += scalar * get_row(r - i - 1);
1807 }
1808 }
1810 }
1811 // sort vectors so that two spans consisting of the same vectors compare (==) to equal
1812 std::sort(res.begin(), res.end(), [](Vector<T>& a, Vector<T>& b) { return a.as_integer() > b.as_integer(); });
1813 return res;
1814}
1815
1816template <ComponentType T>
1817template <typename U>
1818Matrix<T>& Matrix<T>::set_component(size_t i, size_t j, U&& c)
1819 requires std::convertible_to<std::decay_t<U>, T>
1820{
1821 if (i >= m || j >= n) throw std::invalid_argument("trying to access non-existent component of matrix");
1822
1823 T& old_value = (!transposed) ? data[i * n + j] : data[i + j * m];
1824
1825 T new_value(std::forward<U>(c));
1826 if (old_value == new_value) return *this;
1827
1828 switch (type) {
1829 case details::Generic:
1830 break;
1831
1832 case details::Diagonal:
1833 if (i != j) type = details::Generic;
1834 break;
1835
1836 case details::Zero:
1837 if (i == j && m == n)
1839 else
1840 type = details::Generic;
1841 break;
1842
1843 case details::Identity:
1844 if (i == j)
1845 type = details::Diagonal; // we already know it is not one
1846 else
1847 type = details::Generic; // we already know it is not zero
1848 break;
1849
1850 case details::Vandermonde:
1851 case details::Toeplitz:
1852 type = details::Generic;
1853 break;
1854
1855 default:
1856 type = details::Generic;
1857 break;
1858 }
1859
1860 if (!transposed)
1861 data[i * n + j] = new_value;
1862 else
1863 data[i + j * m] = new_value;
1864
1865 cache.invalidate();
1866
1867 return *this;
1868}
1869
1870template <ComponentType T>
1871const T& Matrix<T>::operator()(size_t i, size_t j) const {
1872 if (i >= m || j >= n) throw std::invalid_argument("trying to access non-existent component of matrix");
1873 if (!transposed) return data[i * n + j];
1874 return data[i + j * m];
1875}
1876
1877template <ComponentType T>
1879 if (i >= m) throw std::invalid_argument("trying to access non-existent row");
1880 Vector<T> res(n);
1881 if (!transposed) {
1882 std::copy(data.begin() + i * n, data.begin() + (i + 1) * n, res.data.begin());
1883 } else {
1884 for (size_t j = 0; j < n; ++j) res.data[j] = (*this)(i, j);
1885 }
1886 return res;
1887}
1888
1889template <ComponentType T>
1891 if (j >= n) throw std::invalid_argument("trying to access non-existent column");
1892 Vector<T> res(m);
1893 if (transposed) {
1894 std::copy(data.begin() + j * m, data.begin() + (j + 1) * m, res.data.begin());
1895 } else {
1896 for (size_t i = 0; i < m; ++i) res.data[i] = (*this)(i, j);
1897 }
1898 return res;
1899}
1900
1901template <ComponentType T>
1903 if (i + h > m || j + w > n)
1904 throw std::invalid_argument(
1905 "trying to extract a submatrix with incompatible "
1906 "dimensions");
1907 Matrix res(h, w);
1909 if (!transposed && !res.transposed) {
1910 for (size_t mu = 0; mu < h; ++mu)
1911 std::copy(data.begin() + (i + mu) * n + j, data.begin() + (i + mu) * n + j + w,
1912 res.data.begin() + mu * w);
1914 } else {
1915 for (size_t mu = 0; mu < h; ++mu)
1916 for (size_t nu = 0; nu < w; ++nu) res.set_component(mu, nu, (*this)(i + mu, j + nu));
1917 }
1918 if (type == details::Vandermonde && i == 0) {
1920 } else if (type == details::Toeplitz) {
1922 }
1923 } else if (type == details::Zero) {
1924 /* no-op */
1925 } else if (type == details::Diagonal || type == details::Identity) {
1926 for (size_t mu = 0; mu < h; ++mu)
1927 for (size_t nu = 0; nu < w; ++nu)
1928 if (i + mu == j + nu) res.set_component(mu, nu, (*this)(i + mu, j + nu));
1929 if (i == j) {
1930 if (type == details::Diagonal && h == w) {
1932 } else if (type == details::Identity) {
1934 }
1935 }
1936 }
1937 return res;
1938}
1939
1940template <ComponentType T>
1942 if (m < i + N.m || n < j + N.n)
1943 throw std::invalid_argument(
1944 "trying to replace submatrix with "
1945 "matrix of incompatible dimensions");
1946
1947 if (!transposed && !N.transposed) {
1948 for (size_t mu = 0; mu < N.m; ++mu)
1949 std::copy(N.data.begin() + mu * N.n, N.data.begin() + (mu + 1) * N.n, data.begin() + (i + mu) * n + j);
1950 } else {
1951 for (size_t mu = 0; mu < N.m; ++mu)
1952 for (size_t nu = 0; nu < N.n; ++nu) set_component(i + mu, j + nu, N(mu, nu));
1953 }
1954 type = details::Generic;
1955 cache.invalidate();
1956 return *this;
1957}
1958
1959template <ComponentType T>
1961 if (m != other.m)
1962 throw std::invalid_argument(
1963 "trying to horizontally join two "
1964 "matrices of incompatible dimensions");
1965 if (type == details::Zero && other.type == details::Zero) {
1966 *this = ZeroMatrix<T>(m, n + other.n);
1967 } else {
1968 Matrix temp(m, n + other.n);
1969 temp.set_submatrix(0, 0, *this);
1971 *this = std::move(temp);
1972 }
1973 return *this;
1974}
1975
1976template <ComponentType T>
1978 if (n != other.n)
1979 throw std::invalid_argument(
1980 "trying to vertically join two "
1981 "matrices of incompatible dimensions");
1982 if (type == details::Zero && other.type == details::Zero) {
1983 *this = ZeroMatrix<T>(m + other.m, n);
1984 } else {
1985 Matrix temp(m + other.m, n);
1986 temp.set_submatrix(0, 0, *this);
1988 *this = std::move(temp);
1989 }
1990 return *this;
1991}
1992
1993template <ComponentType T>
1995 if (type == details::Zero && other.type == details::Zero) {
1996 *this = ZeroMatrix<T>(m + other.m, n + other.n);
1997 } else if (type == details::Identity && other.type == details::Identity) {
1998 *this = IdentityMatrix<T>(m + other.m);
1999 } else if ((type == details::Diagonal && other.type == details::Identity) ||
2003 } else {
2004 Matrix temp(m + other.m, n + other.n);
2005 temp.set_submatrix(0, 0, *this);
2007 *this = std::move(temp);
2008 }
2009 return *this;
2010}
2011
2012template <ComponentType T>
2014 if (type == details::Zero || other.type == details::Zero) {
2015 *this = ZeroMatrix<T>(m * other.m, n * other.n);
2016 } else if (type == details::Identity && other.type == details::Identity) {
2017 *this = IdentityMatrix<T>(m * other.m);
2018 } else if (type == details::Diagonal || type == details::Identity) {
2020 auto d1 = diagonal();
2021 auto d2 = other.diagonal();
2022 Vector<T> d(m * other.m);
2023 for (size_t idx = 0; idx < m * other.m; ++idx) d.set_component(idx, d1[idx / other.m] * d2[idx % other.m]);
2024 *this = DiagonalMatrix<T>(std::move(d));
2025 } else {
2026 Matrix temp(m * other.m, n * other.n);
2027 for (size_t mu = 0; mu < m; ++mu) {
2028 if (type == details::Identity)
2030 else
2031 temp.set_submatrix(mu * other.m, mu * other.n, (*this)(mu, mu) * other);
2032 }
2033 *this = std::move(temp);
2034 }
2035 } else {
2036 Matrix temp(m * other.m, n * other.n);
2037 for (size_t mu = 0; mu < m; ++mu)
2038 for (size_t nu = 0; nu < n; ++nu) temp.set_submatrix(mu * other.m, nu * other.n, (*this)(mu, nu) * other);
2039 *this = std::move(temp);
2040 }
2041
2042 return *this;
2043}
2044
2045template <ComponentType T>
2047 if (i >= m || j >= m) throw std::invalid_argument("trying to swap non-existent row(s)");
2048 if (i == j) return *this;
2049 if (type != details::Zero) {
2050 if (!transposed) {
2051 std::swap_ranges(data.begin() + i * n, data.begin() + (i + 1) * n, data.begin() + j * n);
2052 } else {
2053 auto rank_backup = cache.template get<Rank>();
2054 for (size_t nu = 0; nu < n; ++nu) {
2055 const auto temp = (*this)(i, nu);
2056 set_component(i, nu, (*this)(j, nu));
2058 }
2059 if (rank_backup) cache.template set<Rank>(*rank_backup);
2060 }
2061 type = details::Generic;
2062 }
2063 return *this;
2064}
2065
2066template <ComponentType T>
2068 transpose();
2069 swap_rows(i, j);
2070 transpose();
2071 return *this;
2072}
2073
2074template <ComponentType T>
2076 if (i >= m) throw std::invalid_argument("trying to scale non-existent row");
2077 if (s == T(1)) return *this;
2079 if (!transposed) {
2080 std::for_each(data.begin() + i * n, data.begin() + (i + 1) * n, [&s](T& x) { x *= s; });
2082
2083 } else {
2084 for (size_t nu = 0; nu < n; ++nu) data[i + nu * m] *= s;
2086 }
2087 } else if (type == details::Zero) {
2088 /* no-op */
2089 } else if (type == details::Diagonal || type == details::Identity) {
2090 data[i * n + i] *= s;
2092 }
2093 if (s == T(0)) cache.template invalidate<Rank>();
2094 return *this;
2095}
2096
2097template <ComponentType T>
2099 transpose();
2100 scale_row(s, i);
2101 transpose();
2102 return *this;
2103}
2104
2105template <ComponentType T>
2107 if (i >= m || j >= m)
2108 throw std::invalid_argument("trying to add scaled row to other row, at least one of them is non-existent");
2109 if (s == T(0)) return *this;
2111 if (!transposed) {
2112 std::transform(data.begin() + j * n, data.begin() + (j + 1) * n, data.begin() + i * n, data.begin() + j * n,
2113 [&s](const T& target, const T& source) { return target + s * source; });
2115 } else {
2116 for (size_t nu = 0; nu < n; ++nu) data[j + nu * m] += s * data[i + nu * m];
2118 }
2119 } else if (type == details::Zero) {
2120 /* no-op */
2121 } else if (type == details::Diagonal || type == details::Identity) {
2122 data[j * n + i] += data[i * n + i] * s;
2123 type = details::Generic;
2124 }
2125 if (i == j) cache.template invalidate<Rank>();
2126 return *this;
2127}
2128
2129template <ComponentType T>
2131 transpose();
2132 add_scaled_row(s, i, j);
2133 transpose();
2134 return *this;
2135}
2136
2137template <ComponentType T>
2139 if (v.empty()) return *this;
2140
2141 // Validate and create sorted set of unique indices (deduplicate)
2142 // Careful: implicit sorting is ascending, so need to use reverse iterators in next loop!
2143 std::set<size_t> indices(v.begin(), v.end());
2144 for (size_t idx : indices) {
2145 if (idx >= n) throw std::invalid_argument("trying to delete non-existent column");
2146 }
2147
2148 for (auto it = indices.crbegin(); it != indices.crend(); ++it) {
2149 Matrix left = get_submatrix(0, 0, m, *it);
2150 const Matrix right = get_submatrix(0, *it + 1, m, n - (*it + 1));
2151 *this = left.horizontal_join(std::move(right));
2152 }
2153
2154 type = details::Generic;
2155 cache.invalidate();
2156 return *this;
2157}
2158
2159template <ComponentType T>
2161 if (v.empty()) return *this;
2162
2163 // Validate and create sorted set of unique indices (deduplicate)
2164 // Careful: implicit sorting is ascending, so need to use reverse iterators in next loop!
2165 std::set<size_t> indices(v.begin(), v.end());
2166 for (size_t idx : indices) {
2167 if (idx >= m) throw std::invalid_argument("trying to delete non-existent row");
2168 }
2169
2170 for (auto it = indices.crbegin(); it != indices.crend(); ++it) {
2171 Matrix top = get_submatrix(0, 0, *it, n);
2172 Matrix bottom = get_submatrix(*it + 1, 0, m - (*it + 1), n);
2173 *this = top.vertical_join(bottom);
2174 }
2175
2176 type = details::Generic;
2177 cache.invalidate();
2178 return *this;
2179}
2180
2181#ifdef CECCO_ERASURE_SUPPORT
2182
2183template <ComponentType T>
2186{
2187 if (i >= m || j >= n) throw std::invalid_argument("trying to erase component at invalid index");
2188
2189 if (!transposed)
2190 data[i * n + j].erase();
2191 else
2192 data[i + j * m].erase();
2193
2194 type = details::Generic;
2195 cache.invalidate();
2196 return *this;
2197}
2198
2199template <ComponentType T>
2202{
2203 if (i >= m || j >= n) throw std::invalid_argument("trying to un-erase component at invalid index");
2204
2205 if (!transposed)
2206 data[i * n + j].unerase();
2207 else
2208 data[i + j * m].unerase();
2209
2210 type = details::Generic;
2211 cache.invalidate();
2212 return *this;
2213}
2214
2215template <ComponentType T>
2218{
2219 if (v.empty()) return *this;
2220
2221 // Validate and create sorted set of unique indices (deduplicate)
2222 std::set<size_t> indices(v.begin(), v.end());
2223 for (size_t idx : indices) {
2224 if (idx >= n) throw std::invalid_argument("trying to erase non-existent column");
2225 }
2226
2227 // Apply erase using std::for_each
2228 std::for_each(indices.crbegin(), indices.crend(), [&](auto col) {
2229 for (size_t row = 0; row < m; ++row) erase_component(row, col);
2230 });
2231
2232 type = details::Generic;
2233 cache.invalidate();
2234 return *this;
2235}
2236
2237template <ComponentType T>
2240{
2241 if (v.empty()) return *this;
2242
2243 // Validate and create sorted set of unique indices (deduplicate)
2244 std::set<size_t> indices(v.begin(), v.end());
2245 for (size_t idx : indices) {
2246 if (idx >= m) throw std::invalid_argument("trying to erase non-existent row");
2247 }
2248
2249 // Apply erase using std::for_each
2250 std::for_each(indices.crbegin(), indices.crend(), [&](auto row) {
2251 for (size_t col = 0; col < n; ++col) erase_component(row, col);
2252 });
2253
2254 type = details::Generic;
2255 cache.invalidate();
2256 return *this;
2257}
2258
2259template <ComponentType T>
2262{
2263 // Validate and create sorted set of unique indices (deduplicate)
2264 std::set<size_t> indices(v.begin(), v.end());
2265 for (size_t idx : indices) {
2266 if (idx >= n) throw std::invalid_argument("trying to un-erase non-existent column");
2267 }
2268
2269 // Apply erase using std::for_each
2270 std::for_each(indices.crbegin(), indices.crend(), [&](auto col) {
2271 for (size_t row = 0; row < m; ++row) unerase_component(row, col);
2272 });
2273
2274 type = details::Generic;
2275 cache.invalidate();
2276 return *this;
2277}
2278
2279template <ComponentType T>
2282{
2283 // Validate and create sorted set of unique indices (deduplicate)
2284 std::set<size_t> indices(v.begin(), v.end());
2285 for (size_t idx : indices) {
2286 if (idx >= m) throw std::invalid_argument("trying to un-erase non-existent row");
2287 }
2288
2289 // Apply erase using std::for_each
2290 std::for_each(indices.crbegin(), indices.crend(), [&](auto row) {
2291 for (size_t col = 0; col < n; ++col) unerase_component(row, col);
2292 });
2293
2294 type = details::Generic;
2295 cache.invalidate();
2296 return *this;
2297}
2298
2299#endif
2300
2301template <ComponentType T>
2303 if (type != details::Zero) {
2304 if (!transposed) {
2305 // For non-transposed matrices, reverse row-wise using STL
2306 for (size_t mu = 0; mu < m / 2; ++mu)
2307 std::swap_ranges(data.begin() + mu * n, data.begin() + (mu + 1) * n, data.begin() + (m - 1 - mu) * n);
2308 } else {
2309 auto rank_backup = cache.template get<Rank>();
2310 for (size_t mu = 0; mu < m / 2; ++mu)
2311 for (size_t nu = 0; nu < n; ++nu) {
2312 const auto temp = (*this)(mu, nu);
2313 set_component(mu, nu, (*this)(m - 1 - mu, nu));
2314 set_component(m - 1 - mu, nu, temp);
2315 }
2316 if (rank_backup) cache.template set<Rank>(*rank_backup);
2317 }
2318 type = details::Generic;
2319 }
2320 return *this;
2321}
2322
2323template <ComponentType T>
2325 if (type != details::Zero) {
2326 if (!transposed) {
2327 // For non-transposed matrices, reverse elements within each row
2328 for (size_t mu = 0; mu < m; ++mu) std::reverse(data.begin() + mu * n, data.begin() + (mu + 1) * n);
2329 } else {
2330 auto rank_backup = cache.template get<Rank>();
2331 for (size_t mu = 0; mu < m; ++mu)
2332 for (size_t nu = 0; nu < n / 2; ++nu) {
2333 const auto temp = (*this)(mu, nu);
2334 set_component(mu, nu, (*this)(mu, n - 1 - nu));
2335 set_component(mu, n - 1 - nu, temp);
2336 }
2337 if (rank_backup) cache.template set<Rank>(*rank_backup);
2338 }
2339 if (type != details::Vandermonde) {
2340 type = details::Generic;
2341 }
2342 }
2343 return *this;
2344}
2345
2346template <ComponentType T>
2347constexpr Matrix<T>& Matrix<T>::fill(const T& s) {
2348 std::fill(data.begin(), data.end(), s);
2349 if (s == T(0))
2350 type = details::Zero;
2351 else
2352 type = details::Generic;
2353
2354 if (s != T(0))
2355 cache.template set<Rank>(1);
2356 else
2357 cache.template set<Rank>(0);
2358 return *this;
2359}
2360
2361template <ComponentType T>
2362constexpr Matrix<T>& Matrix<T>::transpose() {
2365 std::swap(m, n);
2367 } else if (type == details::Zero) {
2368 std::swap(m, n);
2369 } else if (type == details::Diagonal || type == details::Identity) {
2370 /* no-op */
2371 }
2372 return *this;
2373}
2374
2375template <ComponentType T>
2378{
2379 if (type == details::Generic || type == details::Toeplitz) {
2380 size_t h = 0;
2381 size_t k = 0;
2382
2383 // Branch ONCE on transpose flag - dispatch to optimized elimination kernels
2384 if (!this->transposed)
2385 h = ref_elimination_kernel<false>(this->data.data(), m, n, h, k);
2386 else
2387 h = ref_elimination_kernel<true>(this->data.data(), m, n, h, k);
2388
2389 cache.template set<Rank>(h);
2390 if (rank != nullptr) *rank = h;
2392
2393 } else if (type == details::Vandermonde) {
2394 // For Vandermonde matrices, calculate RREF (as special case of REF)
2395 return rref(rank);
2396 } else if (type == details::Diagonal) {
2398 size_t r = 0;
2399 for (size_t i = 0; i < std::min(m, n); ++i) {
2400 if ((*this)(i, i) == T(0))
2402 else
2403 ++r;
2404 }
2405
2406 if (!zero_rows.empty()) {
2407 this->delete_rows(zero_rows);
2409 type = details::Generic; // No longer purely diagonal
2410 }
2411
2412 cache.template set<Rank>(r);
2413 if (rank != nullptr) *rank = r;
2414 } else if (type == details::Identity) {
2415 cache.template set<Rank>(m);
2416 if (rank != nullptr) *rank = m;
2417 } else if (type == details::Zero) {
2418 cache.template set<Rank>(0);
2419 if (rank != nullptr) *rank = 0;
2420 }
2421 return *this;
2422}
2423
2424template <ComponentType T>
2427{
2428 if (type == details::Generic || type == details::Toeplitz) {
2429 size_t r = 0;
2430 this->ref(&r);
2431
2432 // Branch ONCE on transpose flag - dispatch to optimized backward elimination kernels
2433 if (!this->transposed)
2434 rref_backward_elimination_kernel<false>(this->data.data(), m, n, r);
2435 else
2436 rref_backward_elimination_kernel<true>(this->data.data(), m, n, r);
2437
2438 cache.template set<Rank>(r);
2439 if (rank != nullptr) *rank = r;
2440 if (r == m && m == n)
2442 else if (type == details::Toeplitz)
2443 type = details::Generic;
2444 } else if (type == details::Vandermonde) {
2445 if (m == n) {
2446 // Case 1: Square details::Vandermonde -> I
2447 *this = IdentityMatrix<T>(m);
2448 if (rank != nullptr) *rank = m;
2450
2451 } else if (m < n) {
2452 // Case 2: Wide details::Vandermonde [W | A] with W, A details::Vandermonde -> [I | W^-1 A]
2453 auto W = this->get_submatrix(0, 0, m, m);
2454 auto A = this->get_submatrix(0, m, m, n - m);
2455
2456 W.invert(); // Use known details::Vandermonde inverse of W
2457 auto M = W * A;
2458
2459 *this = IdentityMatrix<T>(m);
2460 this->horizontal_join(std::move(M));
2461
2462 cache.template set<Rank>(m);
2463 if (rank != nullptr) *rank = m;
2464 type = details::Generic;
2465
2466 } else {
2467 // Case 3: Tall details::Vandermonde (more rows than columns) -> I above of Z
2468 *this = IdentityMatrix<T>(n);
2469 this->vertical_join(ZeroMatrix<T>(m - n, n));
2470
2471 cache.template set<Rank>(n);
2472 if (rank != nullptr) *rank = n;
2473 type = details::Toeplitz; // very special case of details::Toeplitz...
2474 }
2475 } else if (type == details::Diagonal) {
2477 size_t r = 0;
2478 for (size_t i = 0; i < std::min(m, n); ++i) {
2479 if ((*this)(i, i) == T(0)) {
2481 } else {
2482 set_component(i, i, T(1)); // Normalize to 1 for RREF
2483 ++r;
2484 }
2485 }
2486
2487 if (!zero_rows.empty()) {
2488 this->delete_rows(zero_rows);
2490 type = details::Generic;
2491 } else {
2493 }
2494
2495 cache.template set<Rank>(r);
2496 if (rank != nullptr) *rank = r;
2497 } else if (type == details::Zero) {
2498 cache.template set<Rank>(0);
2499 if (rank != nullptr) *rank = 0;
2500 } else if (type == details::Identity) {
2501 cache.template set<Rank>(m);
2502 if (rank != nullptr) *rank = m;
2503 }
2504 return *this;
2505}
2506
2507template <ComponentType T>
2510{
2511 if (type == details::Generic || type == details::Toeplitz) {
2512 if (m != n) throw std::invalid_argument("trying to invert a non-square matrix");
2513 const auto I = IdentityMatrix<T>(m);
2514 Matrix temp(m, 2 * m);
2515 temp.set_submatrix(0, 0, *this);
2516 temp.set_submatrix(0, m, I);
2517 temp.rref();
2518 if (temp.get_submatrix(0, 0, m, m) != I)
2519 throw std::invalid_argument("trying to invert a non-invertible matrix");
2520 *this = temp.get_submatrix(0, m, m, m);
2522
2523 } else if (type == details::Vandermonde) {
2524 if (m != n) throw std::invalid_argument("trying to invert a non-square details::Vandermonde matrix");
2527 for (size_t i = 0; i < m; ++i)
2528 for (size_t k = 0; k < m; ++k) {
2529 if (k == i) continue;
2530 Lagrange_polynomials[i] *= Polynomial<T>({-(*this)(1, k), 1}) / ((*this)(1, i) - (*this)(1, k));
2531 }
2532 for (size_t i = 0; i < m; ++i)
2533 for (size_t j = 0; j < m; ++j) set_component(i, j, Lagrange_polynomials[i][j]);
2534 } else if (type == details::Zero) {
2535 throw std::invalid_argument("trying to invert a non-invertible matrix/a zero matrix");
2536 } else if (type == details::Diagonal) {
2537 // Check for zero diagonal elements and invert
2538 for (size_t mu = 0; mu < m; ++mu) {
2539 if ((*this)(mu, mu) == T(0))
2540 throw std::invalid_argument(
2541 "trying to invert a non-invertible matrix/a diagonal matrix with at least one zero on the "
2542 "diagonal");
2543 data[mu * n + mu] = T(1) / data[mu * n + mu];
2544 }
2545 } else if (type == details::Identity) {
2546 /* no-op */
2547 }
2548 return *this;
2549}
2550
2551template <ComponentType T>
2552template <FiniteFieldType S>
2553constexpr Vector<S> Matrix<T>::as_vector() const
2555{
2556 const auto m = S().template as_vector<T>().get_n();
2557 if (m != get_m())
2558 throw std::invalid_argument("trying to create superfield vector from subfield matrix, wrong number of rows");
2559
2560 Vector<S> res(get_n());
2561 Matrix<T> Tp(*this);
2562 Tp.transpose();
2563 for (size_t i = 0; i < get_n(); ++i) {
2564 const auto temp = Tp.get_row(i);
2566 }
2567 return res;
2568}
2569
2570template <ComponentType T>
2572 const size_t m = get_m();
2573 const size_t n = get_n();
2574
2575 Vector<T> v(m * n);
2576
2577 size_t k = 0;
2578 for (size_t i = 0; i < m; ++i) {
2579 for (size_t j = 0; j < n; ++j) {
2580 v.set_component(k, (*this)(i, j));
2581 ++k;
2582 }
2583 }
2584 return v;
2585}
2586
2587template <ComponentType T>
2589 requires FiniteFieldType<T> && (T::get_size() <= 64)
2590{
2591 std::ofstream file;
2593
2594 file << "P3" << std::endl; // PPM with P3 format
2595 file << get_n() << " " << get_m() << std::endl;
2596 file << 255 << std::endl;
2597
2598 for (size_t i = 0; i < get_m(); ++i) {
2599 for (size_t j = 0; j < get_n(); ++j) {
2600#ifdef CECCO_ERASURE_SUPPORT
2601 if ((*this)(i, j).is_erased()) {
2602 file << std::setw(3) << 255 << " " << std::setw(3) << 0 << " " << std::setw(3) << 0 << " ";
2603 if (j == get_n() - 1) file << std::endl;
2604 continue;
2605 }
2606#endif
2607
2608 const auto label = (*this)(i, j).get_label();
2609
2610 const size_t a = 63 - 63 * static_cast<double>(label) / (T::get_size() - 1);
2611
2612 const uint8_t r = details::colormap[a][0];
2613 const uint8_t g = details::colormap[a][1];
2614 const uint8_t b = details::colormap[a][2];
2615
2616 file << std::setw(3) << static_cast<int>(r) << " " << std::setw(3) << static_cast<int>(g) << " "
2617 << std::setw(3) << static_cast<int>(b) << " ";
2618 }
2619 file << std::endl;
2620 }
2621
2622 file.close();
2623}
2624
2625template <ComponentType T>
2626size_t Matrix<T>::calculate_rank() const
2628{
2629 Matrix<T> temp(*this);
2630 size_t r = 0;
2631 temp.ref(&r); // Use REF instead of RREF for better performance
2632 return r;
2633}
2634
2635/* free functions wrt. Matrix */
2636
2637/*
2638 * matrix + matrix
2639 */
2640
2641template <ComponentType T>
2642constexpr Matrix<T> operator+(const Matrix<T>& lhs, const Matrix<T>& rhs) {
2643 Matrix<T> res(lhs);
2644 res += rhs;
2645 return res;
2646}
2647
2648template <ComponentType T>
2649constexpr Matrix<T> operator+(Matrix<T>&& lhs, const Matrix<T>& rhs) {
2650 Matrix<T> res(std::move(lhs));
2651 res += rhs;
2652 return res;
2653}
2654
2655template <ComponentType T>
2656constexpr Matrix<T> operator+(const Matrix<T>& lhs, Matrix<T>&& rhs) {
2657 Matrix<T> res(std::move(rhs));
2658 res += lhs;
2659 return res;
2660}
2661
2662template <ComponentType T>
2663constexpr Matrix<T> operator+(Matrix<T>&& lhs, Matrix<T>&& rhs) {
2664 Matrix<T> res(std::move(lhs));
2665 res += rhs;
2666 return res;
2667}
2668
2669/*
2670 * matrix - matrix
2671 */
2672
2673template <ComponentType T>
2674constexpr Matrix<T> operator-(const Matrix<T>& lhs, const Matrix<T>& rhs) {
2675 Matrix<T> res(lhs);
2676 res -= rhs;
2677 return res;
2678}
2679
2680template <ComponentType T>
2681constexpr Matrix<T> operator-(Matrix<T>&& lhs, const Matrix<T>& rhs) {
2682 Matrix<T> res(std::move(lhs));
2683 res -= rhs;
2684 return res;
2685}
2686
2687template <ComponentType T>
2688constexpr Matrix<T> operator-(const Matrix<T>& lhs, Matrix<T>&& rhs) {
2689 Matrix<T> res(-std::move(rhs));
2690 res += lhs;
2691 return res;
2692}
2693
2694template <ComponentType T>
2695constexpr Matrix<T> operator-(Matrix<T>&& lhs, Matrix<T>&& rhs) {
2696 Matrix<T> res(std::move(lhs));
2697 res -= rhs;
2698 return res;
2699}
2700
2701/*
2702 * matrix * matrix
2703 */
2704
2705template <ComponentType T>
2706constexpr Matrix<T> operator*(const Matrix<T>& lhs, const Matrix<T>& rhs) {
2707 Matrix<T> res(lhs);
2708 res *= rhs;
2709 return res;
2710}
2711
2712template <ComponentType T>
2713constexpr Matrix<T> operator*(Matrix<T>&& lhs, const Matrix<T>& rhs) {
2714 Matrix<T> res(std::move(lhs));
2715 res *= rhs;
2716 return res;
2717}
2718
2719/*
2720 * vector * matrix
2721 */
2722
2723template <ComponentType T>
2724constexpr Vector<T> operator*(const Vector<T>& lhs, const Matrix<T>& rhs) {
2725 return (Matrix<T>(lhs) * rhs).get_row(0);
2726}
2727
2728template <ComponentType T>
2729constexpr Vector<T> operator*(Vector<T>&& lhs, const Matrix<T>& rhs) {
2730 return (Matrix<T>(std::move(lhs)) * rhs).get_row(0);
2731}
2732
2733/*
2734 * matrix * T
2735 */
2736
2737template <ComponentType T>
2738constexpr Matrix<T> operator*(const Matrix<T>& lhs, const T& rhs) {
2739 Matrix<T> res(lhs);
2740 res *= rhs;
2741 return res;
2742}
2743
2744template <ComponentType T>
2745constexpr Matrix<T> operator*(Matrix<T>&& lhs, const T& rhs) {
2746 Matrix<T> res(std::move(lhs));
2747 res *= rhs;
2748 return res;
2749}
2750
2751/*
2752 * T * matrix
2753 */
2754
2755template <ComponentType T>
2756constexpr Matrix<T> operator*(const T& lhs, const Matrix<T>& rhs) {
2757 Matrix<T> res(rhs);
2758 res *= lhs;
2759 return res;
2760}
2761
2762template <ComponentType T>
2763constexpr Matrix<T> operator*(const T& lhs, Matrix<T>&& rhs) {
2764 Matrix<T> res(std::move(rhs));
2765 res *= lhs;
2766 return res;
2767}
2768
2769/*
2770 * matrix / T
2771 */
2772
2773template <ComponentType T>
2774constexpr Matrix<T> operator/(const Matrix<T>& lhs, const T& rhs) {
2775 Matrix<T> res(lhs);
2776 res /= rhs;
2777 return res;
2778}
2779
2780template <ComponentType T>
2781constexpr Matrix<T> operator/(Matrix<T>&& lhs, const T& rhs) {
2782 Matrix<T> res(std::move(lhs));
2783 res /= rhs;
2784 return res;
2785}
2786
2787template <ComponentType T>
2789 Matrix<T> res(M);
2790 res.randomize();
2791 return res;
2792}
2793
2794template <ComponentType T>
2796 Matrix<T> res(std::move(M));
2797 res.randomize();
2798 return res;
2799}
2800
2801template <ReliablyComparableType T>
2802constexpr size_t wH(const Matrix<T>& M) {
2803 return M.wH();
2804}
2805
2806template <ComponentType T>
2807Matrix<T> set_component(auto&& M, size_t i, size_t j, const T& c)
2808 requires std::convertible_to<std::decay_t<decltype(M)>, Matrix<T>>
2809{
2810 Matrix<T> res(std::forward<decltype(M)>(M));
2811 res.set_component(i, j, c);
2812 return res;
2813}
2814
2815template <ComponentType T>
2817 Matrix<T> res(M);
2818 return res.get_submatrix(i, j, h, w);
2819}
2820
2821template <ComponentType T>
2823 Matrix<T> res(std::move(M));
2824 return res.get_submatrix(i, j, h, w);
2825}
2826
2827template <ComponentType T>
2829 Matrix<T> res(M);
2830 res.set_submatrix(i, j, N);
2831 return res;
2832}
2833
2834template <ComponentType T>
2836 Matrix<T> res(std::move(M));
2837 res.set_submatrix(i, j, N);
2838 return res;
2839}
2840
2841template <ComponentType T>
2843 Matrix<T> res(lhs);
2845 return res;
2846}
2847
2848template <ComponentType T>
2850 Matrix<T> res(std::move(lhs));
2852 return res;
2853}
2854
2855template <ComponentType T>
2857 Matrix<T> res(std::move(lhs));
2859 return res;
2860}
2861
2862template <ComponentType T>
2864 Matrix<T> res(lhs);
2866 return res;
2867}
2868
2869template <ComponentType T>
2871 Matrix<T> res(std::move(lhs));
2873 return res;
2874}
2875
2876template <ComponentType T>
2878 Matrix<T> res(std::move(lhs));
2880 return res;
2881}
2882
2883template <ComponentType T>
2885 Matrix<T> res(lhs);
2887 return res;
2888}
2889
2890template <ComponentType T>
2892 Matrix<T> res(std::move(lhs));
2894 return res;
2895}
2896
2897template <ComponentType T>
2899 Matrix<T> res(std::move(lhs));
2901 return res;
2902}
2903
2904template <ComponentType T>
2906 Matrix<T> res(lhs);
2908 return res;
2909}
2910
2911template <ComponentType T>
2913 Matrix<T> res(std::move(lhs));
2915 return res;
2916}
2917
2918template <ComponentType T>
2920 Matrix<T> res(std::move(lhs));
2922 return res;
2923}
2924
2925template <ComponentType T>
2927 Matrix<T> res(M);
2928 res.swap_rows(i, j);
2929 return res;
2930}
2931
2932template <ComponentType T>
2934 Matrix<T> res(M);
2935 res.swap_columns(i, j);
2936 return res;
2937}
2938
2939template <ComponentType T>
2941 Matrix<T> res(std::move(M));
2942 res.swap_rows(i, j);
2943 return res;
2944}
2945
2946template <ComponentType T>
2948 Matrix<T> res(std::move(M));
2949 res.swap_columns(i, j);
2950 return res;
2951}
2952
2953template <ComponentType T>
2954Matrix<T> scale_row(const Matrix<T>& M, const T& s, size_t i) {
2955 Matrix<T> res(M);
2956 res.scale_row(s, i);
2957 return res;
2958}
2959
2960template <ComponentType T>
2961Matrix<T> scale_column(const Matrix<T>& M, const T& s, size_t i) {
2962 Matrix<T> res(M);
2963 res.scale_column(s, i);
2964 return res;
2965}
2966
2967template <ComponentType T>
2969 Matrix<T> res(std::move(M));
2970 res.scale_row(s, i);
2971 return res;
2972}
2973
2974template <ComponentType T>
2976 Matrix<T> res(std::move(M));
2977 res.scale_column(s, i);
2978 return res;
2979}
2980
2981template <ComponentType T>
2983 Matrix<T> res(M);
2984 res.add_scaled_row(s, i, j);
2985 return res;
2986}
2987
2988template <ComponentType T>
2990 Matrix<T> res(M);
2992 return res;
2993}
2994
2995template <ComponentType T>
2997 Matrix<T> res(std::move(M));
2998 res.add_scaled_row(s, i, j);
2999 return res;
3000}
3001
3002template <ComponentType T>
3004 Matrix<T> res(std::move(M));
3006 return res;
3007}
3008
3009template <ComponentType T>
3011 Matrix<T> res(M);
3012 res.add_row(i, j);
3013 return res;
3014}
3015
3016template <ComponentType T>
3018 Matrix<T> res(M);
3019 res.add_column(i, j);
3020 return res;
3021}
3022
3023template <ComponentType T>
3025 Matrix<T> res(std::move(M));
3026 res.add_row(i, j);
3027 return res;
3028}
3029
3030template <ComponentType T>
3032 Matrix<T> res(std::move(M));
3033 res.add_column(i, j);
3034 return res;
3035}
3036
3037template <ComponentType T>
3038constexpr Matrix<T> delete_columns(const Matrix<T>& lhs, const std::vector<size_t>& v) {
3039 Matrix<T> res(lhs);
3041 return res;
3042}
3043
3044template <ComponentType T>
3045constexpr Matrix<T> delete_columns(Matrix<T>&& lhs, const std::vector<size_t>& v) {
3046 Matrix<T> res(std::move(lhs));
3048 return res;
3049}
3050
3051template <ComponentType T>
3052constexpr Matrix<T> delete_column(const Matrix<T>& lhs, size_t i) {
3053 Matrix<T> res(lhs);
3055 return res;
3056}
3057
3058template <ComponentType T>
3060 Matrix<T> res(std::move(lhs));
3062 return res;
3063}
3064
3065template <ComponentType T>
3066constexpr Matrix<T> delete_rows(const Matrix<T>& lhs, const std::vector<size_t>& v) {
3067 Matrix<T> res(lhs);
3068 res.delete_rows(v);
3069 return res;
3070}
3071
3072template <ComponentType T>
3073constexpr Matrix<T> delete_rows(Matrix<T>&& lhs, const std::vector<size_t>& v) {
3074 Matrix<T> res(std::move(lhs));
3075 res.delete_rows(v);
3076 return res;
3077}
3078
3079template <ComponentType T>
3080constexpr Matrix<T> delete_row(const Matrix<T>& lhs, size_t i) {
3081 Matrix<T> res(lhs);
3082 res.delete_row(i);
3083 return res;
3084}
3085
3086template <ComponentType T>
3088 Matrix<T> res(std::move(lhs));
3089 res.delete_row(i);
3090 return res;
3091}
3092
3093#ifdef CECCO_ERASURE_SUPPORT
3094
3095template <ComponentType T>
3097 Matrix<T> res(lhs);
3099 return res;
3100}
3101
3102template <ComponentType T>
3104 Matrix<T> res(std::move(lhs));
3106 return res;
3107}
3108
3109template <ComponentType T>
3111 Matrix<T> res(lhs);
3113 return res;
3114}
3115
3116template <ComponentType T>
3118 Matrix<T> res(std::move(lhs));
3120 return res;
3121}
3122
3123template <ComponentType T>
3124constexpr Matrix<T> erase_columns(const Matrix<T>& lhs, const std::vector<size_t>& v) {
3125 Matrix<T> res(lhs);
3127 return res;
3128}
3129
3130template <ComponentType T>
3131constexpr Matrix<T> erase_columns(Matrix<T>&& lhs, const std::vector<size_t>& v) {
3132 Matrix<T> res(std::move(lhs));
3134 return res;
3135}
3136
3137template <ComponentType T>
3138constexpr Matrix<T> erase_column(const Matrix<T>& lhs, size_t i) {
3139 Matrix<T> res(lhs);
3141 return res;
3142}
3143
3144template <ComponentType T>
3146 Matrix<T> res(std::move(lhs));
3148 return res;
3149}
3150
3151template <ComponentType T>
3152constexpr Matrix<T> unerase_columns(const Matrix<T>& lhs, const std::vector<size_t>& v) {
3153 Matrix<T> res(lhs);
3155 return res;
3156}
3157
3158template <ComponentType T>
3159constexpr Matrix<T> unerase_columns(Matrix<T>&& lhs, const std::vector<size_t>& v) {
3160 Matrix<T> res(std::move(lhs));
3162 return res;
3163}
3164
3165template <ComponentType T>
3166constexpr Matrix<T> unerase_column(const Matrix<T>& lhs, size_t i) {
3167 Matrix<T> res(lhs);
3169 return res;
3170}
3171
3172template <ComponentType T>
3174 Matrix<T> res(std::move(lhs));
3176 return res;
3177}
3178
3179template <ComponentType T>
3180constexpr Matrix<T> erase_rows(const Matrix<T>& lhs, const std::vector<size_t>& v) {
3181 Matrix<T> res(lhs);
3182 res.erase_rows(v);
3183 return res;
3184}
3185
3186template <ComponentType T>
3187constexpr Matrix<T> erase_rows(Matrix<T>&& lhs, const std::vector<size_t>& v) {
3188 Matrix<T> res(std::move(lhs));
3189 res.erase_rows(v);
3190 return res;
3191}
3192
3193template <ComponentType T>
3194constexpr Matrix<T> erase_row(const Matrix<T>& lhs, size_t i) {
3195 Matrix<T> res(lhs);
3196 res.erase_row(i);
3197 return res;
3198}
3199
3200template <ComponentType T>
3202 Matrix<T> res(std::move(lhs));
3203 res.erase_row(i);
3204 return res;
3205}
3206
3207template <ComponentType T>
3208constexpr Matrix<T> unerase_rows(const Matrix<T>& lhs, const std::vector<size_t>& v) {
3209 Matrix<T> res(lhs);
3211 return res;
3212}
3213
3214template <ComponentType T>
3215constexpr Matrix<T> unerase_rows(Matrix<T>&& lhs, const std::vector<size_t>& v) {
3216 Matrix<T> res(std::move(lhs));
3218 return res;
3219}
3220
3221template <ComponentType T>
3222constexpr Matrix<T> unerase_row(const Matrix<T>& lhs, size_t i) {
3223 Matrix<T> res(lhs);
3224 res.unerase_row(i);
3225 return res;
3226}
3227
3228template <ComponentType T>
3230 Matrix<T> res(std::move(lhs));
3231 res.unerase_row(i);
3232 return res;
3233}
3234
3235#endif
3236
3237template <ComponentType T>
3239 Matrix<T> res(M);
3240 res.reverse_rows();
3241 return res;
3242}
3243
3244template <ComponentType T>
3246 Matrix<T> res(std::move(M));
3247 res.reverse_rows();
3248 return res;
3249}
3250
3251template <ComponentType T>
3253 Matrix<T> res(M);
3255 return res;
3256}
3257
3258template <ComponentType T>
3260 Matrix<T> res(std::move(M));
3262 return res;
3263}
3264
3265template <ComponentType T>
3266constexpr Matrix<T> fill(const Matrix<T>& m, const T& value) {
3267 Matrix<T> res(m);
3268 res.fill(value);
3269 return res;
3270}
3271
3272template <ComponentType T>
3273constexpr Matrix<T> fill(Matrix<T>&& m, const T& value) {
3274 Matrix<T> res(std::move(m));
3275 res.fill(value);
3276 return res;
3277}
3278
3279template <ComponentType T>
3280constexpr Matrix<T> transpose(const Matrix<T>& M) {
3281 Matrix<T> res(M);
3282 res.transpose();
3283 return res;
3284}
3285
3286template <ComponentType T>
3287constexpr Matrix<T> transpose(Matrix<T>&& M) {
3288 Matrix<T> res(std::move(M));
3289 res.transpose();
3290 return res;
3291}
3292
3293template <FieldType T>
3294Matrix<T> rref(const Matrix<T>& M) {
3295 Matrix<T> res(M);
3296 res.rref();
3297 return res;
3298}
3299
3300template <FieldType T>
3302 Matrix<T> res(std::move(M));
3303 res.rref();
3304 return res;
3305}
3306
3307template <ComponentType T>
3309 Matrix<T> res(M);
3310 res.invert();
3311 return res;
3312}
3313
3314template <ComponentType T>
3316 Matrix<T> res(std::move(M));
3317 res.invert();
3318 return res;
3319}
3320
3321/**
3322 * @brief Equality of two matrices
3323 *
3324 * @return true iff dimensions match and all components are equal
3325 *
3326 * Tag-aware fast paths handle structured operands (e.g. two Toeplitz matrices compare only
3327 * their first row and column).
3328 */
3329template <ReliablyComparableType T>
3330constexpr bool operator==(const Matrix<T>& lhs, const Matrix<T>& rhs) {
3331 if (lhs.m != rhs.m || lhs.n != rhs.n) return false;
3332
3333 if (lhs.m == 0) {
3334 return true;
3335 } else if (lhs.m == 1) {
3336 return lhs.get_row(0) == rhs.get_row(0);
3337 } else if (lhs.n == 1) {
3338 return lhs.get_col(0) == rhs.get_col(0);
3339 } else if (lhs.type == details::Toeplitz && rhs.type == details::Toeplitz) {
3340 // Compare left column (in reverse order for details::Toeplitz structure)
3341 for (size_t mu = 0; mu < lhs.m; ++mu)
3342 if (lhs(lhs.m - 1 - mu, 0) != rhs(lhs.m - 1 - mu, 0)) return false;
3343 // Compare top row (excluding first element)
3344 for (size_t nu = 1; nu < lhs.n; ++nu)
3345 if (lhs(0, nu) != rhs(0, nu)) return false;
3346 } else if (lhs.type == details::Vandermonde && rhs.type == details::Vandermonde) {
3347 return lhs.get_row(1) == rhs.get_row(1);
3348 } else if ((lhs.type == details::Diagonal && rhs.type == details::Diagonal) ||
3351 return lhs.diagonal() == rhs.diagonal();
3352 } else if (lhs.type == details::Identity && rhs.type == details::Identity) {
3353 /* no-op */
3354 } else if ((lhs.type == details::Zero && rhs.type != details::Zero) ||
3355 (lhs.type != details::Zero && rhs.type == details::Zero)) {
3356 return false;
3357 } else {
3358 if (!lhs.transposed && !rhs.transposed) {
3359 return std::equal(lhs.data.begin(), lhs.data.end(), rhs.data.begin());
3360 } else {
3361 for (size_t mu = 0; mu < lhs.m; ++mu)
3362 for (size_t nu = 0; nu < lhs.n; ++nu)
3363 if (lhs(mu, nu) != rhs(mu, nu)) return false;
3364 return true;
3365 }
3366 }
3367 return true;
3368}
3369
3370/**
3371 * @brief Negation of @ref operator==
3372 *
3373 * @return true iff dimensions or any component differ
3374 */
3375template <ReliablyComparableType T>
3376constexpr bool operator!=(const Matrix<T>& lhs, const Matrix<T>& rhs) {
3377 return !(lhs == rhs);
3378}
3379
3380/**
3381 * @brief Pretty-print the matrix with column alignment and bracket borders
3382 *
3383 * @return Reference to @p os for chaining
3384 *
3385 * An empty matrix is printed as "(empty matrix)".
3386 */
3387template <ComponentType T>
3389 if (rhs.m == 0 || rhs.n == 0) {
3390 os << " (empty matrix)";
3391 return os;
3392 }
3393 size_t max = 0;
3395 for (size_t i = 0; i < rhs.m; ++i)
3396 for (size_t j = 0; j < rhs.n; ++j) {
3397 ss << rhs(i, j);
3398 max = std::max(ss.str().length(), max);
3399 ss.str(std::string()); // clear stringstream
3400 }
3401 os << " " << (rhs.m == 1 ? "(" : "⌈");
3402 for (size_t j = 0; j + 1 < rhs.n; ++j) {
3403 os << std::setw(max) << rhs(0, j);
3404 os << " "; // must be in extra line due to set::setw()
3405 }
3406 os << std::setw(max) << rhs(0, rhs.n - 1);
3407 os << (rhs.m == 1 ? ")" : "⌉");
3408 if (rhs.m > 1) os << std::endl;
3409 if (rhs.m > 2) {
3410 for (size_t i = 1; i + 1 < rhs.m; ++i) {
3411 os << " |";
3412 for (size_t j = 0; j + 1 < rhs.n; ++j) {
3413 os << std::setw(max) << rhs(i, j);
3414 os << " "; // must be in extra line due to set::setw()
3415 }
3416 os << std::setw(max) << rhs(i, rhs.n - 1);
3417 os << "|" << std::endl;
3418 }
3419 }
3420 if (rhs.m > 1) {
3421 os << " ⌊";
3422 for (size_t j = 0; j + 1 < rhs.n; ++j) {
3423 os << std::setw(max) << rhs(rhs.m - 1, j);
3424 os << " "; // must be in extra line due to set::setw()
3425 }
3426 os << std::setw(max) << rhs(rhs.m - 1, rhs.n - 1);
3427 os << "⌋";
3428 }
3429 return os;
3430}
3431
3432/*
3433 * factories
3434 */
3435
3436/**
3437 * @brief m × n matrix of zeros (tag @ref details::Zero)
3438 *
3439 * @param m Number of rows
3440 * @param n Number of columns
3441 * @return Newly constructed zero matrix
3442 */
3443template <ComponentType T>
3445 return Matrix<T>(m, n);
3446}
3447
3448/**
3449 * @brief m × m identity matrix I_m (tag @ref details::Identity)
3450 *
3451 * @param m Side length
3452 * @return Newly constructed identity matrix
3453 */
3454template <ComponentType T>
3456 auto res = Matrix<T>(m, m);
3457 for (size_t i = 0; i < m; ++i) res.set_component(i, i, T(1));
3459 return res;
3460}
3461
3462/**
3463 * @brief Permutation matrix P with P_{i, perm[i]} = 1
3464 *
3465 * @param perm Permutation of {0, …, m-1}; m is inferred from `perm.size()`
3466 * @return m × m permutation matrix; the identity permutation returns @ref IdentityMatrix
3467 *
3468 * @throws std::invalid_argument if perm contains out-of-range or duplicate indices
3469 */
3470template <ComponentType T>
3472 const size_t m = perm.size();
3473
3474 std::vector<bool> seen(m, false);
3475 bool is_identity_perm = true;
3476 for (size_t i = 0; i < m; ++i) {
3477 if (perm[i] >= m || seen[perm[i]])
3478 throw std::invalid_argument("Trying to construct permutation matrix from a list that is not a permutation");
3479 seen[perm[i]] = true;
3480 if (perm[i] != i) is_identity_perm = false;
3481 }
3482
3483 if (is_identity_perm) return IdentityMatrix<T>(m);
3484
3485 auto res = Matrix<T>(m, m);
3486 for (size_t i = 0; i < m; ++i) res.set_component(i, perm[i], T(1));
3487
3488 return res;
3489}
3490
3491/**
3492 * @brief m × m exchange matrix (ones on the anti-diagonal)
3493 *
3494 * @param m Side length
3495 * @return Matrix with E_{i, j} = 1 iff i + j = m − 1
3496 */
3497template <ComponentType T>
3499 auto res = IdentityMatrix<T>(m);
3501 return res;
3502}
3503
3504/**
3505 * @brief Diagonal matrix with diagonal v (tag @ref details::Diagonal)
3506 *
3507 * @param v Vector of diagonal entries; the result is `v.length()` × `v.length()`
3508 * @return Diagonal matrix with v[i] on position (i, i), zeros elsewhere
3509 */
3510template <ComponentType T>
3511constexpr Matrix<T> DiagonalMatrix(const Vector<T>& v) {
3512 const size_t m = v.get_n();
3513 Matrix<T> res(m, m);
3514 for (size_t i = 0; i < m; ++i) res.set_component(i, i, v[i]);
3516 return res;
3517}
3518
3519/**
3520 * @brief m × n Toeplitz matrix from its diagonal entries (tag @ref details::Toeplitz)
3521 *
3522 * @param v Diagonal values, ordered so that v[i − j + n − 1] sits on entry (i, j); length m + n − 1
3523 * @param m Number of rows
3524 * @param n Number of columns
3525 * @return Toeplitz matrix; each descending diagonal is constant
3526 *
3527 * @throws std::invalid_argument if `v.length() != m + n - 1`
3528 */
3529template <ComponentType T>
3530constexpr Matrix<T> ToeplitzMatrix(const Vector<T>& v, size_t m, size_t n) {
3531 if (v.get_n() != m + n - 1)
3532 throw std::invalid_argument(
3533 "vector for constructing m x n details::Toeplitz matrix must have "
3534 "length m+n-1");
3535 Matrix<T> res(m, n);
3536
3537 // Fill first column: v[0] to v[m-1] in reverse order
3538 for (size_t i = 0; i < m; ++i) res.set_component(m - 1 - i, 0, v[i]);
3539
3540 // Fill first row: v[m-1] to v[m+n-2]
3541 for (size_t j = 1; j < n; ++j) res.set_component(0, j, v[m - 1 + j]);
3542
3543 // Fill remaining elements using diagonal copy pattern
3544 for (size_t i = 1; i < m; ++i)
3545 for (size_t j = 1; j < n; ++j) res.set_component(i, j, res(i - 1, j - 1));
3547 return res;
3548}
3549
3550/**
3551 * @brief m × n Hankel matrix from its anti-diagonal entries
3552 *
3553 * @param v Anti-diagonal values; length m + n − 1
3554 * @param m Number of rows
3555 * @param n Number of columns
3556 * @return Hankel matrix; each ascending anti-diagonal is constant
3557 *
3558 * Constructed as `ToeplitzMatrix(reverse(v), m, n) * ExchangeMatrix(n)`.
3559 *
3560 * @throws std::invalid_argument if `v.length() != m + n - 1`
3561 */
3562template <ComponentType T>
3563constexpr Matrix<T> HankelMatrix(const Vector<T>& v, size_t m, size_t n) {
3564 return ToeplitzMatrix<T>(reverse(v), m, n) * ExchangeMatrix<T>(n);
3565}
3566
3567/**
3568 * @brief Vandermonde matrix V_{i, j} = v[j]^i (tag @ref details::Vandermonde)
3569 *
3570 * @param v Evaluation points; must be pairwise distinct
3571 * @param m Number of rows (i.e. the highest power is m − 1)
3572 * @return m × `v.length()` Vandermonde matrix
3573 *
3574 * @throws std::invalid_argument if v is empty, has duplicates, or m is zero
3575 */
3576template <ComponentType T>
3577constexpr Matrix<T> VandermondeMatrix(const Vector<T>& v, size_t m) {
3578 const size_t n = v.get_n();
3579 if (n == 0)
3580 throw std::invalid_argument(
3581 "vector for constructing details::Vandermonde matrix must have "
3582 "at least one element");
3583 if (m == 0) throw std::invalid_argument("trying to construct details::Vandermonde matrix with zero rows");
3584 if (!v.is_pairwise_distinct())
3585 throw std::invalid_argument(
3586 "vector for constructing details::Vandermonde matrix must have pairwise distinct elements");
3587
3588 Matrix<T> res(m, n);
3589
3590 // First row: all ones
3591
3592 for (size_t i = 0; i < n; ++i) res.set_component(0, i, T(1));
3593
3594 if (m > 1) {
3595 // Second row: copy from vector v
3596 for (size_t i = 0; i < n; ++i) res.set_component(1, i, v[i]);
3597
3598 // Remaining rows: compute powers
3599 for (size_t j = 2; j < m; ++j)
3600 for (size_t i = 0; i < n; ++i) res.set_component(j, i, res(j - 1, i) * v[i]);
3601 }
3603 return res;
3604}
3605
3606/**
3607 * @brief m × m upper shift matrix (ones on the superdiagonal)
3608 *
3609 * @param m Side length
3610 * @return Matrix with U_{i, i+1} = 1, all other entries 0
3611 */
3612template <ComponentType T>
3614 Matrix<T> res(m, m);
3615 for (size_t i = 0; i + 1 < m; ++i) res.set_component(i, i + 1, T(1));
3616 return res;
3617}
3618
3619/**
3620 * @brief m × m lower shift matrix (ones on the subdiagonal); transpose of @ref UpperShiftMatrix
3621 *
3622 * @param m Side length
3623 * @return Matrix with L_{i+1, i} = 1, all other entries 0
3624 */
3625template <ComponentType T>
3627 return transpose(UpperShiftMatrix<T>(m));
3628}
3629
3630/**
3631 * @brief Companion matrix of a monic polynomial p(x) = x^n + a_{n−1} x^{n−1} + … + a_0
3632 *
3633 * @param poly Monic polynomial of degree n
3634 * @return n × n matrix whose characteristic polynomial is `poly`
3635 *
3636 * Built from a lower shift matrix; the last column holds the negated coefficients of `poly`.
3637 *
3638 * @throws std::invalid_argument if `poly` is not monic
3639 */
3640template <ComponentType T>
3642 if (!poly.is_monic()) throw std::invalid_argument("companion matrices only defined for monic polynomials");
3644
3645 // Fill last column with negated polynomial coefficients
3646 for (size_t i = 0; i < poly.degree(); ++i) res.set_component(i, poly.degree() - 1, -poly[i]);
3647
3648 return res;
3649}
3650
3651} // namespace CECCO
3652
3653#endif
Dense m × n matrix over a CECCO::ComponentType.
Definition matrices.hpp:174
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:377
constexpr bool is_zero()
Check if the matrix is zero, caching the result via the type tag.
Definition matrices.hpp:387
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:284
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:405
std::vector< Vector< T > > span() const
Alias for rowspace.
Definition matrices.hpp:516
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 (tag details::Toeplitz)
Matrix< T > & set_component(size_t i, size_t j, U &&c)
bool is_invertible() const
Check if matrix is invertible.
Definition matrices.hpp:427
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:470
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).
std::vector< Vector< T > > rowspace() const
Enumerate every vector in the row space.
constexpr size_t get_m() const noexcept
Get the number of rows.
Definition matrices.hpp:363
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:399
Matrix< T > & reverse_columns()
Reverse the column order.
constexpr Matrix(const Matrix &other)
Definition matrices.hpp:220
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:370
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.
Polynomial< T > characteristic_polynomial() const
Characteristic polynomial det(λI − A).
Matrix< T > & rref(size_t *rank=nullptr)
Reduced row echelon form (RREF).
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:194
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 (tag details::Diagonal).
friend constexpr Matrix< T > IdentityMatrix(size_t m)
m × m identity matrix I_m (tag details::Identity)
constexpr Matrix< T > & transpose()
Transpose in place.
constexpr Matrix(Matrix &&other) noexcept
Definition matrices.hpp:228
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:715
constexpr Matrix operator+() &&noexcept
Unary + (rvalue): returns the rvalue itself.
Definition matrices.hpp:286
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).
Matrix & operator/=(const T &s)
Divide every component by the scalar s.
Matrix< T > & ref(size_t *rank=nullptr)
Row echelon form (REF), with a binary-field fast path.
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 Matrix< T > & fill(const T &s)
Set every component to s.
constexpr Vector< S > as_vector() const
Reinterpret the columns as elements of a superfield.
friend constexpr Matrix< T > VandermondeMatrix(const Vector< T > &v, size_t m)
Vandermonde matrix V_{i, j} = v[j]^i (tag details::Vandermonde).
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:757
Matrix< T > & add_column(size_t i, size_t j)
col[j] ← col[j] + col[i]
Definition matrices.hpp:730
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.
Matrix< T > & delete_row(size_t i)
Delete row i (single-index convenience for delete_rows).
Definition matrices.hpp:777
Matrix & randomize()
Fill matrix with random values.
Univariate polynomial p(x) = a₀ + a₁x + … + aₙxⁿ over a CECCO::ComponentType.
Vector v = (v₀, v₁, …, vₙ₋₁) over a CECCO::ComponentType.
Definition vectors.hpp:115
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 (tag details::Toeplitz)
constexpr Matrix< T > VandermondeMatrix(const Vector< T > &v, size_t m)
Vandermonde matrix V_{i, j} = v[j]^i (tag details::Vandermonde).
constexpr Matrix< T > DiagonalMatrix(const Vector< T > &v)
Diagonal matrix with diagonal v (tag details::Diagonal).
constexpr Matrix< T > IdentityMatrix(size_t m)
m × m identity matrix I_m (tag details::Identity)
constexpr Matrix< T > ZeroMatrix(size_t m, size_t n)
m × n matrix of zeros (tag details::Zero)