Quantum 3xplorer (Q3)
An AI-native quantum simulation engine
Loading...
Searching...
No Matches
dense_matrix.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * DenseMatrix — a small dense matrix stored column-major, the layout every
5 * BLAS and LAPACK routine reads and every checkpoint block is written in.
6 *
7 * Matrix<T> (types/matrix.hpp) is row-major and is the shape of a plane-wave
8 * coefficient array: a row per band, indexed [band][plane_wave]. A matrix that
9 * is multiplied, adjointed or diagonalised is a different object -- it goes to
10 * zgemm and zheev as a pointer and a leading dimension, and those read
11 * columns -- so it has a type of its own rather than a transposed reading of
12 * the other. Element (row, column) lives at data[column * rows + row].
13 *
14 * The products, the eigensolver and the singular value decomposition live in
15 * their own files beside this one; this is the storage and the element-wise
16 * operations only.
17 */
18
19#include <complex>
20#include <cstddef>
21#include <stdexcept>
22#include <string>
23#include <utility>
24#include <vector>
25
27
28namespace q3 {
29
30namespace internal::dense_matrix {
31
32[[nodiscard]] inline Real conjugate(Real value) {
33 return value;
34}
35
36[[nodiscard]] inline Complex conjugate(const Complex& value) {
37 return std::conj(value);
38}
39
40} // namespace internal::dense_matrix
41
42template <typename T> class [[nodiscard]] DenseMatrix {
43 public:
44 DenseMatrix() = default;
45
46 DenseMatrix(int rows, int columns, const T& initial_value = T{})
47 : data_(static_cast<std::size_t>(require_dimension(rows, "rows")) *
48 static_cast<std::size_t>(require_dimension(columns, "columns")),
49 initial_value)
50 , rows_(rows)
51 , columns_(columns) {}
52
53 /// Adopts a column-major buffer; refuses one whose size is not rows * columns.
54 DenseMatrix(int rows, int columns, std::vector<T> column_major_data)
55 : data_(std::move(column_major_data))
56 , rows_(require_dimension(rows, "rows"))
57 , columns_(require_dimension(columns, "columns")) {
58 if (data_.size() != number_of_elements()) {
59 throw std::invalid_argument(
60 "DenseMatrix: a " + std::to_string(rows) + " x " + std::to_string(columns) +
61 " matrix holds " + std::to_string(number_of_elements()) + " elements, not the " +
62 std::to_string(data_.size()) + " given");
63 }
64 }
65
66 [[nodiscard]] static DenseMatrix identity(int dimension) {
67 DenseMatrix matrix(dimension, dimension, T{});
68 for (int index = 0; index < dimension; index++) {
69 matrix(index, index) = T{1};
70 }
71 return matrix;
72 }
73
74 [[nodiscard]] int rows() const {
75 return rows_;
76 }
77 [[nodiscard]] int columns() const {
78 return columns_;
79 }
80 [[nodiscard]] std::size_t number_of_elements() const {
81 return static_cast<std::size_t>(rows_) * static_cast<std::size_t>(columns_);
82 }
83 [[nodiscard]] bool empty() const {
84 return data_.empty();
85 }
86
87 T& operator()(int row, int column) {
88 return data_[index_of(row, column)];
89 }
90 [[nodiscard]] const T& operator()(int row, int column) const {
91 return data_[index_of(row, column)];
92 }
93
94 /// The column-major buffer: with rows() it is the (pointer, leading dimension) BLAS takes.
95 T* data() {
96 return data_.data();
97 }
98 [[nodiscard]] const T* data() const {
99 return data_.data();
100 }
101 /// Read-only: a caller that could resize the buffer could break rows() x columns().
102 [[nodiscard]] const std::vector<T>& raw() const {
103 return data_;
104 }
105
106 /// The conjugate transpose, columns x rows.
107 [[nodiscard]] DenseMatrix adjoint() const {
108 DenseMatrix result(columns_, rows_, T{});
109 for (int column = 0; column < columns_; column++) {
110 for (int row = 0; row < rows_; row++) {
111 result(column, row) = internal::dense_matrix::conjugate((*this)(row, column));
112 }
113 }
114 return result;
115 }
116
117 /// The sum of every element's squared modulus.
118 [[nodiscard]] Real frobenius_norm_squared() const {
119 Real total = 0.0;
120 for (const T& value : data_) {
121 total += std::norm(value);
122 }
123 return total;
124 }
125
126 /// factor * this, element by element.
127 template <typename Factor> [[nodiscard]] DenseMatrix scaled(const Factor& factor) const {
128 DenseMatrix result = *this;
129 for (T& value : result.data_) {
130 value *= factor;
131 }
132 return result;
133 }
134
135 /// this += factor * other, element by element; the two shapes must agree.
136 template <typename Factor>
137 DenseMatrix& add_scaled(const Factor& factor, const DenseMatrix& other) {
138 require_same_shape(other, "add_scaled");
139 for (std::size_t index = 0; index < data_.size(); index++) {
140 data_[index] += factor * other.data_[index];
141 }
142 return *this;
143 }
144
145 /**
146 * Re tr(this^dagger other), the sum of Re(conj(a) b) over matching
147 * elements: the inner product under which the matrices of one shape are
148 * a real vector space, which is what a gradient and a search direction
149 * are compared in.
150 */
151 [[nodiscard]] Real real_inner_product(const DenseMatrix& other) const {
152 require_same_shape(other, "real_inner_product");
153 Real total = 0.0;
154 for (std::size_t index = 0; index < data_.size(); index++) {
155 total +=
156 std::real(internal::dense_matrix::conjugate(data_[index]) * other.data_[index]);
157 }
158 return total;
159 }
160
161 /// The rows and columns named, in the order named; an index outside the matrix is refused.
162 [[nodiscard]] DenseMatrix submatrix(const std::vector<int>& row_indices,
163 const std::vector<int>& column_indices) const {
164 DenseMatrix result(
165 static_cast<int>(row_indices.size()), static_cast<int>(column_indices.size()), T{});
166 for (std::size_t column = 0; column < column_indices.size(); column++) {
167 for (std::size_t row = 0; row < row_indices.size(); row++) {
168 result(static_cast<int>(row), static_cast<int>(column)) =
169 (*this)(row_indices[row], column_indices[column]);
170 }
171 }
172 return result;
173 }
174
175 /// diag(factors) * this: row i scaled by factors[i], which needs one factor per row.
176 template <typename Factor>
177 [[nodiscard]] DenseMatrix scaled_rows(const std::vector<Factor>& factors) const {
178 require_count(factors.size(), rows_, "row");
179 DenseMatrix result = *this;
180 for (int column = 0; column < columns_; column++) {
181 for (int row = 0; row < rows_; row++) {
182 result(row, column) *= factors[static_cast<std::size_t>(row)];
183 }
184 }
185 return result;
186 }
187
188 /// this * diag(factors): column j scaled by factors[j], which needs one factor per column.
189 template <typename Factor>
190 [[nodiscard]] DenseMatrix scaled_columns(const std::vector<Factor>& factors) const {
191 require_count(factors.size(), columns_, "column");
192 DenseMatrix result = *this;
193 for (int column = 0; column < columns_; column++) {
194 for (int row = 0; row < rows_; row++) {
195 result(row, column) *= factors[static_cast<std::size_t>(column)];
196 }
197 }
198 return result;
199 }
200
201 [[nodiscard]] bool operator==(const DenseMatrix& other) const {
202 return rows_ == other.rows_ && columns_ == other.columns_ && data_ == other.data_;
203 }
204 [[nodiscard]] bool operator!=(const DenseMatrix& other) const {
205 return !(*this == other);
206 }
207
208 private:
209 [[nodiscard]] std::size_t index_of(int row, int column) const {
210 if (row < 0 || row >= rows_ || column < 0 || column >= columns_) {
211 throw std::out_of_range("DenseMatrix: element (" + std::to_string(row) + ", " +
212 std::to_string(column) + ") is outside a " +
213 std::to_string(rows_) + " x " + std::to_string(columns_) +
214 " matrix");
215 }
216 return static_cast<std::size_t>(column) * static_cast<std::size_t>(rows_) +
217 static_cast<std::size_t>(row);
218 }
219
220 [[nodiscard]] static int require_dimension(int value, const char* name) {
221 if (value < 0) {
222 throw std::invalid_argument("DenseMatrix: the number of " + std::string(name) +
223 " cannot be negative, got " + std::to_string(value));
224 }
225 return value;
226 }
227
228 void require_same_shape(const DenseMatrix& other, const char* operation) const {
229 if (rows_ != other.rows_ || columns_ != other.columns_) {
230 throw std::invalid_argument(
231 "DenseMatrix::" + std::string(operation) + ": a " + std::to_string(rows_) + " x " +
232 std::to_string(columns_) + " matrix and a " + std::to_string(other.rows_) + " x " +
233 std::to_string(other.columns_) + " one have different shapes");
234 }
235 }
236
237 static void require_count(std::size_t given, int wanted, const char* axis) {
238 if (given != static_cast<std::size_t>(wanted)) {
239 throw std::invalid_argument("DenseMatrix: one factor per " + std::string(axis) +
240 " is required, " + std::to_string(wanted) +
241 " of them, not " + std::to_string(given));
242 }
243 }
244
245 std::vector<T> data_;
246 int rows_ = 0;
247 int columns_ = 0;
248};
249
250} // namespace q3
int rows() const
DenseMatrix scaled_columns(const std::vector< Factor > &factors) const
this * diag(factors): column j scaled by factors[j], which needs one factor per column.
bool operator!=(const DenseMatrix &other) const
Real frobenius_norm_squared() const
The sum of every element's squared modulus.
T & operator()(int row, int column)
bool operator==(const DenseMatrix &other) const
DenseMatrix submatrix(const std::vector< int > &row_indices, const std::vector< int > &column_indices) const
The rows and columns named, in the order named; an index outside the matrix is refused.
const std::vector< T > & raw() const
Read-only: a caller that could resize the buffer could break rows() x columns().
DenseMatrix & add_scaled(const Factor &factor, const DenseMatrix &other)
this += factor * other, element by element; the two shapes must agree.
DenseMatrix(int rows, int columns, std::vector< T > column_major_data)
Adopts a column-major buffer; refuses one whose size is not rows * columns.
T * data()
The column-major buffer: with rows() it is the (pointer, leading dimension) BLAS takes.
DenseMatrix(int rows, int columns, const T &initial_value=T{})
std::size_t number_of_elements() const
DenseMatrix scaled(const Factor &factor) const
factor * this, element by element.
bool empty() const
DenseMatrix scaled_rows(const std::vector< Factor > &factors) const
diag(factors) * this: row i scaled by factors[i], which needs one factor per row.
DenseMatrix()=default
Real real_inner_product(const DenseMatrix &other) const
const T * data() const
const T & operator()(int row, int column) const
static DenseMatrix identity(int dimension)
DenseMatrix adjoint() const
The conjugate transpose, columns x rows.
int columns() const
double Real
std::complex< double > Complex