mxlib
c++ tools for analyzing astronomical data and other tasks by Jared R. Males. [git repo]
Loading...
Searching...
No Matches
eigenLapack.hpp
Go to the documentation of this file.
1/** \file eigenLapack.hpp
2 * \brief Interfaces to Lapack and BLAS for Eigen-like arrays.
3 *
4 * \author Jared R. Males (jaredmales@gmail.com)
5 *
6 * \ingroup gen_math_files
7 *
8 */
9
10//***********************************************************************//
11// Copyright 2015, 2016, 2017 Jared R. Males (jaredmales@gmail.com)
12//
13// This file is part of mxlib.
14//
15// mxlib is free software: you can redistribute it and/or modify
16// it under the terms of the GNU General Public License as published by
17// the Free Software Foundation, either version 3 of the License, or
18// (at your option) any later version.
19//
20// mxlib is distributed in the hope that it will be useful,
21// but WITHOUT ANY WARRANTY; without even the implied warranty of
22// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23// GNU General Public License for more details.
24//
25// You should have received a copy of the GNU General Public License
26// along with mxlib. If not, see <http://www.gnu.org/licenses/>.
27//***********************************************************************//
28
29#ifndef math_eigenLapack_hpp
30#define math_eigenLapack_hpp
31
32#pragma GCC system_header
33#include <Eigen/Dense>
34
35#include <cstddef>
36#include <cmath>
37#include <cstdlib>
38#include <limits>
39
40#include "floatUtils.hpp"
41#include "templateBLAS.hpp"
42#include "templateLapack.hpp"
43
44#include "../sys/timeUtils.hpp"
45
46// #include "vectorUtils.hpp"
47
49
50namespace mx
51{
52namespace math
53{
54
55/// \cond eigenLapack_test_detail
56namespace detail
57{
58
59/// Test-only injection points for deterministic eigensolver failure coverage.
60/** Production code must leave both hooks null so the normal allocator and LAPACK wrapper are used.
61 */
62template <typename floatT>
63struct eigenLapackTestHooks
64{
65 /// Malloc-compatible allocation function used by workspace-growth tests.
66 using allocatorT = void *(*)( std::size_t );
67
68 /// Function signature of the LAPACK SYEVR wrapper.
69 using solverT = MXLAPACK_INT ( * )( char,
70 char,
71 char,
72 MXLAPACK_INT,
73 floatT *,
74 MXLAPACK_INT,
75 floatT,
76 floatT,
77 MXLAPACK_INT,
78 MXLAPACK_INT,
79 floatT,
80 MXLAPACK_INT *,
81 floatT *,
82 floatT *,
83 MXLAPACK_INT,
84 MXLAPACK_INT *,
85 floatT *,
86 MXLAPACK_INT,
87 MXLAPACK_INT *,
88 MXLAPACK_INT );
89
90 /// Optional malloc-compatible test allocator; null selects `::malloc`.
91 static inline allocatorT allocator{ nullptr };
92
93 /// Optional test eigensolver; null selects `math::syevr<floatT>`.
94 static inline solverT solver{ nullptr };
95};
96
97/// Allocate or grow a SYEVR workspace buffer without discarding a usable buffer on failure.
98template <typename floatT, typename itemT>
99bool resizeSyevrBuffer( itemT *&buffer, /**< [in,out] workspace buffer */
100 MXLAPACK_INT &capacity, /**< [in,out] current item capacity */
101 MXLAPACK_INT requestedItems /**< [in] required item capacity */ )
102{
103 if( capacity >= requestedItems )
104 {
105 return true;
106 }
107
108 void *rawBuffer{ nullptr };
109 if( eigenLapackTestHooks<floatT>::allocator )
110 {
111 rawBuffer = eigenLapackTestHooks<floatT>::allocator( requestedItems * sizeof( itemT ) );
112 }
113 else
114 {
115 rawBuffer = ::malloc( requestedItems * sizeof( itemT ) );
116 }
117
118 if( rawBuffer == nullptr )
119 {
120 return false;
121 }
122
123 if( buffer )
124 {
125 ::free( buffer );
126 }
127
128 buffer = static_cast<itemT *>( rawBuffer );
129 capacity = requestedItems;
130 return true;
131}
132
133/// Release a SYEVR workspace buffer and reset its ownership state.
134template <typename itemT>
135void releaseSyevrBuffer( itemT *&buffer, /**< [in,out] workspace buffer */
136 MXLAPACK_INT &capacity /**< [out] reset item capacity */ )
137{
138 if( buffer )
139 {
140 ::free( buffer );
141 }
142
143 buffer = nullptr;
144 capacity = 0;
145}
146
147/// Invoke the injected test eigensolver or the production LAPACK wrapper.
148template <typename floatT>
149MXLAPACK_INT callSyevr( char JOBZ, /**< [in] eigenvector request */
150 char RANGE, /**< [in] eigenvalue selection mode */
151 char UPLO, /**< [in] populated input triangle */
152 MXLAPACK_INT N, /**< [in] matrix order */
153 floatT *A, /**< [in,out] input matrix */
154 MXLAPACK_INT LDA, /**< [in] input leading dimension */
155 floatT VL, /**< [in] lower value bound */
156 floatT VU, /**< [in] upper value bound */
157 MXLAPACK_INT IL, /**< [in] one-based lower index */
158 MXLAPACK_INT IU, /**< [in] one-based upper index */
159 floatT ABSTOL, /**< [in] convergence tolerance */
160 MXLAPACK_INT *M, /**< [out] selected eigenvalue count */
161 floatT *W, /**< [out] eigenvalues */
162 floatT *Z, /**< [out] eigenvectors */
163 MXLAPACK_INT LDZ, /**< [in] eigenvector leading dimension */
164 MXLAPACK_INT *ISUPPZ, /**< [out] eigenvector support */
165 floatT *WORK, /**< [in,out] floating workspace */
166 MXLAPACK_INT LWORK, /**< [in] floating workspace size */
167 MXLAPACK_INT *IWORK, /**< [in,out] integer workspace */
168 MXLAPACK_INT LIWORK /**< [in] integer workspace size */ )
169{
170 if( eigenLapackTestHooks<floatT>::solver )
171 {
172 return eigenLapackTestHooks<floatT>::solver( JOBZ,
173 RANGE,
174 UPLO,
175 N,
176 A,
177 LDA,
178 VL,
179 VU,
180 IL,
181 IU,
182 ABSTOL,
183 M,
184 W,
185 Z,
186 LDZ,
187 ISUPPZ,
188 WORK,
189 LWORK,
190 IWORK,
191 LIWORK );
192 }
193
194 return math::syevr<floatT>( JOBZ,
195 RANGE,
196 UPLO,
197 N,
198 A,
199 LDA,
200 VL,
201 VU,
202 IL,
203 IU,
204 ABSTOL,
205 M,
206 W,
207 Z,
208 LDZ,
209 ISUPPZ,
210 WORK,
211 LWORK,
212 IWORK,
213 LIWORK );
214}
215
216} // namespace detail
217/// \endcond
218
219/// Calculates the lower triangular part of the covariance matrix of ims.
220/** Uses cblas_ssyrk. cv is resized to ims.cols() X ims.cols().
221 * Calculates \f$ cv = A^T*A \f$.
222 *
223 *
224 * \tparam eigenT1 is the eigen matrix/array type of cv.
225 * \tparam eigenT2 is the eigen matrix/array type of ims
226 *
227 * \ingroup eigen_lapack
228 */
229template <typename eigenT1, typename eigenT2>
230void eigenSYRK( eigenT1 &cv, ///< [out] is the eigen matrix/array where to store the result
231 const eigenT2 &ims ///< [in] is the eigen matrix/array (images as columns) to
232 ///< calculate the covariance of
233)
234{
235 cv.resize( ims.cols(), ims.cols() );
236
237 math::syrk<typename eigenT1::Scalar>( /*const enum CBLAS_ORDER Order*/ CblasColMajor,
238 /*const enum CBLAS_UPLO Uplo*/ CblasLower,
239 /*const enum CBLAS_TRANSPOSE Trans*/ CblasTrans,
240 /*const MXLAPACK_INT N*/ ims.cols(),
241 /*const MXLAPACK_INT K*/ ims.rows(),
242 /*const float alpha*/ 1.0,
243 /*const float *A*/ ims.data(),
244 /*const MXLAPACK_INT lda*/ ims.rows(),
245 /*const float beta*/ 0.,
246 /*float *C*/ cv.data(),
247 /*const MXLAPACK_INT ldc*/ cv.rows() );
248}
249
250/// A struct to hold the working memory for eigenSYEVR and maintain it between calls if desired.
251/** \todo this should have the working memory for the first exploratory call to ?syevr as well.
252 */
253template <typename floatT>
255{
256 /// Triangle selection associated with the cached workspace query.
257 char UPLO{ 'L' };
258
259 /// Matrix order associated with the cached workspace query.
260 MXLAPACK_INT n{ 0 };
261
262 /// Eigenvalue-range selection associated with the cached workspace query.
263 char RANGE{ 'A' };
264
265 /// Number of eigenvalues reported by the most recent workspace query.
266 MXLAPACK_INT numeig{ 0 };
267
268 /// One-based lower eigenvalue index associated with the cached workspace query.
269 MXLAPACK_INT IL{ 0 };
270
271 /// One-based upper eigenvalue index associated with the cached workspace query.
272 MXLAPACK_INT IU{ 0 };
273
274 /// Capacity of the eigenvector-support workspace.
275 MXLAPACK_INT sizeISuppZ{ 0 };
276
277 /// Capacity of the minimum floating-point query workspace.
278 MXLAPACK_INT sizeMinWork{ 0 };
279
280 /// Capacity of the optimized floating-point workspace.
281 MXLAPACK_INT sizeWork{ 0 };
282
283 /// Capacity of the minimum integer query workspace.
284 MXLAPACK_INT sizeMinIWork{ 0 };
285
286 /// Capacity of the optimized integer workspace.
287 MXLAPACK_INT sizeIWork{ 0 };
288
289 /// LAPACK eigenvector-support workspace owned by this object.
290 MXLAPACK_INT *iSuppZ{ nullptr };
291
292 /// Minimum floating-point workspace used for LAPACK size queries.
293 floatT *minWork{ nullptr };
294
295 /// Optimized floating-point LAPACK workspace owned by this object.
296 floatT *work{ nullptr };
297
298 /// Minimum integer workspace used for LAPACK size queries.
299 MXLAPACK_INT *minIWork{ nullptr };
300
301 /// Optimized integer LAPACK workspace owned by this object.
302 MXLAPACK_INT *iWork{ nullptr };
303
304 /// Calculation-type covariance storage used by higher-level eigensolver helpers.
305 Eigen::Array<floatT, Eigen::Dynamic, Eigen::Dynamic> cvd;
306
307 /// Calculation-type eigenvector storage used by higher-level eigensolver helpers.
308 Eigen::Array<floatT, Eigen::Dynamic, Eigen::Dynamic> evecsd;
309
310 /// Calculation-type eigenvalue storage used by higher-level eigensolver helpers.
311 Eigen::Array<floatT, Eigen::Dynamic, Eigen::Dynamic> evalsd;
312
313 /// Construct an empty reusable SYEVR workspace.
315 {
316 }
317
318 /// Copying an owning SYEVR workspace is prohibited.
319 syevrMem( const syevrMem &other /**< [in] workspace that cannot be copied */ ) = delete;
320
321 /// Copy assignment of an owning SYEVR workspace is prohibited.
322 syevrMem &operator=( const syevrMem &other /**< [in] workspace that cannot be copied */ ) = delete;
323
324 /// Release every allocation owned by this workspace.
326 {
327 free();
328 }
329
330 /// Release all workspace allocations and reset the cached LAPACK configuration.
331 void free()
332 {
333 detail::releaseSyevrBuffer( iSuppZ, sizeISuppZ );
334 detail::releaseSyevrBuffer( minWork, sizeMinWork );
335 detail::releaseSyevrBuffer( work, sizeWork );
336 detail::releaseSyevrBuffer( minIWork, sizeMinIWork );
337 detail::releaseSyevrBuffer( iWork, sizeIWork );
338
339 UPLO = 'L';
340 n = 0;
341 RANGE = 'A';
342 numeig = 0;
343 IL = 0;
344 IU = 0;
345 }
346};
347
348/// Calculate select eigenvalues and eigenvectors of an Eigen Array
349/** Uses the templateLapack wrapper for syevr.
350 *
351 * \tparam arrT is the eigen-like type containing the data
352 *
353 * \returns -1 for invalid matrix/range geometry or invalid workspace-query results.
354 * \returns -1000 on an malloc allocation error.
355 * \returns the return code from syevr (info) otherwise.
356 *
357 * \ingroup eigen_lapack
358 */
359template <typename arrT>
360MXLAPACK_INT eigenSYEVR( arrT &eigvec, /**< [out] will contain the eigenvectors as columns*/
361 arrT &eigval, /**< [out] will contain the eigenvalues*/
362 arrT &X, /**< [in] is a square matrix which is either upper
363 or lower (default) triangular*/
364 int ev0 = 0, /**< [in] [opt] is the first desired eigenvalue
365 (default = 0)*/
366 int ev1 = -1, /**< [in] [opt] exclusive upper bound of the desired eigenvalue indices.
367 If -1 all eigenvalues are returned.*/
368 char UPLO = 'L', /**< [in] [opt] specifies whether X is upper ('U')
369 or lower ('L') triangular.
370 Default is ('L').*/
371 syevrMem<typename arrT::Scalar> *mem = 0 /**< [in] [opt] holds the working memory arrays,
372 can be re-passed to avoid unnecessary
373 re-allocations*/
374)
375{
376 typedef typename arrT::Scalar calcT;
377
378 MXLAPACK_INT numeig{ 0 };
379 MXLAPACK_INT info;
380 char RANGE = 'A';
381
382 MXLAPACK_INT n = X.rows();
383 if( n <= 0 || X.cols() != n )
384 {
385 return -1;
386 }
387
388 if( ev1 < -1 || ( ev1 != -1 && ( ev0 < 0 || ev0 >= ev1 || ev1 > n ) ) )
389 {
390 return -1;
391 }
392
393 MXLAPACK_INT localMem = 0;
394
395 if( mem == 0 )
396 {
397 mem = new syevrMem<calcT>;
398 localMem = 1;
399 }
400
401 MXLAPACK_INT IL = 1;
402 MXLAPACK_INT IU = n;
403 if( ev1 != -1 )
404 {
405 RANGE = 'I';
406 IL = ev0 + 1; // This is FORTRAN, after all
407 IU = ev1;
408 }
409
410 eigvec.resize( n, IU - IL + 1 );
411 eigval.resize( n, 1 );
412
413 if( UPLO != mem->UPLO || n != mem->n || RANGE != mem->RANGE || mem->IL != IL || mem->IU != IU )
414 {
415 if( !detail::resizeSyevrBuffer<calcT>( mem->iSuppZ, mem->sizeISuppZ, 2 * n ) ||
416 !detail::resizeSyevrBuffer<calcT>( mem->minWork, mem->sizeMinWork, 26 * n ) ||
417 !detail::resizeSyevrBuffer<calcT>( mem->minIWork, mem->sizeMinIWork, 10 * n ) )
418 {
419 internal::mxlib_error_report( error_t::allocerr, "malloc failed in eigenSYEVR." );
420 if( localMem )
421 {
422 delete mem;
423 }
424 return -1000;
425 }
426
427 // Query for optimum sizes for workspace
428 info = detail::callSyevr<calcT>( 'V',
429 RANGE,
430 UPLO,
431 n,
432 X.data(),
433 n,
434 0,
435 0,
436 IL,
437 IU,
438 math::lamch<calcT>( 'S' ),
439 &numeig,
440 eigval.data(),
441 eigvec.data(),
442 n,
443 mem->iSuppZ,
444 mem->minWork,
445 -1,
446 mem->minIWork,
447 -1 );
448
449 if( info != 0 )
450 {
452 if( localMem )
453 {
454 delete mem;
455 }
456
457 return info;
458 }
459
460 const calcT queriedWork = mem->minWork[0];
461 const MXLAPACK_INT requestedIWork = mem->minIWork[0];
462 if( !math::isFinite( queriedWork ) || queriedWork < 1 ||
463 static_cast<long double>( queriedWork ) >
464 static_cast<long double>( std::numeric_limits<MXLAPACK_INT>::max() ) ||
465 requestedIWork < 1 )
466 {
467 internal::mxlib_error_report( error_t::lapackerr, "invalid workspace sizes returned by SYEVR query" );
468 if( localMem )
469 {
470 delete mem;
471 }
472 return -1;
473 }
474
475 const MXLAPACK_INT requestedWork = static_cast<MXLAPACK_INT>( queriedWork );
476 if( !detail::resizeSyevrBuffer<calcT>( mem->work, mem->sizeWork, requestedWork ) ||
477 !detail::resizeSyevrBuffer<calcT>( mem->iWork, mem->sizeIWork, requestedIWork ) )
478 {
479 internal::mxlib_error_report( error_t::allocerr, "malloc failed in eigenSYEVR." );
480 if( localMem )
481 {
482 delete mem;
483 }
484 return -1000;
485 }
486
487 mem->UPLO = UPLO;
488 mem->n = n;
489 mem->RANGE = RANGE;
490 mem->numeig = numeig;
491 mem->IL = IL;
492 mem->IU = IU;
493 }
494
495 // Now actually do the calculation
496 info = detail::callSyevr<calcT>( 'V',
497 RANGE,
498 UPLO,
499 n,
500 X.data(),
501 n,
502 0,
503 0,
504 IL,
505 IU,
506 math::lamch<calcT>( 'S' ),
507 &numeig,
508 eigval.data(),
509 eigvec.data(),
510 n,
511 mem->iSuppZ,
512 mem->work,
513 mem->sizeWork,
514 mem->iWork,
515 mem->sizeIWork );
516
517 /* Cleanup and exit */
518
519 if( localMem )
520 {
521 delete mem;
522 }
523
524 return info;
525}
526
527/// Calculate the eigenvectors and eigenvalues given a triangular matrix
528/** Eigen-decomposition of the matrix is performed using \ref eigenSYEVR().
529 *
530 * \tparam evCalcT is the type in which to perform eigen-decomposition, which may be different
531 * from the input array and output arrays (which must be the same).
532 * \tparam eigenT is a 2D Eigen-like type
533 *
534 * \ingroup eigen_lapack
535 */
536template <typename _evCalcT = double, typename eigenT>
537MXLAPACK_INT calcEigenVecs( eigenT &evecs, /**< [out] on exit contains the eigen vectors*/
538 eigenT &evals, /**< [out] on exit contains the eigen vectors*/
539 eigenT &cv, /**< [in] a lower-triangle (in the Lapack sense) square
540 covariance matrix.*/
541 int nVecs = 0, /**< [in] [opt] The maximum number of modes to solve
542 for. If 0 all modes are solved for.*/
543 bool normalize = false, /**< [in] [opt] flag specifying whether or not to
544 normalize the eigenvectors.*/
545 bool check = false, /**< [in] [opt] flag specifying whether or not to
546 check the eigenvalues/vectors for
547 validity. Requires normalize=true.*/
548 syevrMem<_evCalcT> *mem = 0, /**< [in] [opt] A memory structure which can be
549 re-used by SYEVR for efficiency.*/
550 double *t_eigenv = nullptr /**< [out] [opt] if not null, will be filled in
551 with the time taken to calculate
552 eigenvalues.*/ )
553{
554 typedef _evCalcT evCalcT;
555 typedef typename eigenT::Scalar realT;
556
557 bool localMem = false;
558
559 if( mem == 0 )
560 {
561 mem = new syevrMem<evCalcT>;
562 localMem = true;
563 }
564
565 if( cv.rows() <= 0 || cv.rows() != cv.cols() )
566 {
567 std::cerr << "calcEigenVecs: covariance matrix must be non-empty and square\n";
568 if( localMem )
569 {
570 delete mem;
571 }
572 return -1;
573 }
574
575 MXLAPACK_INT tNims = cv.rows();
576
577 if( nVecs <= 0 || nVecs > tNims )
578 {
579 nVecs = tNims;
580 }
581
582 if( t_eigenv )
583 {
584 *t_eigenv = sys::get_curr_time();
585 }
586
587 mem->cvd = cv.template cast<evCalcT>();
588
589 // Calculate eigenvectors and eigenvalues
590 /* SYEVR sorts eigenvalues in ascending order, so we specifiy the top n_modes
591 */
592 MXLAPACK_INT info = eigenSYEVR( mem->evecsd, mem->evalsd, mem->cvd, tNims - nVecs, tNims, 'L', mem );
593
594 if( info != 0 )
595 {
596 std::cerr << "calcEigenVecs: eigenSYEVR returned an error (info = " << info << ")\n";
597 if( localMem )
598 {
599 delete mem;
600 }
601
602 return -1;
603 }
604
605 evecs = mem->evecsd.template cast<realT>();
606 evals = mem->evalsd.template cast<realT>();
607
608 if( normalize )
609 {
610 // Normalize the eigenvectors
611 if( !check )
612 {
613 for( MXLAPACK_INT i = 0; i < nVecs; ++i )
614 {
615 evecs.col( i ) = evecs.col( i ) / sqrt( evals( i ) );
616 }
617 }
618 else // here we check for invalid results and 0 things out
619 {
620 for( MXLAPACK_INT i = 0; i < nVecs; ++i )
621 {
622 if( !math::isFinite( evals( i ) ) )
623 {
624 std::cerr << "got non-finite eigenvalue (# " << i << ")\n";
625 evecs.col( i ).setZero();
626 }
627 else if( evals( i ) == 0 )
628 {
629 std::cerr << "got 0 eigenvalue (# " << i << ")\n";
630 evecs.col( i ).setZero();
631 }
632 else if( evals( i ) < 0 )
633 {
634 std::cerr << "got < 0 eigenvalue (# " << i << ")\n";
635 evecs.col( i ).setZero();
636 }
637 else
638 {
639 evecs.col( i ) = evecs.col( i ) / sqrt( evals( i ) );
640 }
641
642 for( int r = 0; r < evecs.rows(); ++r )
643 {
644 if( !math::isFinite( evecs.col( i )( r ) ) )
645 {
646 std::cerr << "got non-finite eigenvector entry (# " << i << "," << r << ")\n";
647 evecs.col( i ).setZero();
648 break;
649 }
650 }
651 }
652 }
653 }
654
655 if( t_eigenv )
656 {
657 *t_eigenv = sys::get_curr_time() - *t_eigenv;
658 }
659
660 if( localMem )
661 {
662 delete mem;
663 }
664
665 return 0;
666
667} // calcKLModes
668
669/// Calculate the K-L modes, or principle components, given a covariance matrix.
670/** Eigen-decomposition of the covariance matrix is performed using \ref eigenSYEVR().
671 *
672 * \tparam evCalcT is the type in which to perform eigen-decomposition.
673 * \tparam eigenT is a 2D Eigen-like type
674 * \tparam eigenT1 is a 2D Eigen-like type.
675 *
676 * \ingroup eigen_lapack
677 */
678template <typename _evCalcT = double, typename eigenT, typename eigenT1>
679MXLAPACK_INT calcKLModes( eigenT &klModes, /**< [out] on exit contains the K-L modes (or P.C.s) */
680 eigenT &cv, /**< [in] a lower-triangle (in the Lapack sense) square
681 covariance matrix.*/
682 const eigenT1 &Rims, /**< [in] The reference data. cv.rows() == Rims.cols().*/
683 int n_modes = 0, /**< [in] [opt] Tbe maximum number of modes to solve for.
684 If 0 all modes are solved for.*/
685 syevrMem<_evCalcT> *mem = 0, /**< [in] [opt] A memory structure which can be re-used
686 by SYEVR for efficiency.*/
687 double *t_eigenv = nullptr, /**< [out] [opt] if not null, will be filled in with the time
688 taken to calculate eigenvalues.*/
689 double *t_klim = nullptr /**< [out] [opt] if not null, will be filled in with the time
690 taken to calculate the KL modes.*/)
691{
692 typedef _evCalcT evCalcT;
693 typedef typename eigenT::Scalar realT;
694
695 bool localMem = false;
696
697 if( mem == 0 )
698 {
699 mem = new syevrMem<evCalcT>;
700 localMem = true;
701 }
702
703 if( cv.rows() != Rims.cols() )
704 {
705 std::cerr << "Covariance matrix - reference image size mismatch in calcKLModes\n";
706 if( localMem )
707 {
708 delete mem;
709 }
710 return -1;
711 }
712
713 eigenT evecs, evals;
714
715 MXLAPACK_INT tNims = cv.rows();
716 MXLAPACK_INT tNpix = Rims.rows();
717
718 if( n_modes <= 0 || n_modes > tNims )
719 {
720 n_modes = tNims;
721 }
722
723 // Eigen::Array<evCalcT, Eigen::Dynamic, Eigen::Dynamic> evecsd, evalsd;
724 mem->cvd = cv.template cast<evCalcT>();
725 MXLAPACK_INT info = calcEigenVecs( mem->evecsd, mem->evalsd, mem->cvd, n_modes, true, true, mem, t_eigenv );
726
727 if( info != 0 )
728 {
729 std::cerr << "calckKLModes: eigenSYEVR returned an error (info = " << info << ")\n";
730 if( localMem )
731 {
732 delete mem;
733 }
734 return -1;
735 }
736
737 evecs = mem->evecsd.template cast<realT>();
738 evals = mem->evalsd.template cast<realT>();
739
740 klModes.resize( n_modes, tNpix );
741
742 if( t_klim )
743 {
744 *t_klim = sys::get_curr_time();
745 }
746
747 // Now calculate KL images
748 /*
749 * KL = E^T * R ==> C = A^T * B
750 */
751 constexpr realT alpha{ 1 };
752 constexpr realT beta{ 0 };
753 gemm<realT>( CblasColMajor,
754 CblasTrans,
755 CblasTrans,
756 n_modes,
757 tNpix,
758 tNims,
759 alpha,
760 evecs.data(),
761 cv.rows(),
762 Rims.data(),
763 Rims.rows(),
764 beta,
765 klModes.data(),
766 klModes.rows() );
767
768 if( t_klim )
769 {
770 *t_klim = sys::get_curr_time() - *t_klim;
771 }
772
773 if( localMem )
774 {
775 delete mem;
776 }
777
778 return 0;
779
780} // calcKLModes
781
782/// Compute the SVD of an Eigen::Array using LAPACK's xgesdd
783/** Computes the SVD of A, \f$ A = U S V^T \f$.
784 *
785 * \returns 0 on success
786 * \returns -i on error in ith parameter (from LAPACK xgesdd)
787 * \returns >0 did not converge (from LAPACK xgesdd)
788 *
789 * \tparam dataT is either float or double.
790 *
791 * \ingroup eigen_lapack
792 */
793template <typename dataT>
794MXLAPACK_INT eigenGESDD( Eigen::Array<dataT, -1, -1> &U, ///< [out] the A.rows() x A.rows() left matrix
795 Eigen::Array<dataT, -1, -1> &S, ///< [out] the A.cols() x 1 matrix of singular values
796 Eigen::Array<dataT, -1, -1> &VT, /**< [out] the A.cols() x A.cols() right matrix, note this
797 is the transpose. */
798 Eigen::Array<dataT, -1, -1> &A ///< [in] the input matrix to be decomposed
799)
800{
801 char JOBZ = 'A';
802 MXLAPACK_INT M = A.rows();
803 MXLAPACK_INT N = A.cols();
804 MXLAPACK_INT LDA = M;
805 S.resize( N, 1 );
806 U.resize( M, M );
807 MXLAPACK_INT LDU = M;
808 VT.resize( N, N );
809 MXLAPACK_INT LDVT = N;
810
811 dataT wkOpt;
812 MXLAPACK_INT LWORK = -1;
813
814 MXLAPACK_INT *IWORK = new MXLAPACK_INT[8 * M];
815 MXLAPACK_INT INFO;
816
817 INFO =
818 math::gesdd<dataT>( JOBZ, M, N, A.data(), LDA, S.data(), U.data(), LDU, VT.data(), LDVT, &wkOpt, LWORK, IWORK );
819
820 LWORK = wkOpt;
821 // delete WORK;
822 dataT *WORK = new dataT[LWORK];
823
824 INFO =
825 math::gesdd<dataT>( JOBZ, M, N, A.data(), LDA, S.data(), U.data(), LDU, VT.data(), LDVT, WORK, LWORK, IWORK );
826
827 delete[] WORK;
828 delete[] IWORK;
829
830 return INFO;
831}
832
833#define MX_PINV_NO_INTERACT 0
834#define MX_PINV_PLOT 1
835#define MX_PINV_ASK 2
836#define MX_PINV_ASK_NMODES 4
837
838/// Calculate the pseudo-inverse of a patrix given its SVD
839/** Given the SVD of A, \f$ A = U S V^T \f$, as calculated by eigenGESDD the psuedo-inverse is
840 * calculated as \f$ A^+ = V S^+ U^T\f$.
841 *
842 * The parameter \p interact is intepreted as a bitmask. The values can be
843 * - `MX_PINV_PLOT`, which will cause a plot to be displayed of the singular values
844 * - `MX_PINV_ASK`, which will ask the user for a max. condition number using stdin
845 * - `MX_PINV_ASK_NMODES`, which will ask the user for a max number of modes to include using
846 * stdin. Overrides MX_PINV_ASK. If \p interact is 0 then no interaction is used and \p
847 * maxCondition controls the inversion.
848 *
849 * \tparam dataT is either float or double.
850 *
851 * \ingroup eigen_lapack
852 */
853template <typename dataT>
854int eigenPseudoInverse( Eigen::Array<dataT, -1, -1> &PInv, ///< [out] The pseudo-inverse of A
855 dataT &condition, ///< [out] The final condition number.
856 int &nRejected, ///< [out] The number of eigenvectors rejected
857 Eigen::Array<dataT, -1, -1> &U, ///< [in] the A.rows() x A.rows() left matrix
858 Eigen::Array<dataT, -1, -1> &S, ///< [in] the A.cols() x 1 matrix of singular values
859 Eigen::Array<dataT, -1, -1> &VT, /**< [in] the A.cols() x A.cols() right matrix, note this
860 is the transpose. */
861 int minMN, ///< [in] The minimum size of the matrix to invert.
862 dataT &maxCondition, /**< [in] If > 0, the maximum condition number desired.
863 If <0 the number of modes to keep. Used to
864 threshold the singular values. Set to 0 to
865 include all eigenvalues/vectors. Ignored if
866 interactive. */
867 dataT alpha = 0, /**< [in] [opt] the Tikhonov regularization value, as
868 a fraction of the highest singular value.
869 If alpha < 0, then it is treated as a (positive)
870 floor (as a fraction of highest singular value)
871 for the singular values, which is not the same as
872 Tikhonov (alpha > 0).*/
873 int interact = MX_PINV_NO_INTERACT /**< [in] [opt] a bitmask controlling interaction.
874 See above.*/
875)
876{
877
878 dataT Smax = S.maxCoeff();
879
880 if( alpha > 0 )
881 {
882 for( MXLAPACK_INT i = 0; i < S.rows(); ++i )
883 {
884 S( i ) = ( pow( S( i ), 2 ) + pow( alpha * Smax, 2 ) ) / S( i );
885 }
886 }
887
888 if( alpha < 0 )
889 {
890 for( MXLAPACK_INT i = 0; i < S.rows(); ++i )
891 {
892 S( i ) = S( i ) + -alpha * Smax;
893 }
894 }
895
896 int modesToReject = 0;
897 if( maxCondition < 0 ) // Rejecting mode numbers
898 {
899 modesToReject = -maxCondition;
900
901 if( modesToReject - 1 < S.rows() )
902 {
903 maxCondition = Smax / S( modesToReject - 1, 0 );
904 }
905 }
906
907 if( interact & MX_PINV_PLOT )
908 {
909 gnuPlot gp;
910 gp.command( "set title \"SVD Singular Values\"" );
911 gp.logy();
912 gp.plot( S.data(), S.rows(), " w lp", "singular values" );
913 }
914
915 if( interact & MX_PINV_ASK && !( interact & MX_PINV_ASK_NMODES ) )
916 {
917 dataT mine;
918 std::cout << "Maximum singular value: " << Smax << "\n";
919 std::cout << "Minimum singular value: " << S.minCoeff() << "\n";
920 std::cout << "Enter singular value threshold: ";
921 std::cin >> mine;
922
923 if( mine > 0 )
924 {
925 maxCondition = Smax / mine;
926 }
927 else
928 {
929 maxCondition = Smax / S( S.rows() - 1, 0 );
930 }
931 }
932 else if( interact & MX_PINV_ASK_NMODES )
933 {
934 unsigned mine;
935 std::cout << "Maximum singular value: " << Smax << "\n";
936 std::cout << "Minimum singular value: " << S.minCoeff() << "\n";
937 std::cout << "Enter number of modes to keep: ";
938 std::cin >> mine;
939 modesToReject = S.rows() - mine;
940
941 if( modesToReject <= 0 || modesToReject > S.rows() )
942 {
943 modesToReject = 0;
944 }
945
946 maxCondition = -modesToReject;
947 }
948
949 Eigen::Array<dataT, -1, -1> sigma;
950 sigma.resize( S.rows(), S.rows() );
951 sigma.setZero();
952
953 nRejected = 0;
954
955 if( maxCondition > 0 )
956 {
957 dataT threshold = 0;
958
959 if( maxCondition > 0 )
960 {
961 threshold = Smax / maxCondition;
962
963 condition = 1;
964
965 for( MXLAPACK_INT i = 0; i < S.rows(); ++i )
966 {
967 if( S( i ) >= threshold )
968 {
969 sigma( i, i ) = 1. / S( i );
970 if( Smax / S( i ) > condition )
971 {
972 condition = Smax / S( i );
973 }
974 }
975 else
976 {
977 sigma( i, i ) = 0;
978 ++nRejected;
979 }
980 }
981 }
982 }
983 else // rejecting modes
984 {
985 std::cerr << "rejecting based on modes\n";
986 std::cerr << " modes to reject: " << modesToReject << "\n";
987 for( MXLAPACK_INT i = 0; i < S.rows(); ++i )
988 {
989 if( i < S.rows() - modesToReject )
990 {
991 sigma( i, i ) = 1. / S( i );
992 if( Smax / S( i ) > condition )
993 condition = Smax / S( i );
994 }
995 else
996 {
997 sigma( i, i ) = 0;
998 ++nRejected;
999 }
1000 }
1001 }
1002
1003 if( interact & MX_PINV_PLOT )
1004 {
1005 std::vector<dataT> vsig( sigma.rows() );
1006 for( int rr = 0; rr < sigma.rows(); ++rr )
1007 {
1008 vsig[rr] = sigma( rr, rr );
1009 }
1010
1011 gnuPlot gp;
1012 gp.command( "set title \"Inverted Singular Values\"" );
1013 gp.logy();
1014 gp.plot( vsig.data(), vsig.size(), " w lp", "inverted singular values" );
1015 }
1016
1017 if( interact & MX_PINV_ASK || interact & MX_PINV_ASK_NMODES )
1018 {
1019 dataT mine;
1020 std::cout << "Modes Rejected: " << nRejected << "\n";
1021 std::cout << "Condition Number: " << condition << "\n";
1022 }
1023
1024 PInv = ( VT.matrix().transpose() * sigma.matrix().transpose() ) *
1025 U.block( 0, 0, U.rows(), minMN ).matrix().transpose();
1026
1027 return 0;
1028}
1029
1030/// Calculate the pseudo-inverse of a patrix using the SVD
1031/** First computes the SVD of A, \f$ A = U S V^T \f$, using eigenGESDD. Then the psuedo-inverse is
1032 * calculated as \f$ A^+ = V S^+ U^T\f$.
1033 *
1034 * The parameter \p interact is intepreted as a bitmask. The values can be
1035 * - `MX_PINV_PLOT`, which will cause a plot to be displayed of the singular values
1036 * - `MX_PINV_ASK`, which will ask the user for a max. condition number using stdin
1037 * - `MX_PINV_ASK_NMODES`, which will ask the user for a max number of modes to include using
1038 * stdin. Overrides MX_PINV_ASK. If \p interact is 0 then no interaction is used and \p
1039 * maxCondition controls the inversion.
1040 *
1041 * \tparam dataT is either float or double.
1042 *
1043 * \ingroup eigen_lapack
1044 */
1045template <typename dataT>
1046int eigenPseudoInverse( Eigen::Array<dataT, -1, -1> &PInv, ///< [out] The pseudo-inverse of A
1047 dataT &condition, ///< [out] The final condition number.
1048 int &nRejected, ///< [out] The number of eigenvectors rejected
1049 Eigen::Array<dataT, -1, -1> &U, ///< [out] the A.rows() x A.rows() left matrix
1050 Eigen::Array<dataT, -1, -1> &S, ///< [out] the A.cols() x 1 matrix of singular values
1051 Eigen::Array<dataT, -1, -1> &VT, /**< [out] the A.cols() x A.cols() right matrix, note this
1052 is the transpose. */
1053 Eigen::Array<dataT, -1, -1> &A, ///< [in] The matrix to invert. This will be modified!
1054 dataT &maxCondition, /**< [in] If > 0, the maximum condition number desired.
1055 If <0 the number of modes to keep. Used to
1056 threshold the singular values. Set to 0 to
1057 include all eigenvalues/vectors. Ignored if
1058 interactive. */
1059 dataT alpha = 0, /**< [in] [opt] the Tikhonov regularization value, as
1060 a fraction of the highest singular value.
1061 If alpha < 0, then it is treated as a (positive)
1062 floor (as a fraction of highest singular value)
1063 for the singular values, which is not the same as
1064 Tikhonov (alpha > 0).*/
1065 int interact = MX_PINV_NO_INTERACT /**< [in] [opt] a bitmask controlling interaction.
1066 See above.*/
1067)
1068{
1069
1070 int minMN = std::min( A.rows(), A.cols() );
1071
1072 MXLAPACK_INT info;
1073 info = eigenGESDD( U, S, VT, A );
1074
1075 if( info != 0 )
1076 {
1077 std::cerr << "eigenPseudoInverse: eigenGESDD failed with info = " << info << "\n";
1078 return info;
1079 }
1080
1081 return eigenPseudoInverse( PInv, condition, nRejected, U, S, VT, minMN, maxCondition, alpha, interact );
1082}
1083
1084/// Calculate the pseudo-inverse of a matrix using the SVD
1085/** First computes the SVD of A, \f$ A = U S V^T \f$, using eigenGESDD. Then the psuedo-inverse is
1086 * calculated as \f$ A^+ = V S^+ U^T\f$. This interface does not provide access to U, S and VT.
1087 *
1088 * The parameter \p interact is intepreted as a bitmask. The values can be
1089 * - `MX_PINV_PLOT`, which will cause a plot to be displayed of the singular values
1090 * - `MX_PINV_ASK`, which will ask the user for a max. condition number using stdin
1091 * - `MX_PINV_ASK_NMODES`, which will ask the user for a max number of modes to include using
1092 * stdin. Overrides MX_PINV_ASK. If \p interact is 0 then no interaction is used and maxCondition
1093 * controls the inversion.
1094 * *
1095 * \tparam dataT is either float or double.
1096 *
1097 * \overload
1098 *
1099 * \ingroup eigen_lapack
1100 */
1101template <typename dataT>
1102int eigenPseudoInverse( Eigen::Array<dataT, -1, -1> &PInv, ///< [out] The pseudo-inverse of A
1103 dataT &condition, ///< [out] The final condition number.
1104 int &nRejected, /**< [out] The number of eigenvectors
1105 rejected*/
1106 Eigen::Array<dataT, -1, -1> &A, /**< [in] The matrix to invert, will be
1107 altered!*/
1108 dataT &maxCondition, /**< [in] If > 0, the maximum condition number desired.
1109 If <0 the number of modes to keep. Used to
1110 threshold the singular values. Set to 0 to
1111 include all eigenvalues/vectors. Ignored if
1112 interactive.*/
1113 dataT alpha = 0, /**< [in] [opt] the Tikhonov regularization value,
1114 as a fraction of the highest singular value. If
1115 alpha < 0, then it is treated as a (positive)
1116 floor (as a fraction of highest singular value)
1117 for the singular values, which is not the same
1118 as Tikhonov (alpha > 0).*/
1119 int interact = MX_PINV_NO_INTERACT /**< [in] [opt] a bitmask controlling interaction.
1120 See above.*/
1121)
1122{
1123 Eigen::Array<dataT, -1, -1> S, U, VT;
1124
1125 return eigenPseudoInverse( PInv, condition, nRejected, U, S, VT, A, maxCondition, alpha, interact );
1126}
1127
1128} // namespace math
1129} // namespace mx
1130
1131#endif // math_eigenLapack_hpp
An interactive c++ interface to gnuplot.
Definition gnuPlot.hpp:194
int plot(const std::string &fname, const std::string &modifiers, const std::string &title, const std::string &name)
Plot from a file specifying all curve components.
int logy()
Set the y axis to log scale.
int command(const std::string &com, bool flush=true)
Send a command to gnuplot.
Floating-point classification utilities that remain reliable under fast-math optimization.
Declaration and definition of an interface to the gnuplot program.
MXLAPACK_INT calcKLModes(eigenT &klModes, eigenT &cv, const eigenT1 &Rims, int n_modes=0, syevrMem< _evCalcT > *mem=0, double *t_eigenv=nullptr, double *t_klim=nullptr)
Calculate the K-L modes, or principle components, given a covariance matrix.
MXLAPACK_INT calcEigenVecs(eigenT &evecs, eigenT &evals, eigenT &cv, int nVecs=0, bool normalize=false, bool check=false, syevrMem< _evCalcT > *mem=0, double *t_eigenv=nullptr)
Calculate the eigenvectors and eigenvalues given a triangular matrix.
void eigenSYRK(eigenT1 &cv, const eigenT2 &ims)
Calculates the lower triangular part of the covariance matrix of ims.
int eigenPseudoInverse(Eigen::Array< dataT, -1, -1 > &PInv, dataT &condition, int &nRejected, Eigen::Array< dataT, -1, -1 > &U, Eigen::Array< dataT, -1, -1 > &S, Eigen::Array< dataT, -1, -1 > &VT, int minMN, dataT &maxCondition, dataT alpha=0, int interact=MX_PINV_NO_INTERACT)
Calculate the pseudo-inverse of a patrix given its SVD.
MXLAPACK_INT eigenSYEVR(arrT &eigvec, arrT &eigval, arrT &X, int ev0=0, int ev1=-1, char UPLO='L', syevrMem< typename arrT::Scalar > *mem=0)
Calculate select eigenvalues and eigenvectors of an Eigen Array.
MXLAPACK_INT eigenGESDD(Eigen::Array< dataT, -1, -1 > &U, Eigen::Array< dataT, -1, -1 > &S, Eigen::Array< dataT, -1, -1 > &VT, Eigen::Array< dataT, -1, -1 > &A)
Compute the SVD of an Eigen::Array using LAPACK's xgesdd.
@ lapackerr
An error was returned by Lapack.
Definition error_t.hpp:69
@ allocerr
An error occurred during memory allocation.
Definition error_t.hpp:36
error_t mxlib_error_report(const error_t &code, const std::string &expl, const std::source_location &loc=std::source_location::current())
Print a report to stderr given an mxlib error_t code and explanation and return the code.
Definition error.hpp:331
bool isFinite(realT value)
Test whether a floating-point value is finite, including under finite-math-only optimization.
void syrk(const CBLAS_ORDER Order, const CBLAS_UPLO Uplo, const CBLAS_TRANSPOSE Trans, const int N, const int K, const dataT &alpha, const dataT *A, const int lda, const dataT &beta, dataT *C, const int ldc)
Template Wrapper for cblas xSYRK.
void gemm(const CBLAS_ORDER Order, const CBLAS_TRANSPOSE TransA, const CBLAS_TRANSPOSE TransB, const int M, const int N, const int K, const dataT &alpha, const dataT *A, const int lda, const dataT *B, const int ldb, const dataT &beta, dataT *C, const int ldc)
Template Wrapper for cblas xGEMM.
MXLAPACK_INT gesdd(char JOBZ, MXLAPACK_INT M, MXLAPACK_INT N, dataT *A, MXLAPACK_INT LDA, dataT *S, dataT *U, MXLAPACK_INT LDU, dataT *VT, MXLAPACK_INT LDVT, dataT *WORK, MXLAPACK_INT LWORK, MXLAPACK_INT *IWORK)
Compute the singular value decomposition (SVD) of a real matrix with GESDD.
MXLAPACK_INT syevr(char JOBZ, char RANGE, char UPLO, MXLAPACK_INT N, dataT *A, MXLAPACK_INT LDA, dataT VL, dataT VU, MXLAPACK_INT IL, MXLAPACK_INT IU, dataT ABSTOL, MXLAPACK_INT *M, dataT *W, dataT *Z, MXLAPACK_INT LDZ, MXLAPACK_INT *ISUPPZ, dataT *WORK, MXLAPACK_INT LWORK, MXLAPACK_INT *IWORK, MXLAPACK_INT LIWORK)
Compute selected eigenvalues and, optionally, eigenvectors of a real symmetric matrix.
dataT lamch(char CMACH)
Determine machine parameters.
typeT get_curr_time()
Get the current system time in seconds.
The mxlib c++ namespace.
Definition mxlib.hpp:37
A struct to hold the working memory for eigenSYEVR and maintain it between calls if desired.
MXLAPACK_INT IU
One-based upper eigenvalue index associated with the cached workspace query.
MXLAPACK_INT * iSuppZ
LAPACK eigenvector-support workspace owned by this object.
syevrMem()
Construct an empty reusable SYEVR workspace.
floatT * work
Optimized floating-point LAPACK workspace owned by this object.
MXLAPACK_INT sizeISuppZ
Capacity of the eigenvector-support workspace.
void free()
Release all workspace allocations and reset the cached LAPACK configuration.
MXLAPACK_INT n
Matrix order associated with the cached workspace query.
~syevrMem()
Release every allocation owned by this workspace.
MXLAPACK_INT sizeMinWork
Capacity of the minimum floating-point query workspace.
MXLAPACK_INT IL
One-based lower eigenvalue index associated with the cached workspace query.
Eigen::Array< floatT, Eigen::Dynamic, Eigen::Dynamic > evalsd
Calculation-type eigenvalue storage used by higher-level eigensolver helpers.
char UPLO
Triangle selection associated with the cached workspace query.
MXLAPACK_INT sizeMinIWork
Capacity of the minimum integer query workspace.
MXLAPACK_INT * iWork
Optimized integer LAPACK workspace owned by this object.
syevrMem(const syevrMem &other)=delete
Copying an owning SYEVR workspace is prohibited.
Eigen::Array< floatT, Eigen::Dynamic, Eigen::Dynamic > cvd
Calculation-type covariance storage used by higher-level eigensolver helpers.
MXLAPACK_INT * minIWork
Minimum integer workspace used for LAPACK size queries.
syevrMem & operator=(const syevrMem &other)=delete
Copy assignment of an owning SYEVR workspace is prohibited.
MXLAPACK_INT sizeWork
Capacity of the optimized floating-point workspace.
MXLAPACK_INT numeig
Number of eigenvalues reported by the most recent workspace query.
floatT * minWork
Minimum floating-point workspace used for LAPACK size queries.
Eigen::Array< floatT, Eigen::Dynamic, Eigen::Dynamic > evecsd
Calculation-type eigenvector storage used by higher-level eigensolver helpers.
char RANGE
Eigenvalue-range selection associated with the cached workspace query.
MXLAPACK_INT sizeIWork
Capacity of the optimized integer workspace.
Declares and defines templatized wrappers for the BLAS.
Declares and defines templatized wrappers for the Lapack library.
Utilities for working with time.