Skip to content

Commit a69f90f

Browse files
author
Fikret Ardal
committed
pull from upstream and fix merge conflicts
2 parents a59a9e6 + e683cf4 commit a69f90f

File tree

2 files changed

+147
-97
lines changed

2 files changed

+147
-97
lines changed

c++/nda/concepts.hpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,17 @@ namespace nda {
159159
} -> std::same_as<native_simd<T>>;
160160
} or std::is_base_of_v<simd::mock_simd<F, T>, F>;
161161

162+
/**
163+
* @brief True iif T is same_as any of the Us
164+
*
165+
* @details See nda::is_any_of for implementation.
166+
*
167+
* @tparam T Type to check
168+
* @tparam Us Types to check against
169+
*/
170+
template <typename T, typename... Us>
171+
concept AnyOf = is_any_of<T, Us...>;
172+
162173
/** @} */
163174

164175
namespace mem {

c++/nda/lapack/gelss_worker.hpp

Lines changed: 136 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -45,197 +45,236 @@ namespace nda::lapack {
4545
*/
4646

4747
/**
48-
* @brief Worker class for solving linear least square problems.
48+
* @brief Worker class for solving linear least squares problems.
4949
*
5050
* @details Solving a linear least squares problem means finding the minimum norm solution \f$ \mathbf{x} \f$ of a
5151
* linear system of equations, i.e.
5252
* \f[
5353
* \min_x | \mathbf{b} - \mathbf{A x} |_2 \; ,
5454
* \f]
5555
* where \f$ \mathbf{A} \f$ is a given matrix and \f$ \mathbf{b} \f$ is a given vector (although it can also be a
56-
* matrix, in this case one gets a solution matrix\f$ \mathbf{X} \f$).
56+
* matrix, in this case one searches for a solution matrix \f$ \mathbf{X} \f$).
5757
*
58-
* See https://math.stackexchange.com/questions/772039/how-does-the-svd-solve-the-least-squares-problem for the
59-
* notation used in this file.
58+
* Let \f$ \mathbf{A} \in \mathbb{C}^{M \times N} \f$ with rank \f$ \rho \leq \min(M, N) \f$. Its singular value
59+
* decomposition (SVD) is given by
60+
* \f[
61+
* \mathbf{A} = \mathbf{U} \mathbf{\Sigma} \mathbf{V}^H =
62+
* \begin{bmatrix} \mathbf{U}_R & \mathbf{U}_N \end{bmatrix}
63+
* \begin{bmatrix} \mathbf{S} & 0 \\ 0 & 0 \end{bmatrix}
64+
* \begin{bmatrix} \mathbf{V}_R^H \\ \mathbf{V}_N^H \end{bmatrix} \; ,
65+
* \f]
66+
* where \f$ \mathbf{U}_R \in \mathbb{C}^{M \times \rho} \f$, \f$ \mathbf{U}_N \in \mathbb{C}^{M \times (M - \rho)}
67+
* \f$, \f$ \mathbf{S} \in \mathbb{R}^{\rho \times \rho} \f$, \f$ \mathbf{V}_R \in \mathbb{C}^{N \times \rho} \f$,
68+
* and \f$ \mathbf{V}_N \in \mathbb{C}^{N \times (N - \rho)} \f$.
69+
*
70+
* The least squares solution can then be written as
71+
* \f[
72+
* \mathbf{x} = \mathbf{A}^{+} \mathbf{b} = \mathbf{V} \mathbf{\Sigma}^{+} \mathbf{U}^H \mathbf{b} =
73+
* \mathbf{V}_R \mathbf{S}^{-1} \mathbf{U}_R^H \mathbf{b} \; ,
74+
* \f]
75+
* where \f$ \mathbf{M}^{+} \f$ denotes the Moore-Penrose pseudo-inverse of the matrix \f$ \mathbf{M} \f$, and the
76+
* residual error as
77+
* \f[
78+
* \epsilon = \left\| \mathbf{A} \mathbf{x} - \mathbf{b} \right\|_2 = \left\| \mathbf{U}_N^H \mathbf{b} \right\|_2
79+
* \; .
80+
* \f]
81+
*
82+
* See <a href="https://math.stackexchange.com/questions/772039/how-does-the-svd-solve-the-least-squares-problem">this
83+
* question</a> on math.stackexchange for more information.
6084
*
6185
* @tparam T Value type of the given problem.
6286
*/
6387
template <typename T>
6488
class gelss_worker {
6589
// Number of rows (M) and columns (N) of the Matrix A.
66-
long M, N;
67-
68-
// FIXME Do we need to store it ? only use n_var
69-
// Matrix to be decomposed by SVD.
70-
matrix<T> A;
90+
long M_, N_;
7191

72-
// (Pseudo) Inverse of A, i.e. V * Diag(S_vec)^{-1} * UH, for the least square problem.
73-
matrix<T> V_x_InvS_x_UH;
92+
// Pseudo inverse of A, i.e. A^{+} = V * \Sigma^{+} * U^H.
93+
matrix<T> A_plus_;
7494

75-
// Part of UH fixing the error of the least square problem.
76-
matrix<T> UH_NULL;
95+
// U_N^H defining the error of the least squares problem.
96+
matrix<T> U_N_H_;
7797

7898
// Array containing the singular values.
79-
array<double, 1> s_vec;
99+
array<double, 1> s_;
80100

81101
public:
82102
/**
83-
* @brief Get the number of variables of the given problem.
103+
* @brief Get the number of variables of the given problem, i.e. the size of the vector \f$ \mathbf{x} \f$.
84104
* @return Number of columns of the matrix \f$ \mathbf{A} \f$ .
85105
*/
86-
int n_var() const { return A.extent(1); }
106+
int n_var() const { return N_; }
87107

88108
/**
89-
* @brief Get the singular value array.
90-
* @return 1-dimensional array containing the singular values.
109+
* @brief Get the singular values, i.e. the diagonal elements of the matrix \f$ \mathbf{S} \f$.
110+
* @return 1-dimensional nda::array containing the singular values.
91111
*/
92-
[[nodiscard]] array<double, 1> const &S_vec() const { return s_vec; }
112+
[[nodiscard]] array<double, 1> const &S_vec() const { return s_; }
93113

94114
/**
95115
* @brief Construct a new worker object for a given matrix \f$ \mathbf{A} \f$ .
96116
*
97-
* @details It performs the SVD decomposition of the given matrix \f$ \mathbf{A} \f$ and calculates the (pseudo)
98-
* inverse of \f$ \mathbf{A} \f$. Furthermore, it sets the null space term which determines the error of the least
99-
* square problem.
117+
* @details It performs the SVD decomposition of the given matrix \f$ \mathbf{A} \f$ and stores its pseudo inverse
118+
* \f$ \mathbf{A}^{+} \f$, its singular values \f$ \mathbf{s} = \mathrm{diag}(\mathbf{S}) \f$ and the matrix \f$
119+
* \mathbf{U}_N^H \f$. The latter is used to calculate the error of the least squares problem.
100120
*
101-
* @param A_ Matrix to be decomposed by SVD.
121+
* @param A %Matrix \f$ \mathbf{A} \f$ used in the least squares problem.
102122
*/
103-
gelss_worker(matrix<T> A_) : M(A_.extent(0)), N(A_.extent(1)), A(std::move(A_)), s_vec(std::min(M, N)) {
104-
if (N > M) NDA_RUNTIME_ERROR << "Error in nda::lapack::gelss_worker: Matrix A cannot have more columns than rows";
123+
gelss_worker(matrix_const_view<T> A) : M_(A.extent(0)), N_(A.extent(1)), s_(std::min(M_, N_)) {
124+
if (N_ > M_) NDA_RUNTIME_ERROR << "Error in nda::lapack::gelss_worker: Matrix A cannot have more columns than rows";
105125

106126
// initialize matrices
107-
matrix<T, F_layout> A_FL{A};
108-
matrix<T, F_layout> U(M, M);
109-
matrix<T, F_layout> VH(N, N);
127+
matrix<T, F_layout> A_work{A};
128+
matrix<T, F_layout> U(M_, M_);
129+
matrix<T, F_layout> V_H(N_, N_);
110130

111-
// calculate the SVD: A = U * Diag(S_vec) * VH
112-
gesvd(A_FL, s_vec, U, VH);
131+
// calculate the SVD: A = U * \Sigma * V^H
132+
gesvd(A_work, s_, U, V_H);
113133

114-
// calculate the matrix V * Diag(S_vec)^{-1} * UH for the least square procedure
115-
matrix<double, F_layout> S_inv(N, M);
116-
S_inv = 0.;
117-
for (long i : range(std::min(M, N))) S_inv(i, i) = 1.0 / s_vec(i);
118-
V_x_InvS_x_UH = dagger(VH) * S_inv * dagger(U);
134+
// calculate the pseudo inverse A^{+} = V * \Sigma^{+} * U^H
135+
matrix<double, F_layout> S_plus(N_, M_);
136+
S_plus = 0.;
137+
for (long i : range(s_.size())) S_plus(i, i) = 1.0 / s_(i);
138+
A_plus_ = dagger(V_H) * S_plus * dagger(U);
119139

120-
// read off UH_Null for defining the error of the least square procedure
121-
if (N < M) UH_NULL = dagger(U)(range(N, M), range(M));
140+
// set U_N^H
141+
if (N_ < M_) U_N_H_ = dagger(U)(range(N_, M_), range(M_));
122142
}
123143

124144
/**
125-
* @brief Solve the least-square problem for a given right hand side matrix \f$ \mathbf{B} \f$.
145+
* @brief Solve the least squares problem for a given right hand side matrix \f$ \mathbf{B} \f$.
146+
*
147+
* @details It calculates and returns the least squares solution \f$ \mathbf{X} = \mathbf{A}^{+} \mathbf{B} \f$ and
148+
* the error
149+
* \f[
150+
* \epsilon = \max\left\{ \frac{ \left\| \mathbf{U}_N^H \mathbf{b} \right\|_2 }{ \sqrt{N} } : \mathbf{b}
151+
* \text{ is a column vector in } \mathbf{B} \right\} \; .
152+
* \f]
126153
*
127154
* @param B Right hand side matrix.
128-
* @return A std::pair containing the solution matrix \f$ \mathbf{X} \f$ and the error of the least square problem.
155+
* @return A `std::pair<matrix<T>, double>` containing the solution matrix \f$ \mathbf{X} \f$ and the error \f$
156+
* \epsilon \f$.
129157
*/
130-
std::pair<matrix<T>, double> operator()(matrix_const_view<T> B, std::optional<long> /* inner_matrix_dim */ = {}) const {
158+
auto operator()(matrix_const_view<T> B, std::optional<long> /* inner_matrix_dim */ = {}) const {
131159
using std::sqrt;
132160
double err = 0.0;
133-
if (M != N) {
161+
if (M_ != N_) {
134162
std::vector<double> err_vec;
135-
for (long i : range(B.shape()[1])) err_vec.push_back(frobenius_norm(UH_NULL * B(range::all, range(i, i + 1))) / sqrt(B.shape()[0]));
136-
err = *std::max_element(err_vec.begin(), err_vec.end());
163+
for (long i : range(B.shape()[1])) err_vec.push_back(frobenius_norm(U_N_H_ * B(range::all, range(i, i + 1))) / sqrt(B.shape()[0]));
164+
err = *std::ranges::max_element(err_vec);
137165
}
138-
return std::make_pair(V_x_InvS_x_UH * B, err);
166+
return std::pair<matrix<T>, double>{A_plus_ * B, err};
139167
}
140168

141169
/**
142-
* @brief Solve the least-square problem for a given right hand side vector \f$ \mathbf{b} \f$.
170+
* @brief Solve the least squares problem for a given right hand side vector \f$ \mathbf{b} \f$.
171+
*
172+
* @details It calculates and returns the least squares solution \f$ \mathbf{x} = \mathbf{A}^{+} \mathbf{b} \f$ and
173+
* the error \f$ \epsilon = \frac{ \left\| \mathbf{U}_N^H \mathbf{b} \right\|_2 }{ \sqrt{N} } \f$.
143174
*
144175
* @param b Right hand side vector.
145-
* @return A std::pair containing the solution vector \f$ \mathbf{x} \f$ and the error of the least square problem.
176+
* @return A `std::pair<vector<T>, double>` containing the solution vector \f$ \mathbf{x} \f$ and the error \f$
177+
* \epsilon \f$.
146178
*/
147-
std::pair<vector<T>, double> operator()(vector_const_view<T> b, std::optional<long> /*inner_matrix_dim*/ = {}) const {
179+
auto operator()(vector_const_view<T> b, std::optional<long> /*inner_matrix_dim*/ = {}) const {
148180
using std::sqrt;
149181
double err = 0.0;
150-
if (M != N) { err = norm(UH_NULL * b) / sqrt(b.size()); }
151-
return std::make_pair(V_x_InvS_x_UH * b, err);
182+
if (M_ != N_) { err = norm(U_N_H_ * b) / sqrt(b.size()); }
183+
return std::pair<vector<T>, double>{A_plus_ * b, err};
152184
}
153185
};
154186

155187
/**
156-
* @brief Worker class for solving linear least square problems for hermitian tail-fitting.
157-
* @details Restrict the resulting vector of moment matrices to one of hermitian matrices.
188+
* @brief Specialized worker class for solving linear least squares problems while enforcing a certain hermitian
189+
* symmetry.
190+
*
191+
* @details This is a more specialized version of nda::lapack::gelss_worker that is tailored to perform tail fitting
192+
* for scalar- or matrix-valued functions defined on a Matsubara frequency mesh satisfying the symmetry \f$ \left[
193+
* f(-i\omega_n) \right]_{ij} = \left[ f(i\omega_n) \right]_{ji}^* \f$.
194+
*
195+
* The least squares problem to be solved is given by
196+
* \f[
197+
* \mathbf{A}_E \mathbf{X} = \begin{bmatrix} \mathbf{A} \\ \mathbf{A}^* \end{bmatrix} \mathbf{X} =
198+
* \begin{bmatrix} \mathbf{B} \\ \tilde{\mathbf{B}} \end{bmatrix} = \mathbf{B}_E \; ,
199+
* \f]
200+
* where the original problem \f$ \mathbf{A} \mathbf{X} = \mathbf{B} \f$ has been extended by \f$ \mathbf{A}^*
201+
* \mathbf{X} = \tilde{\mathbf{B}} \f$ to account for the symmetry given above.
202+
*
203+
* The calculated solution \f$ \mathbf{X} \f$ is explictly symmetrized at the end to ensure that the symmetry is
204+
* satisfied.
158205
*
159-
* See also nda::lapack::gelss_worker.
206+
* See `triqs::mesh::tail_fitter` for more information.
160207
*/
161208
struct gelss_worker_hermitian {
162209
private:
163210
// Complex double type.
164211
using dcomplex = std::complex<double>;
165212

166-
// Matrix to be decomposed by SVD.
167-
matrix<dcomplex> A;
213+
// Worker for the original least squares problem.
214+
gelss_worker<dcomplex> lss_;
168215

169-
// Solver for the associated real-valued least-squares problem.
170-
gelss_worker<dcomplex> _lss;
171-
172-
// Solver for the associated real-valued least-squares problem imposing hermiticity.
173-
gelss_worker<dcomplex> _lss_matrix;
216+
// Worker for the extended least squares problem.
217+
gelss_worker<dcomplex> lss_herm_;
174218

175219
public:
176220
/**
177221
* @brief Get the number of variables of the given problem.
178222
* @return Number of columns of the matrix \f$ \mathbf{A} \f$.
179223
*/
180-
int n_var() const { return static_cast<int>(A.extent(1)); }
224+
int n_var() const { return lss_herm_.n_var(); }
181225

182226
/**
183-
* @brief Get the singular value array.
184-
* @return 1-dimensional array containing the singular values.
227+
* @brief Get the singular values of the original matrix \f$ A \f$.
228+
* @return 1-dimensional nda::array containing the singular values.
185229
*/
186-
array<double, 1> const &S_vec() const { return _lss.S_vec(); }
230+
[[nodiscard]] array<double, 1> const &S_vec() const { return lss_.S_vec(); }
187231

188232
/**
189233
* @brief Construct a new worker object for a given matrix \f$ \mathbf{A} \f$.
190-
* @param A_ Matrix to be decomposed by SVD.
234+
* @param A %Matrix \f$ \mathbf{A} \f$ used in the least squares problem.
191235
*/
192-
gelss_worker_hermitian(matrix<dcomplex> A_) : A(std::move(A_)), _lss(A), _lss_matrix(vstack(A, conj(A))) {}
236+
gelss_worker_hermitian(matrix_const_view<dcomplex> A) : lss_(A), lss_herm_(vstack(A, conj(A))) {}
193237

194238
/**
195-
* @brief Solve the least-square problem for a given right hand side matrix \f$ \mathbf{B} \f$.
239+
* @brief Solve the least squares problem for a given right hand side matrix \f$ \mathbf{B} \f$.
240+
*
241+
* @details The inner matrix dimension \f$ d \f$ specifies the shape of the matrix that \f$ f(i\omega_n) \f$
242+
* returns. It is used to make sure that the symmetry is satisfied. For scalar-valued functions, \f$ d = 1 \f$.
243+
*
196244
* @param B Right hand side matrix.
197-
* @param inner_matrix_dim Inner matrix dimension for hermitian least square fitting.
198-
* @return A std::pair containing the solution matrix \f$ \mathbf{X} \f$ and the error of the least square problem.
245+
* @param inner_matrix_dim Inner matrix dimension \f$ d \f$.
246+
* @return A `std::pair<matrix<dcomplex>, double>` containing the solution matrix \f$ \mathbf{X} \f$ and the error
247+
* \f$ \epsilon \f$.
199248
*/
200-
std::pair<matrix<dcomplex>, double> operator()(matrix_const_view<dcomplex> B, std::optional<long> inner_matrix_dim = {}) const {
249+
auto operator()(matrix_const_view<dcomplex> B, std::optional<long> inner_matrix_dim = {}) const {
201250
if (not inner_matrix_dim.has_value())
202251
NDA_RUNTIME_ERROR << "Error in nda::lapack::gelss_worker_hermitian: Inner matrix dimension required for hermitian least square fitting";
203252
long d = *inner_matrix_dim;
204253

205-
// Construction of an inner 'adjoint' matrix by performing the following steps
206-
// * reshape B from (M, M1) to (M, N, d, d)
207-
// * for each M and N take the adjoint matrix (d, d)
208-
// * reshape to (M, M)
209-
auto inner_adjoint = [d](auto &M) {
210-
auto idx_map = M.indexmap();
211-
auto l = idx_map.lengths();
212-
//auto s = idx_map.strides();
213-
214-
NDA_ASSERT2(l[1] % (d * d) == 0, "Error in nda::lapack::gelss_worker_hermitian: Data shape incompatible with given dimension");
215-
long N = l[1] / (d * d);
216-
217-
// We reshape the Matrix into a dim=4 array and swap the two innermost indices
254+
// take the inner 'adjoint' of a matrix C:
255+
// * reshape C -> C': (M, N) -> (M, N', d, d)
256+
// * for each m and n: C'(m, n, :, :) -> C'(m, n, :, :)^/dagger
257+
// * reshape C' -> C: (M, N', d, d) -> (M, N)
258+
auto inner_adjoint = [d](auto &C) {
259+
NDA_ASSERT2(C.shape()[1] % (d * d) == 0, "Error in nda::lapack::gelss_worker_hermitian: Data shape incompatible with inner matrix dimension");
260+
auto shape = C.shape();
218261

219-
// FIXME OLD CODE SURPRESS AFTER PORTING
220-
// FIXME We would like to write: transpose(reshape(idx_map, {l[0], N, d, d}), {0, 1, 3, 2})
221-
// auto idx_map_inner_transpose = array_view<dcomplex, 4>::layout_t{{l[0], N, d, d}, {s[0], d * d * s[1], s[1], d * s[1]}};
222-
// Deep copy
223-
//array<dcomplex, 4> arr_dag = conj(array_const_view<dcomplex, 4>{idx_map_inner_transpose, M.storage()});
224-
//return matrix<dcomplex>{matrix<dcomplex>::layout_t{l, s}, std::move(arr_dag).storage()};
262+
// get extent N' of second dimension
263+
long N = shape[1] / (d * d);
225264

226-
// FIXME C++20 remove encode
227-
array<dcomplex, 4> arr_dag = conj(permuted_indices_view<encode(std::array{0, 1, 3, 2})>(reshape(M, std::array{l[0], N, d, d})));
265+
// reshape, transpose and take the complex conjugate
266+
array<dcomplex, 4> arr_dag = conj(permuted_indices_view<encode(std::array{0, 1, 3, 2})>(reshape(C, std::array{shape[0], N, d, d})));
228267

229-
return matrix<dcomplex>{reshape(std::move(arr_dag), l)}; // move into a matrix
268+
// return the result in a new matrix
269+
return matrix<dcomplex>{reshape(std::move(arr_dag), shape)};
230270
};
231271

232-
// Solve the enlarged system vstack(A, A*) * x = vstack(B, B_dag)
233-
matrix<dcomplex> B_dag = inner_adjoint(B);
234-
auto B_stack = vstack(B, B_dag);
235-
auto [x, err] = _lss_matrix(B_stack);
272+
// solve the extended system vstack(A, A*) * X = vstack(B, B_dag)
273+
auto B_dag = inner_adjoint(B);
274+
auto [x, err] = lss_herm_(vstack(B, B_dag));
236275

237-
// Resymmetrize results to cure small hermiticity violations
238-
return {0.5 * (x + inner_adjoint(x)), err};
276+
// resymmetrize the results to cure small hermiticity violations
277+
return std::pair<matrix<dcomplex>, double>{0.5 * (x + inner_adjoint(x)), err};
239278
}
240279
};
241280

0 commit comments

Comments
 (0)