TRIQS/nda 1.3.0
Multi-dimensional array library for C++
Loading...
Searching...
No Matches
gelss_worker.hpp
Go to the documentation of this file.
1// Copyright (c) 2020-2024 Simons Foundation
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0.txt
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//
15// Authors: Jason Kaye, Olivier Parcollet, Nils Wentzell
16/**
17 * @file
18 * @brief Provides worker classes that can be used for solving linear least square problems.
19 */
20
21#pragma once
22
23#include "./gesvd.hpp"
24#include "../algorithms.hpp"
25#include "../basic_array.hpp"
26#include "../declarations.hpp"
27#include "../exceptions.hpp"
28#include "../layout/policies.hpp"
29#include "../layout_transforms.hpp"
30#include "../linalg.hpp"
31#include "../mapped_functions.hpp"
32#include "../matrix_functions.hpp"
33
34#include <itertools/itertools.hpp>
35
36#include <algorithm>
37#include <array>
38#include <cmath>
39#include <complex>
40#include <optional>
41#include <utility>
42#include <vector>
43
44namespace nda::lapack {
45
46 /**
47 * @addtogroup linalg_lapack
48 * @{
49 */
50
51 /**
52 * @brief Worker class for solving linear least square problems.
53 *
54 * @details Solving a linear least squares problem means finding the minimum norm solution \f$ \mathbf{x} \f$ of a
55 * linear system of equations, i.e.
56 * \f[
57 * \min_x | \mathbf{b} - \mathbf{A x} |_2 \; ,
58 * \f]
59 * where \f$ \mathbf{A} \f$ is a given matrix and \f$ \mathbf{b} \f$ is a given vector (although it can also be a
60 * matrix, in this case one gets a solution matrix\f$ \mathbf{X} \f$).
61 *
62 * See https://math.stackexchange.com/questions/772039/how-does-the-svd-solve-the-least-squares-problem for the
63 * notation used in this file.
64 *
65 * @tparam T Value type of the given problem.
66 */
67 template <typename T>
69 // Number of rows (M) and columns (N) of the Matrix A.
70 long M, N;
71
72 // FIXME Do we need to store it ? only use n_var
73 // Matrix to be decomposed by SVD.
74 matrix<T> A;
75
76 // (Pseudo) Inverse of A, i.e. V * Diag(S_vec)^{-1} * UH, for the least square problem.
77 matrix<T> V_x_InvS_x_UH;
78
79 // Part of UH fixing the error of the least square problem.
80 matrix<T> UH_NULL;
81
82 // Array containing the singular values.
83 array<double, 1> s_vec;
84
85 public:
86 /**
87 * @brief Get the number of variables of the given problem.
88 * @return Number of columns of the matrix \f$ \mathbf{A} \f$ .
89 */
90 int n_var() const { return A.extent(1); }
91
92 /**
93 * @brief Get the singular value array.
94 * @return 1-dimensional array containing the singular values.
95 */
96 [[nodiscard]] array<double, 1> const &S_vec() const { return s_vec; }
97
98 /**
99 * @brief Construct a new worker object for a given matrix \f$ \mathbf{A} \f$ .
100 *
101 * @details It performs the SVD decomposition of the given matrix \f$ \mathbf{A} \f$ and calculates the (pseudo)
102 * inverse of \f$ \mathbf{A} \f$. Furthermore, it sets the null space term which determines the error of the least
103 * square problem.
104 *
105 * @param A_ Matrix to be decomposed by SVD.
106 */
107 gelss_worker(matrix<T> A_) : M(A_.extent(0)), N(A_.extent(1)), A(std::move(A_)), s_vec(std::min(M, N)) {
108 if (N > M) NDA_RUNTIME_ERROR << "Error in nda::lapack::gelss_worker: Matrix A cannot have more columns than rows";
109
110 // initialize matrices
111 matrix<T, F_layout> A_FL{A};
112 matrix<T, F_layout> U(M, M);
113 matrix<T, F_layout> VH(N, N);
114
115 // calculate the SVD: A = U * Diag(S_vec) * VH
116 gesvd(A_FL, s_vec, U, VH);
117
118 // calculate the matrix V * Diag(S_vec)^{-1} * UH for the least square procedure
119 matrix<double, F_layout> S_inv(N, M);
120 S_inv = 0.;
121 for (long i : range(std::min(M, N))) S_inv(i, i) = 1.0 / s_vec(i);
122 V_x_InvS_x_UH = dagger(VH) * S_inv * dagger(U);
123
124 // read off UH_Null for defining the error of the least square procedure
125 if (N < M) UH_NULL = dagger(U)(range(N, M), range(M));
126 }
127
128 /**
129 * @brief Solve the least-square problem for a given right hand side matrix \f$ \mathbf{B} \f$.
130 *
131 * @param B Right hand side matrix.
132 * @return A std::pair containing the solution matrix \f$ \mathbf{X} \f$ and the error of the least square problem.
133 */
134 std::pair<matrix<T>, double> operator()(matrix_const_view<T> B, std::optional<long> /* inner_matrix_dim */ = {}) const {
135 using std::sqrt;
136 double err = 0.0;
137 if (M != N) {
138 std::vector<double> err_vec;
139 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]));
140 err = *std::max_element(err_vec.begin(), err_vec.end());
141 }
142 return std::make_pair(V_x_InvS_x_UH * B, err);
143 }
144
145 /**
146 * @brief Solve the least-square problem for a given right hand side vector \f$ \mathbf{b} \f$.
147 *
148 * @param b Right hand side vector.
149 * @return A std::pair containing the solution vector \f$ \mathbf{x} \f$ and the error of the least square problem.
150 */
151 std::pair<vector<T>, double> operator()(vector_const_view<T> b, std::optional<long> /*inner_matrix_dim*/ = {}) const {
152 using std::sqrt;
153 double err = 0.0;
154 if (M != N) { err = norm(UH_NULL * b) / sqrt(b.size()); }
155 return std::make_pair(V_x_InvS_x_UH * b, err);
156 }
157 };
158
159 /**
160 * @brief Worker class for solving linear least square problems for hermitian tail-fitting.
161 * @details Restrict the resulting vector of moment matrices to one of hermitian matrices.
162 *
163 * See also nda::lapack::gelss_worker.
164 */
166 private:
167 // Complex double type.
168 using dcomplex = std::complex<double>;
169
170 // Matrix to be decomposed by SVD.
171 matrix<dcomplex> A;
172
173 // Solver for the associated real-valued least-squares problem.
174 gelss_worker<dcomplex> _lss;
175
176 // Solver for the associated real-valued least-squares problem imposing hermiticity.
177 gelss_worker<dcomplex> _lss_matrix;
178
179 public:
180 /**
181 * @brief Get the number of variables of the given problem.
182 * @return Number of columns of the matrix \f$ \mathbf{A} \f$.
183 */
184 int n_var() const { return static_cast<int>(A.extent(1)); }
185
186 /**
187 * @brief Get the singular value array.
188 * @return 1-dimensional array containing the singular values.
189 */
190 array<double, 1> const &S_vec() const { return _lss.S_vec(); }
191
192 /**
193 * @brief Construct a new worker object for a given matrix \f$ \mathbf{A} \f$.
194 * @param A_ Matrix to be decomposed by SVD.
195 */
196 gelss_worker_hermitian(matrix<dcomplex> A_) : A(std::move(A_)), _lss(A), _lss_matrix(vstack(A, conj(A))) {}
197
198 /**
199 * @brief Solve the least-square problem for a given right hand side matrix \f$ \mathbf{B} \f$.
200 * @param B Right hand side matrix.
201 * @param inner_matrix_dim Inner matrix dimension for hermitian least square fitting.
202 * @return A std::pair containing the solution matrix \f$ \mathbf{X} \f$ and the error of the least square problem.
203 */
204 std::pair<matrix<dcomplex>, double> operator()(matrix_const_view<dcomplex> B, std::optional<long> inner_matrix_dim = {}) const {
205 if (not inner_matrix_dim.has_value())
206 NDA_RUNTIME_ERROR << "Error in nda::lapack::gelss_worker_hermitian: Inner matrix dimension required for hermitian least square fitting";
207 long d = *inner_matrix_dim;
208
209 // Construction of an inner 'adjoint' matrix by performing the following steps
210 // * reshape B from (M, M1) to (M, N, d, d)
211 // * for each M and N take the adjoint matrix (d, d)
212 // * reshape to (M, M)
213 auto inner_adjoint = [d](auto &M) {
214 auto idx_map = M.indexmap();
215 auto l = idx_map.lengths();
216 //auto s = idx_map.strides();
217
218 NDA_ASSERT2(l[1] % (d * d) == 0, "Error in nda::lapack::gelss_worker_hermitian: Data shape incompatible with given dimension");
219 long N = l[1] / (d * d);
220
221 // We reshape the Matrix into a dim=4 array and swap the two innermost indices
222
223 // FIXME OLD CODE SURPRESS AFTER PORTING
224 // FIXME We would like to write: transpose(reshape(idx_map, {l[0], N, d, d}), {0, 1, 3, 2})
225 // 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]}};
226 // Deep copy
227 //array<dcomplex, 4> arr_dag = conj(array_const_view<dcomplex, 4>{idx_map_inner_transpose, M.storage()});
228 //return matrix<dcomplex>{matrix<dcomplex>::layout_t{l, s}, std::move(arr_dag).storage()};
229
230 // FIXME C++20 remove encode
231 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})));
232
233 return matrix<dcomplex>{reshape(std::move(arr_dag), l)}; // move into a matrix
234 };
235
236 // Solve the enlarged system vstack(A, A*) * x = vstack(B, B_dag)
237 matrix<dcomplex> B_dag = inner_adjoint(B);
238 auto B_stack = vstack(B, B_dag);
239 auto [x, err] = _lss_matrix(B_stack);
240
241 // Resymmetrize results to cure small hermiticity violations
242 return {0.5 * (x + inner_adjoint(x)), err};
243 }
244 };
245
246 /** @} */
247
248} // namespace nda::lapack
long extent(int i) const noexcept
Get the extent of the ith dimension.
basic_array(basic_array &&)=default
Default move constructor moves the memory handle and layout.
Worker class for solving linear least square problems.
array< double, 1 > const & S_vec() const
Get the singular value array.
gelss_worker(matrix< T > A_)
Construct a new worker object for a given matrix .
std::pair< vector< T >, double > operator()(vector_const_view< T > b, std::optional< long >={}) const
Solve the least-square problem for a given right hand side vector .
int n_var() const
Get the number of variables of the given problem.
std::pair< matrix< T >, double > operator()(matrix_const_view< T > B, std::optional< long >={}) const
Solve the least-square problem for a given right hand side matrix .
#define NDA_RUNTIME_ERROR
#define NDA_ASSERT2(X,...)
matrix< get_value_t< A > > vstack(A const &a, B const &b)
Stack two 2-dimensional arrays/views vertically.
decltype(auto) conj(A &&a)
Lazy, coefficient-wise complex conjugate function for nda::Array types.
Array auto operator+(L &&l, R &&r)
Addition operator for two nda::Array types.
auto operator*(L &&l, R &&r)
Multiplication operator for two nda::Array types.
constexpr uint64_t encode(std::array< int, N > const &a)
Encode a std::array<int, N> in a uint64_t.
Contiguous layout policy with Fortran-order (column-major order).
Definition policies.hpp:63
Worker class for solving linear least square problems for hermitian tail-fitting.
int n_var() const
Get the number of variables of the given problem.
array< double, 1 > const & S_vec() const
Get the singular value array.
std::pair< matrix< dcomplex >, double > operator()(matrix_const_view< dcomplex > B, std::optional< long > inner_matrix_dim={}) const
Solve the least-square problem for a given right hand side matrix .
gelss_worker_hermitian(matrix< dcomplex > A_)
Construct a new worker object for a given matrix .