7#include "../../catch2/catch.hpp"
36matrixT sineFactor( Eigen::Index rows,
39 matrixT factor( rows, cols );
40 const realT scale = std::sqrt( realT( 2 ) /
static_cast<realT
>( rows + 1 ) );
41 const realT
pi = std::acos( realT( -1 ) );
42 for( Eigen::Index row = 0; row < rows; ++row )
44 for( Eigen::Index column = 0; column < cols; ++column )
46 factor( row, column ) = scale * std::sin( pi *
static_cast<realT
>( ( row + 1 ) * ( column + 1 ) ) /
47 static_cast<realT
>( rows + 1 ) );
54matrixT identityMatrix( Eigen::Index size )
56 matrixT identity( size, size );
57 identity.matrix().setIdentity();
62matrixT representedMatrix(
const matrixT &left,
63 const vectorT &values,
64 const matrixT &right )
66 return ( left.matrix() * values.matrix().asDiagonal() * right.matrix().transpose() ).array();
70matrixT retainedRows(
const matrixT &matrix,
71 std::span<const Eigen::Index> deleted )
73 matrixT retained( matrix.rows() -
static_cast<Eigen::Index
>( deleted.size() ), matrix.cols() );
74 Eigen::Index output{ 0 };
75 std::size_t nextDeleted{ 0 };
76 for( Eigen::Index row = 0; row < matrix.rows(); ++row )
78 if( nextDeleted < deleted.size() && row == deleted[nextDeleted] )
83 retained.matrix().row( output++ ) = matrix.matrix().row( row );
89matrixT retainedColumns(
const matrixT &matrix,
90 std::span<const Eigen::Index> deleted )
92 matrixT retained( matrix.rows(), matrix.cols() -
static_cast<Eigen::Index
>( deleted.size() ) );
93 Eigen::Index output{ 0 };
94 std::size_t nextDeleted{ 0 };
95 for( Eigen::Index column = 0; column < matrix.cols(); ++column )
97 if( nextDeleted < deleted.size() && column == deleted[nextDeleted] )
102 retained.matrix().col( output++ ) = matrix.matrix().col( column );
108vectorT directSingularValues(
const matrixT &matrix,
111 Eigen::JacobiSVD<Eigen::Matrix<realT, Eigen::Dynamic, Eigen::Dynamic>> svd( matrix.matrix(),
112 Eigen::ComputeThinU |
113 Eigen::ComputeThinV );
114 vectorT values( rank );
116 const Eigen::Index copyCount = std::min<Eigen::Index>( rank, svd.singularValues().size() );
117 values.matrix().head( copyCount ) = svd.singularValues().head( copyCount );
122matrixT representedCovariance(
const matrixT &preservedFactor,
125 const Eigen::Matrix<realT, Eigen::Dynamic, Eigen::Dynamic> directions =
126 preservedFactor.matrix() * result.
rotation().matrix();
128 directions.transpose() )
133struct deletionComparison
135 realT squaredSingularError{ 0 };
136 realT squaredSingularTolerance{ 0 };
137 realT singularError{ 0 };
138 realT singularTolerance{ 0 };
139 realT covarianceError{ 0 };
140 realT covarianceTolerance{ 0 };
144deletionComparison compareRowResult(
const matrixT &matrix,
145 const matrixT &right,
146 std::span<const Eigen::Index> deleted,
148 realT tolerance = 5e-11 )
150 const matrixT retained = retainedRows( matrix, deleted );
151 const vectorT direct = directSingularValues( retained, result.
baseRank() );
152 const vectorT directSquared = direct.square();
153 const matrixT expected = ( retained.matrix().transpose() * retained.matrix() ).array();
154 const matrixT actual = representedCovariance( right, result );
156 deletionComparison comparison;
157 comparison.squaredSingularError = ( result.
squaredSingularValues() - directSquared ).matrix().norm();
158 comparison.squaredSingularTolerance = tolerance * std::max<realT>( 1, directSquared.matrix().norm() );
159 comparison.singularError = ( result.
singularValues() - direct ).matrix().norm();
160 comparison.singularTolerance = realT( 16 ) * std::sqrt( std::numeric_limits<realT>::epsilon() ) *
161 std::max<realT>( 1, direct.size() > 0 ? direct( 0 ) : realT( 0 ) );
162 comparison.covarianceError = ( actual - expected ).matrix().norm();
163 comparison.covarianceTolerance = tolerance * std::max<realT>( 1, expected.matrix().norm() );
168deletionComparison compareColumnResult(
const matrixT &matrix,
170 std::span<const Eigen::Index> deleted,
172 realT tolerance = 5e-11 )
174 const matrixT retained = retainedColumns( matrix, deleted );
175 const vectorT direct = directSingularValues( retained, result.
baseRank() );
176 const vectorT directSquared = direct.square();
177 const matrixT expected = ( retained.matrix() * retained.matrix().transpose() ).array();
178 const matrixT actual = representedCovariance( left, result );
180 deletionComparison comparison;
181 comparison.squaredSingularError = ( result.
squaredSingularValues() - directSquared ).matrix().norm();
182 comparison.squaredSingularTolerance = tolerance * std::max<realT>( 1, directSquared.matrix().norm() );
183 comparison.singularError = ( result.
singularValues() - direct ).matrix().norm();
184 comparison.singularTolerance = realT( 16 ) * std::sqrt( std::numeric_limits<realT>::epsilon() ) *
185 std::max<realT>( 1, direct.size() > 0 ? direct( 0 ) : realT( 0 ) );
186 comparison.covarianceError = ( actual - expected ).matrix().norm();
187 comparison.covarianceTolerance = tolerance * std::max<realT>( 1, expected.matrix().norm() );
191mx::math::detail::svdDeletionTestOperation failingOperation =
192 mx::math::detail::svdDeletionTestOperation::prepareWorkspace;
195enum class solverHookMode
215solverHookMode syevrMode = solverHookMode::production;
216solverHookMode gesvdMode = solverHookMode::production;
217solverHookMode laed9Mode = solverHookMode::production;
220void throwAllocation( mx::math::detail::svdDeletionTestOperation operation )
222 if( operation == failingOperation )
224 throw std::bad_alloc();
229void throwLengthError( mx::math::detail::svdDeletionTestOperation operation )
231 if( operation == failingOperation )
233 throw std::length_error(
"injected SVD deletion storage length" );
238MXLAPACK_INT syevrHook(
char jobz,
246 MXLAPACK_INT indexLow,
247 MXLAPACK_INT indexHigh,
253 MXLAPACK_INT *support,
256 MXLAPACK_INT *integerWork,
257 MXLAPACK_INT integerWorkSize )
259 static_cast<void>( jobz );
260 static_cast<void>( range );
261 static_cast<void>( uplo );
262 static_cast<void>( matrix );
263 static_cast<void>( lda );
264 static_cast<void>( valueLower );
265 static_cast<void>( valueUpper );
266 static_cast<void>( indexLow );
267 static_cast<void>( indexHigh );
268 static_cast<void>( tolerance );
269 static_cast<void>( support );
271 if( lwork == -1 || integerWorkSize == -1 )
273 if( syevrMode == solverHookMode::queryFailure )
277 work[0] = syevrMode == solverHookMode::invalidQuery ? realT( 0 ) : realT( std::max( 1, 32 * n ) );
278 integerWork[0] = syevrMode == solverHookMode::invalidIntegerQuery ? 0 : std::max<MXLAPACK_INT>( 1, 10 * n );
281 if( syevrMode == solverHookMode::solveFailure )
286 *found = syevrMode == solverHookMode::countMismatch ? std::max<MXLAPACK_INT>( 0, n - 1 ) : n;
287 for( MXLAPACK_INT column = 0; column < n; ++column )
289 eigenvalues[column] =
static_cast<realT
>( column + 1 );
290 for( MXLAPACK_INT row = 0; row < n; ++row )
292 eigenvectors[row + column * ldz] = row == column ? realT( 1 ) : realT( 0 );
295 if( syevrMode == solverHookMode::nonFiniteValue )
297 eigenvalues[0] = std::numeric_limits<realT>::infinity();
299 else if( syevrMode == solverHookMode::nonFiniteVector )
301 eigenvectors[0] = std::numeric_limits<realT>::infinity();
303 else if( syevrMode == solverHookMode::invalidOrdering && n > 1 )
305 eigenvalues[0] = realT( 2 );
306 eigenvalues[1] = realT( 1 );
308 else if( syevrMode == solverHookMode::roundoffClamp )
310 eigenvalues[0] = -std::numeric_limits<realT>::epsilon();
312 else if( syevrMode == solverHookMode::indefinite )
314 eigenvalues[0] = realT( -0.25 );
320MXLAPACK_INT gesvdHook(
char jobu,
329 realT *rightTranspose,
334 static_cast<void>( jobu );
335 static_cast<void>( jobvt );
336 static_cast<void>( rows );
337 static_cast<void>( matrix );
338 static_cast<void>( lda );
339 static_cast<void>( left );
340 static_cast<void>( ldu );
344 if( gesvdMode == solverHookMode::queryFailure )
348 work[0] = gesvdMode == solverHookMode::invalidQuery ? realT( 0 ) : realT( std::max( 1, 5 * cols ) );
351 if( gesvdMode == solverHookMode::solveFailure )
356 for( MXLAPACK_INT index = 0; index < cols; ++index )
358 singular[index] =
static_cast<realT
>( cols - index );
359 for( MXLAPACK_INT row = 0; row < cols; ++row )
361 rightTranspose[row + index * ldvt] = row == index ? realT( 1 ) : realT( 0 );
364 if( gesvdMode == solverHookMode::nonFiniteValue )
366 singular[0] = std::numeric_limits<realT>::infinity();
368 else if( gesvdMode == solverHookMode::nonFiniteVector )
370 rightTranspose[0] = std::numeric_limits<realT>::infinity();
372 else if( gesvdMode == solverHookMode::invalidOrdering && cols > 1 )
374 singular[0] = realT( 1 );
375 singular[1] = realT( 2 );
377 else if( gesvdMode == solverHookMode::negativeSpectrum )
379 singular[cols - 1] = realT( -1 );
381 else if( gesvdMode == solverHookMode::tinySpectrum )
383 singular[0] = realT( 1e-200 );
389MXLAPACK_INT laed9Hook( realT *eigenvalues,
393 MXLAPACK_INT leadingDimension,
398 static_cast<void>( delta );
399 static_cast<void>( rho );
400 static_cast<void>( update );
402 if( laed9Mode == solverHookMode::solveFailure )
407 for( MXLAPACK_INT column = 0; column < rank; ++column )
409 eigenvalues[column] = poles[column];
410 for( MXLAPACK_INT row = 0; row < rank; ++row )
412 eigenvectors[row + column * leadingDimension] = row == column ? realT( 1 ) : realT( 0 );
415 if( laed9Mode == solverHookMode::nonFiniteValue )
417 eigenvalues[0] = std::numeric_limits<realT>::infinity();
419 else if( laed9Mode == solverHookMode::nonFiniteVector )
421 eigenvectors[0] = std::numeric_limits<realT>::infinity();
423 else if( laed9Mode == solverHookMode::invalidOrdering && rank > 1 )
425 eigenvalues[0] = realT( 1 );
426 eigenvalues[1] = realT( 0 );
428 else if( laed9Mode == solverHookMode::outsideInterlacing )
430 eigenvalues[0] = poles[0] - realT( 1 );
432 else if( laed9Mode == solverHookMode::invalidVectorNorm )
434 eigenvectors[0] = realT( 2 );
436 else if( laed9Mode == solverHookMode::roundoffClamp )
438 eigenvalues[rank - 1] = std::numeric_limits<realT>::epsilon();
450 mx::math::detail::svdDeletionHooks<double>() = {};
451 mx::math::detail::svdDeletionHooks<float>() = {};
452 syevrMode = solverHookMode::production;
453 gesvdMode = solverHookMode::production;
454 laed9Mode = solverHookMode::production;
460 mx::math::detail::svdDeletionHooks<double>() = {};
461 mx::math::detail::svdDeletionHooks<float>() = {};
462 syevrMode = solverHookMode::production;
463 gesvdMode = solverHookMode::production;
464 laed9Mode = solverHookMode::production;
471namespace unitTest::math_svdDowndate_test
479TEST_CASE(
"SVD row deletion matches direct full SVDs",
"[math::svdDowndate][rows]" )
481 for(
const auto [rows, cols, deleted] :
482 std::vector<std::tuple<Eigen::Index, Eigen::Index, std::vector<Eigen::Index>>>{ { 7, 4, { 0, 5 } },
485 { 9, 3, { 0, 2, 4, 6 } } } )
487 const Eigen::Index rank = std::min( rows, cols );
488 const matrixT left = sineFactor( rows, rank );
489 const matrixT right = sineFactor( cols, rank );
490 vectorT singular( rank );
491 for( Eigen::Index index = 0; index < rank; ++index )
493 singular( index ) = realT( 9 - 2 * index ) + realT( 0.25 * index );
495 const matrixT matrix = representedMatrix( left, singular, right );
497 for(
const backendT backend : { backendT::leadingCovariance, backendT::stableCore } )
499 CAPTURE( rows, cols, deleted );
505 REQUIRE( result.
backend() == backend );
506 const deletionComparison comparison = compareRowResult( matrix, right, deleted, result );
507 REQUIRE( comparison.squaredSingularError <= comparison.squaredSingularTolerance );
508 REQUIRE( comparison.singularError <= comparison.singularTolerance );
509 REQUIRE( comparison.covarianceError <= comparison.covarianceTolerance );
519TEST_CASE(
"SVD column deletion matches direct full SVDs",
"[math::svdDowndate][columns]" )
521 for(
const auto [rows, cols, deleted] :
522 std::vector<std::tuple<Eigen::Index, Eigen::Index, std::vector<Eigen::Index>>>{ { 7, 4, { 1 } },
524 { 5, 5, { 1, 3 } } } )
526 const Eigen::Index rank = std::min( rows, cols );
527 const matrixT left = sineFactor( rows, rank );
528 const matrixT right = sineFactor( cols, rank );
529 vectorT singular( rank );
530 for( Eigen::Index index = 0; index < rank; ++index )
532 singular( index ) = realT( 11 - 2 * index ) + realT( 0.125 * index );
534 const matrixT matrix = representedMatrix( left, singular, right );
536 for(
const backendT backend : { backendT::leadingCovariance, backendT::stableCore } )
538 CAPTURE( rows, cols, deleted );
542 const statusT status =
545 const deletionComparison comparison = compareColumnResult( matrix, left, deleted, result );
546 REQUIRE( comparison.squaredSingularError <= comparison.squaredSingularTolerance );
547 REQUIRE( comparison.singularError <= comparison.singularTolerance );
548 REQUIRE( comparison.covarianceError <= comparison.covarianceTolerance );
558TEST_CASE(
"SVD deletion core entry points agree",
"[math::svdDowndate][core]" )
560 const matrixT left = sineFactor( 7, 4 );
561 const matrixT right = sineFactor( 5, 4 );
562 vectorT singular( 4 );
563 singular << 10, 6, 3, 0.5;
564 const matrixT matrix = representedMatrix( left, singular, right );
565 const std::vector<Eigen::Index> deleted{ 1, 5 };
566 matrixT deletedRows( deleted.size(), left.cols() );
567 for( Eigen::Index row = 0; row < deletedRows.rows(); ++row )
569 deletedRows.matrix().row( row ) = left.matrix().row( deleted[row] );
576 const deletionComparison leadingComparison = compareRowResult( matrix, right, deleted, leadingResult );
577 REQUIRE( leadingComparison.squaredSingularError <= leadingComparison.squaredSingularTolerance );
578 REQUIRE( leadingComparison.singularError <= leadingComparison.singularTolerance );
579 REQUIRE( leadingComparison.covarianceError <= leadingComparison.covarianceTolerance );
585 const deletionComparison stableComparison = compareRowResult( matrix, right, deleted, stableResult );
586 REQUIRE( stableComparison.squaredSingularError <= stableComparison.squaredSingularTolerance );
587 REQUIRE( stableComparison.singularError <= stableComparison.singularTolerance );
588 REQUIRE( stableComparison.covarianceError <= stableComparison.covarianceTolerance );
596 backendT::stableCore ) ) );
602 REQUIRE( dispatchResult.
rotation().cols() == 3 );
604 const matrixT retained = retainedRows( matrix, deleted );
605 Eigen::JacobiSVD<Eigen::Matrix<realT, Eigen::Dynamic, Eigen::Dynamic>> directSvd( retained.matrix(),
606 Eigen::ComputeThinV );
607 const Eigen::Matrix<realT, Eigen::Dynamic, Eigen::Dynamic> directDirections = directSvd.matrixV().leftCols( 3 );
608 const Eigen::Matrix<realT, Eigen::Dynamic, Eigen::Dynamic> updatedDirections =
609 right.matrix() * dispatchResult.
rotation().matrix();
610 const Eigen::Matrix<realT, Eigen::Dynamic, Eigen::Dynamic> directProjector =
611 directDirections * directDirections.transpose();
612 const Eigen::Matrix<realT, Eigen::Dynamic, Eigen::Dynamic> updatedProjector =
613 updatedDirections * updatedDirections.transpose();
614 REQUIRE( ( updatedProjector - directProjector ).norm() < 1e-10 );
623TEST_CASE(
"Rank-one secular SVD deletion matches direct row and column SVDs",
"[math::svdDowndate][rankOneSecular]" )
625 const Eigen::Index rank{ 4 };
626 const matrixT left = sineFactor( 9, rank );
627 const matrixT right = sineFactor( 7, rank );
628 vectorT singular( rank );
629 singular << 11, 6, 2.5, 0.75;
630 const matrixT matrix = representedMatrix( left, singular, right );
632 SECTION(
"row deletion" )
634 const std::vector<Eigen::Index> deleted{ 4 };
640 REQUIRE( result.
backend() == backendT::rankOneSecular );
641 const deletionComparison comparison = compareRowResult( matrix, right, deleted, result );
642 REQUIRE( comparison.squaredSingularError <= comparison.squaredSingularTolerance );
643 REQUIRE( comparison.singularError <= comparison.singularTolerance );
644 REQUIRE( comparison.covarianceError <= comparison.covarianceTolerance );
647 SECTION(
"column deletion" )
649 const std::vector<Eigen::Index> deleted{ 2 };
655 REQUIRE( result.
backend() == backendT::rankOneSecular );
656 const deletionComparison comparison = compareColumnResult( matrix, left, deleted, result );
657 REQUIRE( comparison.squaredSingularError <= comparison.squaredSingularTolerance );
658 REQUIRE( comparison.singularError <= comparison.singularTolerance );
659 REQUIRE( comparison.covarianceError <= comparison.covarianceTolerance );
662 SECTION(
"leading output prefix" )
664 constexpr Eigen::Index outputRank{ 2 };
665 const std::vector<Eigen::Index> deleted{ 4 };
666 const matrixT retained = retainedRows( matrix, deleted );
667 Eigen::JacobiSVD<Eigen::Matrix<realT, Eigen::Dynamic, Eigen::Dynamic>> direct( retained.matrix(),
668 Eigen::ComputeThinV );
678 backendT::rankOneSecular ) == statusT::success );
680 REQUIRE( result.
rotation().rows() == rank );
681 REQUIRE( result.
rotation().cols() == outputRank );
683 REQUIRE( ( result.
singularValues().head( outputRank ).matrix() - direct.singularValues().head( outputRank ) )
686 const Eigen::Matrix<realT, Eigen::Dynamic, Eigen::Dynamic> expectedDirections =
687 direct.matrixV().leftCols( outputRank );
688 const Eigen::Matrix<realT, Eigen::Dynamic, Eigen::Dynamic> actualDirections =
689 right.matrix() * result.
rotation().matrix();
690 const Eigen::Matrix<realT, Eigen::Dynamic, Eigen::Dynamic> expectedProjector =
691 expectedDirections * expectedDirections.transpose();
692 const Eigen::Matrix<realT, Eigen::Dynamic, Eigen::Dynamic> actualProjector =
693 actualDirections * actualDirections.transpose();
694 REQUIRE( ( actualProjector - expectedProjector ).norm() < 5e-10 );
704TEST_CASE(
"Rank-one secular SVD deletion handles deflation and leverage edge cases",
705 "[math::svdDowndate][rankOneSecular][conditioning]" )
707 SECTION(
"scalar secular system" )
709 matrixT left( 2, 1 );
710 left << std::sqrt( 0.5 ), std::sqrt( 0.5 );
711 vectorT singular( 1 );
712 singular << std::sqrt( 2.0 );
713 const std::vector<Eigen::Index> deleted{ 0 };
719 REQUIRE( result.
singularValues()( 0 ) == Approx( 1.0 ).epsilon( 1e-12 ) );
721 REQUIRE( std::abs( result.
rotation()( 0, 0 ) ) == Approx( 1.0 ).epsilon( 1e-12 ) );
724 SECTION(
"exact repeated spectrum" )
726 const matrixT left = sineFactor( 8, 4 );
727 const matrixT right = sineFactor( 6, 4 );
728 vectorT singular( 4 );
729 singular << 9, 9, 3, 3;
730 const matrixT matrix = representedMatrix( left, singular, right );
731 const std::vector<Eigen::Index> deleted{ 3 };
737 const deletionComparison comparison = compareRowResult( matrix, right, deleted, result, 2e-10 );
738 REQUIRE( comparison.squaredSingularError <= comparison.squaredSingularTolerance );
739 REQUIRE( comparison.singularError <= comparison.singularTolerance );
740 REQUIRE( comparison.covarianceError <= comparison.covarianceTolerance );
743 SECTION(
"clustered spectrum" )
745 const matrixT left = sineFactor( 9, 4 );
746 const matrixT right = sineFactor( 7, 4 );
747 const realT spacing = realT( 8 ) * std::numeric_limits<realT>::epsilon();
748 vectorT singular( 4 );
749 singular << 10, 10 * ( 1 - spacing ), 2, 2 * ( 1 - spacing );
750 const matrixT matrix = representedMatrix( left, singular, right );
751 const std::vector<Eigen::Index> deleted{ 5 };
757 const deletionComparison comparison = compareRowResult( matrix, right, deleted, result, 5e-10 );
758 REQUIRE( comparison.squaredSingularError <= comparison.squaredSingularTolerance );
759 REQUIRE( comparison.singularError <= comparison.singularTolerance );
760 REQUIRE( comparison.covarianceError <= comparison.covarianceTolerance );
763 SECTION(
"zero singular spectrum" )
765 const matrixT left = sineFactor( 8, 4 );
766 const matrixT right = sineFactor( 6, 4 );
767 vectorT singular( 4 );
768 singular << 8, 3, 0, 0;
769 const matrixT matrix = representedMatrix( left, singular, right );
770 const std::vector<Eigen::Index> deleted{ 2 };
776 const deletionComparison comparison = compareRowResult( matrix, right, deleted, result, 2e-10 );
777 REQUIRE( comparison.squaredSingularError <= comparison.squaredSingularTolerance );
778 REQUIRE( comparison.singularError <= comparison.singularTolerance );
779 REQUIRE( comparison.covarianceError <= comparison.covarianceTolerance );
782 SECTION(
"zero leverage" )
784 matrixT left( 5, 4 );
786 left.matrix().topRows( 4 ).setIdentity();
787 const matrixT right = sineFactor( 6, 4 );
788 vectorT singular( 4 );
789 singular << 8, 4, 2, 1;
790 const matrixT matrix = representedMatrix( left, singular, right );
791 const std::vector<Eigen::Index> deleted{ 4 };
797 const deletionComparison comparison = compareRowResult( matrix, right, deleted, result );
798 REQUIRE( comparison.squaredSingularError <= comparison.squaredSingularTolerance );
799 REQUIRE( comparison.singularError <= comparison.singularTolerance );
800 REQUIRE( comparison.covarianceError <= comparison.covarianceTolerance );
803 SECTION(
"high leverage" )
805 matrixT left( 6, 3 );
807 const realT residualLeverage = 1e-10;
808 left( 0, 0 ) = std::sqrt( 1 - residualLeverage );
809 left( 3, 0 ) = std::sqrt( residualLeverage );
810 left( 1, 1 ) = std::sqrt( 0.75 );
812 left( 2, 2 ) = std::sqrt( 0.6 );
813 left( 5, 2 ) = std::sqrt( 0.4 );
814 const matrixT right = sineFactor( 5, 3 );
815 vectorT singular( 3 );
816 singular << 10, 4, 1;
817 const matrixT matrix = representedMatrix( left, singular, right );
818 const std::vector<Eigen::Index> deleted{ 0 };
825 const deletionComparison comparison = compareRowResult( matrix, right, deleted, result, 5e-10 );
826 REQUIRE( comparison.squaredSingularError <= comparison.squaredSingularTolerance );
827 REQUIRE( comparison.singularError <= comparison.singularTolerance );
828 REQUIRE( comparison.covarianceError <= comparison.covarianceTolerance );
838TEST_CASE(
"Rank-one secular SVD deletion supports float and enforces its deletion contract",
839 "[math::svdDowndate][rankOneSecular][float][errors]" )
844 "unsupportedDeletionCount" );
846 SECTION(
"float row deletion" )
850 const floatMatrixT left = sineFactor( 7, 3 ).cast<
float>();
851 const floatMatrixT right = sineFactor( 5, 3 ).cast<
float>();
852 floatVectorT singular( 3 );
853 singular << 7, 3, 0.5F;
854 const Eigen::MatrixXf matrix = left.matrix() * singular.matrix().asDiagonal() * right.matrix().transpose();
855 Eigen::MatrixXf retained( 6, 5 );
856 retained.topRows( 2 ) = matrix.topRows( 2 );
857 retained.bottomRows( 4 ) = matrix.bottomRows( 4 );
858 Eigen::JacobiSVD<Eigen::MatrixXf> direct( retained, Eigen::ComputeThinV );
859 const std::vector<Eigen::Index> deleted{ 2 };
865 REQUIRE( result.
backend() == backendT::rankOneSecular );
866 REQUIRE( ( result.
singularValues().matrix() - direct.singularValues().head( 3 ) ).norm() < 5e-4F );
869 SECTION(
"empty deletion is identity" )
871 vectorT singular( 3 );
872 singular << 7, 2, 0.5;
873 const matrixT factor = identityMatrix( 3 );
874 const std::vector<Eigen::Index> deleted;
878 REQUIRE(
mx::math::svdRemoveRows( result, singular, factor, deleted, 2, workspace, backendT::rankOneSecular ) ==
880 REQUIRE( result.
backend() == backendT::rankOneSecular );
882 REQUIRE( ( result.
singularValues() - singular ).matrix().norm() == Approx( 0.0 ) );
883 REQUIRE( ( result.
rotation() - identityMatrix( 3 ).leftCols( 2 ) ).matrix().norm() == Approx( 0.0 ) );
887 SECTION(
"direct empty deletion is identity" )
889 vectorT singular( 3 );
890 singular << 7, 2, 0.5;
891 matrixT deletedRows( 0, 3 );
897 REQUIRE( result.
backend() == backendT::rankOneSecular );
898 REQUIRE( ( result.
singularValues() - singular ).matrix().norm() == Approx( 0.0 ) );
899 REQUIRE( ( result.
rotation() - identityMatrix( 3 ).leftCols( 2 ) ).matrix().norm() == Approx( 0.0 ) );
902 SECTION(
"zero spectrum returns an arbitrary identity basis" )
904 vectorT singular = vectorT::Zero( 2 );
905 matrixT deletedRows( 1, 2 );
906 deletedRows << 0.25, 0.5;
912 REQUIRE( result.
singularValues().matrix().norm() == Approx( 0.0 ) );
913 REQUIRE( ( result.
rotation() - identityMatrix( 2 ) ).matrix().norm() == Approx( 0.0 ) );
916 SECTION(
"tiny update is treated as identity" )
918 vectorT singular( 2 );
920 matrixT deletedRows( 1, 2 );
921 deletedRows << 1e-200, 0;
927 REQUIRE( ( result.
singularValues() - singular ).matrix().norm() == Approx( 0.0 ) );
928 REQUIRE( ( result.
rotation() - identityMatrix( 2 ) ).matrix().norm() == Approx( 0.0 ) );
931 SECTION(
"invalid direct core shape is rejected" )
933 vectorT singular( 2 );
935 matrixT deletedRows( 1, 1 );
941 statusT::invalidInput );
942 REQUIRE( result.
status() == statusT::invalidInput );
945 SECTION(
"result preparation failure is propagated" )
947 vectorT singular( 2 );
949 matrixT deletedRows( 1, 2 );
950 deletedRows << 0.25, 0.1;
954 failingOperation = mx::math::detail::svdDeletionTestOperation::prepareResult;
955 mx::math::detail::svdDeletionHooks<double>().operation = throwAllocation;
957 statusT::allocationFailure );
958 REQUIRE( result.
status() == statusT::allocationFailure );
961 SECTION(
"multiple deletions are rejected" )
963 const matrixT factor = sineFactor( 6, 3 );
964 vectorT singular( 3 );
966 const std::vector<Eigen::Index> deleted{ 1, 4 };
967 matrixT deletedRows( 2, 3 );
968 deletedRows.matrix().row( 0 ) = factor.matrix().row( 1 );
969 deletedRows.matrix().row( 1 ) = factor.matrix().row( 4 );
973 REQUIRE( workspace.
prepare( 3, 2, backendT::rankOneSecular ) == statusT::unsupportedDeletionCount );
976 statusT::unsupportedDeletionCount );
977 REQUIRE( result.
status() == statusT::unsupportedDeletionCount );
978 REQUIRE(
mx::math::svdRemoveRows( result, singular, factor, deleted, 3, workspace, backendT::rankOneSecular ) ==
979 statusT::unsupportedDeletionCount );
980 REQUIRE( result.
status() == statusT::unsupportedDeletionCount );
983 SECTION(
"finite oversized update is rejected before squaring" )
985 vectorT singular( 2 );
987 matrixT deletedRows( 1, 2 );
988 deletedRows << std::numeric_limits<realT>::max() / 4, std::numeric_limits<realT>::max() / 4;
993 statusT::invalidInput );
994 REQUIRE( result.
status() == statusT::invalidInput );
997 SECTION(
"materially indefinite core is rejected" )
999 vectorT singular( 2 );
1001 matrixT deletedRows( 1, 2 );
1002 deletedRows << 2, 0;
1007 statusT::nonPositiveSemidefinite );
1011 SECTION(
"finite result that cannot be rescaled is rejected" )
1013 const realT scale = realT( 2 ) * std::sqrt( std::numeric_limits<realT>::max() );
1014 vectorT singular( 2 );
1015 singular << scale, scale / realT( 2 );
1016 matrixT deletedRows( 1, 2 );
1017 deletedRows << 0.25, 0;
1022 statusT::rescalingOverflow );
1023 REQUIRE( result.
status() == statusT::rescalingOverflow );
1033TEST_CASE(
"Rank-one secular SVD deletion reports solver failures",
1034 "[math::svdDowndate][rankOneSecular][errors][solver]" )
1037 vectorT singular( 3 );
1038 singular << 8, 4, 2;
1039 matrixT deleted( 1, 3 );
1040 deleted << 0.25, 0.2, 0.1;
1042 SECTION(
"LAED9 solve failure" )
1046 laed9Mode = solverHookMode::solveFailure;
1047 mx::math::detail::svdDeletionHooks<double>().laed9 = laed9Hook;
1049 statusT::solverFailure );
1053 SECTION(
"LAED9 non-finite values and vectors" )
1055 for(
const solverHookMode mode : { solverHookMode::nonFiniteValue, solverHookMode::nonFiniteVector } )
1057 CAPTURE(
static_cast<int>( mode ) );
1061 mx::math::detail::svdDeletionHooks<double>().laed9 = laed9Hook;
1063 statusT::nonFiniteOutput );
1067 SECTION(
"LAED9 invalid ordering" )
1071 laed9Mode = solverHookMode::invalidOrdering;
1072 mx::math::detail::svdDeletionHooks<double>().laed9 = laed9Hook;
1074 statusT::invalidSolverOutput );
1077 SECTION(
"LAED9 result outside secular interlacing bounds" )
1081 laed9Mode = solverHookMode::outsideInterlacing;
1082 mx::math::detail::svdDeletionHooks<double>().laed9 = laed9Hook;
1084 statusT::invalidSolverOutput );
1087 SECTION(
"LAED9 non-unit eigenvector" )
1091 laed9Mode = solverHookMode::invalidVectorNorm;
1092 mx::math::detail::svdDeletionHooks<double>().laed9 = laed9Hook;
1094 statusT::invalidSolverOutput );
1097 SECTION(
"LAED9 eigenvector with a large matrix-free residual" )
1101 laed9Mode = solverHookMode::invalidResidual;
1102 mx::math::detail::svdDeletionHooks<double>().laed9 = laed9Hook;
1104 statusT::invalidSolverOutput );
1107 SECTION(
"small positive roundoff eigenvalue is clamped" )
1109 vectorT clampSingular( 2 );
1110 clampSingular << 1, 0.5;
1111 matrixT clampDeleted( 1, 2 );
1112 clampDeleted << 1, 0;
1115 laed9Mode = solverHookMode::roundoffClamp;
1116 mx::math::detail::svdDeletionHooks<double>().laed9 = laed9Hook;
1120 statusT::successWithClamping );
1132TEST_CASE(
"SVD deletion preserves the projected-factor contract",
"[math::svdDowndate][projected]" )
1134 const matrixT fullLeft = sineFactor( 7, 5 );
1135 const matrixT fullRight = sineFactor( 5, 5 );
1136 vectorT fullSingular( 5 );
1137 fullSingular << 10, 8, 6, 5, 4;
1138 const matrixT full = representedMatrix( fullLeft, fullSingular, fullRight );
1140 const Eigen::Index rank = 3;
1141 const matrixT left = fullLeft.matrix().leftCols( rank ).array();
1142 const matrixT right = fullRight.matrix().leftCols( rank ).array();
1143 const vectorT singular = fullSingular.head( rank );
1144 const matrixT projected = representedMatrix( left, singular, right );
1146 const std::vector<Eigen::Index> deletedRows{ 1, 5 };
1150 mx::math::svdRemoveRows( rowResult, singular, left, deletedRows, rank, rowWorkspace, backendT::stableCore ) ) );
1151 const deletionComparison rowComparison = compareRowResult( projected, right, deletedRows, rowResult );
1152 REQUIRE( rowComparison.squaredSingularError <= rowComparison.squaredSingularTolerance );
1153 REQUIRE( rowComparison.singularError <= rowComparison.singularTolerance );
1154 REQUIRE( rowComparison.covarianceError <= rowComparison.covarianceTolerance );
1155 const vectorT projectedRowValues = directSingularValues( retainedRows( projected, deletedRows ), rank );
1156 const vectorT fullRowValues = directSingularValues( retainedRows( full, deletedRows ), rank );
1157 REQUIRE( ( projectedRowValues - fullRowValues ).matrix().norm() > 0.1 );
1159 const std::vector<Eigen::Index> deletedColumns{ 0, 4 };
1168 backendT::leadingCovariance ) ) );
1169 const deletionComparison columnComparison = compareColumnResult( projected, left, deletedColumns, columnResult );
1170 REQUIRE( columnComparison.squaredSingularError <= columnComparison.squaredSingularTolerance );
1171 REQUIRE( columnComparison.singularError <= columnComparison.singularTolerance );
1172 REQUIRE( columnComparison.covarianceError <= columnComparison.covarianceTolerance );
1173 const vectorT projectedColumnValues = directSingularValues( retainedColumns( projected, deletedColumns ), rank );
1174 const vectorT fullColumnValues = directSingularValues( retainedColumns( full, deletedColumns ), rank );
1175 REQUIRE( ( projectedColumnValues - fullColumnValues ).matrix().norm() > 0.1 );
1184TEST_CASE(
"SVD deletion handles repeated and high-leverage systems",
"[math::svdDowndate][conditioning]" )
1186 SECTION(
"repeated spectrum" )
1188 const matrixT left = sineFactor( 8, 4 );
1189 const matrixT right = sineFactor( 6, 4 );
1190 vectorT singular( 4 );
1191 singular << 9, 9, 3, 3;
1192 const matrixT matrix = representedMatrix( left, singular, right );
1193 const std::vector<Eigen::Index> deleted{ 0, 4, 7 };
1195 for(
const backendT backend : { backendT::leadingCovariance, backendT::stableCore } )
1202 const deletionComparison comparison = compareRowResult( matrix, right, deleted, result );
1203 REQUIRE( comparison.squaredSingularError <= comparison.squaredSingularTolerance );
1204 REQUIRE( comparison.singularError <= comparison.singularTolerance );
1205 REQUIRE( comparison.covarianceError <= comparison.covarianceTolerance );
1209 SECTION(
"high leverage" )
1211 matrixT left( 6, 3 );
1213 const realT residualLeverage = 1e-8;
1214 left( 0, 0 ) = std::sqrt( 1 - residualLeverage );
1215 left( 3, 0 ) = std::sqrt( residualLeverage );
1216 left( 1, 1 ) = std::sqrt( 0.75 );
1218 left( 2, 2 ) = std::sqrt( 0.6 );
1219 left( 5, 2 ) = std::sqrt( 0.4 );
1220 const matrixT right = sineFactor( 5, 3 );
1221 vectorT singular( 3 );
1222 singular << 10, 4, 1;
1223 const matrixT matrix = representedMatrix( left, singular, right );
1224 const std::vector<Eigen::Index> deleted{ 0, 1 };
1227 for(
const backendT backend : { backendT::leadingCovariance, backendT::stableCore } )
1234 const deletionComparison comparison = compareRowResult( matrix, right, deleted, result, 2e-10 );
1235 REQUIRE( comparison.squaredSingularError <= comparison.squaredSingularTolerance );
1236 REQUIRE( comparison.singularError <= comparison.singularTolerance );
1237 REQUIRE( comparison.covarianceError <= comparison.covarianceTolerance );
1241 SECTION(
"one-row deletion" )
1243 matrixT left( 2, 1 );
1244 left << std::sqrt( 0.5 ), std::sqrt( 0.5 );
1245 vectorT singular( 1 );
1246 singular << std::sqrt( 2.0 );
1247 const std::vector<Eigen::Index> deleted{ 0 };
1249 for(
const backendT backend : { backendT::leadingCovariance, backendT::stableCore } )
1255 REQUIRE( result.
singularValues()( 0 ) == Approx( 1.0 ).epsilon( 1e-12 ) );
1259 const realT literalLongMalesValue = singular( 0 ) * ( realT( 1 ) - left( 0, 0 ) * left( 0, 0 ) );
1260 REQUIRE( literalLongMalesValue == Approx( realT( 1 ) / std::sqrt( realT( 2 ) ) ).epsilon( 1e-12 ) );
1261 REQUIRE( std::abs( literalLongMalesValue - realT( 1 ) ) > 0.25 );
1264 SECTION(
"square deleted-side factor" )
1266 const matrixT left = identityMatrix( 2 );
1267 vectorT singular( 2 );
1269 const std::vector<Eigen::Index> deleted{ 0 };
1270 matrixT literalLongMalesCore = identityMatrix( 2 );
1271 literalLongMalesCore.matrix().noalias() -= left.matrix().row( 0 ).transpose() * left.matrix().row( 0 );
1272 literalLongMalesCore.matrix() = singular.matrix().asDiagonal() * literalLongMalesCore.matrix();
1273 const vectorT literalLongMalesValues = directSingularValues( literalLongMalesCore, 2 );
1279 REQUIRE( ( result.
singularValues() - literalLongMalesValues ).matrix().norm() < 1e-12 );
1288TEST_CASE(
"SVD deletion is invariant across finite scales",
"[math::svdDowndate][scaling]" )
1290 const matrixT left = sineFactor( 7, 3 );
1291 vectorT baseSingular( 3 );
1292 baseSingular << 8, 3, 1;
1293 const std::vector<Eigen::Index> deleted{ 1, 5 };
1295 for(
const backendT backend : { backendT::leadingCovariance, backendT::stableCore } )
1302 for(
const realT scale : { realT( 1e140 ), realT( 1e-140 ) } )
1304 const vectorT scaledSingular = baseSingular * scale;
1309 for( Eigen::Index index = 0; index < 3; ++index )
1312 Approx( reference.
singularValues()( index ) * scale ).epsilon( 5e-11 ) );
1326TEST_CASE(
"SVD deletion handles structural and numerical edge cases",
"[math::svdDowndate][rank]" )
1328 matrixT left( 3, 2 );
1329 left << 1, 0, 0, std::sqrt( 0.5 ), 0, std::sqrt( 0.5 );
1330 const matrixT right = identityMatrix( 2 );
1331 vectorT singular( 2 );
1333 const std::vector<Eigen::Index> deleted{ 0 };
1336 matrixT invalidFactor = left;
1337 invalidFactor( 1, 1 ) *= 2;
1342 for(
const backendT backend : { backendT::leadingCovariance, backendT::stableCore } )
1350 const std::vector<Eigen::Index> noDeletion;
1353 REQUIRE( ( result.
rotation() - identityMatrix( 2 ) ).matrix().norm() == Approx( 0.0 ) );
1356 matrixT noDeletedRows( 0, 2 );
1362 vectorT zeroSingular( 2 );
1363 zeroSingular << 5, 0;
1364 matrixT deletedRow( 1, 2 );
1365 deletedRow << std::sqrt( 0.5 ), std::sqrt( 0.5 );
1368 REQUIRE( result.
singularValues()( 1 ) == Approx( 0.0 ).margin( 1e-13 ) );
1370 vectorT allZero( 2 );
1372 for(
const backendT backend : { backendT::leadingCovariance, backendT::stableCore } )
1376 REQUIRE( result.
singularValues().matrix().norm() == Approx( 0.0 ) );
1377 REQUIRE( ( result.
rotation() - identityMatrix( 2 ) ).matrix().norm() == Approx( 0.0 ) );
1380 matrixT zeroLeverageFactor( 3, 2 );
1381 zeroLeverageFactor << 1, 0, 0, 1, 0, 0;
1382 vectorT zeroLeverageSingular( 2 );
1383 zeroLeverageSingular << 2, 1;
1384 const matrixT zeroLeverageMatrix =
1385 representedMatrix( zeroLeverageFactor, zeroLeverageSingular, identityMatrix( 2 ) );
1386 const std::vector<Eigen::Index> zeroLeverageDeletion{ 2 };
1387 for(
const backendT backend : { backendT::leadingCovariance, backendT::stableCore } )
1393 zeroLeverageSingular,
1395 zeroLeverageDeletion,
1397 zeroLeverageWorkspace,
1399 const deletionComparison comparison =
1400 compareRowResult( zeroLeverageMatrix, identityMatrix( 2 ), zeroLeverageDeletion, zeroLeverageResult );
1401 REQUIRE( comparison.squaredSingularError <= comparison.squaredSingularTolerance );
1402 REQUIRE( comparison.singularError <= comparison.singularTolerance );
1403 REQUIRE( comparison.covarianceError <= comparison.covarianceTolerance );
1412TEST_CASE(
"SVD deletion supports float and workspace reuse",
"[math::svdDowndate][float][workspace]" )
1421 floatVectorT singular( 3 );
1422 singular << 6, 3, 1;
1423 floatMatrixT deleted( 1, 3 );
1424 deleted << 0.5f, 0.25f, 0.125f;
1428 REQUIRE( workspace.
prepare( 3, 4, backendT::leadingCovariance ) == statusT::success );
1431 REQUIRE( workspace.
baseRank() == 3 );
1433 REQUIRE( workspace.
backend() == backendT::leadingCovariance );
1441 REQUIRE( workspace.
prepare( 3, 4, backendT::stableCore ) == statusT::success );
1445 floatMatrixT floatIdentity( 3, 3 );
1446 floatIdentity.matrix().setIdentity();
1451 REQUIRE( movedWorkspace.
prepared() );
1454 assignedWorkspace = std::move( movedWorkspace );
1455 REQUIRE( !movedWorkspace.
prepared() );
1456 REQUIRE( movedWorkspace.
baseRank() == 0 );
1458 REQUIRE( movedWorkspace.
backend() == backendT::stableCore );
1460 movedWorkspace.
clear();
1461 REQUIRE( !movedWorkspace.
prepared() );
1462 REQUIRE( assignedWorkspace.
prepared() );
1465 REQUIRE( movedWorkspace.
prepare( 3, 1, backendT::leadingCovariance ) == statusT::success );
1466 REQUIRE( movedWorkspace.
prepared() );
1467 movedWorkspace.
clear();
1469 const floatMatrixT physicalLeft = sineFactor( 6, 3 ).cast<
float>();
1470 const floatMatrixT physicalRight = sineFactor( 5, 3 ).cast<
float>();
1471 floatVectorT physicalSingular( 3 );
1472 physicalSingular << 7, 3, 0.5f;
1473 const Eigen::MatrixXf physicalMatrix =
1474 physicalLeft.matrix() * physicalSingular.matrix().asDiagonal() * physicalRight.matrix().transpose();
1475 Eigen::MatrixXf retainedMatrix( 4, 5 );
1476 retainedMatrix.row( 0 ) = physicalMatrix.row( 1 );
1477 retainedMatrix.row( 1 ) = physicalMatrix.row( 2 );
1478 retainedMatrix.row( 2 ) = physicalMatrix.row( 3 );
1479 retainedMatrix.row( 3 ) = physicalMatrix.row( 5 );
1480 Eigen::JacobiSVD<Eigen::MatrixXf> directFloat( retainedMatrix, Eigen::ComputeThinV );
1481 const std::vector<Eigen::Index> physicalDeleted{ 0, 4 };
1482 for(
const backendT backend : { backendT::leadingCovariance, backendT::stableCore } )
1493 REQUIRE( ( physicalResult.
singularValues().matrix() - directFloat.singularValues().head( 3 ) ).norm() < 2e-4f );
1497 REQUIRE( movedResult.
baseRank() == 3 );
1499 assignedResult = std::move( movedResult );
1500 REQUIRE( assignedResult.
baseRank() == 3 );
1501 REQUIRE( movedResult.
baseRank() == 0 );
1504 REQUIRE( movedResult.
status() == statusT::notComputed );
1505 REQUIRE( movedResult.
backend() == backendT::stableCore );
1511 REQUIRE( movedResult.
rotation().size() == 0 );
1513 statusT::invalidInput );
1514 REQUIRE( movedResult.
status() == statusT::invalidInput );
1515 REQUIRE( movedResult.
prepare( 3, 2 ) == statusT::success );
1516 REQUIRE( movedResult.
baseRank() == 3 );
1519 assignedWorkspace.
clear();
1520 REQUIRE( !assignedWorkspace.
prepared() );
1521 REQUIRE( assignedWorkspace.
baseRank() == 0 );
1523 REQUIRE( assignedWorkspace.
lapackInfo() == 0 );
1532TEST_CASE(
"SVD deletion accepts views and reuses prepared capacity",
"[math::svdDowndate][workspace][views]" )
1535 vectorT singularStorage( 4 );
1536 singularStorage << 8, 4, 2, 0.5;
1537 matrixT factorStorage( 6, 4 );
1538 factorStorage.setZero();
1539 factorStorage.matrix().leftCols( 3 ) = sineFactor( 6, 3 ).matrix();
1540 matrixT deletedStorage( 3, 4 );
1541 deletedStorage.setZero();
1542 deletedStorage.matrix().topLeftCorner( 1, 3 ) = factorStorage.matrix().block( 2, 0, 1, 3 );
1547 REQUIRE( result.
prepare( 3, 3 ) == statusT::success );
1548 REQUIRE( workspace.
prepare( 3, 3, backendT::stableCore ) == statusT::success );
1550 failingOperation = mx::math::detail::svdDeletionTestOperation::prepareWorkspace;
1551 mx::math::detail::svdDeletionHooks<double>().operation = throwAllocation;
1553 singularStorage.head( 3 ),
1554 deletedStorage.topLeftCorner( 1, 3 ),
1557 backendT::stableCore ) ) );
1561 REQUIRE( result.
rotation().cols() == 2 );
1564 matrixT tooManyDeleted( 4, 3 );
1565 tooManyDeleted.setZero();
1567 singularStorage.head( 3 ),
1571 backendT::stableCore ) == statusT::allocationFailure );
1580TEST_CASE(
"SVD deletion accepts under-aligned consumer storage",
"[math::svdDowndate][abi][alignment]" )
1582#ifdef MXLIB_SVD_DELETION_TEST_CONSUMER_ALIGNMENT
1583 STATIC_REQUIRE( EIGEN_MAX_ALIGN_BYTES == 16 );
1584 STATIC_REQUIRE( EIGEN_MAX_STATIC_ALIGN_BYTES == 16 );
1587 constexpr Eigen::Index rows{ 7 };
1588 constexpr Eigen::Index rank{ 3 };
1589 constexpr std::uintptr_t testedAlignment{ 32 };
1591 std::vector<realT> factorStorage(
static_cast<std::size_t
>( rows * rank ) + 4 );
1592 realT *factorData = factorStorage.data();
1593 while(
reinterpret_cast<std::uintptr_t
>( factorData ) % testedAlignment != 16 )
1597 REQUIRE(
reinterpret_cast<std::uintptr_t
>( factorData ) % testedAlignment == 16 );
1599 using unalignedMatrixMap = Eigen::Map<matrixT, Eigen::Unaligned>;
1600 unalignedMatrixMap factor( factorData, rows, rank );
1601 factor = sineFactor( rows, rank );
1604 std::vector<realT> singularStorage(
static_cast<std::size_t
>( rank ) + 4 );
1605 realT *singularData = singularStorage.data();
1606 while(
reinterpret_cast<std::uintptr_t
>( singularData ) % testedAlignment != 16 )
1610 REQUIRE(
reinterpret_cast<std::uintptr_t
>( singularData ) % testedAlignment == 16 );
1612 using unalignedVectorMap = Eigen::Map<vectorT, Eigen::Unaligned>;
1613 unalignedVectorMap singularValues( singularData, rank );
1614 singularValues << 5, 3, 1;
1616 const std::vector<Eigen::Index> deletedRows{ 2 };
1620 mx::math::svdRemoveRows( result, singularValues, factor, deletedRows, rank, workspace, backendT::stableCore ) ==
1622 REQUIRE( result.
rotation().rows() == rank );
1623 REQUIRE( result.
rotation().cols() == rank );
1624 REQUIRE( result.
rotation().matrix().squaredNorm() == Approx(
static_cast<realT
>( rank ) ).margin( 1e-12 ) );
1634TEST_CASE(
"SVD deletion accepts a default empty ABI index descriptor",
"[math::svdDowndate][abi][identity]" )
1636 vectorT singular( 2 );
1638 matrixT factor = identityMatrix( 2 );
1646 factor.outerStride() };
1648 REQUIRE( mx::math::detail::svdRemoveRowsAbiV2<realT>( result,
1654 backendT::stableCore ) == statusT::success );
1655 REQUIRE( ( result.
singularValues() - singular ).matrix().norm() == Approx( 0.0 ) );
1656 REQUIRE( ( result.
rotation() - identityMatrix( 2 ) ).matrix().norm() == Approx( 0.0 ) );
1665TEST_CASE(
"SVD deletion rejects malformed ABI descriptors",
"[math::svdDowndate][abi][errors]" )
1667 vectorT singular( 2 );
1669 matrixT factor = identityMatrix( 2 );
1670 const Eigen::Index deletedIndex{ 0 };
1678 factor.outerStride() };
1679 const auto *misalignedScalar =
1680 reinterpret_cast<const realT *
>(
reinterpret_cast<const unsigned char *
>( singular.data() ) + 1 );
1682 REQUIRE( result.
prepare( -1, 1 ) == statusT::invalidInput );
1683 REQUIRE( mx::math::detail::validateSvdDeletionFactorAbiV2( { factor.data(), -1, 2, factor.outerStride() }, 0 ) ==
1684 statusT::invalidInput );
1685 REQUIRE( mx::math::detail::validateSvdDeletionFactorAbiV2(
1687 0.0 ) == statusT::invalidInput );
1688 REQUIRE( mx::math::detail::validateSvdDeletionFactorAbiV2( { misalignedScalar, 2, 2, 2 }, 0.0 ) ==
1689 statusT::invalidInput );
1690 REQUIRE( mx::math::detail::validateSvdDeletionFactorAbiV2( { factor.data(), 2, 2, 1 }, 0 ) ==
1691 statusT::invalidInput );
1693 mx::math::detail::validateSvdDeletionFactorAbiV2(
1694 { factor.data(), std::numeric_limits<std::int64_t>::max(), 1, std::numeric_limits<std::int64_t>::max() },
1695 0 ) == statusT::invalidInput );
1696 REQUIRE( mx::math::detail::validateSvdDeletionFactorAbiV2(
1697 { factor.data(), 2, 2, std::numeric_limits<std::int64_t>::max() },
1698 0 ) == statusT::invalidInput );
1699 REQUIRE( mx::math::detail::validateSvdDeletionFactorAbiV2(
1701 0.0F ) == statusT::invalidInput );
1702 REQUIRE( mx::math::detail::svdDeletionLeadingCoreAbiV2<realT>(
1704 { singular.data(), std::numeric_limits<std::int64_t>::max() },
1707 workspace ) == statusT::invalidInput );
1708 REQUIRE( mx::math::detail::svdDeletionLeadingCoreAbiV2<realT>( result,
1709 { misalignedScalar, singular.size() },
1712 workspace ) == statusT::invalidInput );
1713 REQUIRE( mx::math::detail::svdDeletionStableCoreAbiV2<realT>(
1715 { singular.data(), std::numeric_limits<std::int64_t>::max() },
1718 workspace ) == statusT::invalidInput );
1719 REQUIRE( mx::math::detail::svdRemoveRowsAbiV2<realT>(
1723 { &deletedIndex, std::numeric_limits<std::int64_t>::max(),
sizeof( deletedIndex ) },
1726 backendT::stableCore ) == statusT::invalidInput );
1727 REQUIRE( mx::math::detail::svdRemoveRowsAbiV2<realT>( result,
1730 { &deletedIndex, 1, 3 },
1733 backendT::stableCore ) == statusT::invalidInput );
1734 REQUIRE( mx::math::detail::svdRemoveRowsAbiV2<realT>( result,
1737 {
nullptr, 1,
sizeof( deletedIndex ) },
1740 backendT::stableCore ) == statusT::invalidInput );
1741 REQUIRE( mx::math::detail::svdRemoveColumnsAbiV2<realT>( result,
1744 {
nullptr, 1,
sizeof( deletedIndex ) },
1747 backendT::stableCore ) == statusT::invalidInput );
1749 const std::int8_t deleted8{ 0 };
1750 const std::int16_t deleted16{ 0 };
1751 const std::int32_t deleted32{ 0 };
1758 mx::math::detail::svdRemoveRowsAbiV2<
1759 realT>( result, singularView, factorView, indices, 2, workspace, backendT::leadingCovariance ) ) );
1769TEST_CASE(
"SVD deletion reports invalid inputs and allocation failures",
"[math::svdDowndate][errors]" )
1776 for(
const statusT status : { statusT::notComputed,
1778 statusT::successWithClamping,
1779 statusT::invalidInput,
1780 statusT::allocationFailure,
1781 statusT::workspaceQueryFailure,
1782 statusT::solverFailure,
1783 statusT::nonFiniteOutput,
1784 statusT::invalidSolverOutput,
1785 statusT::rescalingOverflow,
1786 statusT::nonPositiveSemidefinite,
1787 statusT::factorNotOrthonormal } )
1796 vectorT singular( 2 );
1798 matrixT factor = identityMatrix( 2 );
1804 failingOperation = mx::math::detail::svdDeletionTestOperation::prepareResult;
1805 mx::math::detail::svdDeletionHooks<double>().operation = throwAllocation;
1807 statusT::allocationFailure );
1808 mx::math::detail::svdDeletionHooks<double>().operation = throwLengthError;
1809 REQUIRE( movedFromResult.
prepare( 2, 2 ) == statusT::allocationFailure );
1810 mx::math::detail::svdDeletionHooks<double>().operation =
nullptr;
1811 REQUIRE( movedFromResult.
prepare( 2, 2 ) == statusT::success );
1815 failingOperation = mx::math::detail::svdDeletionTestOperation::prepareWorkspace;
1816 mx::math::detail::svdDeletionHooks<double>().operation = throwAllocation;
1817 REQUIRE( movedFromWorkspace.
prepare( 2, 1, backendT::stableCore ) == statusT::allocationFailure );
1818 mx::math::detail::svdDeletionHooks<double>().operation = throwLengthError;
1819 REQUIRE( movedFromWorkspace.
prepare( 2, 1, backendT::stableCore ) == statusT::allocationFailure );
1820 mx::math::detail::svdDeletionHooks<double>().operation =
nullptr;
1821 REQUIRE( movedFromWorkspace.
prepare( 2, 1, backendT::stableCore ) == statusT::success );
1823 REQUIRE( result.
status() == statusT::notComputed );
1824 REQUIRE( result.
prepare( 0, 0 ) == statusT::invalidInput );
1825 REQUIRE( result.
prepare( 2, 3 ) == statusT::invalidInput );
1826 REQUIRE( workspace.
prepare( 0, -1, backendT::stableCore ) == statusT::invalidInput );
1827 REQUIRE( workspace.
prepare( 2, 1,
static_cast<backendT
>( 99 ) ) == statusT::invalidInput );
1829 failingOperation = mx::math::detail::svdDeletionTestOperation::prepareResult;
1830 mx::math::detail::svdDeletionHooks<double>().operation = throwAllocation;
1831 REQUIRE( result.
prepare( 2, 2 ) == statusT::allocationFailure );
1833 failingOperation = mx::math::detail::svdDeletionTestOperation::prepareWorkspace;
1834 REQUIRE( workspace.
prepare( 2, 1, backendT::stableCore ) == statusT::allocationFailure );
1836 failingOperation = mx::math::detail::svdDeletionTestOperation::validateFactor;
1838 mx::math::detail::svdDeletionHooks<double>().operation =
nullptr;
1840 failingOperation = mx::math::detail::svdDeletionTestOperation::prepareResult;
1841 mx::math::detail::svdDeletionHooks<double>().operation = throwLengthError;
1842 REQUIRE( result.
prepare( 3, 3 ) == statusT::allocationFailure );
1843 failingOperation = mx::math::detail::svdDeletionTestOperation::prepareWorkspace;
1844 REQUIRE( workspace.
prepare( 3, 1, backendT::stableCore ) == statusT::allocationFailure );
1845 failingOperation = mx::math::detail::svdDeletionTestOperation::validateFactor;
1847 mx::math::detail::svdDeletionHooks<double>().operation =
nullptr;
1849 matrixT noDeletedRows( 0, 2 );
1850 failingOperation = mx::math::detail::svdDeletionTestOperation::prepareResult;
1851 mx::math::detail::svdDeletionHooks<double>().operation = throwAllocation;
1854 statusT::allocationFailure );
1856 matrixT oneDeletedRow( 1, 2 );
1857 oneDeletedRow << 0.25, 0.5;
1860 statusT::allocationFailure );
1863 const std::vector<Eigen::Index> oneRow{ 0 };
1870 backendT::leadingCovariance ) == statusT::allocationFailure );
1871 mx::math::detail::svdDeletionHooks<double>().operation =
nullptr;
1873 REQUIRE( workspace.
prepare( 2, 1, backendT::stableCore ) == statusT::success );
1874 REQUIRE( workspace.
prepare( 2, 1, backendT::stableCore ) == statusT::success );
1876 const std::vector<Eigen::Index> allRows{ 0, 1 };
1878 statusT::invalidInput );
1879 const std::vector<Eigen::Index> duplicate{ 0, 0 };
1880 REQUIRE(
mx::math::svdRemoveRows( result, singular, factor, duplicate, 2, workspace, backendT::stableCore ) ==
1881 statusT::invalidInput );
1882 const std::vector<Eigen::Index> unsorted{ 1, 0 };
1884 statusT::invalidInput );
1885 const std::vector<Eigen::Index> negative{ -1 };
1887 statusT::invalidInput );
1888 const std::vector<Eigen::Index> outOfRange{ 2 };
1890 statusT::invalidInput );
1892 matrixT nonfinite = factor;
1893 nonfinite( 0, 0 ) = std::numeric_limits<realT>::infinity();
1897 matrixT tooWide( 1, 2 );
1900 const std::vector<Eigen::Index> noIndices;
1901 REQUIRE(
mx::math::svdRemoveRows( result, singular, tooWide, noIndices, 2, workspace, backendT::stableCore ) ==
1902 statusT::invalidInput );
1903 matrixT emptyFactor( 2, 0 );
1905 matrixT overflowFactor( 2, 1 );
1906 overflowFactor.setConstant( std::numeric_limits<realT>::max() );
1909 vectorT invalidSingular = singular;
1910 invalidSingular( 1 ) = -1;
1911 const std::vector<Eigen::Index> one{ 0 };
1912 REQUIRE(
mx::math::svdRemoveRows( result, invalidSingular, factor, one, 2, workspace, backendT::stableCore ) ==
1913 statusT::invalidInput );
1914 invalidSingular << 1, 2;
1915 REQUIRE(
mx::math::svdRemoveRows( result, invalidSingular, factor, one, 2, workspace, backendT::stableCore ) ==
1916 statusT::invalidInput );
1917 invalidSingular = singular;
1918 invalidSingular( 0 ) = std::numeric_limits<realT>::infinity();
1919 REQUIRE(
mx::math::svdRemoveRows( result, invalidSingular, factor, one, 2, workspace, backendT::stableCore ) ==
1920 statusT::invalidInput );
1922 statusT::invalidInput );
1924 matrixT wrongColumns( 0, 1 );
1926 statusT::invalidInput );
1928 statusT::invalidInput );
1929 matrixT nonfiniteDeleted( 1, 2 );
1930 nonfiniteDeleted << 0, std::numeric_limits<realT>::infinity();
1932 statusT::invalidInput );
1935 statusT::invalidInput );
1937 matrixT selectedFinite = factor;
1938 selectedFinite( 1, 0 ) = std::numeric_limits<realT>::infinity();
1940 mx::math::svdRemoveRows( result, singular, selectedFinite, one, 2, workspace, backendT::leadingCovariance ) ) );
1941 const std::vector<Eigen::Index> selectNonfinite{ 1 };
1948 backendT::leadingCovariance ) == statusT::invalidInput );
1950 const Eigen::Index huge =
static_cast<Eigen::Index
>( std::numeric_limits<MXLAPACK_INT>::max() );
1951 REQUIRE( workspace.
prepare( huge, 0, backendT::stableCore ) == statusT::invalidInput );
1952 REQUIRE( workspace.
prepare( huge, 1, backendT::stableCore ) == statusT::invalidInput );
1961TEST_CASE(
"SVD deletion reports workspace query failures",
"[math::svdDowndate][errors][query]" )
1965 SECTION(
"SYEVR query INFO" )
1968 syevrMode = solverHookMode::queryFailure;
1969 mx::math::detail::svdDeletionHooks<double>().syevr = syevrHook;
1970 REQUIRE( workspace.
prepare( 3, 1, backendT::leadingCovariance ) == statusT::workspaceQueryFailure );
1975 SECTION(
"SYEVR floating query size" )
1978 syevrMode = solverHookMode::invalidQuery;
1979 mx::math::detail::svdDeletionHooks<double>().syevr = syevrHook;
1980 REQUIRE( workspace.
prepare( 3, 1, backendT::leadingCovariance ) == statusT::workspaceQueryFailure );
1984 SECTION(
"SYEVR integer query size" )
1987 syevrMode = solverHookMode::invalidIntegerQuery;
1988 mx::math::detail::svdDeletionHooks<double>().syevr = syevrHook;
1989 REQUIRE( workspace.
prepare( 3, 1, backendT::stableCore ) == statusT::workspaceQueryFailure );
1992 SECTION(
"GESVD query INFO" )
1995 gesvdMode = solverHookMode::queryFailure;
1996 mx::math::detail::svdDeletionHooks<double>().gesvd = gesvdHook;
1997 REQUIRE( workspace.
prepare( 3, 1, backendT::stableCore ) == statusT::workspaceQueryFailure );
2001 SECTION(
"GESVD query size" )
2004 gesvdMode = solverHookMode::invalidQuery;
2005 mx::math::detail::svdDeletionHooks<double>().gesvd = gesvdHook;
2006 REQUIRE( workspace.
prepare( 3, 1, backendT::stableCore ) == statusT::workspaceQueryFailure );
2010 SECTION(
"query failure propagated to a result" )
2012 vectorT singular( 2 );
2014 matrixT deleted( 1, 2 );
2015 deleted << 0.25, 0.5;
2018 syevrMode = solverHookMode::queryFailure;
2019 mx::math::detail::svdDeletionHooks<double>().syevr = syevrHook;
2021 statusT::workspaceQueryFailure );
2022 REQUIRE( result.
status() == statusT::workspaceQueryFailure );
2034TEST_CASE(
"SVD deletion reports numerical solver outcomes",
"[math::svdDowndate][errors][solver]" )
2037 vectorT singular( 3 );
2038 singular << 8, 4, 2;
2039 matrixT oneDeleted( 1, 3 );
2040 oneDeleted << 0.25, 0.2, 0.1;
2041 matrixT twoDeleted( 2, 3 );
2042 twoDeleted << 0.25, 0.2, 0.1, 0.1, 0.15, 0.2;
2044 SECTION(
"leading SYEVR solve failure" )
2048 REQUIRE( workspace.
prepare( 3, 1, backendT::leadingCovariance ) == statusT::success );
2049 syevrMode = solverHookMode::solveFailure;
2050 mx::math::detail::svdDeletionHooks<double>().syevr = syevrHook;
2052 statusT::solverFailure );
2056 SECTION(
"leading SYEVR count mismatch" )
2060 REQUIRE( workspace.
prepare( 3, 1, backendT::leadingCovariance ) == statusT::success );
2061 syevrMode = solverHookMode::countMismatch;
2062 mx::math::detail::svdDeletionHooks<double>().syevr = syevrHook;
2064 statusT::solverFailure );
2068 SECTION(
"leading non-finite values and vectors" )
2070 for(
const solverHookMode mode : { solverHookMode::nonFiniteValue, solverHookMode::nonFiniteVector } )
2074 REQUIRE( workspace.
prepare( 3, 1, backendT::leadingCovariance ) == statusT::success );
2076 mx::math::detail::svdDeletionHooks<double>().syevr = syevrHook;
2078 statusT::nonFiniteOutput );
2082 SECTION(
"leading invalid ordering" )
2086 REQUIRE( workspace.
prepare( 3, 1, backendT::leadingCovariance ) == statusT::success );
2087 syevrMode = solverHookMode::invalidOrdering;
2088 mx::math::detail::svdDeletionHooks<double>().syevr = syevrHook;
2090 statusT::invalidSolverOutput );
2093 SECTION(
"leading clamping and indefiniteness" )
2097 REQUIRE( workspace.
prepare( 3, 1, backendT::leadingCovariance ) == statusT::success );
2098 mx::math::detail::svdDeletionHooks<double>().syevr = syevrHook;
2099 syevrMode = solverHookMode::roundoffClamp;
2101 statusT::successWithClamping );
2103 REQUIRE( result.
minimumPSDValue() == Approx( -std::numeric_limits<realT>::epsilon() ) );
2105 syevrMode = solverHookMode::indefinite;
2107 statusT::nonPositiveSemidefinite );
2111 SECTION(
"stable complement solver outcomes" )
2115 REQUIRE( workspace.
prepare( 3, 2, backendT::stableCore ) == statusT::success );
2116 mx::math::detail::svdDeletionHooks<double>().syevr = syevrHook;
2118 for(
const solverHookMode mode : { solverHookMode::nonFiniteValue, solverHookMode::nonFiniteVector } )
2122 statusT::nonFiniteOutput );
2124 syevrMode = solverHookMode::countMismatch;
2126 statusT::solverFailure );
2127 syevrMode = solverHookMode::invalidOrdering;
2129 statusT::invalidSolverOutput );
2130 syevrMode = solverHookMode::indefinite;
2132 statusT::nonPositiveSemidefinite );
2134 syevrMode = solverHookMode::roundoffClamp;
2136 statusT::successWithClamping );
2140 SECTION(
"stable GESVD solver outcomes" )
2142 for(
const auto [mode, expected] : std::vector<std::pair<solverHookMode, statusT>>{
2143 { solverHookMode::solveFailure, statusT::solverFailure },
2144 { solverHookMode::nonFiniteValue, statusT::nonFiniteOutput },
2145 { solverHookMode::nonFiniteVector, statusT::nonFiniteOutput },
2146 { solverHookMode::invalidOrdering, statusT::invalidSolverOutput },
2147 { solverHookMode::negativeSpectrum, statusT::invalidSolverOutput } } )
2151 REQUIRE( workspace.
prepare( 3, 1, backendT::stableCore ) == statusT::success );
2153 mx::math::detail::svdDeletionHooks<double>().gesvd = gesvdHook;
2155 if( mode == solverHookMode::solveFailure )
2159 mx::math::detail::svdDeletionHooks<double>().gesvd =
nullptr;
2163 SECTION(
"stable rescaling retains a tiny normalized singular value" )
2165 vectorT largeSingular( 1 );
2166 largeSingular << 1e200;
2167 matrixT deleted( 1, 1 );
2171 REQUIRE( workspace.
prepare( 1, 1, backendT::stableCore ) == statusT::success );
2172 gesvdMode = solverHookMode::tinySpectrum;
2173 mx::math::detail::svdDeletionHooks<double>().gesvd = gesvdHook;
2175 REQUIRE( result.
singularValues()( 0 ) == Approx( 1.0 ).epsilon( 1e-12 ) );
2186TEST_CASE(
"SVD deletion reports rescaling overflow",
"[math::svdDowndate][errors][overflow]" )
2188 vectorT singular( 1 );
2189 singular << std::numeric_limits<realT>::max() / 2;
2190 matrixT factor( 2, 1 );
2191 factor << std::sqrt( 0.5 ), std::sqrt( 0.5 );
2192 const std::vector<Eigen::Index> none;
2193 const std::vector<Eigen::Index> deletedIndex{ 0 };
2195 for(
const backendT backend : { backendT::leadingCovariance, backendT::stableCore } )
2201 statusT::rescalingOverflow );
2202 const statusT deletionStatus =
2205 REQUIRE( deletionStatus == statusT::rescalingOverflow );
Result of deleting rows or columns from a represented thin SVD.
MXLIB_SVD_DELETION_HEADER_ADAPTER svdDeletionStatus prepare(Eigen::Index baseRank, Eigen::Index outputRank)
Prepare or reuse output storage for a base and requested active output rank.
MXLIB_SVD_DELETION_HEADER_ADAPTER svdDeletionConstMatrixRef< realT > rotation() const noexcept
Return the preserved-side rotation, with updated directions in columns.
svdDeletionStatus status() const noexcept
Return the most recent operation status.
MXLIB_SVD_DELETION_HEADER_ADAPTER svdDeletionConstVectorRef< realT > singularValues() const noexcept
Return an unaligned borrowed view of all baseRank() descending updated singular values.
realT minimumPSDValue() const noexcept
Return the smallest pre-clamp eigenvalue from the normalized backend PSD validation core.
std::int64_t clampedEigenvalues() const noexcept
Return the number of roundoff-scale negative eigenvalues clamped to zero.
std::int64_t outputRank() const noexcept
Return the requested published rank.
std::int64_t maximumOutputRank() const noexcept
Return the allocated rotation-column capacity for the current base rank.
MXLIB_SVD_DELETION_HEADER_ADAPTER svdDeletionConstVectorRef< realT > squaredSingularValues() const noexcept
Return an unaligned borrowed view of all corresponding descending squared singular values.
std::int64_t baseRank() const noexcept
Return the base factorization rank for which storage is prepared.
MXLAPACK_INT lapackInfo() const noexcept
Return the underlying LAPACK status from the most recent failed query or solve.
svdDeletionBackend backend() const noexcept
Return the backend that produced the current result.
Reusable, non-shared storage for SVD deletion operations.
bool prepared() const noexcept
Report whether this workspace has completed preparation.
MXLAPACK_INT lapackInfo() const noexcept
Return the underlying LAPACK status from the most recent failed workspace query.
void clear() noexcept
Release all prepared storage and reset dimensions.
std::int64_t baseRank() const noexcept
Return the prepared base rank.
svdDeletionBackend backend() const noexcept
Return the prepared numerical backend.
MXLIB_SVD_DELETION_HEADER_ADAPTER svdDeletionStatus prepare(Eigen::Index baseRank, Eigen::Index maximumDeleted, svdDeletionBackend backend)
Prepare reusable storage and LAPACK work arrays.
std::int64_t maximumDeleted() const noexcept
Return the prepared maximum deletion count.
constexpr T pi()
Get the value of pi.
TEST_CASE("SVD row deletion matches direct full SVDs", "[math::svdDowndate][rows]")
SVD row deletion matches direct full SVDs.
const char * svdDeletionStatusName(svdDeletionStatus status)
Return a stable text representation of an SVD deletion status.
MXLIB_SVD_DELETION_HEADER_ADAPTER svdDeletionStatus svdDeletionCore(svdDeletionResult< realT > &result, std::type_identity_t< svdDeletionConstVectorRef< realT > > singularValues, std::type_identity_t< svdDeletionConstMatrixRef< realT > > deletedRows, Eigen::Index outputRank, svdDeletionWorkspace< realT > &workspace, svdDeletionBackend backend=svdDeletionBackend::stableCore)
Delete supplied singular-factor rows with an explicitly selected backend.
MXLIB_SVD_DELETION_HEADER_ADAPTER svdDeletionStatus svdRemoveColumns(svdDeletionResult< realT > &result, std::type_identity_t< svdDeletionConstVectorRef< realT > > singularValues, std::type_identity_t< svdDeletionConstMatrixRef< realT > > rightFactor, std::span< const Eigen::Index > deletedIndices, Eigen::Index outputRank, svdDeletionWorkspace< realT > &workspace, svdDeletionBackend backend=svdDeletionBackend::stableCore)
Delete physical columns from the matrix represented by a thin SVD.
svdDeletionStatus
Completion status for an SVD deletion operation.
MXLIB_SVD_DELETION_HEADER_ADAPTER svdDeletionStatus validateSvdDeletionFactor(svdDeletionConstMatrixRef< float > factor, float tolerance=0)
Validate that a supplied thin singular-vector factor has orthonormal columns.
MXLIB_SVD_DELETION_HEADER_ADAPTER svdDeletionStatus svdDeletionLeadingCore(svdDeletionResult< realT > &result, std::type_identity_t< svdDeletionConstVectorRef< realT > > singularValues, std::type_identity_t< svdDeletionConstMatrixRef< realT > > deletedRows, Eigen::Index outputRank, svdDeletionWorkspace< realT > &workspace)
Delete supplied singular-factor rows with the full-spectrum symmetric covariance core.
svdDeletionBackend
Numerical backend used to delete rows or columns from thin-SVD factors.
Eigen::Array< realT, Eigen::Dynamic, 1 > svdDeletionVector
Dynamic column vector used by the SVD deletion API.
const char * svdDeletionBackendName(svdDeletionBackend backend)
Return a stable text representation of an SVD deletion backend.
MXLIB_SVD_DELETION_HEADER_ADAPTER svdDeletionStatus svdRemoveRows(svdDeletionResult< realT > &result, std::type_identity_t< svdDeletionConstVectorRef< realT > > singularValues, std::type_identity_t< svdDeletionConstMatrixRef< realT > > leftFactor, std::span< const Eigen::Index > deletedIndices, Eigen::Index outputRank, svdDeletionWorkspace< realT > &workspace, svdDeletionBackend backend=svdDeletionBackend::stableCore)
Delete physical rows from the matrix represented by a thin SVD.
MXLIB_SVD_DELETION_HEADER_ADAPTER svdDeletionStatus svdDeletionStableCore(svdDeletionResult< realT > &result, std::type_identity_t< svdDeletionConstVectorRef< realT > > singularValues, std::type_identity_t< svdDeletionConstMatrixRef< realT > > deletedRows, Eigen::Index outputRank, svdDeletionWorkspace< realT > &workspace)
Delete supplied singular-factor rows with the complement-preserving small-SVD core.
Eigen::Array< realT, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor > svdDeletionMatrix
Column-major dynamic matrix used by the SVD deletion API.
bool svdDeletionSucceeded(svdDeletionStatus status) noexcept
Return true when a status represents usable numerical output.
ABI-stable borrowed signed-index storage descriptor.
ABI-stable borrowed column-major matrix storage descriptor.
ABI-stable borrowed contiguous-vector storage descriptor.
Reusable row and column deletion updates for thin singular value decompositions.