@@ -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