matrix.hh
Go to the documentation of this file.
1#pragma once
2#include <random>
3#include <memory>
4#include <complex>
5#include <type_traits>
6#include <iostream> // Added for std::ostream, std::cout
7#include <iomanip> // Added for std::setw, std::scientific, etc.
8#include <algorithm> // Added for std::min
9#include <tuple>
10#include <array> // Added for std::array element types
11#include <sstream> // Added for temporary string formatting of non-streamable types
12#include <cmath>
13#include <cstdint>
14#include <optional>
15#include <span>
16#include <vector>
17#include <batchlas/export.hh>
22
23/// @file
24/// @brief The batched containers: Matrix (owning), MatrixView (non-owning),
25/// KernelMatrixView (device-side), Vector and VectorView.
26///
27/// Dense storage is column-major. Element (i, j) of batch item b lives at
28/// `data[b * stride + j * ld + i]`, all three counted in elements.
29/// @see @ref design_matrix_model
30
31namespace batchlas {
32 // Forward declarations with default template parameters
33 template <typename T = float, MatrixFormat MType = MatrixFormat::Dense>
34 class BATCHLAS_API Matrix;
35
36 template <typename T = float, MatrixFormat MType = MatrixFormat::Dense>
37 class BATCHLAS_API MatrixView;
38
39 //Forward declare VectorView with default parameter
40 template <typename T = float>
41 class BATCHLAS_API VectorView;
42
43 template <typename T>
44 struct Vector;
45
46 /// @brief Backend-library descriptor for a matrix (cuSPARSE / rocSPARSE handles); defined in src/.
47 /// @ingroup api_internal_helpers
48 template <typename T = float, MatrixFormat MType = MatrixFormat::Dense>
50
51 /// @brief Non-zero count (per-item capacity) of a CSR matrix, as a distinct type.
52 ///
53 /// The dense and CSR constructors would otherwise differ only in what the third
54 /// `int` means: `Matrix(rows, cols, batch)` versus `Matrix(rows, cols, nnz)`.
55 /// @invariant `operator int` is deleted so the tag cannot decay back into that ambiguity.
56 /// @see @ref matrix-model-strong-types-for-positional-integers
57 /// @ingroup api_matrix
58 struct NonZeros {
59 int value; ///< The wrapped value.
60 explicit constexpr NonZeros(int v) : value(v) {} ///< Wraps @p v.
61 constexpr operator int() const = delete; ///< Deleted: no silent decay back to int.
62 };
63 static_assert(std::is_trivially_copyable_v<NonZeros>, "NonZeros must stay trivially copyable");
64
65 /// @brief Element distance between consecutive vector entries, as a distinct type.
66 ///
67 /// Vector takes (stride, inc) where VectorView's positional constructors take
68 /// (inc, stride); both readings fit the same buffer, so a transliterated call
69 /// reads the wrong addresses silently. The tags make the order a compile error.
70 /// @invariant `operator int` is deleted: without it the tag decays back into the ambiguity.
71 /// @see @ref matrix-model-strong-types-for-positional-integers
72 /// @ingroup api_matrix
73 struct Inc {
74 int value; ///< The wrapped value.
75 explicit constexpr Inc(int v = 1) : value(v) {} ///< Wraps @p v.
76 constexpr operator int() const = delete; ///< Deleted: no silent decay back to int.
77 };
78 static_assert(std::is_trivially_copyable_v<Inc>, "Inc must stay trivially copyable");
79
80 /// @brief Element distance between consecutive batch items, as a distinct type (0 = packed).
81 /// @see Inc
82 /// @ingroup api_matrix
83 struct Stride {
84 int value; ///< The wrapped value.
85 explicit constexpr Stride(int v = 0) : value(v) {} ///< Wraps @p v.
86 constexpr operator int() const = delete; ///< Deleted: no silent decay back to int.
87 };
88 static_assert(std::is_trivially_copyable_v<Stride>, "Stride must stay trivially copyable");
89
90 /// @brief Leading dimension (column pitch), as a distinct type (0 = packed).
91 /// @see Inc
92 /// @ingroup api_matrix
93 struct Ld {
94 int value; ///< The wrapped value.
95 explicit constexpr Ld(int v = 0) : value(v) {} ///< Wraps @p v.
96 constexpr operator int() const = delete; ///< Deleted: no silent decay back to int.
97 };
98 static_assert(std::is_trivially_copyable_v<Ld>, "Ld must stay trivially copyable");
99
100 /// @brief Number of batch items, as a distinct type.
101 /// @see Inc
102 /// @ingroup api_matrix
103 struct BatchSize {
104 int value; ///< The wrapped value.
105 explicit constexpr BatchSize(int v = 1) : value(v) {} ///< Wraps @p v.
106 constexpr operator int() const = delete; ///< Deleted: no silent decay back to int.
107 };
108 static_assert(std::is_trivially_copyable_v<BatchSize>, "BatchSize must stay trivially copyable");
109
110 /// @brief Marker for "to the end" in a Slice: `Slice(k, SliceEnd{})` selects [k, dim).
111 /// @ingroup api_matrix
112 struct SliceEnd {};
113
114 /// @brief A half-open index range [start, end) along one dimension.
115 ///
116 /// A default Slice selects the whole dimension; `Slice(k)` selects [k, dim).
117 /// A negative `start` or `end` counts from the end of the dimension (`-1` is
118 /// the last index). Slicing is dense-only and never copies.
119 /// @ingroup api_matrix
120 struct Slice { //Default Slice selects entire matrix
121 int64_t start = std::numeric_limits<int64_t>::min(); ///< First index; negative counts from the end. The default (min) with a default end means the whole dimension.
122 int64_t end = std::numeric_limits<int64_t>::max(); ///< One past the last index; negative counts from the end; max means the end of the dimension.
123 /// @brief [@p start, dim).
124 Slice(int64_t start, SliceEnd) : start(start), end(std::numeric_limits<int64_t>::max()) {}
125 /// @brief [@p start, @p end); either bound may be negative (counted from the end).
126 Slice(int64_t start, int64_t end) : start(start), end(end) {}
127 /// @brief [@p start, dim) (implicit, so an integer can stand in for a Slice).
128 Slice(int64_t start) : Slice(start, SliceEnd()) {}
129 /// @brief The whole dimension.
130 Slice() = default;
131 };
132
133 /// @brief Trivially copyable view of a MatrixView for use inside a SYCL kernel.
134 ///
135 /// Obtained from Matrix::kernel_view() or MatrixView::kernel_view() on the host
136 /// and captured by value. It holds raw pointers and extents only: no ownership,
137 /// no backend handle, no bounds checks beyond debug-build asserts. Specialised
138 /// on the format so format-specific paths resolve at compile time; slicing is
139 /// dense-only. The fields are public so kernels can read them without a call.
140 /// @tparam T scalar type
141 /// @tparam MType MatrixFormat::Dense or MatrixFormat::CSR
142 /// @see @ref matrix-model-kernelmatrixview-the-device-side-view
143 /// @ingroup api_matrix
144 template <typename T, MatrixFormat MType>
146 T* data_ = nullptr; ///< Values of batch item 0.
147 int rows_ = 0; ///< Rows (the capacity when heterogeneous).
148 int cols_ = 0; ///< Columns (the capacity when heterogeneous).
149 int batch_size_ = 1; ///< Number of batch items.
150
151 int ld_ = 0; ///< Dense: leading dimension, in elements.
152 int stride_ = 0; ///< Dense: batch stride, in elements.
153 int* active_rows_ = nullptr; ///< Dense: per-item row counts, or null when homogeneous.
154 int* active_cols_ = nullptr; ///< Dense: per-item column counts, or null when homogeneous.
155
156 int* row_offsets_ = nullptr; ///< CSR: rows + 1 offsets per item, items offset_stride_ apart.
157 int* col_indices_ = nullptr; ///< CSR: column indices, items matrix_stride_ apart.
158 int nnz_ = 0; ///< CSR: per-item non-zero capacity.
159 int matrix_stride_ = 0; ///< CSR: element distance between items' value (and index) arrays.
160 int offset_stride_ = 0; ///< CSR: element distance between items' offset arrays.
161
162 /// @brief Dense element (i, j) of batch item b: `data_[b * stride_ + j * ld_ + i]`.
163 /// @pre 0 <= i < rows_, 0 <= j < cols_, 0 <= b < batch_size_ (asserted in debug builds)
164 template <MatrixFormat MF = MType>
165 requires DenseMatrixFormat<MF>
166 inline constexpr T& operator()(int i, int j, int b = 0) const {
167 assert(i < rows_); assert(i >= 0);
168 assert(j < cols_); assert(j >= 0);
169 assert(b < batch_size_); assert(b >= 0);
170 // Only the batch term is widened; j * ld_ + i cannot overflow at allocatable sizes.
171 // evidence: docs/design/matrix-model.md#matrix-model-the-element-address-and-its-64-bit-batch-term
172 assert(static_cast<int64_t>(b) * stride_ + static_cast<int64_t>(j) * ld_ + i <
173 static_cast<int64_t>(batch_size_) *
174 (stride_ > 0 ? static_cast<int64_t>(stride_)
175 : static_cast<int64_t>(ld_) * cols_));
176 return data_[static_cast<int64_t>(b) * stride_ + j * ld_ + i]; }
177
178 /// @brief CSR coefficient (i, j) of batch item b, or zero if not stored.
179 ///
180 /// A linear search over row i's entries: O(nnz in row i).
181 template <MatrixFormat MF = MType>
182 requires CsrMatrixFormat<MF>
183 inline constexpr T get(int i, int j, int b = 0) const {
184 const std::ptrdiff_t ro_base = static_cast<std::ptrdiff_t>(b) * offset_stride_;
185 const std::ptrdiff_t val_base = static_cast<std::ptrdiff_t>(b) * matrix_stride_;
186 int rs = row_offsets_[ro_base + i];
187 int re = row_offsets_[ro_base + i + 1];
188 for (int p = rs; p < re; ++p) {
189 if (col_indices_[val_base + p] == j) return data_[val_base + p];
190 }
191 return T(0);
192 }
193
194 /// @brief A single-item view (batch_size_ = 1) of batch item @p b.
195 ///
196 /// Dense: rows and columns are the item's active extents, and the result is
197 /// homogeneous. An out-of-range @p b yields a 0 x 0 view rather than a fault.
198 inline constexpr KernelMatrixView batch_item(int b) const {
199 KernelMatrixView out = *this;
200 if (b < 0 || b >= batch_size_) { out.rows_ = out.cols_ = 0; return out; }
201 if constexpr (MType == MatrixFormat::Dense) {
202 // int64_t as in operator(): an overflowed product here poisons a raw pointer.
203 out.data_ += static_cast<int64_t>(b) * stride_;
206 out.active_rows_ = nullptr;
207 out.active_cols_ = nullptr;
208 } else if constexpr (MType == MatrixFormat::CSR) {
209 out.data_ += static_cast<int64_t>(b) * matrix_stride_;
210 out.row_offsets_ += static_cast<int64_t>(b) * offset_stride_;
211 out.col_indices_ += static_cast<int64_t>(b) * matrix_stride_; // assumes same stride
212 }
213 out.batch_size_ = 1;
214 return out;
215 }
216
217 inline auto data() const { return data_; } ///< Base pointer of item 0.
218 /// @brief Row count (the capacity on a heterogeneous batch).
219 inline auto rows() const { return rows_; }
220 /// @brief Active row count of batch item @p batch_index.
221 inline auto rows(int batch_index) const { return active_rows_ ? active_rows_[batch_index] : rows_; }
222 /// @brief Column count (the capacity on a heterogeneous batch).
223 inline auto cols() const { return cols_; }
224 /// @brief Active column count of batch item @p batch_index.
225 inline auto cols(int batch_index) const { return active_cols_ ? active_cols_[batch_index] : cols_; }
226 inline auto batch_size() const { return batch_size_; } ///< Number of batch items.
227 inline auto ld() const { return ld_; } ///< Dense leading dimension, in elements.
228 inline auto stride() const { return stride_; } ///< Dense batch stride, in elements.
229 /// @brief CSR per-item non-zero *capacity*; over-counts every item smaller than the largest.
230 inline auto nnz() const { return nnz_; }
231 /// @brief CSR non-zeros actually stored by batch item @p b, read from its row offsets.
232 inline auto nnz(int b) const {
233 const auto base = static_cast<int64_t>(b) * offset_stride_;
234 return row_offsets_[base + rows_] - row_offsets_[base];
235 }
236 /// @brief True when per-item active extents are attached.
237 inline bool is_heterogeneous() const { return active_rows_ || active_cols_; }
238
239 /// @brief Dense sub-block view; same pointer arithmetic as MatrixView::operator()(Slice, Slice).
240 /// @note Empty or inverted slices are not diagnosed here (the check is a no-op assert).
241 template <MatrixFormat MF = MType>
242 requires DenseMatrixFormat<MF>
243 KernelMatrixView operator()(Slice rows_slice, Slice cols_slice = {}) const;
244 /// @brief Dense row-range view over all columns.
245 template <MatrixFormat MF = MType>
246 requires DenseMatrixFormat<MF>
247 KernelMatrixView operator()(Slice rows_slice) const { return (*this)(rows_slice, {}); }
248
249 /// @brief Part of row @p row as a batched VectorView (inc = ld_, stride = stride_).
250 template <MatrixFormat MF = MType>
251 requires DenseMatrixFormat<MF>
252 VectorView<T> operator()(int32_t row, Slice cols_slice) const;
253
254 /// @brief Part of column @p col as a batched VectorView (inc = 1, stride = stride_).
255 template <MatrixFormat MF = MType>
256 requires DenseMatrixFormat<MF>
257 VectorView<T> operator()(Slice rows_slice, int32_t col) const;
258
259 /// @brief A null 0 x 0 view with batch size 1.
260 KernelMatrixView() = default;
261 /// @name Copy and move (trivial: the view is captured by value)
262 /// @{
267 /// @}
268
269 /// @brief Dense view over raw device-accessible memory.
270 /// @param data base of batch item 0
271 /// @param rows rows per item
272 /// @param cols columns per item
273 /// @param ld leading dimension; 0 means @p rows
274 /// @param stride batch stride; 0 means (resolved ld) * @p cols
275 /// @param batch_size number of items
276 // stride_ resolves against the *resolved* ld: `ld * cols` would give stride_ = 0 for
277 // the three-argument spelling and fire the element-access assert on every access.
278 template <MatrixFormat MF = MType>
279 requires DenseMatrixFormat<MF>
280 KernelMatrixView(T* data, int rows, int cols, int ld = 0, int stride = 0, int batch_size = 1)
282 ld_(ld > 0 ? ld : rows), stride_(stride > 0 ? stride : (ld > 0 ? ld : rows) * cols) {}
283 };
284
285 /// @cond BATCHLAS_DETAIL
286 namespace detail {
287 inline std::pair<int64_t,int64_t> normalize_slice_component(Slice s, int64_t dim) {
288 int64_t len;
289 if (s.start == std::numeric_limits<int64_t>::min() && s.end == std::numeric_limits<int64_t>::max()) { s.start = 0; len = dim; }
290 else if (s.end == std::numeric_limits<int64_t>::max()) { s.start = s.start < 0 ? dim + s.start : s.start; len = dim - s.start; }
291 else { if (s.start < 0) s.start = dim + s.start; if (s.end < 0) s.end = dim + s.end; len = s.end - s.start; }
292 return {s.start, len};
293 }
294
295 // Host slicing only: KernelMatrixView shares normalize_slice_component() in device code,
296 // which cannot throw. An empty in-range slice is left to the caller's invalid_argument.
297 inline std::pair<int64_t,int64_t> checked_slice_component(Slice s, int64_t dim) {
298 const auto [start, len] = normalize_slice_component(s, dim);
299 if (start < 0 || start > dim || start + std::max<int64_t>(len, 0) > dim) {
300 throw batchlas::out_of_range("Slice [" + std::to_string(start) + ", " +
301 std::to_string(start + len) + ") is outside a dimension of " +
302 std::to_string(dim));
303 }
304 return {start, len};
305 }
306
307 inline void validate_dense_active_dims(int rows_capacity,
308 int cols_capacity,
309 int batch_size,
310 Span<int> active_rows,
311 Span<int> active_cols) {
312 if (active_rows.empty() && active_cols.empty()) {
313 return;
314 }
315
316 if (active_rows.size() != static_cast<std::size_t>(batch_size) ||
317 active_cols.size() != static_cast<std::size_t>(batch_size)) {
318 throw batchlas::invalid_argument("Dense heterogeneous metadata must provide rows and cols for every batch item");
319 }
320
321 for (int batch_index = 0; batch_index < batch_size; ++batch_index) {
322 const int rows = active_rows[batch_index];
323 const int cols = active_cols[batch_index];
324 if (rows < 0 || cols < 0) {
325 throw batchlas::invalid_argument("Dense heterogeneous metadata cannot contain negative dimensions");
326 }
327 if (rows > rows_capacity || cols > cols_capacity) {
328 throw batchlas::invalid_argument("Dense heterogeneous metadata exceeds the matrix storage capacity");
329 }
330 }
331 }
332
333 inline int dense_active_rows(int rows_capacity, Span<int> active_rows, int batch_index) {
334 return active_rows.empty() ? rows_capacity : active_rows[batch_index];
335 }
336
337 inline int dense_active_cols(int cols_capacity, Span<int> active_cols, int batch_index) {
338 return active_cols.empty() ? cols_capacity : active_cols[batch_index];
339 }
340
341 template <typename T>
342 inline void apply_dense_slice_pointer_arithmetic(T*& base, int ld, int64_t row_start, int64_t col_start) {
343 base += col_start * ld + row_start; // column-major offset
344 }
345
346
347 template <typename T>
348 T convert_to_fill_value(int64_t value) {
349 if constexpr (std::is_floating_point_v<T>) {
350 return static_cast<T>(value);
351 } else if constexpr (std::is_integral_v<T>) {
352 return static_cast<T>(value);
353 } else if constexpr (std::is_same_v<T, std::complex<float>> ||
354 std::is_same_v<T, std::complex<double>> ||
355 std::is_same_v<T, std::complex<long double>>) {
356 using Real = typename T::value_type;
357 return T(static_cast<Real>(value));
358 } else if constexpr (
359 // Detect std::array<T, N>
360 std::is_class_v<T> &&
361 std::is_same_v<T, std::array<typename T::value_type,
362 std::tuple_size<T>::value>>
363 ) {
364 T result{};
365 for (auto &elem : result) {
366 using Elem = typename T::value_type;
367 elem = convert_to_fill_value<Elem>(value);
368 }
369 return result;
370 } else {
371 return T{};
372 }
373 }
374
375 // Detection idiom for streamability
376 template <typename U, typename = void>
377 struct is_streamable : std::false_type {};
378 template <typename U>
379 struct is_streamable<U, std::void_t<decltype(std::declval<std::ostream&>() << std::declval<const U&>())>> : std::true_type {};
380
381 // Trait to detect std::array
382 template <typename U>
383 struct is_std_array : std::false_type {};
384 template <typename V, std::size_t N>
385 struct is_std_array<std::array<V, N>> : std::true_type {};
386
387 // Generic element printer (no width handling)
388 template <typename U>
389 inline void print_value(std::ostream& os, const U& value) {
390 if constexpr (is_streamable<U>::value) {
391 os << value;
392 } else if constexpr (is_std_array<U>::value) {
393 os << '[';
394 for (std::size_t i = 0; i < value.size(); ++i) {
395 if (i) os << ',';
396 os << value[i];
397 }
398 os << ']';
399 } else {
400 os << "{?}"; // Fallback for unknown, non-streamable types
401 }
402 }
403
404 // Width-aware printer used in formatted matrix printing
405 template <typename U>
406 inline void print_with_width(std::ostream& os, const U& value, int width) {
407 if constexpr (is_streamable<U>::value) {
408 os << std::setw(width) << value;
409 } else {
410 std::ostringstream tmp;
411 print_value(tmp, value);
412 const std::string s = tmp.str();
413 if ((int)s.size() < width) {
414 os << std::setw(width) << s;
415 } else {
416 // If the representation is wider than the field, print as-is to retain info
417 os << s;
418 }
419 }
420 }
421 }
422
423 // Out-of-line KernelMatrixView members: documented at their declarations.
424 template <typename T, MatrixFormat MType>
425 template <MatrixFormat MF>
426 requires DenseMatrixFormat<MF>
427 KernelMatrixView<T, MType> KernelMatrixView<T, MType>::operator()(Slice rows_slice, Slice cols_slice) const {
428 KernelMatrixView<T, MType> out = *this;
429 auto [r_start, r_len] = detail::normalize_slice_component(rows_slice, rows_);
430 auto [c_start, c_len] = detail::normalize_slice_component(cols_slice, cols_);
431 if (r_len <= 0 || c_len <= 0) {
432 assert(r_len > 0 && c_len > 0 && "Invalid slice dimensions in KernelMatrixView");
433 }
434 detail::apply_dense_slice_pointer_arithmetic(out.data_, ld_, r_start, c_start);
435 out.rows_ = static_cast<int>(r_len);
436 out.cols_ = static_cast<int>(c_len);
437 return out;
438 }
439
440 template <typename T, MatrixFormat MType>
441 template <MatrixFormat MF>
442 requires DenseMatrixFormat<MF>
443 VectorView<T> KernelMatrixView<T, MType>::operator()(int32_t row, Slice cols_slice) const {
444 auto [c_start, c_len] = detail::normalize_slice_component(cols_slice, cols_);
445 if (c_len <= 0) {
446 assert(c_len > 0 && "Invalid slice dimensions in KernelMatrixView row extraction");
447 }
448 T* row_data = data_ + row + c_start * ld_;
449 return VectorView<T>(row_data, static_cast<int>(c_len), batch_size_, ld_, stride_);
450 }
451
452 template <typename T, MatrixFormat MType>
453 template <MatrixFormat MF>
454 requires DenseMatrixFormat<MF>
455 VectorView<T> KernelMatrixView<T, MType>::operator()(Slice rows_slice, int32_t col) const {
456 auto [r_start, r_len] = detail::normalize_slice_component(rows_slice, rows_);
457 if (r_len <= 0) {
458 assert(r_len > 0 && "Invalid slice dimensions in KernelMatrixView column extraction");
459 }
460 T* col_data = data_ + r_start + col * ld_;
461 return VectorView<T>(col_data, static_cast<int>(r_len), batch_size_, 1, stride_);
462 }
463 /// @endcond
464
465 static_assert(std::is_trivially_copyable_v<KernelMatrixView<float, MatrixFormat::Dense>>, "KernelMatrixView Dense must be trivially copyable");
466 static_assert(std::is_trivially_copyable_v<KernelMatrixView<float, MatrixFormat::CSR>>, "KernelMatrixView CSR must be trivially copyable");
467
468 /// @brief Non-owning view of a batch of matrices: the argument type of every entry point.
469 ///
470 /// Same layout as Matrix (column-major dense with ld and stride, or strided CSR)
471 /// over memory it does not own. Copying a view is cheap and aliases the data;
472 /// the viewed memory must outlive every use, including kernels still in flight.
473 /// The memory must be USM that the Queue's device can access; entry points check
474 /// the pointers at the call. A view may be built over raw pointers, a Matrix
475 /// (every Matrix is a MatrixView), a sub-block (operator()(Slice, Slice)), a single
476 /// item (batch_item()) or a VectorView.
477 ///
478 /// Members taking a `const Queue&` enqueue a kernel and return its Event without
479 /// waiting. The queue-less overloads run on a new Queue() on Device::default_device().
480 /// @tparam T scalar type
481 /// @tparam MType MatrixFormat::Dense (default) or MatrixFormat::CSR
482 /// @see @ref design_matrix_model
483 /// @ingroup api_matrix
484 template <typename T, MatrixFormat MType>
485 class BATCHLAS_API MatrixView {
486 public:
487 /// @brief Dense view over caller memory.
488 ///
489 /// Note the argument order: the batch size is the *sixth* argument here and the
490 /// third in `Matrix(rows, cols, batch_size)`.
491 /// @param data base of item 0 (may be null for a shape-only view, e.g. a workspace query)
492 /// @param rows rows per item
493 /// @param cols columns per item
494 /// @param ld leading dimension; 0 means @p rows
495 /// @param stride batch stride; 0 means (resolved ld) * @p cols. Not a broadcast.
496 /// @param batch_size number of items
497 /// @param data_ptrs optional per-item pointer array for pointer-array backends
498 /// @throws batchlas::invalid_argument on a negative argument or a resolved ld < rows
499 /// (the message names the intended spelling)
500 /// @note `stride < ld * cols` is not rejected here, unlike the Matrix constructor.
501 template <typename U = T, MatrixFormat M = MType>
502 requires DenseMatrixFormat<M>
503 MatrixView(T* data, int rows, int cols, int ld = 0,
504 int stride = 0, int batch_size = 1, T** data_ptrs = nullptr);
505
506 /// @brief CSR view over caller arrays; layout as in the CSR Matrix from-data constructor.
507 /// @param data_ptrs optional per-item pointer array to the values
508 template <typename U = T, MatrixFormat M = MType>
509 requires CsrMatrixFormat<M>
510 MatrixView(T* data, int* row_offsets, int* col_indices,
511 int rows, int cols, NonZeros nnz, int matrix_stride = 0,
512 int offset_stride = 0, int batch_size = 1, T** data_ptrs = nullptr);
513
514 /// @brief Deleted so the pre-tag order (nnz before rows, cols) is a compile error.
515 template <typename U = T, MatrixFormat M = MType>
516 requires CsrMatrixFormat<M>
517 MatrixView(T* data, int* row_offsets, int* col_indices,
518 int nnz, int rows, int cols, int matrix_stride = 0,
519 int offset_stride = 0, int batch_size = 1, T** data_ptrs = nullptr) = delete;
520
521 /// @brief Views a batched vector as n x 1 (Column) or 1 x n (Row) matrices; the stride carries over.
522 /// @throws batchlas::invalid_argument for Column when the vector's inc != 1
523 template <typename U = T, MatrixFormat M = MType>
524 requires DenseMatrixFormat<M>
525 MatrixView(const VectorView<T>& vector_view, VectorOrientation orientation = VectorOrientation::Column);
526
527 /// @brief View of @p matrix_view with any of its extents or strides overridden; a value <= 0 keeps the view's.
528 ///
529 /// Active extents carry over only when every override is a no-op.
530 template <typename U = T, MatrixFormat M = MType>
531 requires DenseMatrixFormat<M>
532 MatrixView(const MatrixView<T, MType>& matrix_view,
533 int rows = -1, int cols = -1, int ld = -1, int stride = -1, int batch_size = -1);
534
535 /// @brief An empty 0 x 0 view with batch size 0.
536 MatrixView() = default;
537
538 /// @brief Enqueues dest := src on @p ctx, honouring each side's ld and stride.
539 /// @return the event of the copy; a no-op when both views share a base pointer
540 /// @throws batchlas::invalid_argument if rows, cols or batch size differ
541 /// @throws batchlas::unsupported for CSR views
542 static Event copy(Queue& ctx, const MatrixView<T, MType>& dest, const MatrixView<T, MType>& src);
543
544 /// @name Copy and move
545 /// Shallow: the copy aliases the same memory and shares the backend handle.
546 /// @{
547 MatrixView(const MatrixView&) = default;
548 MatrixView& operator=(const MatrixView&) = default;
549 MatrixView(MatrixView&&) noexcept = default;
550 MatrixView& operator=(MatrixView&&) noexcept = default;
551 /// @}
552
553 /// @brief Releases nothing but the shared backend handle; the viewed memory is untouched.
554 ~MatrixView() = default;
555
556 /// @brief Creates the backend descriptor now instead of on first use; idempotent.
557 void init() const;
558
559 /// @brief The backend descriptor, created on first use.
560 BackendMatrixHandle<T, MType>* operator->() const;
561 /// @brief The backend descriptor, created on first use.
562 BackendMatrixHandle<T, MType>& operator*() const;
563
564 /// @brief batch_item(@p i).
565 MatrixView<T, MType> operator[](int i) const;
566
567 /// @name Public shape fields
568 /// Rows, columns and batch size (capacity extents on a heterogeneous batch). Read
569 /// through the accessors; never write.
570 /// @{
571 // Must stay initialised: the entry points' USM check reads these extents to decide
572 // whether a null data pointer is legal on a default-constructed view.
573 int rows_ = 0, cols_ = 0, batch_size_ = 0;
574 /// @}
575
576 /// @brief The viewed value storage, from item 0 to the end of the last item.
577 Span<T> data() const { return data_; }
578 /// @name USM memory hints
579 /// As Matrix::set_access_device() and Matrix::prefetch(), for the viewed memory.
580 /// @{
581 /// @brief Maps the viewed memory for access by the device of @p ctx.
582 Event set_access_device(const Queue& ctx) const {
583 if constexpr (MType == MatrixFormat::CSR) {
584 (void)row_offsets_.set_access_device(ctx);
585 (void)col_indices_.set_access_device(ctx);
586 }
587 return data_.set_access_device(ctx);
588 }
589
590 /// @brief Migrates the viewed memory to the device of @p ctx.
591 Event prefetch(const Queue& ctx) const {
592 if constexpr (MType == MatrixFormat::CSR) {
593 (void)row_offsets_.prefetch(ctx);
594 (void)col_indices_.prefetch(ctx);
595 }
596 return data_.prefetch(ctx);
597 }
598 /// @}
599 /// @brief Base pointer of item 0.
600 T* data_ptr() const { return data_.data(); }
601
602
603 int batch_size() const { return batch_size_; } ///< Number of batch items.
604 /// @brief Rows per item (the capacity on a heterogeneous batch).
605 int rows() const { return rows_; }
606 /// @brief Active rows of item @p batch_index.
607 int rows(int batch_index) const {
608 return detail::dense_active_rows(rows_, active_rows_, batch_index);
609 }
610 /// @brief Columns per item (the capacity on a heterogeneous batch).
611 int cols() const { return cols_; }
612 /// @brief Active columns of item @p batch_index.
613 int cols(int batch_index) const {
614 return detail::dense_active_cols(cols_, active_cols_, batch_index);
615 }
616 /// @brief Equal to rows().
617 int rows_capacity() const { return rows_; }
618 /// @brief Equal to cols().
619 int cols_capacity() const { return cols_; }
620 /// @brief True when per-item active extents are attached.
621 bool is_heterogeneous() const { return !active_rows_.empty() || !active_cols_.empty(); }
622
623 /// @brief Per-item active row counts; empty when homogeneous.
624 template <MatrixFormat M = MType>
625 requires DenseMatrixFormat<M>
626 Span<int> active_rows() const { return active_rows_; }
627
628 /// @brief Per-item active column counts; empty when homogeneous.
629 template <MatrixFormat M = MType>
630 requires DenseMatrixFormat<M>
631 Span<int> active_cols() const { return active_cols_; }
632
633 /// @brief Packed dense copy with every element converted by `static_cast<U>`; host loop, no Queue.
634 template <typename U>
635 Matrix<U, MType> astype() const;
636
637 /// @name Verification
638 /// Accuracy checks of a computed result against its inputs (@ref design_verification). Each
639 /// reads every operand through ld() and stride(), promotes to double / complex<double>, runs
640 /// on @p ctx (an in-order sibling of it when @p ctx is out of order), waits, and returns the
641 /// worst value over @p items (empty: default_items(); no items checked returns 0); a NaN
642 /// anywhere is the result. Operands are read as stored: apply triangularize(), hermitize()
643 /// or symmetrize() to a copy first to check against a triangle or a mirrored matrix.
644 /// @pre every operand is host-readable (unified or host memory) and the work that wrote it
645 /// has completed: the caller waits on its queue first.
646 /// @{
647
648 /// @brief T's promoted type: double, or complex<double> for complex T.
649 using promoted_type = std::conditional_t<is_std_complex_v<T>, std::complex<double>, double>;
650
651 /// @brief @p x as promoted_type.
652 template <typename U = T>
653 requires(std::is_floating_point_v<U> || is_std_complex_v<U>)
654 static promoted_type up(U x) { return promoted_type(x); }
655
656 /// @brief re + i im rounded to T; the imaginary part is dropped for a real T.
657 template <typename U = T>
658 requires(std::is_floating_point_v<U> || is_std_complex_v<U>)
659 static U make(double re, double im = 0.0) {
660 if constexpr (is_std_complex_v<U>) return U(float_t<U>(re), float_t<U>(im));
661 else { (void)im; return U(re); }
662 }
663
664 /// @brief The complex conjugate of @p x; @p x itself for a real T.
665 template <typename U = T>
666 requires(std::is_floating_point_v<U> || is_std_complex_v<U>)
667 static U conj(U x) {
668 if constexpr (is_std_complex_v<U>) return std::conj(x);
669 else return x;
670 }
671
672 /// @brief |re| + |im|, LAPACK's partial-pivoting metric; |x| for a real T.
673 template <typename U = T>
674 requires(std::is_floating_point_v<U> || is_std_complex_v<U>)
675 static double cabs1(U x) {
676 if constexpr (is_std_complex_v<U>) return std::fabs(double(x.real())) + std::fabs(double(x.imag()));
677 else return std::fabs(double(x));
678 }
679
680 /// @brief True when every component of @p x is finite.
681 template <typename U = T>
682 requires(std::is_floating_point_v<U> || is_std_complex_v<U>)
683 static bool finite(U x) {
684 if constexpr (is_std_complex_v<U>) return std::isfinite(double(x.real())) && std::isfinite(double(x.imag()));
685 else return std::isfinite(double(x));
686 }
687
688 /// @brief Every item index, 0 .. batch_size() - 1.
689 std::vector<int> all_items() const;
690
691 /// @brief {0, batch_size() / 2, batch_size() - 1}, sorted and de-duplicated ({} for batch 0): what a check reads by default.
692 std::vector<int> default_items() const;
693
694 /// @brief astype() of @p items only, packed in that order (item g at g * rows() * cols()).
695 /// @throws batchlas::out_of_range for an item outside [0, batch_size())
696 /// @throws batchlas::invalid_argument for a heterogeneous view
697 template <typename U>
698 Matrix<U, MType> astype(std::span<const int> items) const;
699
700 /// @brief C := op(A) op(B) per item: one work-item per element of C, the inner sum in order.
701 /// The reference product of the checks, independent of gemm's tuned kernels.
702 /// @throws batchlas::invalid_argument on a shape mismatch
703 static Event product(const Queue& ctx, const MatrixView& A, Transpose ta, const MatrixView& B, Transpose tb,
704 const MatrixView& C);
705
706 /// @brief ||A0 - L L^H||_F / ||A0||_F (Lower) or ||A0 - U^H U||_F / ||A0||_F (Upper) over the
707 /// factor's triangle; the other triangle of @p F is not read.
708 static double potrf_residual(Queue& ctx, const MatrixView& A0, const MatrixView& F, Uplo uplo,
709 std::span<const int> items = {});
710
711 /// @brief ||P A0 - L U||_F / ||A0||_F for an m x n factor with packed 1-based pivots (min(m, n)
712 /// per item); an out-of-range pivot makes that item NaN.
713 static double getrf_residual(Queue& ctx, const MatrixView& A0, const MatrixView& F,
714 const VectorView<std::int32_t>& piv, std::span<const int> items = {});
715
716 /// @brief ||A0 - Q R||_F / ||A0||_F for @p F in LAPACK geqrf storage and its min(m, n) @p tau.
717 static double qr_residual(Queue& ctx, const MatrixView& A0, const MatrixView& F, const VectorView<T>& tau,
718 std::span<const int> items = {});
719
720 /// @brief The first @p cols columns of Q = H_0 ... H_{k-1} of item @p item of a geqrf factor
721 /// (k = min(m, n)); F's diagonal and upper triangle are not read.
722 static Matrix<promoted_type, MType> form_q(Queue& ctx, const MatrixView& F, const VectorView<T>& tau, int item,
723 int cols);
724
725 /// @brief form_q() of each of @p items (empty: default_items()), packed in that order: item g of
726 /// the result is the first @p cols columns of Q of item items[g].
727 static Matrix<promoted_type, MType> form_q(Queue& ctx, const MatrixView& F, const VectorView<T>& tau,
728 std::span<const int> items, int cols);
729
730 /// @brief ||A0 - Q triu(R)||_F / ||A0||_F with an explicit m x k @p Q; the first k rows of @p R
731 /// are read on and above the diagonal.
732 static double qr_reconstruction(Queue& ctx, const MatrixView& A0, const MatrixView& Q, const MatrixView& R,
733 std::span<const int> items = {});
734
735 /// @brief ||A0 - Q B Q^H||_F / ||A0||_F for square operands of equal order.
736 static double similarity_residual(Queue& ctx, const MatrixView& A0, const MatrixView& Q, const MatrixView& B,
737 std::span<const int> items = {});
738
739 /// @brief ||A0 - U diag(s) Vh||_F / ||A0||_F for an m x n @p A0 over the first k = min(m, n)
740 /// columns of @p U, values of @p s and rows of @p Vh (thin or full factors).
741 /// @throws batchlas::invalid_argument on a shape mismatch
742 static double svd_residual(Queue& ctx, const MatrixView& A0, const MatrixView& U, const VectorView<float_t<T>>& s,
743 const MatrixView& Vh, std::span<const int> items = {});
744
745 /// @brief ||op(A0) X - B0||_F / (||A0||_F ||X||_F); an empty @p B0 (rows() == 0) is the identity.
746 /// Above @p full_limit multiply-adds per item, 8 seeded probe columns of X stand in for all.
747 static double solve_residual(Queue& ctx, const MatrixView& A0, Transpose ta, const MatrixView& X,
748 const MatrixView& B0, std::span<const int> items = {},
749 double full_limit = double(1 << 28));
750
751 /// @brief solve_residual() with op(A0) = A0.
752 static double solve_residual(Queue& ctx, const MatrixView& A0, const MatrixView& X, const MatrixView& B0,
753 std::span<const int> items = {});
754
755 /// @brief solve_residual() with A = P L U formed per item from getrf factors @p F and pivots @p piv;
756 /// @p full_limit as for solve_residual().
757 static double lu_solve_residual(Queue& ctx, const MatrixView& F, const VectorView<std::int32_t>& piv,
758 Transpose ta, const MatrixView& X, const MatrixView& B0,
759 std::span<const int> items = {}, double full_limit = double(1 << 28));
760
761 /// @brief Componentwise max |C - (alpha op(A) op(B) + beta C0)| / (|alpha| |op(A)| |op(B)| + |beta| |C0|),
762 /// over @p c_triangle of C when given; C0 is not read when beta == 0.
763 static double gemm_backward_error(Queue& ctx, const MatrixView& A, Transpose ta, const MatrixView& B, Transpose tb,
764 const MatrixView& C0, const MatrixView& C, promoted_type alpha,
765 promoted_type beta, std::span<const int> items = {},
766 std::optional<Uplo> c_triangle = std::nullopt);
767
768 /// @brief The largest denominator of gemm_backward_error() over @p items (empty: A's default_items(),
769 /// while gemm_backward_error() defaults to C's: pass both the same span when A's batch differs).
770 static double gemm_max_denominator(Queue& ctx, const MatrixView& A, Transpose ta, const MatrixView& B,
771 Transpose tb, const MatrixView& C0, promoted_type alpha, promoted_type beta,
772 std::span<const int> items = {});
773
774 /// @brief gemm_backward_error() of y = alpha op(A) x + beta y0.
775 static double gemv_backward_error(Queue& ctx, const MatrixView& A, Transpose ta, const VectorView<T>& x,
776 const VectorView<T>& y0, const VectorView<T>& y, promoted_type alpha,
777 promoted_type beta, std::span<const int> items = {});
778
779 /// @brief ||op(A) X - alpha B0||_F / (||A||_F ||X||_F + |alpha| ||B0||_F) (Side::Right: X op(A)); only
780 /// the @p uplo triangle of A is read, its diagonal only for Diag::NonUnit.
781 static double trsm_residual(Queue& ctx, const MatrixView& A, Side side, Uplo uplo, Transpose ta, Diag diag,
782 const MatrixView& X, const MatrixView& B0, promoted_type alpha,
783 std::span<const int> items = {});
784
785 /// @brief gemm_backward_error() with a CSR @p A; an out-of-range column index makes the result NaN.
786 static double spmm_backward_error(Queue& ctx, const MatrixView<T, MatrixFormat::CSR>& A, Transpose ta,
787 const MatrixView& B, Transpose tb, const MatrixView& C0, const MatrixView& C,
788 promoted_type alpha, promoted_type beta, std::span<const int> items = {});
789
790 /// @brief Componentwise backward error of a rank-2k update over the @p uplo triangle of C: her2k
791 /// (@p hermitian, real beta, C0's diagonal read as real) or syr2k.
792 static double rank2k_backward_error(Queue& ctx, const MatrixView& A, const MatrixView& B, Transpose trans,
793 const MatrixView& C0, const MatrixView& C, Uplo uplo, promoted_type alpha,
794 promoted_type beta, bool hermitian, std::span<const int> items = {});
795
796 /// @brief ||Q^H Q - I||_F over Q's columns.
797 static double orthogonality(Queue& ctx, const MatrixView& Q, std::span<const int> items = {});
798
799 /// @brief ||A V - V diag(w)||_F / ||A||_F; A Hermitian, read from its lower triangle.
800 static double eigen_residual(Queue& ctx, const MatrixView& A, const MatrixView& V, const VectorView<float_t<T>>& w,
801 std::span<const int> items = {});
802
803 /// @brief max |w - ref| / scale per item; ref[item] holds w.size() values in the caller's order.
804 static double values_error(const VectorView<float_t<T>>& w, const std::vector<std::vector<double>>& ref,
805 double scale, std::span<const int> items = {});
806
807 /// @brief max cabs1(L(i,k) U(k,k)) / cabs1(U(k,k)) over a getrf factor: at most 1, up to rounding,
808 /// for cabs1 partial pivoting (a residual cannot see the pivot rule).
809 static double pivot_ratio(const MatrixView& F, std::span<const int> items = {});
810
811 /// @brief True when every pivot of every item lies in [1, n].
812 static bool pivots_valid(const VectorView<std::int32_t>& piv, int n);
813 /// @}
814
815 /// @brief Leading dimension: element distance between columns.
816 template <MatrixFormat M = MType>
817 requires DenseMatrixFormat<M>
818 int ld() const { return ld_; }
819
820 /// @brief Batch stride: element distance between items.
821 template <MatrixFormat M = MType>
822 requires DenseMatrixFormat<M>
823 int stride() const { return stride_; }
824
825 /// @brief Always 1: the element distance down a column.
826 template <MatrixFormat M = MType>
827 requires DenseMatrixFormat<M>
828 int inc() const { return 1; } // Assuming contiguous elements for vector views
829
830 /// @brief Zero-based row offsets, `offset_stride()` per item.
831 template <MatrixFormat M = MType>
832 requires CsrMatrixFormat<M>
833 Span<int> row_offsets() const { return row_offsets_; }
834
835 /// @brief Column indices, `matrix_stride()` per item.
836 template <MatrixFormat M = MType>
837 requires CsrMatrixFormat<M>
838 Span<int> col_indices() const { return col_indices_; }
839
840 /// @brief Per-item non-zero capacity, not a count; see Matrix::nnz().
841 template <MatrixFormat M = MType>
842 requires CsrMatrixFormat<M>
843 int nnz() const { return nnz_; }
844
845 /// @brief Slots allocated per item; see Matrix::nnz_capacity().
846 template <MatrixFormat M = MType>
847 requires CsrMatrixFormat<M>
848 int nnz_capacity() const { return matrix_stride_; }
849
850 /// @brief Non-zeros actually stored by item @p batch_index, read on the host.
851 /// @pre The row offsets are host-readable: not over `sycl::malloc_device` memory
852 /// (use KernelMatrixView::nnz(int) in a kernel instead).
853 template <MatrixFormat M = MType>
854 requires CsrMatrixFormat<M>
855 int nnz(int batch_index) const {
856 const std::size_t base = static_cast<std::size_t>(batch_index) *
857 static_cast<std::size_t>(offset_stride_);
858 return row_offsets_[base + static_cast<std::size_t>(rows_)] - row_offsets_[base];
859 }
860
861 /// @brief Element distance between items' value (and column-index) arrays.
862 template <MatrixFormat M = MType>
863 requires CsrMatrixFormat<M>
864 int matrix_stride() const { return matrix_stride_; }
865
866 /// @brief Element distance between items' row-offset arrays.
867 template <MatrixFormat M = MType>
868 requires CsrMatrixFormat<M>
869 int offset_stride() const { return offset_stride_; }
870
871 /// @brief Fills the per-item pointer array from this view's base and stride on @p ctx and waits.
872 ///
873 /// The array may be shared with the parent Matrix and its other views, so it
874 /// holds whatever the last view to fill it wrote.
875 /// @throws batchlas::invalid_argument if the view has no pointer array of batch_size() entries
876 Span<T*> data_ptrs(Queue& ctx) const {
877 init_data_ptr_array(ctx);
878 return data_ptrs_;
879 }
880
881 /// @brief Host reference to element (@p row, @p col) of item @p batch.
882 /// @throws batchlas::out_of_range outside the item's active extents
883 template <MatrixFormat M = MType>
884 requires DenseMatrixFormat<M>
885 T& at(int row, int col, int batch = 0);
886
887 /// @brief Host const reference to element (@p row, @p col) of item @p batch.
888 /// @throws batchlas::out_of_range outside the item's active extents
889 template <MatrixFormat M = MType>
890 requires DenseMatrixFormat<M>
891 const T& at(int row, int col, int batch = 0) const;
892
893 /// @brief at(@p row, @p col, @p batch).
894 template <MatrixFormat M = MType>
895 requires DenseMatrixFormat<M>
896 T& operator()(int row, int col, int batch = 0) {
897 return at(row, col, batch);
898 }
899
900 /// @brief at(@p row, @p col, @p batch).
901 template <MatrixFormat M = MType>
902 requires DenseMatrixFormat<M>
903 const T& operator()(int row, int col, int batch = 0) const {
904 return at(row, col, batch);
905 }
906
907 /// @brief View of the sub-block @p rows x @p cols of every item; keeps ld and stride, never copies.
908 /// @throws batchlas::invalid_argument if either slice is empty; batchlas::out_of_range past a dimension
909 template <MatrixFormat M = MType>
910 requires DenseMatrixFormat<M>
911 MatrixView<T, MType> operator()(Slice rows, Slice cols) const {
912 auto [r_start, r_len] = detail::checked_slice_component(rows, rows_);
913 auto [c_start, c_len] = detail::checked_slice_component(cols, cols_);
914 if (r_len <= 0 || c_len <= 0) {
915 throw batchlas::invalid_argument("Invalid slice dimensions: " + std::to_string(r_len) + "x" + std::to_string(c_len));
916 }
917 auto offset = c_start * ld_ + r_start;
918 // Trap: the slice keeps the parent's pointer array, which holds *unsliced* base
919 // addresses until data_ptrs(ctx) refills it for this view.
920 // evidence: docs/design/matrix-model.md#matrix-model-the-pointer-array-and-sliced-views
921 return MatrixView<T, MType>(data_ptr() + offset, static_cast<int>(r_len), static_cast<int>(c_len), ld_, stride_, batch_size_, data_ptrs_.data());
922 }
923
924 /// @brief View of rows @p rows over all columns of every item.
925 template <MatrixFormat M = MType>
926 requires DenseMatrixFormat<M>
927 MatrixView<T, MType> operator()(Slice rows) const {
928 return (*this)(rows, {});
929 }
930
931 /// @brief Part of row @p row of every item as a batched VectorView (inc = ld(), stride = stride()).
932 /// @throws batchlas::invalid_argument if the slice is empty; batchlas::out_of_range past a dimension
933 template <MatrixFormat M = MType>
934 requires DenseMatrixFormat<M>
935 VectorView<T> operator()(int32_t row, Slice cols) const {
936 auto [c_start, c_len] = detail::checked_slice_component(cols, cols_);
937 if (c_len <= 0) {
938 throw batchlas::invalid_argument("Invalid slice dimensions for row vector: " + std::to_string(c_len));
939 }
940 auto offset = c_start * ld_ + row;
941 return VectorView<T>(data_ptr() + offset, static_cast<int>(c_len), batch_size_, ld_, stride_);
942 }
943
944 /// @brief Part of column @p col of every item as a batched VectorView (inc = 1, stride = stride()).
945 /// @throws batchlas::invalid_argument if the slice is empty; batchlas::out_of_range past a dimension
946 template <MatrixFormat M = MType>
947 requires DenseMatrixFormat<M>
948 VectorView<T> operator()(Slice rows, int32_t col) const {
949 auto [r_start, r_len] = detail::checked_slice_component(rows, rows_);
950 if (r_len <= 0) {
951 throw batchlas::invalid_argument("Invalid slice dimensions for column vector: " + std::to_string(r_len));
952 }
953 auto offset = col * ld_ + r_start;
954 return VectorView<T>(data_ptr() + offset, static_cast<int>(r_len), batch_size_, 1, stride_);
955 }
956
957 /// @name In-place fills and transforms
958 /// Dense-only unless stated. The `const Queue&` overloads enqueue and return the
959 /// kernel's Event without waiting; the queue-less overloads run on a new Queue()
960 /// on Device::default_device_for<T>(). The square transforms read rows() as n.
961 /// @{
962
963 /// @brief Copies the @p uplo triangle onto the other: a_ji := a_ij for (i, j) in @p uplo.
964 /// @pre rows() == cols()
965 template <MatrixFormat M = MType>
966 requires DenseMatrixFormat<M>
967 Event symmetrize(const Queue& ctx, Uplo uplo) const;
968
969 /// @brief symmetrize() on a new default queue.
970 template <MatrixFormat M = MType>
971 requires DenseMatrixFormat<M>
972 Event symmetrize(Uplo uplo) const {
973 return symmetrize(Queue(Device::default_device_for<T>()), uplo);
974 }
975
976 /// @brief Mirrors the @p uplo triangle conjugated (a_ji := conj(a_ij)) and makes the diagonal real.
977 /// @pre rows() == cols()
978 template <MatrixFormat M = MType>
979 requires DenseMatrixFormat<M>
980 Event hermitize(const Queue& ctx, Uplo uplo) const;
981
982 /// @brief hermitize() on a new default queue.
983 template <MatrixFormat M = MType>
984 requires DenseMatrixFormat<M>
985 Event hermitize(Uplo uplo) const {
986 return hermitize(Queue(Device::default_device_for<T>()), uplo);
987 }
988
989 /// @brief In-place (conjugate) transpose.
990 /// @note Declared but not defined by the library: a call fails to link.
991 template <MatrixFormat M = MType>
992 requires DenseMatrixFormat<M>
993 Event transpose(const Queue& ctx, bool conjugate = false) const;
994
995 /// @brief transpose() on a new default queue; likewise undefined.
996 template <MatrixFormat M = MType>
997 requires DenseMatrixFormat<M>
998 Event transpose(bool conjugate = false) const {
999 return transpose(Queue(Device::default_device_for<T>()), conjugate);
1000 }
1001
1002 /// @brief Zeros the strict triangle opposite @p uplo, keeping the @p uplo triangle;
1003 /// with Diag::Unit also sets the diagonal to 1.
1004 ///
1005 /// `Uplo::Upper` leaves an upper-triangular matrix. Honours rows() != cols().
1006 template <MatrixFormat M = MType>
1007 requires DenseMatrixFormat<M>
1008 Event triangularize(const Queue& ctx, Uplo uplo, Diag diag) const;
1009
1010 /// @brief triangularize() on a new default queue.
1011 template <MatrixFormat M = MType>
1012 requires DenseMatrixFormat<M>
1013 Event triangularize(Uplo uplo, Diag diag) const {
1014 return triangularize(Queue(Device::default_device_for<T>()), uplo, diag);
1015 }
1016
1017 /// @brief Fills with values uniform in [-1, 1], deterministic in @p seed; see Matrix::Random().
1018 /// @note Writes every element of data(), padding between columns and items included.
1019 /// @throws batchlas::invalid_argument if @p hermitian and rows() != cols()
1020 template <MatrixFormat M = MType>
1021 requires DenseMatrixFormat<M>
1022 Event fill_random(const Queue& ctx, bool hermitian = false, unsigned int seed = 42) const;
1023
1024 /// @brief fill_random() on a new default queue.
1025 template <MatrixFormat M = MType>
1026 requires DenseMatrixFormat<M>
1027 Event fill_random(bool hermitian = false, unsigned int seed = 42) const {
1028 return fill_random(Queue(Device::default_device_for<T>()), hermitian, seed);
1029 }
1030
1031 /// @brief CSR: fills this storage with a random diagonally dominant Hermitian pattern and values.
1032 ///
1033 /// See csr_generators::random_sparse_hermitian_csr() for the construction.
1034 /// Blocks on @p ctx while building the pattern; the returned Event is the value fill.
1035 /// @throws batchlas::invalid_argument unless the view is square, non-empty, and
1036 /// its nnz() equals the count @p density implies
1037 template <MatrixFormat M = MType>
1038 requires CsrMatrixFormat<M>
1039 Event fill_random_sparse_hermitian(const Queue& ctx,
1040 float density,
1041 unsigned int seed = 42,
1042 typename base_type<T>::type diagonal_boost = typename base_type<T>::type(1),
1043 bool shared_pattern = true) const;
1044
1045 /// @brief fill_random_sparse_hermitian() on a new default queue.
1046 template <MatrixFormat M = MType>
1047 requires CsrMatrixFormat<M>
1048 Event fill_random_sparse_hermitian(float density,
1049 unsigned int seed = 42,
1050 typename base_type<T>::type diagonal_boost = typename base_type<T>::type(1),
1051 bool shared_pattern = true) const {
1052 return fill_random_sparse_hermitian(Queue(Device::default_device_for<T>()), density, seed, diagonal_boost, shared_pattern);
1053 }
1054
1055 /// @brief Sets every element of every item (dense: within rows() x cols(); CSR: every stored value).
1056 Event fill(const Queue& ctx, T value) const;
1057
1058 /// @brief fill() on a new default queue.
1059 Event fill(T value) const {
1060 return fill(Queue(Device::default_device_for<T>()), value);
1061 }
1062
1063 /// @brief fill() with zero. Deliberately has no queue-less form, which would run on the default device.
1064 Event fill_zeros(const Queue& ctx) const {
1065 return fill(ctx, detail::convert_to_fill_value<T>(0));
1066 }
1067
1068 /// @brief Zeros every item, then sets its main diagonal to @p value.
1069 template <MatrixFormat M = MType>
1070 requires DenseMatrixFormat<M>
1071 Event fill_identity(const Queue& ctx, T value = T(1)) const;
1072
1073 /// @brief Writes diagonal @p k (k > 0 above the main diagonal, k < 0 below).
1074 ///
1075 /// The diagonal has d = max(0, min(rows() - max(-k, 0), cols() - max(k, 0))) entries.
1076 /// @p diag_values holds either d values used for every item, or d * batch_size()
1077 /// values, one run per item. Other entries are untouched; d = 0 writes nothing.
1078 template <MatrixFormat M = MType>
1079 requires DenseMatrixFormat<M>
1080 Event fill_diagonal(const Queue& ctx, const Span<T>& diag_values, int64_t k = 0) const;
1081
1082 /// @brief Writes diagonal @p k from a VectorView: per item when its batch size equals
1083 /// batch_size(), otherwise its item 0 for every item.
1084 /// @pre `diag_values.size() >= d`, d as in the Span overload
1085 template <MatrixFormat M = MType>
1086 requires DenseMatrixFormat<M>
1087 Event fill_diagonal(const Queue& ctx, const VectorView<T>& diag_values, int64_t k = 0) const;
1088
1089 /// @brief fill_diagonal() on a new default queue.
1090 template <MatrixFormat M = MType>
1091 requires DenseMatrixFormat<M>
1092 Event fill_diagonal(const Span<T>& diag_values, int64_t k = 0) const {
1093 return fill_diagonal(Queue(Device::default_device_for<T>()), diag_values, k);
1094 }
1095
1096 /// @brief Sets the main diagonal of every item to @p value.
1097 template <MatrixFormat M = MType>
1098 requires DenseMatrixFormat<M>
1099 Event fill_diagonal(const Queue& ctx, const T& value) const;
1100
1101 /// @brief fill_diagonal() on a new default queue.
1102 template <MatrixFormat M = MType>
1103 requires DenseMatrixFormat<M>
1104 Event fill_diagonal(const T& value) const {
1105 return fill_diagonal(Queue(Device::default_device_for<T>()), value);
1106 }
1107
1108 /// @brief Writes a full triangular matrix: @p diagonal_value on the diagonal,
1109 /// @p non_diagonal_value in the @p uplo strict triangle, zero elsewhere.
1110 template <MatrixFormat M = MType>
1111 requires DenseMatrixFormat<M>
1112 Event fill_triangular(const Queue& ctx, Uplo uplo, T diagonal_value = T(1),
1113 T non_diagonal_value = T(0.5)) const;
1114
1115 /// @brief fill_triangular() on a new default queue.
1116 template <MatrixFormat M = MType>
1117 requires DenseMatrixFormat<M>
1118 Event fill_triangular(Uplo uplo, T diagonal_value = T(1),
1119 T non_diagonal_value = T(0.5)) const {
1120 return fill_triangular(Queue(Device::default_device_for<T>()), uplo, diagonal_value, non_diagonal_value);
1121 }
1122
1123 /// @brief Writes the three diagonals from per-item vectors; other entries are untouched.
1124 template <MatrixFormat M = MType>
1125 requires DenseMatrixFormat<M>
1126 Event fill_tridiag(const Queue& ctx, VectorView<T> sub_diag,
1127 VectorView<T> diag, VectorView<T> super_diag) const {
1128 // (void) on an Event: deliberate. This Queue is in-order, so the next submission
1129 // is already ordered after this one and the Event carries nothing the caller needs.
1130 (void)fill_diagonal(ctx, diag);
1131 (void)fill_diagonal(ctx, sub_diag, -1);
1132 return fill_diagonal(ctx, super_diag, 1);
1133 }
1134
1135 /// @brief Writes a full tridiagonal Toeplitz matrix (zero off the three diagonals).
1136 /// @pre The view is packed: ld() == rows() == cols(), stride() == rows() * cols()
1137 template <MatrixFormat M = MType>
1138 requires DenseMatrixFormat<M>
1139 Event fill_tridiag_toeplitz(const Queue& ctx, T diag = T(1),
1140 T sub_diag = T(-0.5), T super_diag = T(0.5)) const;
1141
1142 /// @brief fill_tridiag_toeplitz() on a new default queue.
1143 template <MatrixFormat M = MType>
1144 requires DenseMatrixFormat<M>
1145 Event fill_tridiag_toeplitz(T diag = T(1),
1146 T sub_diag = T(-0.5), T super_diag = T(0.5)) const {
1147 return fill_tridiag_toeplitz(Queue(Device::default_device_for<T>()), diag, sub_diag, super_diag);
1148 }
1149
1150 /// @brief Writes a full random triangular matrix: uniform [-1, 1] in the @p uplo
1151 /// triangle, 1 on the diagonal for Diag::Unit, zero elsewhere.
1152 /// @note Every item receives the same matrix (the values depend on @p seed and the position only).
1153 /// @pre The view is packed: ld() == rows() == cols(), stride() == rows() * cols()
1154 template <MatrixFormat M = MType>
1155 requires DenseMatrixFormat<M>
1156 Event fill_triangular_random(const Queue& ctx, Uplo uplo,
1157 Diag diag = Diag::Unit,
1158 unsigned int seed = 42) const;
1159
1160 /// @brief fill_triangular_random() on a new default queue.
1161 template <MatrixFormat M = MType>
1162 requires DenseMatrixFormat<M>
1163 Event fill_triangular_random(Uplo uplo,
1164 Diag diag = Diag::Unit,
1165 unsigned int seed = 42) const {
1166 return fill_triangular_random(Queue(Device::default_device_for<T>()), uplo, diag, seed);
1167 }
1168
1169 /// @brief Random Hermitian (real T: symmetric) positive definite matrix, a different one per item.
1170 ///
1171 /// The Hermitian fill_random() values with the shift n (2n for complex T, whose entries reach
1172 /// modulus sqrt(2)) added to the real diagonal: every row is strictly diagonally dominant
1173 /// with a positive diagonal. a_ij == conj(a_ji) holds bitwise. Deterministic in @p seed,
1174 /// independent of ld() and stride(); writes only the rows() x cols() entries.
1175 /// @throws batchlas::invalid_argument if rows() != cols()
1176 template <MatrixFormat M = MType>
1177 requires DenseMatrixFormat<M>
1178 Event fill_random_spd(const Queue& ctx, unsigned int seed = 42) const;
1179
1180 /// @brief fill_random_spd() on a new default queue.
1181 template <MatrixFormat M = MType>
1182 requires DenseMatrixFormat<M>
1183 Event fill_random_spd(unsigned int seed = 42) const {
1184 return fill_random_spd(Queue(Device::default_device_for<T>()), seed);
1185 }
1186
1187 /// @brief Row-permuted diagonally dominant random matrix, so LU partial pivoting is not the identity.
1188 ///
1189 /// Item b is P_b D_b: D_b holds uniform [-1, 1) entries plus the fill_random_spd() shift on
1190 /// the diagonal, and P_b is a seeded random n-cycle (no row stays in place for n >= 2), a
1191 /// different one per item. Each column's dominant entry therefore sits in a distinct row.
1192 /// Deterministic in @p seed; writes only the rows() x cols() entries.
1193 /// @throws batchlas::invalid_argument if rows() != cols(), or if n ints exceed the device's local memory
1194 template <MatrixFormat M = MType>
1195 requires DenseMatrixFormat<M>
1196 Event fill_random_pivoted(const Queue& ctx, unsigned int seed = 42) const;
1197
1198 /// @brief fill_random_pivoted() on a new default queue.
1199 template <MatrixFormat M = MType>
1200 requires DenseMatrixFormat<M>
1201 Event fill_random_pivoted(unsigned int seed = 42) const {
1202 return fill_random_pivoted(Queue(Device::default_device_for<T>()), seed);
1203 }
1204
1205 /// @brief Hermitian Q diag(lambda) Q^H with condition number 10^@p log10_cond, a different Q per item.
1206 ///
1207 /// lambda_i = 10^(-log10_cond * i / (n - 1)) for i = 0..n-1 (1 when n == 1). Q is the product
1208 /// of n Householder reflectors I - 2 v v^H of seeded random unit vectors (real for real T),
1209 /// applied in T's precision. The result is exactly Hermitian with a real diagonal.
1210 /// Deterministic in @p seed; writes only the rows() x cols() entries.
1211 /// @throws batchlas::invalid_argument if rows() != cols(), if @p log10_cond is not finite,
1212 /// or if 2n elements of T exceed the device's local memory
1213 template <MatrixFormat M = MType>
1214 requires DenseMatrixFormat<M>
1215 Event fill_graded_hermitian(const Queue& ctx, double log10_cond, unsigned int seed = 42) const;
1216
1217 /// @brief fill_graded_hermitian() on a new default queue.
1218 template <MatrixFormat M = MType>
1219 requires DenseMatrixFormat<M>
1220 Event fill_graded_hermitian(double log10_cond, unsigned int seed = 42) const {
1221 return fill_graded_hermitian(Queue(Device::default_device_for<T>()), log10_cond, seed);
1222 }
1223
1224 /// @brief Clustered-spectrum Hermitian matrix: diagonal floor(i / 4) + (i % 4) 1e-4 plus
1225 /// @p noise times a seeded Hermitian perturbation.
1226 ///
1227 /// Off-diagonal a_ij = @p noise * z_ij (i > j, z uniform [-1, 1), both parts for complex T) and
1228 /// a_ji = conj(a_ij) bitwise; the diagonal adds @p noise * Re(z_ii). Groups of four nearly equal
1229 /// eigenvalues exercise deflation and tie-breaking. Deterministic in @p seed (Philox, the same on
1230 /// every compiler), independent of ld() and stride(); writes only the rows() x cols() entries.
1231 /// @throws batchlas::invalid_argument if rows() != cols()
1232 template <MatrixFormat M = MType>
1233 requires DenseMatrixFormat<M>
1234 Event fill_near_degenerate_hermitian(const Queue& ctx, double noise, unsigned int seed = 42) const;
1235 /// @}
1236
1237
1238 /// @brief A batch-size-1 view of item @p batch_index.
1239 ///
1240 /// Dense: its active extents, the parent's ld, no pointer array. CSR: the parent's strides.
1241 /// @throws batchlas::out_of_range if @p batch_index is outside [0, batch_size())
1242 MatrixView<T, MType> batch_item(int batch_index) const;
1243
1244 /// @brief A copy of this view with per-item active extents attached (not copied: the spans must outlive the view).
1245 /// @throws batchlas::invalid_argument under the same rules as Matrix::set_active_dims()
1246 template <MatrixFormat M = MType>
1247 requires DenseMatrixFormat<M>
1248 MatrixView<T, MType> with_active_dims(Span<int> active_rows, Span<int> active_cols) const {
1249 detail::validate_dense_active_dims(rows_, cols_, batch_size_, active_rows, active_cols);
1250 MatrixView<T, MType> out = *this;
1251 out.active_rows_ = active_rows;
1252 out.active_cols_ = active_cols;
1253 return out;
1254 }
1255
1256 /// @brief True when every element of @p a and @p b agrees to relative tolerance @p tol; waits on @p ctx.
1257 ///
1258 /// Elements agree when |x - y| / max(|x|, |y|) <= tol (the denominator is 1 when
1259 /// the larger magnitude is below @p tol, and two values both below epsilon
1260 /// agree); complex compares the real and imaginary parts separately. Dense only.
1261 /// @throws batchlas::invalid_argument if the shapes or batch sizes differ
1262 /// @throws batchlas::unsupported for CSR
1263 static bool all_close(Queue& ctx, const MatrixView<T, MType>& a, const MatrixView<T, MType>& b, float_t<T> tol = std::numeric_limits<float_t<T>>::epsilon());
1264
1265 /// @brief Prints each item's leading block (dense) or leading entries (CSR) in scientific notation.
1266 /// @return @p os, with its format flags restored
1267 std::ostream& stream_formatted_to(std::ostream& os, int max_rows_to_print = 10, int max_cols_to_print = 10, int max_elements_to_print_csr = 20) const {
1268 std::ios_base::fmtflags original_flags = os.flags();
1269 os << std::scientific << std::setprecision(4);
1270
1271 for (int b_idx = 0; b_idx < batch_size_; ++b_idx) {
1272 if (batch_size_ > 1) {
1273 os << "Batch " << b_idx << ":\n";
1274 }
1275 MatrixView<T, MType> current_item_view = this->batch_item(b_idx);
1276
1277 if constexpr (MType == MatrixFormat::Dense) {
1278 for (int r = 0; r < std::min(current_item_view.rows_, max_rows_to_print); ++r) {
1279 os << " ";
1280 for (int c = 0; c < std::min(current_item_view.cols_, max_cols_to_print); ++c) {
1281 detail::print_with_width(os, current_item_view.at(r, c, 0), 13); // Supports non-streamable element types
1282 }
1283 if (current_item_view.cols_ > max_cols_to_print) {
1284 os << " ...";
1285 }
1286 os << "\n";
1287 }
1288 if (current_item_view.rows_ > max_rows_to_print) {
1289 os << " ...\n";
1290 }
1291 } else if constexpr (MType == MatrixFormat::CSR) {
1292 os << "CSR Matrix (rows: " << current_item_view.rows_
1293 << ", cols: " << current_item_view.cols_
1294 << ", nnz: " << current_item_view.nnz_ << ")\n";
1295
1296 os << " Values: [";
1297 for (int i = 0; i < std::min(current_item_view.nnz_, max_elements_to_print_csr); ++i) {
1298 os << std::setw(13) << current_item_view.data()[i] << (i == std::min(current_item_view.nnz_, max_elements_to_print_csr) - 1 ? "" : ", ");
1299 }
1300 if (current_item_view.nnz_ > max_elements_to_print_csr) os << " ...";
1301 os << " ]\n";
1302
1303 os << " Row Offsets: [";
1304 // CSR row_offsets has size rows + 1
1305 int num_row_offsets = current_item_view.rows_ + 1;
1306 for (int i = 0; i < std::min(num_row_offsets, max_elements_to_print_csr); ++i) {
1307 os << std::setw(6) << current_item_view.row_offsets()[i] << (i == std::min(num_row_offsets, max_elements_to_print_csr) - 1 ? "" : ", ");
1308 }
1309 if (num_row_offsets > max_elements_to_print_csr) os << " ...";
1310 os << " ]\n";
1311
1312 os << " Col Indices: [";
1313 for (int i = 0; i < std::min(current_item_view.nnz_, max_elements_to_print_csr); ++i) {
1314 os << std::setw(6) << current_item_view.col_indices()[i] << (i == std::min(current_item_view.nnz_, max_elements_to_print_csr) - 1 ? "" : ", ");
1315 }
1316 if (current_item_view.nnz_ > max_elements_to_print_csr) os << " ...";
1317 os << " ]\n";
1318 }
1319 if (b_idx < batch_size_ - 1) {
1320 os << "\n"; // Separator between batches
1321 }
1322 }
1323 os.flags(original_flags); // Reset stream flags
1324 return os;
1325 }
1326
1327 /// @brief stream_formatted_to() without the return value; reads the data on the host.
1328 void print(std::ostream& os = std::cout, int max_rows_to_print = 10, int max_cols_to_print = 10, int max_elements_to_print_csr = 20) const {
1329 stream_formatted_to(os, max_rows_to_print, max_cols_to_print, max_elements_to_print_csr);
1330 }
1331
1332 /// @brief The trivially copyable device-side view, for capture in a kernel.
1333 KernelMatrixView<T, MType> kernel_view() const noexcept {
1334 KernelMatrixView<T, MType> kv;
1335 kv.data_ = const_cast<T*>(data_.data());
1336 kv.rows_ = rows_;
1337 kv.cols_ = cols_;
1338 kv.ld_ = ld_;
1339 kv.stride_ = stride_;
1340 kv.active_rows_ = active_rows_.empty() ? nullptr : active_rows_.data();
1341 kv.active_cols_ = active_cols_.empty() ? nullptr : active_cols_.data();
1342 kv.batch_size_ = batch_size_;
1343 if constexpr (MType == MatrixFormat::CSR) {
1344 kv.row_offsets_ = const_cast<int*>(row_offsets_.data());
1345 kv.col_indices_ = const_cast<int*>(col_indices_.data());
1346 kv.nnz_ = nnz_;
1347 kv.matrix_stride_ = matrix_stride_;
1348 kv.offset_stride_ = offset_stride_;
1349 }
1350 return kv;
1351 }
1352
1353 protected:
1354 void init_data_ptr_array(Queue& ctx, Span<T*> target = {}) const;
1355
1356 Span<T> data_;
1357
1358 int ld_ = 0;
1359 int stride_ = 0;
1360 Span<int> active_rows_;
1361 Span<int> active_cols_;
1362
1363 Span<T*> data_ptrs_; // may alias the parent Matrix's pointer array
1364
1365 Span<int> row_offsets_;
1366 Span<int> col_indices_;
1367 int nnz_ = 0;
1368 int matrix_stride_ = 0;
1369 int offset_stride_ = 0;
1370
1371 // Shared with every copy of the view; mutable so const methods can create it lazily.
1372 mutable std::shared_ptr<BackendMatrixHandle<T, MType>> backend_handle_;
1373 };
1374
1375 /// @brief Owning batch of `batch_size()` matrices of equal (capacity) shape.
1376 ///
1377 /// **Dense** (the default): column-major, element (i, j) of item b at
1378 /// `data()[b * stride() + j * ld() + i]`, with `ld() >= rows()` and
1379 /// `stride() >= ld() * cols()`. **CSR**: per item, `rows() + 1` row offsets
1380 /// (items `offset_stride()` apart) and `nnz_capacity()` values and column indices
1381 /// (items `matrix_stride()` apart), zero-based.
1382 ///
1383 /// Storage is USM shared memory (UnifiedVector) on Device::default_device(), so it
1384 /// is host-accessible and device-accessible. A Matrix is a MatrixView of its own
1385 /// storage, so it is accepted wherever a MatrixView is and has every view member.
1386 /// Copy construction and copy assignment deep-copy the data; moves transfer it.
1387 /// The shape members are public but are not to be written.
1388 ///
1389 /// A dense batch may be **heterogeneous**: set_active_dims() attaches per-item
1390 /// active extents no larger than the allocated rows() x cols().
1391 /// @tparam T scalar type (`float`, `double`, `std::complex<float>`, `std::complex<double>`)
1392 /// @tparam MType MatrixFormat::Dense (default) or MatrixFormat::CSR
1393 /// @see @ref design_matrix_model, and the user guide's data-layout section in @ref md_docs_2cpp-api
1394 /// @ingroup api_matrix
1395 // BATCHLAS_API sits on the class template, not on the explicit instantiations: only it
1396 // reaches the member-template instantiations, and g++ rejects it on an instantiation.
1397 // evidence: docs/design/symbol-visibility.md#symbol-visibility-the-export-attribute-on-the-matrix-class-template
1398 template <typename T, MatrixFormat MType>
1399 class BATCHLAS_API Matrix : public MatrixView<T, MType> {
1400 using Base = MatrixView<T, MType>;
1401 using Base::ld_, Base::stride_, Base::nnz_, Base::matrix_stride_, Base::offset_stride_;
1402
1403 public:
1404 using Base::rows_, Base::cols_, Base::batch_size_;
1405
1406
1407 /// @brief Allocates an uninitialised dense batch.
1408 /// @param rows rows per item
1409 /// @param cols columns per item
1410 /// @param batch_size number of items
1411 /// @param ld leading dimension; 0 means @p rows
1412 /// @param stride batch stride in elements; 0 means (resolved ld) * @p cols
1413 /// @throws batchlas::invalid_argument on a negative argument, a resolved ld < rows,
1414 /// or a resolved stride < ld * cols
1415 /// @note Exactly `stride * batch_size` elements are allocated; nothing pads.
1416 template <typename U = T, MatrixFormat M = MType>
1417 requires DenseMatrixFormat<M>
1418 Matrix(int rows, int cols, int batch_size = 1, int ld = 0, int stride = 0);
1419
1420 /// @brief Allocates an uninitialised CSR batch with room for @p nnz entries per item.
1421 /// @param rows rows per item
1422 /// @param cols columns per item
1423 /// @param nnz per-item capacity; `matrix_stride() == nnz`, `offset_stride() == rows + 1`
1424 /// @param batch_size number of items
1425 template <typename U = T, MatrixFormat M = MType>
1426 requires CsrMatrixFormat<M>
1427 Matrix(int rows, int cols, NonZeros nnz, int batch_size = 1);
1428
1429 /// @brief Deleted: spell the count `NonZeros{nnz}`; for a dense Matrix the third int is the batch size.
1430 template <typename U = T, MatrixFormat M = MType>
1431 requires CsrMatrixFormat<M>
1432 Matrix(int rows, int cols, int nnz, int batch_size = 1) = delete;
1433
1434 /// @brief Dense batch copied from existing host-readable memory.
1435 ///
1436 /// (@p ld, @p stride) describe the *source*: element (i, j, b) is read from
1437 /// `data[b * stride + j * ld + i]`. The copy keeps the source's leading
1438 /// dimension but packs the items back to back (`stride() == ld() * cols()`);
1439 /// padding rows in the copy are zeroed and padding in the source is never read.
1440 /// @param data source; must hold `(batch_size-1)*stride + (cols-1)*ld + rows` elements
1441 /// @param rows rows per item
1442 /// @param cols columns per item
1443 /// @param ld source leading dimension; 0 means @p rows
1444 /// @param stride source batch stride; 0 means (resolved ld) * @p cols
1445 /// @param batch_size number of items
1446 /// @throws batchlas::invalid_argument on null @p data, rows or cols <= 0,
1447 /// batch_size <= 0, a resolved ld < rows, or (batch_size > 1) a stride < ld * cols
1448 template <typename U = T, MatrixFormat M = MType>
1449 requires DenseMatrixFormat<M>
1450 Matrix(const T* data, int rows, int cols, int ld, int stride = 0, int batch_size = 1);
1451
1452 /// @brief Dense batch copied from a Span; as the pointer overload, plus a length check.
1453 /// @throws batchlas::invalid_argument also when @p data is shorter than the layout needs
1454 template <typename U = T, MatrixFormat M = MType>
1455 requires DenseMatrixFormat<M>
1456 Matrix(Span<const T> data, int rows, int cols, int ld, int stride = 0, int batch_size = 1);
1457
1458 /// @brief Mutable-Span (and UnifiedVector) spelling of the Span<const T> overload.
1459 template <typename U = T, MatrixFormat M = MType>
1460 requires DenseMatrixFormat<M>
1461 Matrix(Span<T> data, int rows, int cols, int ld, int stride = 0, int batch_size = 1)
1462 : Matrix(Span<const T>(data.data(), data.size()), rows, cols, ld, stride, batch_size) {}
1463
1464 /// @brief CSR batch copied from existing host-readable arrays.
1465 /// @param values `matrix_stride * batch_size` values
1466 /// @param row_offsets `offset_stride * batch_size` zero-based offsets
1467 /// @param col_indices `matrix_stride * batch_size` column indices
1468 /// @param rows rows per item
1469 /// @param cols columns per item
1470 /// @param nnz declared per-item non-zero count
1471 /// @param matrix_stride distance between items' value arrays; 0 means @p nnz. May exceed it.
1472 /// @param offset_stride distance between items' offset arrays; 0 means rows + 1
1473 /// @param batch_size number of items
1474 template <typename U = T, MatrixFormat M = MType>
1475 requires CsrMatrixFormat<M>
1476 Matrix(const T* values, const int* row_offsets, const int* col_indices,
1477 int rows, int cols, NonZeros nnz, int matrix_stride = 0,
1478 int offset_stride = 0, int batch_size = 1);
1479
1480 /// @brief Deleted so the pre-tag order (nnz before rows, cols) is a compile error.
1481 template <typename U = T, MatrixFormat M = MType>
1482 requires CsrMatrixFormat<M>
1483 Matrix(const T* values, const int* row_offsets, const int* col_indices,
1484 int nnz, int rows, int cols, int matrix_stride = 0,
1485 int offset_stride = 0, int batch_size = 1) = delete;
1486
1487
1488 /// @name Factories
1489 /// Each returns a packed dense batch (ld == rows, stride == rows * cols) and
1490 /// waits for its fill kernel before returning.
1491 /// @{
1492
1493 /// @brief A batch of n x n identity matrices.
1494 template <typename U = T, MatrixFormat M = MType>
1495 requires DenseMatrixFormat<M>
1496 static Matrix<T, MType> Identity(int n, int batch_size = 1);
1497
1498 /// @brief Entries uniform in [-1, 1] (real and imaginary parts independently).
1499 ///
1500 /// Deterministic: the same @p seed and shape give the same matrix.
1501 /// With @p hermitian, the lower triangle is overwritten with the conjugate of
1502 /// the upper and the diagonal made real.
1503 /// @throws batchlas::invalid_argument if @p hermitian and rows != cols
1504 template <typename U = T, MatrixFormat M = MType>
1505 requires DenseMatrixFormat<M>
1506 static Matrix<T, MType> Random(int rows, int cols, bool hermitian = false, int batch_size = 1, unsigned int seed = 42);
1507
1508 /// @brief Random diagonally dominant sparse Hermitian batch; see csr_generators::random_sparse_hermitian_csr().
1509 template <typename U = T, MatrixFormat M = MType>
1510 requires CsrMatrixFormat<M>
1511 static Matrix<T, MType> RandomSparseHermitian(int n,
1512 float density,
1513 int batch_size = 1,
1514 unsigned int seed = 42,
1515 typename base_type<T>::type diagonal_boost = typename base_type<T>::type(1),
1516 bool shared_pattern = true);
1517
1518 /// @brief Random n x n triangular batch; see MatrixView::fill_triangular_random().
1519 template <typename U = T, MatrixFormat M = MType>
1520 requires DenseMatrixFormat<M>
1521 static Matrix<T, MType> RandomTriangular(int n, Uplo uplo, Diag diag = Diag::NonUnit, int batch_size = 1, unsigned int seed = 42) {
1522 auto result = Matrix<T, MType>(n, n, batch_size);
1523 result.fill_triangular_random(uplo, diag, seed).wait();
1524 return result;
1525 }
1526
1527 /// @brief A batch of zero matrices.
1528 template <typename U = T, MatrixFormat M = MType>
1529 requires DenseMatrixFormat<M>
1530 static Matrix<T, MType> Zeros(int rows, int cols, int batch_size = 1);
1531
1532 /// @brief A batch of all-ones matrices.
1533 template <typename U = T, MatrixFormat M = MType>
1534 requires DenseMatrixFormat<M>
1535 static Matrix<T, MType> Ones(int rows, int cols, int batch_size = 1);
1536
1537 /// @brief A batch of n x n diagonal matrices, n = `diag_values.size()`, each with @p diag_values on the diagonal.
1538 template <typename U = T, MatrixFormat M = MType>
1539 requires DenseMatrixFormat<M>
1540 static Matrix<T, MType> Diagonal(const Span<T>& diag_values, int batch_size = 1);
1541
1542 /// @brief n x n matrices with @p diagonal_value on the diagonal, @p non_diagonal_value in
1543 /// the @p uplo strict triangle and zero elsewhere.
1544 template <typename U = T, MatrixFormat M = MType>
1545 requires DenseMatrixFormat<M>
1546 static Matrix<T, MType> Triangular(int n, Uplo uplo, T diagonal_value = T(1),
1547 T non_diagonal_value = T(0.5), int batch_size = 1);
1548
1549 /// @brief n x n tridiagonal Toeplitz matrices (constant diagonal, sub- and super-diagonal).
1550 template <typename U = T, MatrixFormat M = MType>
1551 requires DenseMatrixFormat<M>
1552 static Matrix<T, MType> TriDiagToeplitz(int n, T diag = T(1),
1553 T sub_diag = T(-0.5), T super_diag = T(0.5), int batch_size = 1);
1554 /// @}
1555
1556 /// @brief Reinterprets this matrix's storage as row-major data and returns it column-major.
1557 ///
1558 /// The row pitch is a parameter, never inferred. The result is packed and owns
1559 /// the memory the kernel writes, so the call waits on @p ctx before returning.
1560 /// @param ctx queue for the conversion kernel
1561 /// @param row_pitch source row pitch in elements; 0 means packed (cols()), accepted
1562 /// only when this matrix is itself packed
1563 /// @return a packed column-major copy
1564 /// @throws std::invalid_argument when the pitch is below cols(), runs past the
1565 /// allocation, straddles the next item, or is 0 on a non-packed matrix
1566 /// @see @ref md_docs_2cpp-api, section "Row-major source data"
1567 // evidence: docs/cpp-api.md#row-major-source-data
1568 template <typename U = T, MatrixFormat M = MType>
1569 requires DenseMatrixFormat<M>
1570 Matrix<T, MType> to_column_major(const Queue& ctx, int row_pitch = 0) const;
1571
1572 /// @brief to_column_major() on an internal queue.
1573 template <typename U = T, MatrixFormat M = MType>
1574 requires DenseMatrixFormat<M>
1575 Matrix<T, MType> to_column_major(int row_pitch = 0) const;
1576
1577 /// @brief Copy in another storage format, on the default queue.
1578 ///
1579 /// Dense to CSR drops entries at or below @p zero_threshold in magnitude (for
1580 /// complex, both parts) and sizes every item by the batch's largest non-zero
1581 /// count (see nnz()). CSR to Dense is supported; converting to the same format
1582 /// returns a copy.
1583 /// @tparam NewMType target format
1584 /// @param zero_threshold magnitude at or below which a dense entry is not stored
1585 /// @throws batchlas::unsupported for any other pair of formats
1586 template <MatrixFormat NewMType>
1587 Matrix<T, NewMType> convert_to(const float_t<T>& zero_threshold = 1e-7) const;
1588
1589 /// @brief Packed row-major copy (row pitch cols(), batch stride rows() * cols()).
1590 ///
1591 /// Exactly what to_column_major() reads with its default pitch, so the two
1592 /// round-trip. Waits on @p ctx before returning.
1593 template <typename U = T, MatrixFormat M = MType>
1594 requires DenseMatrixFormat<M>
1595 Matrix<T, MType> to_row_major(const Queue& ctx) const;
1596
1597 /// @brief to_row_major() on an internal queue.
1598 template <typename U = T, MatrixFormat M = MType>
1599 requires DenseMatrixFormat<M>
1600 Matrix<T, MType> to_row_major() const;
1601
1602 /// @name Copy and move
1603 /// Copies are deep (values, active extents, CSR arrays) and get their own backend
1604 /// handle; moves transfer the storage and leave the source empty.
1605 /// @{
1606 Matrix(const Matrix& other)
1607 : Base(other), values_(other.values_), ptrs_(other.ptrs_), active_rows_store_(other.active_rows_store_),
1608 active_cols_store_(other.active_cols_store_), row_offsets_store_(other.row_offsets_store_),
1609 col_indices_store_(other.col_indices_store_) {
1610 this->backend_handle_.reset();
1611 for (std::size_t b = 0; b < ptrs_.size(); ++b) ptrs_[b] = values_.data() + static_cast<std::ptrdiff_t>(b) * stride_;
1612 bind();
1613 }
1614 Matrix(Matrix&& other) noexcept : Base() { *this = std::move(other); }
1615 Matrix& operator=(const Matrix& other) { return *this = Matrix(other); }
1616 Matrix& operator=(Matrix&& other) noexcept {
1617 Base::operator=(std::move(other));
1618 values_ = std::move(other.values_);
1619 ptrs_ = std::move(other.ptrs_);
1620 active_rows_store_ = std::move(other.active_rows_store_);
1621 active_cols_store_ = std::move(other.active_cols_store_);
1622 row_offsets_store_ = std::move(other.row_offsets_store_);
1623 col_indices_store_ = std::move(other.col_indices_store_);
1624 bind();
1625 other.bind();
1626 return *this;
1627 }
1628 /// @}
1629
1630 /// @brief Deep copy in this matrix's own layout (ld, stride, CSR strides, active extents).
1631 Matrix<T, MType> clone() const {
1632 Matrix<T, MType> result = [&]() -> Matrix<T, MType> {
1633 if constexpr (MType == MatrixFormat::CSR) {
1634 // matrix_stride_ (not nnz_) is the allocated slots per batch item, so
1635 // it is what the copies below need room for.
1636 return Matrix<T, MType>(rows_, cols_, NonZeros{matrix_stride_}, batch_size_);
1637 } else {
1638 // Must be allocated in *this* matrix's layout: the copy below is a flat
1639 // std::copy of length stride_ * batch_size_, so a packed destination is
1640 // overrun -- a heap write past the end -- by any padded ld_.
1641 return Matrix<T, MType>(rows_, cols_, batch_size_, ld_, stride_);
1642 }
1643 }();
1644
1645 std::copy(values_.begin(), values_.end(), result.values_.begin());
1646 if constexpr (MType == MatrixFormat::CSR) {
1647 std::copy(row_offsets_store_.begin(), row_offsets_store_.end(), result.row_offsets_store_.begin());
1648 std::copy(col_indices_store_.begin(), col_indices_store_.end(), result.col_indices_store_.begin());
1649 result.nnz_ = nnz_;
1650 result.matrix_stride_ = matrix_stride_;
1651 result.offset_stride_ = offset_stride_;
1652 } else if (this->is_heterogeneous()) {
1653 result.set_active_dims(this->active_rows(), this->active_cols());
1654 }
1655 return result;
1656 }
1657
1658 /// @brief This matrix as a plain MatrixView (a sliced copy); shares the backend handle.
1659 MatrixView<T, MType> view() const { return *this; }
1660 /// @brief A dense view of the leading @p rows x @p cols block of every item.
1661 /// @param ld leading dimension of the view; <= 0 keeps ld()
1662 /// @param stride batch stride of the view; <= 0 keeps stride()
1663 /// @throws batchlas::unsupported on a CSR matrix
1664 MatrixView<T, MType> view(int rows, int cols, int ld = -1, int stride = -1) const;
1665
1666 /// @brief Dense copy with every element converted by `static_cast<U>`, in this layout.
1667 template <typename U>
1668 Matrix<U, MType> astype() const;
1669 using Base::astype;
1670
1671 /// @brief Sets every element of every item to @p value; waits.
1672 void fill(T value) { Base::fill(value).wait(); }
1673 using Base::fill;
1674
1675 /// @brief Host reference to element (@p row, @p col) of item @p batch anywhere in the
1676 /// allocated rows() x cols(), active extents ignored (MatrixView's checks them).
1677 ///
1678 /// No bounds check beyond UnifiedVector's debug assert. The data is USM shared,
1679 /// so wait for any kernel writing it first.
1680 template <MatrixFormat M = MType>
1681 requires DenseMatrixFormat<M>
1682 T& operator()(int row, int col, int batch) {
1683 // The batch term in int64_t; see KernelMatrixView::operator() for why only that one.
1684 return values_.at(static_cast<int64_t>(batch) * stride_ + col * ld_ + row);
1685 }
1686
1687 /// @brief Host const reference to element (@p row, @p col) of item @p batch.
1688 template <MatrixFormat M = MType>
1689 requires DenseMatrixFormat<M>
1690 const T& operator()(int row, int col, int batch) const {
1691 return values_.at(static_cast<int64_t>(batch) * stride_ + col * ld_ + row);
1692 }
1693 using Base::operator();
1694
1695 /// @brief Host copy of @p src into this matrix's own layout.
1696 /// @pre For CSR, @p src has exactly this matrix's strides and capacity.
1697 /// @throws batchlas::invalid_argument if rows, cols or batch size differ
1698 void copy_from(const MatrixView<T, MType>& src);
1699
1700 /// @brief Attaches per-item active extents (copied), or clears them when both spans are empty.
1701 ///
1702 /// Item b then occupies the leading `active_rows[b] x active_cols[b]` block of
1703 /// its allocated rows() x cols(); layout (ld, stride) is unchanged.
1704 /// @return `*this`
1705 /// @throws batchlas::invalid_argument unless both spans hold batch_size() entries,
1706 /// each in [0, rows()] and [0, cols()] respectively
1707 template <MatrixFormat M = MType>
1708 requires DenseMatrixFormat<M>
1709 Matrix<T, MType>& set_active_dims(Span<int> active_rows, Span<int> active_cols) {
1710 detail::validate_dense_active_dims(rows_, cols_, batch_size_, active_rows, active_cols);
1711 active_rows_store_.resize(active_rows.size());
1712 active_cols_store_.resize(active_cols.size());
1713 std::copy(active_rows.begin(), active_rows.end(), active_rows_store_.begin());
1714 std::copy(active_cols.begin(), active_cols.end(), active_cols_store_.begin());
1715 bind();
1716 return *this;
1717 }
1718
1719 private:
1720 // Points the view half at the owned storage; rerun after every (re)allocation.
1721 void bind() {
1722 this->data_ = values_.to_span();
1723 this->data_ptrs_ = ptrs_.to_span();
1724 this->active_rows_ = active_rows_store_.to_span();
1725 this->active_cols_ = active_cols_store_.to_span();
1726 this->row_offsets_ = row_offsets_store_.to_span();
1727 this->col_indices_ = col_indices_store_.to_span();
1728 }
1729
1730 UnifiedVector<T> values_;
1731 UnifiedVector<T*> ptrs_; // one base pointer per item; allocated only when batch_size > 1
1732 UnifiedVector<int> active_rows_store_;
1733 UnifiedVector<int> active_cols_store_;
1734 UnifiedVector<int> row_offsets_store_;
1735 UnifiedVector<int> col_indices_store_;
1736 };
1737
1738 /// @brief Creates the backend descriptor for a view (src/backends/matrix_handle_impl.cc).
1739 /// @ingroup api_internal_helpers
1740 template <typename T, MatrixFormat MType>
1741 std::shared_ptr<BackendMatrixHandle<T, MType>> createBackendHandle(const MatrixView<T, MType>& view);
1742
1743 /// @brief Non-owning view of a batch of strided vectors: the vector argument type of the entry points.
1744 ///
1745 /// Entry i of item b is at `data_ptr()[b * stride() + i * inc()]`; a stride of 0
1746 /// means `size * inc` (items back to back). The memory must be USM the Queue's
1747 /// device can access and must outlive every use.
1748 /// @trap The positional constructors take (data, size, batch_size, inc, stride),
1749 /// the opposite order to Vector's (size, batch_size, Stride, Inc). Prefer the
1750 /// tagged overloads in new code.
1751 /// @see @ref matrix-model-vectors-inc-and-stride
1752 /// @ingroup api_matrix
1753 template <typename T>
1754 class BATCHLAS_API VectorView {
1755 public:
1756 using value_type = T; ///< Element type.
1757 using pointer = T*; ///< Pointer to an element.
1758 using reference = T&; ///< Reference to an element.
1759 using const_reference = const T&; ///< Const reference to an element.
1760
1761 /// @brief Elements needed to hold the layout: `(batch_size-1)*stride + (size-1)*inc + 1`, or 0 when empty.
1762 static constexpr std::size_t required_span_length(int size, int inc, int stride, int batch_size) {
1763 if (size <= 0 || batch_size <= 0) return 0;
1764 const int64_t max_index = int64_t(batch_size - 1) * int64_t(stride) + int64_t(size - 1) * int64_t(inc);
1765 return static_cast<std::size_t>(max_index + 1);
1766 }
1767
1768 /// @brief An empty view with batch size 1.
1769 VectorView() : data_(), size_(0), inc_(1), stride_(0), batch_size_(1) {}
1770 /// @brief Views @p data with the given layout.
1771 /// @param data storage; must hold required_span_length() elements (debug-asserted)
1772 /// @param size entries per item
1773 /// @param batch_size number of items
1774 /// @param inc element distance between entries
1775 /// @param stride element distance between items; 0 means size * inc
1776 VectorView(Span<T> data, int size, int batch_size = 1, int inc = 1, int stride = 0)
1777 : data_(data.data(), required_span_length(size, inc, (stride > 0 ? stride : size * inc), batch_size)),
1778 size_(size), inc_(inc), stride_(stride > 0 ? stride : size * inc), batch_size_(batch_size) {
1779 assert(data.size() >= data_.size());
1780 }
1781 /// @brief Views a UnifiedVector's storage; parameters as the Span overload.
1782 VectorView(UnifiedVector<T>& data, int size, int batch_size = 1, int inc = 1, int stride = 0)
1783 : data_(data.data(), required_span_length(size, inc, (stride > 0 ? stride : size * inc), batch_size)),
1784 size_(size), inc_(inc), stride_(stride > 0 ? stride : size * inc), batch_size_(batch_size) {
1785 assert(data.size() >= data_.size());
1786 }
1787 /// @brief Views raw memory; parameters as the Span overload (no length check).
1788 VectorView(T* data, int size, int batch_size = 1, int inc = 1, int stride = 0)
1789 : data_(data, required_span_length(size, inc, (stride > 0 ? stride : size * inc), batch_size)),
1790 size_(size), inc_(inc), stride_(stride > 0 ? stride : size * inc), batch_size_(batch_size) {}
1791
1792 /// @name Tagged constructors (preferred for new code)
1793 /// Same as the positional forms, with inc and stride spelled `Inc{}` / `Stride{}`.
1794 /// @{
1795 // Additive on purpose: the in-repo positional constructions are all correct.
1796 /// @brief Views a Span; see the positional Span overload.
1797 VectorView(Span<T> data, int size, int batch_size, Inc inc, Stride stride = Stride{0})
1798 : VectorView(data, size, batch_size, inc.value, stride.value) {}
1799 /// @brief Views a UnifiedVector's storage.
1800 VectorView(UnifiedVector<T>& data, int size, int batch_size, Inc inc, Stride stride = Stride{0})
1801 : VectorView(data, size, batch_size, inc.value, stride.value) {}
1802 /// @brief Views raw memory (no length check).
1803 VectorView(T* data, int size, int batch_size, Inc inc, Stride stride = Stride{0})
1804 : VectorView(data, size, batch_size, inc.value, stride.value) {}
1805 /// @}
1806
1807 /// @name Copy and move
1808 /// Shallow: the copy aliases the same memory.
1809 /// @{
1810 VectorView(const VectorView<T>&) = default;
1811 VectorView& operator=(const VectorView<T>&) = default;
1812 VectorView(VectorView<T>&&) noexcept = default;
1813 VectorView& operator=(VectorView<T>&&) noexcept = default;
1814 /// @}
1815
1816 /// @brief The viewed storage, from entry 0 of item 0 to the last entry of the last item.
1817 Span<T> data() const { return data_; }
1818 /// @name USM memory hints
1819 /// See Span; the Queue is mandatory, as on Matrix.
1820 /// @{
1821 /// @brief Maps the viewed memory for access by the device of @p ctx.
1822 Event set_access_device(const Queue& ctx) const {
1823 return data_.set_access_device(ctx);
1824 }
1825
1826 /// @brief Prefers residence of the viewed memory on the device of @p ctx.
1827 Event set_preferred_location(const Queue& ctx) const {
1828 return data_.set_preferred_location(ctx);
1829 }
1830
1831 /// @brief Migrates the viewed memory to the device of @p ctx.
1832 Event prefetch(const Queue& ctx) const {
1833 return data_.prefetch(ctx);
1834 }
1835 /// @}
1836 T* data_ptr() const { return data_.data(); } ///< Pointer to entry 0 of item 0.
1837 int size() const { return size_; } ///< Entries per item.
1838 /// @brief Element distance between entries.
1839 int inc() const { return inc_; }
1840 /// @brief Element distance between items.
1841 int stride() const { return stride_; }
1842 int batch_size() const { return batch_size_; } ///< Number of batch items.
1843 /// @brief Every item index, 0 .. batch_size() - 1.
1844 std::vector<int> all_items() const {
1845 std::vector<int> items(static_cast<std::size_t>(batch_size_ > 0 ? batch_size_ : 0));
1846 for (std::size_t i = 0; i < items.size(); ++i) items[i] = static_cast<int>(i);
1847 return items;
1848 }
1849
1850 /// @brief {0, batch_size() / 2, batch_size() - 1}, sorted and de-duplicated ({} for batch 0):
1851 /// what a check reads by default, as MatrixView::default_items().
1852 std::vector<int> default_items() const {
1853 if (batch_size_ < 1) return {};
1854 std::vector<int> items{0, batch_size_ / 2, batch_size_ - 1};
1855 items.erase(std::unique(items.begin(), items.end()), items.end());
1856 return items;
1857 }
1858
1859 /// @brief Owning copy with every element converted by `static_cast<U>`, in this layout; host loop.
1860 template <typename U>
1861 Vector<U> astype() const;
1862
1863 /// @brief Enqueues dest := src on @p ctx, honouring each side's inc and stride.
1864 /// @throws batchlas::invalid_argument if the sizes or batch sizes differ
1865 static Event copy(Queue& ctx, const VectorView<T>& dest, const VectorView<T>& src);
1866
1867 /// @brief True when every entry of @p a and @p b agrees to relative tolerance @p tol; waits on @p ctx.
1868 /// @see MatrixView::all_close() for the comparison rule.
1869 static bool all_close(Queue& ctx, const VectorView<T>& a, const VectorView<T>& b, float_t<T> tol = std::numeric_limits<float_t<T>>::epsilon());
1870
1871 /// @brief Entry @p i of item @p batch; usable on the host and in a kernel.
1872 /// @pre 0 <= i < size(), 0 <= batch < batch_size() (asserted in debug builds)
1873 T& at(int i, int batch = 0) const {
1874 assert(i < size_); assert(i >= 0);
1875 assert(batch < batch_size_); assert(batch >= 0);
1876 // Both sides int64_t: an int index has already wrapped, and a size_t
1877 // conversion turns a negative index into one that passes the check.
1878 assert(static_cast<int64_t>(i) * inc_ + static_cast<int64_t>(batch) * stride_ <
1879 static_cast<int64_t>(data_.size()));
1880 return data_[static_cast<int64_t>(i) * inc_ + static_cast<int64_t>(batch) * stride_]; }
1881
1882 /// @brief Entry @p i of item 0.
1883 T& operator[](int i) const { assert(i < size_); assert(i >= 0); assert(static_cast<int64_t>(i) * inc_ < static_cast<int64_t>(data_.size())); return data_[static_cast<int64_t>(i) * inc_]; }
1884
1885 /// @brief at(@p i, @p batch).
1886 T& operator()(int i, int batch = 0) const {return at(i, batch);}
1887
1888 /// @brief A batch-size-1 view of item @p batch_index.
1889 VectorView<T> batch_item(int batch_index) const {
1890 // For a single batch, we need (size-1)*inc + 1 elements starting at batch_index * stride
1891 const std::size_t single_batch_length = (size_ > 0) ? ((size_ - 1) * inc_ + 1) : 0;
1892 return VectorView<T>(Span<T>(data_.data() + static_cast<int64_t>(batch_index) * stride_, single_batch_length), size_, 1, inc_, 0);
1893 }
1894
1895 /// @brief Entries @p slice of every item; keeps inc, stride and batch size.
1896 /// @pre The slice is non-empty (asserted in debug builds)
1897 VectorView<T> operator()(Slice slice) const {
1898 int64_t n;
1899 if (slice.start == std::numeric_limits<int64_t>::min() && slice.end == std::numeric_limits<int64_t>::max()) {
1900 n = size_;
1901 } else if (slice.end == std::numeric_limits<int64_t>::max()) {
1902 slice.start = slice.start < 0 ? size_ + slice.start : slice.start;
1903 n = size_ - slice.start;
1904 } else {
1905 if (slice.start < 0) slice.start = size_ + slice.start;
1906 if (slice.end < 0) slice.end = size_ + slice.end;
1907 n = slice.end - slice.start;
1908 }
1909 if (n <= 0) {
1910 assert("Invalid slice dimensions for VectorView encountered in operator()(Slice)" && false);
1911 }
1912 const auto required = required_span_length(static_cast<int>(n), inc_, stride_, batch_size_);
1913 return VectorView<T>(Span<T>(data_.data() + slice.start * inc_, required), n, batch_size_, inc_, stride_);
1914 }
1915
1916 /// @brief Prints the leading @p max_elements entries of each item in scientific notation.
1917 /// @return @p os, with its format flags restored
1918 std::ostream& stream_formatted_to(std::ostream& os, int max_elements = 10, int max_cols_to_print = 10) const {
1919 std::ios_base::fmtflags original_flags = os.flags();
1920 os << std::scientific << std::setprecision(4);
1921
1922 for (int b_idx = 0; b_idx < batch_size_; ++b_idx) {
1923 if (batch_size_ > 1) {
1924 os << "Batch " << b_idx << ":\n";
1925 }
1926 os << " [";
1927 for (int i = 0; i < std::min(size_, max_elements); ++i) {
1928 detail::print_with_width(os, at(i, b_idx), 13); // Supports non-streamable element types
1929 }
1930 if (size_ > max_elements) {
1931 os << " ...";
1932 }
1933 os << " ]\n";
1934 }
1935 os.flags(original_flags); // Reset stream flags
1936 return os;
1937 }
1938
1939 /// @brief stream_formatted_to(@p os, @p max_elements).
1940 void print(std::ostream& os = std::cout, int max_elements = 10) const {
1941 stream_formatted_to(os, max_elements);
1942 }
1943
1944 protected:
1945 Span<T> data_;
1946 int size_ = 0;
1947 int inc_ = 1;
1948 int stride_ = 0;
1949 int batch_size_ = 1;
1950 };
1951
1952 /// @brief Owning batch of `batch_size()` vectors of length `size()` in USM shared memory.
1953 ///
1954 /// Element i of item b lives at `data()[b * stride() + i * inc()]`. The default
1955 /// stride is `size() * inc()` (items back to back). A Vector is a VectorView of its
1956 /// own storage, so it is accepted wherever a VectorView is.
1957 /// @trap The constructor takes (size, batch_size, Stride, Inc) while VectorView's
1958 /// positional constructor takes (data, size, batch_size, inc, stride); the tag types
1959 /// and the deleted bare-int overloads keep the two from being transliterated.
1960 /// @see @ref matrix-model-vectors-inc-and-stride
1961 /// @ingroup api_matrix
1962 template <typename T>
1963 struct Vector : public VectorView<T> {
1964 using Base = VectorView<T>;
1965
1966 /// @brief An empty vector with batch size 1.
1967 Vector() = default;
1968
1969 /// @brief Allocates an uninitialised batch.
1970 /// @param size elements per item
1971 /// @param batch_size number of items
1972 /// @param stride element distance between items; Stride{0} means size * inc
1973 /// @param inc element distance between entries
1974 Vector(int size, int batch_size = 1, Stride stride = Stride{0}, Inc inc = Inc{1})
1975 : Vector(size, batch_size, stride, inc, UnifiedVector<T>(Base::required_span_length(size, inc.value, resolved(size, stride, inc), batch_size))) {}
1976 /// @brief Allocates a batch with every stored element (gaps included) set to @p value.
1977 Vector(int size, T value, int batch_size = 1, Stride stride = Stride{0}, Inc inc = Inc{1})
1978 : Vector(size, batch_size, stride, inc, UnifiedVector<T>(Base::required_span_length(size, inc.value, resolved(size, stride, inc), batch_size), value)) {}
1979
1980 /// @brief Deleted: spell the layout `Stride{s}, Inc{i}`.
1981 Vector(int size, int batch_size, int stride, int inc) = delete;
1982 /// @brief Deleted: spell the layout `Stride{s}, Inc{i}`.
1983 Vector(int size, T value, int batch_size, int stride, int inc) = delete;
1984 /// @brief Deleted: `Vector<float>(n, batch, stride)` would otherwise pick the
1985 /// (size, value, batch_size) overload and turn `batch` into the fill value.
1986 // Vector<int> loses (size, value, batch) to ambiguity; nothing in the repo uses it.
1987 Vector(int size, int batch_size, int stride) = delete;
1988
1989 /// @brief A batch of zero vectors.
1990 static Vector<T> zeros(int size, int batch_size = 1, Stride stride = Stride{0}, Inc inc = Inc{1}) {
1991 return Vector<T>(size, T(0), batch_size, stride, inc);
1992 }
1993 /// @brief A batch of all-ones vectors.
1994 static Vector<T> ones(int size, int batch_size = 1, Stride stride = Stride{0}, Inc inc = Inc{1}) {
1995 return Vector<T>(size, T(1), batch_size, stride, inc);
1996 }
1997 /// @brief Entries uniform in [0, 1) (real part only), from a host RNG with a fixed seed of 42.
1998 static Vector<T> random(int size, int batch_size = 1, Stride stride = Stride{0}, Inc inc = Inc{1}) {
1999 Vector<T> vec(size, batch_size, stride, inc);
2000 std::mt19937 rng(42); // Mersenne Twister engine with fixed seed
2001 std::uniform_real_distribution<float_t<T>> dist(0.0f, 1.0f);
2002 for (int b = 0; b < batch_size; ++b) {
2003 for (int i = 0; i < size; ++i) {
2004 vec(i, b) = static_cast<T>(dist(rng));
2005 }
2006 }
2007 return vec;
2008 }
2009 /// @brief Unit vectors e_@p index (zero except a one at @p index), in every item.
2010 static Vector<T> standard_basis(int size, int index, int batch_size = 1, Stride stride = Stride{0}) {
2011 Vector<T> vec(size, T(0), batch_size, stride, Inc{1});
2012 for (int b = 0; b < batch_size; ++b) {
2013 vec(index, b) = T(1);
2014 }
2015 return vec;
2016 }
2017
2018 /// @name Deleted bare-int factory spellings
2019 /// Spell the layout `Stride{s}, Inc{i}`; the old argument order is a compile error.
2020 /// @{
2021 // standard_basis's third argument really is batch_size, so it stays legal.
2022 static Vector<T> zeros(int size, int batch_size, int stride, int inc) = delete;
2023 static Vector<T> ones(int size, int batch_size, int stride, int inc) = delete;
2024 static Vector<T> random(int size, int batch_size, int stride, int inc) = delete;
2025 static Vector<T> zeros(int size, int batch_size, int stride) = delete;
2026 static Vector<T> ones(int size, int batch_size, int stride) = delete;
2027 static Vector<T> random(int size, int batch_size, int stride) = delete;
2028 static Vector<T> standard_basis(int size, int index, int batch_size, int stride) = delete;
2029 /// @}
2030
2031 /// @name Copy and move
2032 /// Copies are deep; moves transfer the storage and leave the source empty.
2033 /// @{
2034 Vector(const Vector& other) : Base(other), values_(other.values_) { bind(); }
2035 Vector(Vector&& other) noexcept : Base() { *this = std::move(other); }
2036 Vector& operator=(const Vector& other) { return *this = Vector(other); }
2037 Vector& operator=(Vector&& other) noexcept {
2038 Base::operator=(std::move(other));
2039 values_ = std::move(other.values_);
2040 bind();
2041 other.bind();
2042 return *this;
2043 }
2044 /// @}
2045
2046 /// @brief This vector as a plain VectorView (a sliced copy).
2047 VectorView<T> view() const { return *this; }
2048
2049 /// @brief Raw storage index @p i, ignoring inc and stride (kept for compatibility).
2050 T& operator[](int i) { return values_[i]; }
2051
2052 private:
2053 static int resolved(int size, Stride stride, Inc inc) { return stride.value > 0 ? stride.value : size * inc.value; }
2054
2055 Vector(int size, int batch_size, Stride stride, Inc inc, UnifiedVector<T>&& values)
2056 : Base(values.data(), size, batch_size, inc.value, resolved(size, stride, inc)), values_(std::move(values)) {
2057 bind();
2058 }
2059
2060 void bind() { this->data_ = values_.to_span(); }
2061
2062 UnifiedVector<T> values_;
2063 };
2064
2065 // ------------------------------------------------------------------
2066 // Conversion helpers
2067 // ------------------------------------------------------------------
2068 template <typename T, MatrixFormat MType>
2069 template <typename U>
2070 Matrix<U, MType> Matrix<T, MType>::astype() const {
2071 static_assert(MType == MatrixFormat::Dense, "Matrix::astype only supports dense matrices");
2072 Matrix<U, MType> result(rows_, cols_, batch_size_, ld_, stride_);
2073 auto src = this->data();
2074 auto dst = result.data();
2075 for (std::size_t i = 0; i < dst.size(); ++i) {
2076 dst[i] = static_cast<U>(src[i]);
2077 }
2078 return result;
2079 }
2080
2081 template <typename T, MatrixFormat MType>
2082 template <typename U>
2083 Matrix<U, MType> MatrixView<T, MType>::astype() const {
2084 static_assert(MType == MatrixFormat::Dense, "MatrixView::astype only supports dense matrices");
2085 Matrix<U, MType> result(rows_, cols_, batch_size_);
2086 // Deliberately a host loop, not a kernel: astype takes no Queue, and building one
2087 // (then waiting on it) would cost more than the copy.
2088 const T* src_ptr = data_.data();
2089 U* dst_ptr = result.data().data();
2090 const std::size_t src_stride = static_cast<std::size_t>(stride_);
2091 const std::size_t packed = static_cast<std::size_t>(rows_) * static_cast<std::size_t>(cols_);
2092
2093 if (ld_ == rows_ && src_stride == packed) {
2094 const std::size_t n = packed * static_cast<std::size_t>(batch_size_);
2095 for (std::size_t k = 0; k < n; ++k) dst_ptr[k] = static_cast<U>(src_ptr[k]);
2096 } else {
2097 for (int b = 0; b < batch_size_; ++b) {
2098 for (int j = 0; j < cols_; ++j) {
2099 const T* src_col = src_ptr + static_cast<std::size_t>(b) * src_stride +
2100 static_cast<std::size_t>(j) * static_cast<std::size_t>(ld_);
2101 U* dst_col = dst_ptr + static_cast<std::size_t>(b) * packed +
2102 static_cast<std::size_t>(j) * static_cast<std::size_t>(rows_);
2103 for (int i = 0; i < rows_; ++i) dst_col[i] = static_cast<U>(src_col[i]);
2104 }
2105 }
2106 }
2107 return result;
2108 }
2109
2110 template <typename T, MatrixFormat MType>
2111 template <typename U>
2112 Matrix<U, MType> MatrixView<T, MType>::astype(std::span<const int> items) const {
2113 static_assert(MType == MatrixFormat::Dense, "MatrixView::astype only supports dense matrices");
2114 if (is_heterogeneous()) throw batchlas::invalid_argument("MatrixView::astype: heterogeneous views are not supported");
2115 Matrix<U, MType> result(rows_, cols_, static_cast<int>(items.size()));
2116 const std::size_t packed = static_cast<std::size_t>(rows_) * static_cast<std::size_t>(cols_);
2117 U* dst = result.data().data();
2118 for (std::size_t g = 0; g < items.size(); ++g) {
2119 if (items[g] < 0 || items[g] >= batch_size_) throw batchlas::out_of_range("MatrixView::astype: batch item out of range");
2120 const T* src = data_.data() + static_cast<std::size_t>(items[g]) * static_cast<std::size_t>(stride_);
2121 for (int j = 0; j < cols_; ++j)
2122 for (int i = 0; i < rows_; ++i)
2123 dst[g * packed + static_cast<std::size_t>(j) * rows_ + i] =
2124 static_cast<U>(src[static_cast<std::size_t>(j) * static_cast<std::size_t>(ld_) + i]);
2125 }
2126 return result;
2127 }
2128
2129 template <typename T, MatrixFormat MType>
2130 std::vector<int> MatrixView<T, MType>::all_items() const {
2131 std::vector<int> items(static_cast<std::size_t>(std::max(batch_size_, 0)));
2132 for (std::size_t i = 0; i < items.size(); ++i) items[i] = static_cast<int>(i);
2133 return items;
2134 }
2135
2136 template <typename T, MatrixFormat MType>
2137 std::vector<int> MatrixView<T, MType>::default_items() const {
2138 if (batch_size_ < 1) return {};
2139 std::vector<int> items{0, batch_size_ / 2, batch_size_ - 1};
2140 items.erase(std::unique(items.begin(), items.end()), items.end());
2141 return items;
2142 }
2143
2144 template <typename T>
2145 template <typename U>
2146 Vector<U> VectorView<T>::astype() const {
2147 Vector<U> result(size_, batch_size_, Stride{stride_}, Inc{inc_});
2148 for (int b = 0; b < batch_size_; ++b) {
2149 for (int i = 0; i < size_; ++i) {
2150 result(i, b) = static_cast<U>((*this)(i, b));
2151 }
2152 }
2153 return result;
2154 }
2155
2156 /// @brief Shape (rows, cols) of op(@p mat) for @p trans: swapped unless NoTrans.
2157 /// @ingroup api_matrix_lowlevel
2158 template <typename T = float, MatrixFormat MType = MatrixFormat::Dense>
2159 std::pair<int, int> get_effective_dims(const MatrixView<T, MType>& mat, Transpose trans) {
2160 return (trans == Transpose::NoTrans)
2161 ? std::make_pair(mat.rows_, mat.cols_)
2162 : std::make_pair(mat.cols_, mat.rows_);
2163 }
2164
2165 /// @brief Shape of op(@p mat) for item @p batch_index, using that item's active extents.
2166 /// @ingroup api_matrix_lowlevel
2167 template <typename T = float, MatrixFormat MType = MatrixFormat::Dense>
2168 std::pair<int, int> get_effective_dims(const MatrixView<T, MType>& mat, Transpose trans, int batch_index) {
2169 const int rows = mat.rows(batch_index);
2170 const int cols = mat.cols(batch_index);
2171 return (trans == Transpose::NoTrans)
2172 ? std::make_pair(rows, cols)
2173 : std::make_pair(cols, rows);
2174 }
2175
2176 /// @brief Prints with MatrixView::stream_formatted_to() defaults.
2177 /// @ingroup api_matrix_lowlevel
2178 template <typename T, MatrixFormat MType>
2179 std::ostream& operator<<(std::ostream& os, const MatrixView<T, MType>& view) {
2180 return view.stream_formatted_to(os); // Uses default arguments from stream_formatted_to
2181 }
2182
2183 /// @brief Prints with VectorView::stream_formatted_to() defaults.
2184 /// @ingroup api_matrix_lowlevel
2185 template <typename T>
2186 std::ostream& operator<<(std::ostream& os, const VectorView<T>& view) {
2187 return view.stream_formatted_to(os); // Uses default arguments from stream_formatted_to
2188 }
2189
2190 /// @brief In-place A := alpha * A on every element of every item (dense).
2191 /// @param ctx queue the kernel is enqueued on
2192 /// @param alpha scale factor
2193 /// @param mat_view matrices to scale, within rows() x cols() (ld and stride honoured)
2194 /// @return event of the kernel
2195 /// @throws batchlas::unsupported for CSR
2196 /// @ingroup api_extra
2197 template <typename T, MatrixFormat MType>
2198 BATCHLAS_API Event scale(Queue& ctx, const T& alpha, const MatrixView<T, MType>& mat_view);
2199
2200 /// @brief In-place x := alpha * x on every entry of every item.
2201 /// @return event of the kernel
2202 /// @ingroup api_extra
2203 template <typename T>
2204 BATCHLAS_API Event scale(Queue& ctx, const T& alpha, const VectorView<T>& vec_view);
2205
2206} // namespace batchlas
Backend-library descriptor for a matrix (cuSPARSE / rocSPARSE handles); defined in src/.
Definition matrix.hh:49
Non-owning view of a batch of matrices: the argument type of every entry point.
Definition matrix.hh:485
Owning batch of batch_size() matrices of equal (capacity) shape.
Definition matrix.hh:1399
Non-owning view of a batch of strided vectors: the vector argument type of the entry points.
Definition matrix.hh:1754
Constrains a member or overload to CSR storage.
Definition enums.hh:103
Constrains a member or overload to dense storage.
Definition enums.hh:99
Enumerations, scalar traits and scalar concepts shared by every public header.
@ Dense
Column-major dense storage with leading dimension and batch stride.
Definition enums.hh:73
@ CSR
Compressed Sparse Row.
Definition enums.hh:74
constexpr BatchSize(int v=1)
Wraps v.
Definition matrix.hh:105
int value
The wrapped value.
Definition matrix.hh:104
constexpr Inc(int v=1)
Wraps v.
Definition matrix.hh:75
int value
The wrapped value.
Definition matrix.hh:74
int * active_rows_
Dense: per-item row counts, or null when homogeneous.
Definition matrix.hh:153
int rows_
Rows (the capacity when heterogeneous).
Definition matrix.hh:147
KernelMatrixView operator()(Slice rows_slice, Slice cols_slice={}) const
Dense sub-block view; same pointer arithmetic as MatrixView::operator()(Slice, Slice).
VectorView< T > operator()(Slice rows_slice, int32_t col) const
Part of column col as a batched VectorView (inc = 1, stride = stride_).
auto data() const
Base pointer of item 0.
Definition matrix.hh:217
T * data_
Values of batch item 0.
Definition matrix.hh:146
int nnz_
CSR: per-item non-zero capacity.
Definition matrix.hh:158
KernelMatrixView(KernelMatrixView &&)=default
int batch_size_
Number of batch items.
Definition matrix.hh:149
auto batch_size() const
Number of batch items.
Definition matrix.hh:226
auto rows() const
Row count (the capacity on a heterogeneous batch).
Definition matrix.hh:219
KernelMatrixView operator()(Slice rows_slice) const
Dense row-range view over all columns.
Definition matrix.hh:247
KernelMatrixView()=default
A null 0 x 0 view with batch size 1.
auto rows(int batch_index) const
Active row count of batch item batch_index.
Definition matrix.hh:221
constexpr KernelMatrixView batch_item(int b) const
A single-item view (batch_size_ = 1) of batch item b.
Definition matrix.hh:198
int * active_cols_
Dense: per-item column counts, or null when homogeneous.
Definition matrix.hh:154
bool is_heterogeneous() const
True when per-item active extents are attached.
Definition matrix.hh:237
auto nnz(int b) const
CSR non-zeros actually stored by batch item b, read from its row offsets.
Definition matrix.hh:232
auto ld() const
Dense leading dimension, in elements.
Definition matrix.hh:227
constexpr T & operator()(int i, int j, int b=0) const
Dense element (i, j) of batch item b: data_[b * stride_ + j * ld_ + i].
Definition matrix.hh:166
KernelMatrixView & operator=(const KernelMatrixView &)=default
KernelMatrixView & operator=(KernelMatrixView &&)=default
auto cols() const
Column count (the capacity on a heterogeneous batch).
Definition matrix.hh:223
int stride_
Dense: batch stride, in elements.
Definition matrix.hh:152
int * col_indices_
CSR: column indices, items matrix_stride_ apart.
Definition matrix.hh:157
int cols_
Columns (the capacity when heterogeneous).
Definition matrix.hh:148
KernelMatrixView(T *data, int rows, int cols, int ld=0, int stride=0, int batch_size=1)
Dense view over raw device-accessible memory.
Definition matrix.hh:280
constexpr T get(int i, int j, int b=0) const
CSR coefficient (i, j) of batch item b, or zero if not stored.
Definition matrix.hh:183
auto cols(int batch_index) const
Active column count of batch item batch_index.
Definition matrix.hh:225
int * row_offsets_
CSR: rows + 1 offsets per item, items offset_stride_ apart.
Definition matrix.hh:156
int offset_stride_
CSR: element distance between items' offset arrays.
Definition matrix.hh:160
VectorView< T > operator()(int32_t row, Slice cols_slice) const
Part of row row as a batched VectorView (inc = ld_, stride = stride_).
int matrix_stride_
CSR: element distance between items' value (and index) arrays.
Definition matrix.hh:159
int ld_
Dense: leading dimension, in elements.
Definition matrix.hh:151
auto nnz() const
CSR per-item non-zero capacity; over-counts every item smaller than the largest.
Definition matrix.hh:230
auto stride() const
Dense batch stride, in elements.
Definition matrix.hh:228
KernelMatrixView(const KernelMatrixView &)=default
constexpr Ld(int v=0)
Wraps v.
Definition matrix.hh:95
int value
The wrapped value.
Definition matrix.hh:94
int value
The wrapped value.
Definition matrix.hh:59
constexpr NonZeros(int v)
Wraps v.
Definition matrix.hh:60
Marker for "to the end" in a Slice: Slice(k, SliceEnd{}) selects [k, dim).
Definition matrix.hh:112
A half-open index range [start, end) along one dimension.
Definition matrix.hh:120
int64_t end
One past the last index; negative counts from the end; max means the end of the dimension.
Definition matrix.hh:122
Slice(int64_t start, SliceEnd)
[start, dim).
Definition matrix.hh:124
Slice(int64_t start, int64_t end)
[start, end); either bound may be negative (counted from the end).
Definition matrix.hh:126
int64_t start
First index; negative counts from the end. The default (min) with a default end means the whole dimen...
Definition matrix.hh:121
Slice()=default
The whole dimension.
Slice(int64_t start)
[start, dim) (implicit, so an integer can stand in for a Slice).
Definition matrix.hh:128
int value
The wrapped value.
Definition matrix.hh:84
constexpr Stride(int v=0)
Wraps v.
Definition matrix.hh:85
Owning batch of batch_size() vectors of length size() in USM shared memory.
Definition matrix.hh:1963
batchlas::Device, batchlas::Event and batchlas::Queue: where work runs and how it is ordered.
batchlas::Span, the non-owning pointer + length every entry point takes for 1-D data.
batchlas::UnifiedVector, an owning growable array in USM shared memory.