4#include "../../catch2/catch.hpp"
6#define MX_NO_ERROR_REPORTS
21TEST_CASE(
"syevrMem workspaces are non-copyable",
"[math::syevrMem]" )
27using matrixT = Eigen::Array<double, Eigen::Dynamic, Eigen::Dynamic>;
29int allocationCall{ 0 };
30int failAllocationOnCall{ 0 };
33MXLAPACK_INT queryInfo{ 0 };
34MXLAPACK_INT solveInfo{ 0 };
35bool overrideQuerySizes{
false };
36double injectedWorkSize{ 0.0 };
37MXLAPACK_INT injectedIntegerWorkSize{ 0 };
38std::vector<double> injectedEigenvalues;
39int nonfiniteEigenvectorRow{ -1 };
40int nonfiniteEigenvectorColumn{ -1 };
46 failAllocationOnCall = 0;
51 overrideQuerySizes =
false;
52 injectedWorkSize = 0.0;
53 injectedIntegerWorkSize = 0;
54 injectedEigenvalues.clear();
55 nonfiniteEigenvectorRow = -1;
56 nonfiniteEigenvectorColumn = -1;
57 mx::math::detail::eigenLapackTestHooks<double>::allocator =
nullptr;
58 mx::math::detail::eigenLapackTestHooks<double>::solver =
nullptr;
78matrixT diagonalMatrix(
const std::vector<double> &diagonal )
80 matrixT matrix( diagonal.size(), diagonal.size() );
82 for( std::size_t index = 0; index < diagonal.size(); ++index )
84 matrix( index, index ) = diagonal[index];
90void *controlledAllocation( std::size_t bytes )
93 if( allocationCall == failAllocationOnCall )
97 return std::malloc( bytes );
101MXLAPACK_INT controlledSyevr(
char jobz,
109 MXLAPACK_INT indexLow,
110 MXLAPACK_INT indexHigh,
114 double *eigenvectors,
116 MXLAPACK_INT *support,
120 MXLAPACK_INT liwork )
122 static_cast<void>( jobz );
123 static_cast<void>( uplo );
124 static_cast<void>( matrix );
125 static_cast<void>( lda );
126 static_cast<void>( valueLower );
127 static_cast<void>( valueUpper );
128 static_cast<void>( tolerance );
129 static_cast<void>( support );
131 if( lwork == -1 || liwork == -1 )
139 work[0] = overrideQuerySizes ? injectedWorkSize : std::max<MXLAPACK_INT>( 1, 26 * n );
140 iwork[0] = overrideQuerySizes ? injectedIntegerWorkSize : std::max<MXLAPACK_INT>( 1, 10 * n );
150 const MXLAPACK_INT first = range ==
'I' ? indexLow - 1 : 0;
151 const MXLAPACK_INT count = range ==
'I' ? indexHigh - indexLow + 1 : n;
154 for( MXLAPACK_INT column = 0; column < count; ++column )
156 const MXLAPACK_INT sourceColumn = first + column;
157 eigenvalues[column] = injectedEigenvalues.empty() ? sourceColumn + 1 : injectedEigenvalues[sourceColumn];
159 for( MXLAPACK_INT row = 0; row < n; ++row )
161 eigenvectors[column * ldz + row] = row == sourceColumn ? 1.0 : 0.0;
165 if( nonfiniteEigenvectorRow >= 0 && nonfiniteEigenvectorColumn >= 0 )
167 eigenvectors[nonfiniteEigenvectorColumn * ldz + nonfiniteEigenvectorRow] =
168 std::numeric_limits<double>::infinity();
179namespace unitTest::math_eigenLapack_test
186TEST_CASE(
"eigenSYEVR solves selected ranges and reuses workspaces",
"[math::eigenLapack][eigenSYEVR]" )
190 matrixT eigenvectors;
193 matrixT covariance = diagonalMatrix( { 1.0, 4.0, 9.0 } );
195 REQUIRE( eigenvectors.rows() == 3 );
196 REQUIRE( eigenvectors.cols() == 3 );
197 REQUIRE( eigenvalues.rows() == 3 );
198 REQUIRE( eigenvalues( 0 ) == Approx( 1.0 ) );
199 REQUIRE( eigenvalues( 1 ) == Approx( 4.0 ) );
200 REQUIRE( eigenvalues( 2 ) == Approx( 9.0 ) );
203 covariance = diagonalMatrix( { 1.0, 4.0, 9.0 } );
204 REQUIRE(
mx::math::eigenSYEVR( eigenvectors, eigenvalues, covariance, 1, 3,
'L', &workspace ) == 0 );
205 REQUIRE( eigenvectors.rows() == 3 );
206 REQUIRE( eigenvectors.cols() == 2 );
207 REQUIRE( eigenvalues( 0 ) == Approx( 4.0 ) );
208 REQUIRE( eigenvalues( 1 ) == Approx( 9.0 ) );
210 const auto *support = workspace.
iSuppZ;
211 const auto *minimumWork = workspace.
minWork;
212 const auto *work = workspace.
work;
213 const auto *minimumIntegerWork = workspace.
minIWork;
214 const auto *integerWork = workspace.
iWork;
216 covariance = diagonalMatrix( { 1.0, 4.0, 9.0 } );
217 REQUIRE(
mx::math::eigenSYEVR( eigenvectors, eigenvalues, covariance, 1, 3,
'L', &workspace ) == 0 );
218 REQUIRE( workspace.
iSuppZ == support );
219 REQUIRE( workspace.
minWork == minimumWork );
220 REQUIRE( workspace.
work == work );
221 REQUIRE( workspace.
minIWork == minimumIntegerWork );
222 REQUIRE( workspace.
iWork == integerWork );
224 covariance = diagonalMatrix( { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0 } );
225 REQUIRE(
mx::math::eigenSYEVR( eigenvectors, eigenvalues, covariance, 0, -1,
'L', &workspace ) == 0 );
230 matrixT upperCovariance( 2, 2 );
231 upperCovariance( 0, 0 ) = 2.0;
232 upperCovariance( 0, 1 ) = 1.0;
233 upperCovariance( 1, 0 ) = 99.0;
234 upperCovariance( 1, 1 ) = 2.0;
235 REQUIRE(
mx::math::eigenSYEVR( eigenvectors, eigenvalues, upperCovariance, 0, -1,
'U', &workspace ) == 0 );
236 REQUIRE( eigenvalues( 0 ) == Approx( 1.0 ) );
237 REQUIRE( eigenvalues( 1 ) == Approx( 3.0 ) );
240 REQUIRE( workspace.
iSuppZ ==
nullptr );
241 REQUIRE( workspace.
minWork ==
nullptr );
242 REQUIRE( workspace.
work ==
nullptr );
243 REQUIRE( workspace.
minIWork ==
nullptr );
244 REQUIRE( workspace.
iWork ==
nullptr );
250 REQUIRE( workspace.
n == 0 );
252 covariance = diagonalMatrix( { 2.0, 5.0 } );
253 REQUIRE(
mx::math::eigenSYEVR( eigenvectors, eigenvalues, covariance, 0, -1,
'L', &workspace ) == 0 );
254 REQUIRE( eigenvalues( 0 ) == Approx( 2.0 ) );
255 REQUIRE( eigenvalues( 1 ) == Approx( 5.0 ) );
265TEST_CASE(
"eigenSYEVR rejects invalid matrix and range geometry",
"[math::eigenLapack][eigenSYEVR]" )
269 matrixT eigenvectors;
272 matrixT empty( 0, 0 );
275 matrixT rectangular( 2, 3 );
276 rectangular.setZero();
279 matrixT covariance = diagonalMatrix( { 1.0, 2.0 } );
290TEST_CASE(
"eigenSYEVR reports allocation and solver failures",
"[math::eigenLapack][eigenSYEVR]" )
293 matrixT eigenvectors;
296 mx::math::detail::eigenLapackTestHooks<double>::allocator = &controlledAllocation;
297 failAllocationOnCall = 1;
298 matrixT covariance = diagonalMatrix( { 1.0, 2.0 } );
302 mx::math::detail::eigenLapackTestHooks<double>::allocator = &controlledAllocation;
303 failAllocationOnCall = 5;
304 covariance = diagonalMatrix( { 1.0, 2.0 } );
307 for(
int failedCall = 1; failedCall <= 5; ++failedCall )
310 mx::math::detail::eigenLapackTestHooks<double>::allocator = &controlledAllocation;
311 failAllocationOnCall = failedCall;
314 covariance = diagonalMatrix( { 1.0, 2.0 } );
315 REQUIRE(
mx::math::eigenSYEVR( eigenvectors, eigenvalues, covariance, 0, -1,
'L', &workspace ) == -1000 );
316 REQUIRE( allocationCall == failedCall );
320 mx::math::detail::eigenLapackTestHooks<double>::solver = &controlledSyevr;
322 covariance = diagonalMatrix( { 1.0, 2.0 } );
324 REQUIRE( queryCall == 1 );
325 REQUIRE( solveCall == 0 );
328 mx::math::detail::eigenLapackTestHooks<double>::solver = &controlledSyevr;
331 covariance = diagonalMatrix( { 1.0, 2.0 } );
332 REQUIRE(
mx::math::eigenSYEVR( eigenvectors, eigenvalues, covariance, 0, -1,
'L', &workspace ) == 23 );
333 REQUIRE( queryCall == 1 );
334 REQUIRE( solveCall == 1 );
341TEST_CASE(
"eigenSYEVR validates queried workspace sizes",
"[math::eigenLapack][eigenSYEVR]" )
344 matrixT eigenvectors;
348 const std::vector<double> invalidFloatingSizes{ 0.0,
350 std::numeric_limits<double>::quiet_NaN(),
351 std::numeric_limits<double>::infinity(),
352 static_cast<double>( std::numeric_limits<MXLAPACK_INT>::max() ) +
355 for(
const double invalidSize : invalidFloatingSizes )
358 mx::math::detail::eigenLapackTestHooks<double>::solver = &controlledSyevr;
359 overrideQuerySizes =
true;
360 injectedWorkSize = invalidSize;
361 injectedIntegerWorkSize = 10;
362 covariance = diagonalMatrix( { 1.0, 2.0 } );
364 REQUIRE( solveCall == 0 );
367 for(
const MXLAPACK_INT invalidSize : { MXLAPACK_INT{ 0 }, MXLAPACK_INT{ -1 } } )
370 mx::math::detail::eigenLapackTestHooks<double>::solver = &controlledSyevr;
371 overrideQuerySizes =
true;
372 injectedWorkSize = 10;
373 injectedIntegerWorkSize = invalidSize;
375 covariance = diagonalMatrix( { 1.0, 2.0 } );
376 REQUIRE(
mx::math::eigenSYEVR( eigenvectors, eigenvalues, covariance, 0, -1,
'L', &workspace ) == -1 );
377 REQUIRE( solveCall == 0 );
385TEST_CASE(
"calcEigenVecs clamps mode requests and controls normalization",
"[math::eigenLapack][calcEigenVecs]" )
388 matrixT eigenvectors;
390 matrixT covariance = diagonalMatrix( { 1.0, 4.0, 9.0 } );
392 double eigenTime{ -1.0 };
394 double>( eigenvectors, eigenvalues, covariance, 1,
false,
false,
nullptr, &eigenTime ) == 0 );
395 REQUIRE( eigenvectors.rows() == 3 );
396 REQUIRE( eigenvectors.cols() == 1 );
397 REQUIRE( eigenvalues( 0 ) == Approx( 9.0 ) );
398 REQUIRE( eigenvectors.matrix().col( 0 ).norm() == Approx( 1.0 ).margin( 1e-12 ) );
399 REQUIRE( eigenTime >= 0.0 );
403 REQUIRE( eigenvectors.cols() == 3 );
404 REQUIRE( eigenvalues( 0 ) == Approx( 1.0 ) );
405 REQUIRE( eigenvalues( 1 ) == Approx( 4.0 ) );
406 REQUIRE( eigenvalues( 2 ) == Approx( 9.0 ) );
409 REQUIRE( eigenvectors.cols() == 3 );
412 REQUIRE( eigenvectors.cols() == 3 );
415 REQUIRE( eigenvectors.cols() == 2 );
416 REQUIRE( eigenvalues( 0 ) == Approx( 4.0 ) );
417 REQUIRE( eigenvalues( 1 ) == Approx( 9.0 ) );
418 REQUIRE( eigenvectors.matrix().col( 0 ).norm() == Approx( 0.5 ).margin( 1e-12 ) );
419 REQUIRE( eigenvectors.matrix().col( 1 ).norm() == Approx( 1.0 / 3.0 ).margin( 1e-12 ) );
426TEST_CASE(
"calcEigenVecs validates exceptional normalized results",
"[math::eigenLapack][calcEigenVecs]" )
429 mx::math::detail::eigenLapackTestHooks<double>::solver = &controlledSyevr;
430 injectedEigenvalues = { 0.0, -1.0, std::numeric_limits<double>::quiet_NaN(), 1e-20, 4.0 };
431 nonfiniteEigenvectorRow = 0;
432 nonfiniteEigenvectorColumn = 4;
434 matrixT covariance = diagonalMatrix( { 1.0, 2.0, 3.0, 4.0, 5.0 } );
435 matrixT eigenvectors;
439 REQUIRE( eigenvectors.rows() == 5 );
440 REQUIRE( eigenvectors.cols() == 5 );
441 REQUIRE( eigenvectors.matrix().col( 0 ).norm() == Approx( 0.0 ) );
442 REQUIRE( eigenvectors.matrix().col( 1 ).norm() == Approx( 0.0 ) );
443 REQUIRE( eigenvectors.matrix().col( 2 ).norm() == Approx( 0.0 ) );
444 REQUIRE( eigenvectors.matrix().col( 3 ).norm() == Approx( 1e10 ).epsilon( 1e-12 ) );
445 REQUIRE( eigenvectors.matrix().col( 4 ).norm() == Approx( 0.0 ) );
446 REQUIRE( eigenvectors.isFinite().all() );
447 REQUIRE( eigenvalues( 0 ) == Approx( 0.0 ) );
448 REQUIRE( eigenvalues( 1 ) == Approx( -1.0 ) );
450 REQUIRE( eigenvalues( 3 ) == Approx( 1e-20 ) );
451 REQUIRE( eigenvalues( 4 ) == Approx( 4.0 ) );
458TEST_CASE(
"calcEigenVecs rejects invalid inputs and solver failures",
"[math::eigenLapack][calcEigenVecs]" )
461 matrixT eigenvectors;
464 matrixT rectangular( 2, 3 );
465 rectangular.setZero();
468 matrixT empty( 0, 0 );
472 mx::math::detail::eigenLapackTestHooks<double>::solver = &controlledSyevr;
474 matrixT covariance = diagonalMatrix( { 1.0, 2.0 } );
478 mx::math::detail::eigenLapackTestHooks<double>::solver = &controlledSyevr;
480 REQUIRE(
mx::math::calcEigenVecs( eigenvectors, eigenvalues, covariance, 0,
false,
false, &workspace ) == -1 );
487TEST_CASE(
"eigenSYRK and calcKLModes form normalized KL modes",
"[math::eigenLapack]" )
489 Eigen::Array<double, Eigen::Dynamic, Eigen::Dynamic> references( 3, 2 );
490 references << 1.0, 0.0, 0.0, 2.0, 0.0, 0.0;
492 Eigen::Array<double, Eigen::Dynamic, Eigen::Dynamic> covariance;
495 REQUIRE( covariance.rows() == 2 );
496 REQUIRE( covariance.cols() == 2 );
497 REQUIRE( covariance( 0, 0 ) == Approx( 1.0 ) );
498 REQUIRE( covariance( 1, 0 ) == Approx( 0.0 ) );
499 REQUIRE( covariance( 1, 1 ) == Approx( 4.0 ) );
501 Eigen::Array<double, Eigen::Dynamic, Eigen::Dynamic> modes;
502 double eigenTime{ -1 };
503 double modeTime{ -1 };
505 REQUIRE( modes.rows() == 2 );
506 REQUIRE( modes.cols() == 3 );
507 REQUIRE( modes.matrix().row( 0 ).norm() == Approx( 1.0 ).margin( 1e-12 ) );
508 REQUIRE( modes.matrix().row( 1 ).norm() == Approx( 1.0 ).margin( 1e-12 ) );
509 REQUIRE( modes.matrix().row( 0 ).dot( modes.matrix().row( 1 ) ) == Approx( 0.0 ).margin( 1e-12 ) );
510 REQUIRE( eigenTime >= 0 );
511 REQUIRE( modeTime >= 0 );
515 REQUIRE( modes.rows() == 1 );
516 REQUIRE( modes.cols() == 3 );
517 REQUIRE( modes.matrix().row( 0 ).norm() == Approx( 1.0 ).margin( 1e-12 ) );
519 const auto *work = workspace.
work;
520 const auto *iWork = workspace.
iWork;
522 REQUIRE( workspace.
work == work );
523 REQUIRE( workspace.
iWork == iWork );
530TEST_CASE(
"calcKLModes rejects invalid covariance geometry",
"[math::eigenLapack]" )
532 Eigen::Array<double, Eigen::Dynamic, Eigen::Dynamic> modes;
534 Eigen::Array<double, Eigen::Dynamic, Eigen::Dynamic> covariance( 2, 2 );
535 covariance.matrix().setIdentity();
536 Eigen::Array<double, Eigen::Dynamic, Eigen::Dynamic> wrongReferenceCount( 3, 3 );
537 wrongReferenceCount.setZero();
540 Eigen::Array<double, Eigen::Dynamic, Eigen::Dynamic> nonSquareCovariance( 2, 3 );
541 nonSquareCovariance.setZero();
542 Eigen::Array<double, Eigen::Dynamic, Eigen::Dynamic> references( 3, 2 );
543 references.setZero();
Interfaces to Lapack and BLAS for Eigen-like arrays.
TEST_CASE("Loading aoAtmosphere config settings", "[ao::analysis::aoAtmosphere]")
Verify parsing and validation of atmosphere configuration settings.
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.
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.
bool isNan(realT value)
Test whether a floating-point value is NaN, including under finite-math-only optimization.
A struct to hold the working memory for eigenSYEVR and maintain it between calls if desired.
MXLAPACK_INT * iSuppZ
LAPACK eigenvector-support workspace owned by this object.
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.
MXLAPACK_INT sizeMinWork
Capacity of the minimum floating-point query workspace.
MXLAPACK_INT sizeMinIWork
Capacity of the minimum integer query workspace.
MXLAPACK_INT * iWork
Optimized integer LAPACK workspace owned by this object.
MXLAPACK_INT * minIWork
Minimum integer workspace used for LAPACK size queries.
MXLAPACK_INT sizeWork
Capacity of the optimized floating-point workspace.
floatT * minWork
Minimum floating-point workspace used for LAPACK size queries.
MXLAPACK_INT sizeIWork
Capacity of the optimized integer workspace.