NumericAL
A header-only C++20 lexer built on REAL
Loading...
Searching...
No Matches
matrix.hpp
Go to the documentation of this file.
1
11#ifndef NUMERICAL_MATRIX_HPP
12#define NUMERICAL_MATRIX_HPP
13
14#include <algorithm>
15#include <cmath>
16#include <complex> // so std::abs resolves for a complex scalar, regardless of include order
17#include <cstddef>
18#include <functional>
19#include <initializer_list>
20#include <utility>
21#include <vector>
22
23#include "gemm.hpp"
24#include "parallel.hpp"
25#include "vector.hpp"
26
27namespace numerical {
28
33 template <typename T>
34 class matrix
35 {
36 public:
37
39 matrix() = default;
40
46 matrix(std::initializer_list<std::initializer_list<T>> rows)
47 : rows_(rows.size())
48 {
49 std::size_t r {0};
50 for (const std::initializer_list<T>& row : rows) {
51 if (r == 0) {
52 cols_ = row.size();
53 }
54 else if (row.size() != cols_) {
55 throw dimension_error("matrix rows have unequal lengths");
56 }
57 data_.insert(data_.end(), row.begin(), row.end());
58 ++r;
59 }
60 }
61
68 explicit matrix(std::size_t rows,
69 std::size_t cols,
72 {
73 if (data_.size() != detail::checked_mul(rows_, cols_)) {
74 throw dimension_error("matrix buffer size does not match dimensions");
75 }
76 }
77
84 explicit matrix(std::size_t rows,
85 std::size_t cols,
86 const std::vector<T>& elements)
87 : matrix(rows,
88 cols,
90 {}
91
98 explicit matrix(std::size_t rows,
99 std::size_t cols,
100 std::initializer_list<T> elements)
101 : matrix(rows,
102 cols,
103 buffer<T>(elements))
104 {}
105
107 [[nodiscard]] std::size_t rows() const noexcept
108 {
109 return rows_;
110 }
111
113 [[nodiscard]] std::size_t cols() const noexcept
114 {
115 return cols_;
116 }
117
119 [[nodiscard]] const T& operator()(std::size_t row,
120 std::size_t col) const
121 {
122 return data_[(row * cols_) + col];
123 }
124
126 [[nodiscard]] bool operator==(const matrix& other) const
127 {
128 return rows_ == other.rows_ && cols_ == other.cols_ && data_ == other.data_;
129 }
130
133 {
134 return data_.begin();
135 }
136
139 {
140 return data_.end();
141 }
142
146 {
147 rows_ = 0;
148 cols_ = 0;
149 return std::move(data_);
150 }
151
152 private:
153
154 std::size_t rows_ {0};
155 std::size_t cols_ {0};
157 };
158
160 template <typename T>
162 const matrix<T>& b)
163 {
164 if (a.rows() != b.rows() || a.cols() != b.cols()) {
165 throw dimension_error("matrix shapes differ in '+'");
166 }
167 buffer<T> out(a.rows() * a.cols());
168 std::transform(a.begin(), a.end(), b.begin(), out.begin(), std::plus<T> {});
169 return matrix<T> {a.rows(), a.cols(), std::move(out)};
170 }
171
173 template <typename T>
175 const matrix<T>& b)
176 {
177 if (a.rows() != b.rows() || a.cols() != b.cols()) {
178 throw dimension_error("matrix shapes differ in '-'");
179 }
180 buffer<T> out(a.rows() * a.cols());
181 std::transform(a.begin(), a.end(), b.begin(), out.begin(), std::minus<T> {});
182 return matrix<T> {a.rows(), a.cols(), std::move(out)};
183 }
184
186 template <typename T>
187 [[nodiscard]] matrix<T> operator*(const T& scalar,
188 const matrix<T>& m)
189 {
190 buffer<T> out(m.rows() * m.cols());
191 std::transform(m.begin(), m.end(), out.begin(),
192 [&scalar](const T& element) { return scalar * element; });
193 return matrix<T> {m.rows(), m.cols(), std::move(out)};
194 }
195
197 template <typename T>
199 const T& scalar)
200 {
201 return scalar * m;
202 }
203
206 template <typename T>
208 const vector<T>& v)
209 {
210 if (a.cols() != v.size()) {
211 throw dimension_error("matrix–vector product: inner dimensions differ");
212 }
213 buffer<T> out(a.rows());
214 if (a.cols() == 0) {
215 std::fill(out.begin(), out.end(), T {}); // each element an empty sum
216 }
217 else {
218 detail::matrix_times_vector(&*a.begin(), a.rows(), a.cols(), &v[0], out.data());
219 }
220 return vector<T> {std::move(out)};
221 }
222
224 template <typename T>
226 const matrix<T>& b)
227 {
228 if (a.cols() != b.rows()) {
229 throw dimension_error("matrix–matrix product: inner dimensions differ");
230 }
231 // detail::gemm adds to `out`, every element in ascending order of the inner index, whatever the blocking and
232 // the number of threads (see gemm.hpp): `out` starts at zero, written by the threads that will compute it.
233 buffer<T> out(detail::checked_mul(a.rows(), b.cols())); // guard the cross-product alloc
234 detail::parallel_zero(out.data(), out.size());
235 if (!out.empty() && a.cols() != 0) {
236 detail::gemm(a.rows(), b.cols(), a.cols(), &*a.begin(), a.cols(), &*b.begin(), b.cols(), out.data(), b.cols());
237 }
238 return matrix<T> {a.rows(), b.cols(), std::move(out)};
239 }
240
243 inline constexpr std::size_t transpose_tile {32};
244
249 template <typename T>
251 {
252 const std::size_t rows {m.rows()};
253 const std::size_t cols {m.cols()};
254 buffer<T> out(rows * cols);
255 detail::parallel_for((cols + transpose_tile - 1) / transpose_tile, transpose_tile * rows, [&](std::size_t block) {
256 const std::size_t j0 {block * transpose_tile};
257 const std::size_t j1 {std::min(j0 + transpose_tile, cols)};
258 for (std::size_t i0 {0}; i0 < rows; i0 += transpose_tile) {
259 const std::size_t i1 {std::min(i0 + transpose_tile, rows)};
260 for (std::size_t j {j0}; j < j1; ++j) {
261 for (std::size_t i {i0}; i < i1; ++i) {
262 out[(j * rows) + i] = m(i, j);
263 }
264 }
265 }
266 });
267 return matrix<T> {cols, rows, std::move(out)};
268 }
269
277 template <typename T>
279 const matrix<T>& b,
280 double tolerance = 1e-9)
281 {
282 if (a.rows() != b.rows() || a.cols() != b.cols()) {
283 return false;
284 }
285 for (std::size_t i {0}; i < a.rows(); ++i) {
286 for (std::size_t j {0}; j < a.cols(); ++j) {
287 // Equal, or within the tolerance: a NaN is neither, so it is never close, and two equal
288 // infinities are equal although their difference is NaN.
289 if (!(a(i, j) == b(i, j)) && !(std::abs(a(i, j) - b(i, j)) <= tolerance)) {
290 return false;
291 }
292 }
293 }
294 return true;
295 }
296} // namespace numerical
297
298#endif // NUMERICAL_MATRIX_HPP
Thrown when an operation's operands have incompatible shapes.
Definition vector.hpp:35
A dense, row-major matrix of T with value semantics.
Definition matrix.hpp:35
bool operator==(const matrix &other) const
Exact equality (same shape and equal elements).
Definition matrix.hpp:126
matrix()=default
An empty matrix (0×0).
buffer< T >::const_iterator end() const noexcept
Const iterator past the last element.
Definition matrix.hpp:138
matrix(std::size_t rows, std::size_t cols, buffer< T > elements)
From dimensions and a flat row-major buffer.
Definition matrix.hpp:68
matrix(std::initializer_list< std::initializer_list< T > > rows)
From a braced list of rows; every row must have the same length.
Definition matrix.hpp:46
buffer< T >::const_iterator begin() const noexcept
Const iterator to the first element (row-major).
Definition matrix.hpp:132
buffer< T > data_
The elements, row-major.
Definition matrix.hpp:156
std::size_t rows() const noexcept
The number of rows.
Definition matrix.hpp:107
std::size_t rows_
The number of rows.
Definition matrix.hpp:154
buffer< T > elements() &&
The elements, row-major, handed over by a matrix about to expire, which is left 0 × 0: a large result...
Definition matrix.hpp:145
const T & operator()(std::size_t row, std::size_t col) const
The element at (row, col).
Definition matrix.hpp:119
std::size_t cols_
The number of columns.
Definition matrix.hpp:155
std::size_t cols() const noexcept
The number of columns.
Definition matrix.hpp:113
matrix(std::size_t rows, std::size_t cols, const std::vector< T > &elements)
From dimensions and a flat row-major std::vector, copied into the storage (a buffer moves in instead)...
Definition matrix.hpp:84
matrix(std::size_t rows, std::size_t cols, std::initializer_list< T > elements)
From dimensions and a braced list of the elements, row-major.
Definition matrix.hpp:98
A dense vector of T with value semantics.
Definition vector.hpp:86
std::size_t size() const noexcept
The number of elements.
Definition vector.hpp:109
The matrix product kernel behind matrix * matrix and the einsum matmul path.
void parallel_zero(T *out, std::size_t count)
Sets the count elements at out to zero, split across the threads the work pays for: a fresh result's ...
Definition parallel.hpp:682
void gemm(std::size_t m, std::size_t n, std::size_t k, const T *a, std::size_t lda, const T *b, std::size_t ldb, T *c, std::size_t ldc)
C += A·B, every element of C summed in ascending order of the depth.
Definition gemm.hpp:2740
void parallel_for(std::size_t count, std::size_t work_per_index, const Body &body)
Runs body(i) for every i in [0, count).
Definition parallel.hpp:580
std::size_t checked_mul(std::size_t a, std::size_t b)
a * b, throwing numerical::dimension_error if the product overflows std::size_t. Guards dimension/sha...
Definition vector.hpp:50
void matrix_times_vector(const T *a, std::size_t rows, std::size_t cols, const T *x, T *y)
y = A x for the row-major rows × cols a: each element the dot product of its row with x,...
Definition gemm.hpp:3196
The NumericAL public API.
Definition amx.hpp:44
std::vector< T, detail::default_init_allocator< T > > buffer
The storage of vector, matrix and tensor, namely a std::vector whose buffer<T>(n) and resize(n) leave...
Definition buffer.hpp:157
matrix< T > operator+(const matrix< T > &a, const matrix< T > &b)
Element-wise sum.
Definition matrix.hpp:161
matrix< T > operator*(const T &scalar, const matrix< T > &m)
Scalar times matrix.
Definition matrix.hpp:187
constexpr std::size_t transpose_tile
The square blocks transpose copies one at a time: a block's rows of the source and of the result both...
Definition matrix.hpp:243
matrix< T > transpose(const matrix< T > &m)
The transpose, by blocks of transpose_tile, the result's blocks of rows shared among the threads.
Definition matrix.hpp:250
@ amx
The Apple AMX coprocessor, fused multiply-adds; the products it does not suit on the NEON path's tile...
matrix< T > operator-(const matrix< T > &a, const matrix< T > &b)
Element-wise difference.
Definition matrix.hpp:174
bool approx_equal(const matrix< T > &a, const matrix< T > &b, double tolerance=1e-9)
Element-wise closeness within a tolerance (for inexact scalars).
Definition matrix.hpp:278
A minimal std::thread parallel-for for the hot kernels.
A dense, value-semantic vector over a scalar type.