mxlib
c++ tools for analyzing astronomical data and other tasks by Jared R. Males. [git repo]
Loading...
Searching...
No Matches
svdDowndate.cpp
Go to the documentation of this file.
1/** \file
2 * \brief Definitions of reusable thin-SVD row and column deletion updates.
3 *
4 * \ingroup gen_math_files
5 */
6
7//***********************************************************************//
8// Copyright 2026 Jared R. Males (jaredmales@gmail.com)
9//
10// This file is part of mxlib.
11//
12// mxlib is free software: you can redistribute it and/or modify
13// it under the terms of the GNU General Public License as published by
14// the Free Software Foundation, either version 3 of the License, or
15// (at your option) any later version.
16//
17// mxlib is distributed in the hope that it will be useful,
18// but WITHOUT ANY WARRANTY; without even the implied warranty of
19// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20// GNU General Public License for more details.
21//
22// You should have received a copy of the GNU General Public License
23// along with mxlib. If not, see <http://www.gnu.org/licenses/>.
24//***********************************************************************//
25
26#include "math/svdDowndate.hpp"
27
28#include "math/floatUtils.hpp"
29
30#include <algorithm>
31#include <cmath>
32#include <cstddef>
33#include <cstring>
34#include <limits>
35#include <new>
36#include <stdexcept>
37#include <utility>
38#include <vector>
39
40namespace mx
41{
42namespace math
43{
44
45#ifdef MXLIB_SVD_DELETION_TEST_PRODUCER_ALIGNMENT
46static_assert( EIGEN_MAX_ALIGN_BYTES == MXLIB_SVD_DELETION_TEST_PRODUCER_ALIGNMENT,
47 "The SVD-deletion test producer must use its configured dynamic alignment." );
48static_assert( EIGEN_MAX_STATIC_ALIGN_BYTES == MXLIB_SVD_DELETION_TEST_PRODUCER_ALIGNMENT,
49 "The SVD-deletion test producer must use its configured static alignment." );
50#endif
51
52namespace
53{
54
55// mxlib-owned storage is deliberately unaligned so allocation and release do not depend on consumer Eigen settings.
56template <typename realT>
57using svdDeletionStorageMatrix =
58 Eigen::Array<realT, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor | Eigen::DontAlign>;
59
60// mxlib-owned vector storage follows the same allocator-independent rule.
61template <typename realT>
62using svdDeletionStorageVector = Eigen::Array<realT, Eigen::Dynamic, 1, Eigen::DontAlign>;
63
64} // namespace
65
66template <typename realT, typename abiT>
67class svdDeletionResult<realT, abiT>::storage
68{
69 public:
70 /// Complete descending updated singular spectrum.
71 svdDeletionStorageVector<realT> m_singularValues;
72
73 /// Complete descending squared singular spectrum.
74 svdDeletionStorageVector<realT> m_squaredSingularValues;
75
76 /// Preserved-side rotation storage; only the leading output-rank columns are active.
77 svdDeletionStorageMatrix<realT> m_rotation;
78
79 /// Most recent operation status.
81
82 /// Backend that produced the current outputs.
84
85 /// Prepared base rank.
86 Eigen::Index m_baseRank{ 0 };
87
88 /// Prepared output rank.
89 Eigen::Index m_outputRank{ 0 };
90
91 /// Allocated maximum output rank for the current base rank.
92 Eigen::Index m_maximumOutputRank{ 0 };
93
94 /// Number of clamped roundoff-scale negative eigenvalues.
95 Eigen::Index m_clampedEigenvalues{ 0 };
96
97 /// Smallest pre-clamp eigenvalue from the normalized backend PSD core.
98 realT m_minimumPSDValue{ 0 };
99
100 /// Underlying LAPACK failure code, or zero.
101 MXLAPACK_INT m_lapackInfo{ 0 };
102};
103
104template <typename realT, typename abiT>
105class svdDeletionWorkspace<realT, abiT>::storage
106{
107 public:
108 /// Gathered deleted rows, with the active operation occupying the leading rows.
109 svdDeletionStorageMatrix<realT> m_deletedRows;
110
111 /// Base singular values normalized by their maximum to prevent overflow.
112 svdDeletionStorageVector<realT> m_normalizedSingularValues;
113
114 /// Scaled deleted-row transpose used by the symmetric core.
115 svdDeletionStorageMatrix<realT> m_scaledDeletedTranspose;
116
117 /// Symmetric covariance or complement matrix overwritten by SYEVR.
118 svdDeletionStorageMatrix<realT> m_psdCore;
119
120 /// Full eigenvectors returned by SYEVR.
121 svdDeletionStorageMatrix<realT> m_eigenvectors;
122
123 /// Full ascending eigenvalues returned by SYEVR.
124 svdDeletionStorageVector<realT> m_eigenvalues;
125
126 /// Complement square root used by the stable backend.
127 svdDeletionStorageMatrix<realT> m_complementRoot;
128
129 /// Complement-preserving rectangular core overwritten by GESVD.
130 svdDeletionStorageMatrix<realT> m_stableCore;
131
132 /// Right singular vectors returned row-wise by GESVD.
133 svdDeletionStorageMatrix<realT> m_rightTranspose;
134
135 /// Singular values returned by GESVD.
136 svdDeletionStorageVector<realT> m_stableSingularValues;
137
138 /// Mutable diagonal poles used by the structured rank-one deflation pass.
139 svdDeletionStorageVector<realT> m_secularPoles;
140
141 /// Mutable normalized update components used by structured deflation.
142 svdDeletionStorageVector<realT> m_secularUpdate;
143
144 /// Compact active poles supplied to LAED9.
145 svdDeletionStorageVector<realT> m_secularActivePoles;
146
147 /// Compact active update supplied to and overwritten by LAED9.
148 svdDeletionStorageVector<realT> m_secularActiveUpdate;
149
150 /// Merged active and deflated eigenvalue candidates before sign reversal.
151 svdDeletionStorageVector<realT> m_secularCandidateValues;
152
153 /// One embedded structured eigenvector transformed back through deflation rotations.
154 svdDeletionStorageVector<realT> m_secularVector;
155
156 /// Active coordinate indices after structured deflation.
157 std::vector<Eigen::Index> m_secularActiveIndices;
158
159 /// Deflated coordinate indices after structured deflation.
160 std::vector<Eigen::Index> m_secularDeflatedIndices;
161
162 /// Merge order for active secular roots and deflated diagonal values.
163 std::vector<Eigen::Index> m_secularOrder;
164
165 /// First coordinate of each recorded structured-deflation Givens rotation.
166 std::vector<Eigen::Index> m_secularRotationFirst;
167
168 /// Second coordinate of each recorded structured-deflation Givens rotation.
169 std::vector<Eigen::Index> m_secularRotationSecond;
170
171 /// Cosine of each recorded structured-deflation Givens rotation.
172 std::vector<realT> m_secularRotationCosine;
173
174 /// Sine of each recorded structured-deflation Givens rotation.
175 std::vector<realT> m_secularRotationSine;
176
177 /// Eigenvector support indices required by SYEVR.
178 std::vector<MXLAPACK_INT> m_support;
179
180 /// Floating-point workspace required by SYEVR.
181 std::vector<realT> m_syevrWork;
182
183 /// Integer workspace required by SYEVR.
184 std::vector<MXLAPACK_INT> m_syevrIWork;
185
186 /// Floating-point workspace required by GESVD.
187 std::vector<realT> m_gesvdWork;
188
189 /// Prepared base rank.
190 Eigen::Index m_baseRank{ 0 };
191
192 /// Prepared maximum deletion count.
193 Eigen::Index m_maximumDeleted{ 0 };
194
195 /// Prepared numerical backend.
197
198 /// True after successful dimensioning and LAPACK workspace queries.
199 bool m_prepared{ false };
200
201 /// Underlying LAPACK workspace-query failure code, or zero.
202 MXLAPACK_INT m_lapackInfo{ 0 };
203};
204
205namespace detail
206{
207
208namespace
209{
210
211// Return true when an Eigen dimension can be passed to the configured LAPACK integer ABI.
212bool validLapackDimension( Eigen::Index dimension /* [in] candidate dimension */ )
213{
214 return dimension >= 0 && static_cast<unsigned long long>( dimension ) <=
215 static_cast<unsigned long long>( std::numeric_limits<MXLAPACK_INT>::max() );
216}
217
218// Return true when an ABI-v2 signed dimension fits Eigen's configured index type.
219bool validAbiDimension( std::int64_t dimension /* [in] ABI-v2 dimension */ )
220{
221 return dimension >= 0 && static_cast<std::uint64_t>( dimension ) <=
222 static_cast<std::uint64_t>( std::numeric_limits<Eigen::Index>::max() );
223}
224
225// Return true when a contiguous ABI-v2 vector descriptor can be mapped safely.
226template <typename realT>
227bool validAbiVectorView( svdDeletionConstVectorViewV2<realT> view /* [in] borrowed vector descriptor */ )
228{
229 return validAbiDimension( view.size ) && ( view.size == 0 || view.data != nullptr ) &&
230 ( view.size == 0 || reinterpret_cast<std::uintptr_t>( view.data ) % alignof( realT ) == 0 ) &&
231 static_cast<std::uint64_t>( view.size ) <=
232 static_cast<std::uint64_t>( std::numeric_limits<std::ptrdiff_t>::max() ) / sizeof( realT );
233}
234
235// Return true when a column-major ABI-v2 matrix descriptor can be mapped safely.
236template <typename realT>
237bool validAbiMatrixView( svdDeletionConstMatrixViewV2<realT> view /* [in] borrowed matrix descriptor */ )
238{
239 if( !validAbiDimension( view.rows ) || !validAbiDimension( view.columns ) ||
240 !validAbiDimension( view.outerStride ) )
241 {
242 return false;
243 }
244 if( view.rows > 0 && view.columns > 0 && view.data == nullptr )
245 {
246 return false;
247 }
248 if( view.rows > 0 && view.columns > 0 && reinterpret_cast<std::uintptr_t>( view.data ) % alignof( realT ) != 0 )
249 {
250 return false;
251 }
252 if( view.columns > 1 && view.outerStride < view.rows )
253 {
254 return false;
255 }
256 if( view.rows == 0 || view.columns == 0 )
257 {
258 return true;
259 }
260
261 const std::uint64_t rows = static_cast<std::uint64_t>( view.rows );
262 const std::uint64_t columns = static_cast<std::uint64_t>( view.columns );
263 const std::uint64_t outerStride = static_cast<std::uint64_t>( view.outerStride );
264 const std::uint64_t maximumScalarOffset =
265 static_cast<std::uint64_t>( std::numeric_limits<std::ptrdiff_t>::max() ) / sizeof( realT );
266 if( rows - 1 > maximumScalarOffset )
267 {
268 return false;
269 }
270 if( columns == 1 )
271 {
272 return true;
273 }
274 return columns - 1 <= ( maximumScalarOffset - ( rows - 1 ) ) / outerStride;
275}
276
277// Return true when an ABI-v2 signed-index descriptor can be decoded safely.
278bool validAbiIndexView( svdDeletionConstIndexViewV2 view /* [in] borrowed index descriptor */ )
279{
280 if( !validAbiDimension( view.size ) || ( view.size > 0 && view.data == nullptr ) )
281 {
282 return false;
283 }
284 if( view.size == 0 )
285 {
286 return true;
287 }
288 const bool supportedWidth =
289 view.elementBytes == 1 || view.elementBytes == 2 || view.elementBytes == 4 || view.elementBytes == 8;
290 if( !supportedWidth )
291 {
292 return false;
293 }
294 return static_cast<std::uint64_t>( view.size - 1 ) <=
295 static_cast<std::uint64_t>( std::numeric_limits<std::ptrdiff_t>::max() ) /
296 static_cast<std::uint64_t>( view.elementBytes );
297}
298
299// Decode one signed integer from an ABI-v2 index descriptor without type-punning across the boundary.
300std::int64_t abiIndexAt( svdDeletionConstIndexViewV2 view, /* [in] borrowed index descriptor */
301 std::int64_t offset /* [in] zero-based element offset */ )
302{
303 const std::ptrdiff_t byteOffset = static_cast<std::ptrdiff_t>( offset * view.elementBytes );
304 const auto *bytes = static_cast<const unsigned char *>( view.data ) + byteOffset;
305 switch( view.elementBytes )
306 {
307 case 1:
308 {
309 std::int8_t value{ 0 };
310 std::memcpy( &value, bytes, sizeof( value ) );
311 return value;
312 }
313 case 2:
314 {
315 std::int16_t value{ 0 };
316 std::memcpy( &value, bytes, sizeof( value ) );
317 return value;
318 }
319 case 4:
320 {
321 std::int32_t value{ 0 };
322 std::memcpy( &value, bytes, sizeof( value ) );
323 return value;
324 }
325 case 8:
326 {
327 std::int64_t value{ 0 };
328 std::memcpy( &value, bytes, sizeof( value ) );
329 return value;
330 }
331 }
332 return -1; // LCOV_EXCL_LINE -- callers validate the element width before decoding.
333}
334
335// Unaligned Eigen map used only inside the mxlib implementation after ABI-v2 descriptor validation.
336template <typename realT>
337using svdDeletionAbiMatrixMap =
338 Eigen::Map<const svdDeletionMatrix<realT>, Eigen::Unaligned, Eigen::OuterStride<Eigen::Dynamic>>;
339
340// Construct a matrix map after validAbiMatrixView has accepted the descriptor.
341template <typename realT>
342svdDeletionAbiMatrixMap<realT>
343mapAbiMatrix( svdDeletionConstMatrixViewV2<realT> view /* [in] validated matrix descriptor */ )
344{
345 return svdDeletionAbiMatrixMap<realT>(
346 view.data,
347 static_cast<Eigen::Index>( view.rows ),
348 static_cast<Eigen::Index>( view.columns ),
349 Eigen::OuterStride<Eigen::Dynamic>( static_cast<Eigen::Index>( view.outerStride ) ) );
350}
351
352// Construct a contiguous vector map after validAbiVectorView has accepted the descriptor.
353template <typename realT>
354Eigen::Map<const svdDeletionVector<realT>, Eigen::Unaligned>
355mapAbiVector( svdDeletionConstVectorViewV2<realT> view /* [in] validated vector descriptor */ )
356{
357 return Eigen::Map<const svdDeletionVector<realT>, Eigen::Unaligned>( view.data,
358 static_cast<Eigen::Index>( view.size ) );
359}
360
361// Return true when a dense array shape fits Eigen indexing and addressable storage.
362template <typename valueT>
363bool validArrayShape( Eigen::Index rows, /* [in] candidate row count */
364 Eigen::Index cols /* [in] candidate column count */ )
365{
366 if( rows < 0 || cols < 0 || ( rows > 0 && cols > std::numeric_limits<Eigen::Index>::max() / rows ) )
367 {
368 return false; // LCOV_EXCL_LINE -- requires dimensions wider than the configured LAPACK integer ABI.
369 }
370 const Eigen::Index elements = rows * cols;
371 return static_cast<unsigned long long>( elements ) <=
372 static_cast<unsigned long long>( std::numeric_limits<std::size_t>::max() / sizeof( valueT ) );
373}
374
375// Return true when an element count fits addressable `std::vector` storage.
376template <typename valueT>
377bool validVectorSize( Eigen::Index elements /* [in] candidate element count */ )
378{
379 return elements >= 0 &&
380 static_cast<unsigned long long>( elements ) <=
381 static_cast<unsigned long long>( std::numeric_limits<std::size_t>::max() / sizeof( valueT ) );
382}
383
384// Return true when every dense Eigen element is finite under mxlib's fast-math-safe predicate.
385template <typename derivedT>
386bool allFinite( const Eigen::DenseBase<derivedT> &values /* [in] dense object to inspect */ )
387{
388 for( Eigen::Index index = 0; index < values.size(); ++index )
389 {
390 if( !math::isFinite( values.derived().data()[index] ) )
391 {
392 return false;
393 }
394 }
395 return true;
396}
397
398// Return true when the leading values are finite, nonnegative, and nonincreasing.
399template <typename derivedT>
400bool validDescendingSpectrum( const Eigen::DenseBase<derivedT> &values /* [in] spectrum to inspect */ )
401{
402 for( Eigen::Index index = 0; index < values.size(); ++index )
403 {
404 const auto value = values.derived()( index );
405 if( !math::isFinite( value ) || value < 0 || ( index > 0 && value > values.derived()( index - 1 ) ) )
406 {
407 return false;
408 }
409 }
410 return true;
411}
412
413// Return true when the leading values are finite and nondecreasing.
414template <typename derivedT>
415bool validAscendingSpectrum( const Eigen::DenseBase<derivedT> &values /* [in] spectrum to inspect */ )
416{
417 for( Eigen::Index index = 0; index < values.size(); ++index )
418 {
419 const auto value = values.derived()( index );
420 if( !math::isFinite( value ) || ( index > 0 && value < values.derived()( index - 1 ) ) )
421 {
422 return false;
423 }
424 }
425 return true;
426}
427
428// Invoke an optional deterministic operation hook.
429template <typename realT>
430void callOperationHook( svdDeletionTestOperation operation /* [in] operation about to execute */ )
431{
432 auto &hooks = svdDeletionHooks<realT>();
433 if( hooks.operation )
434 {
435 hooks.operation( operation );
436 }
437}
438
439// Invoke the injected or production SYEVR implementation.
440template <typename realT>
441MXLAPACK_INT callSyevr( char jobz, /* [in] eigenvector request */
442 char range, /* [in] eigenvalue selection mode */
443 char uplo, /* [in] populated input triangle */
444 MXLAPACK_INT n, /* [in] matrix order */
445 realT *matrix, /* [in,out] symmetric input matrix */
446 MXLAPACK_INT lda, /* [in] input leading dimension */
447 realT valueLower, /* [in] lower value bound */
448 realT valueUpper, /* [in] upper value bound */
449 MXLAPACK_INT indexLow, /* [in] one-based lower index */
450 MXLAPACK_INT indexHigh, /* [in] one-based upper index */
451 realT tolerance, /* [in] convergence tolerance */
452 MXLAPACK_INT *found, /* [out] selected eigenvalue count */
453 realT *eigenvalues, /* [out] ascending eigenvalues */
454 realT *eigenvectors, /* [out] eigenvectors in columns */
455 MXLAPACK_INT ldz, /* [in] eigenvector leading dimension */
456 MXLAPACK_INT *support, /* [out] eigenvector support */
457 realT *work, /* [in,out] floating workspace */
458 MXLAPACK_INT lwork, /* [in] floating workspace size */
459 MXLAPACK_INT *iwork, /* [in,out] integer workspace */
460 MXLAPACK_INT liwork /* [in] integer workspace size */ )
461{
462 auto &hooks = svdDeletionHooks<realT>();
463 if( hooks.syevr )
464 {
465 return hooks.syevr( jobz,
466 range,
467 uplo,
468 n,
469 matrix,
470 lda,
471 valueLower,
472 valueUpper,
473 indexLow,
474 indexHigh,
475 tolerance,
476 found,
477 eigenvalues,
478 eigenvectors,
479 ldz,
480 support,
481 work,
482 lwork,
483 iwork,
484 liwork );
485 }
486
487 return math::syevr<realT>( jobz,
488 range,
489 uplo,
490 n,
491 matrix,
492 lda,
493 valueLower,
494 valueUpper,
495 indexLow,
496 indexHigh,
497 tolerance,
498 found,
499 eigenvalues,
500 eigenvectors,
501 ldz,
502 support,
503 work,
504 lwork,
505 iwork,
506 liwork );
507}
508
509// Invoke the injected or production GESVD implementation.
510template <typename realT>
511MXLAPACK_INT callGesvd( char jobu, /* [in] left singular-vector request */
512 char jobvt, /* [in] right singular-vector request */
513 MXLAPACK_INT rows, /* [in] matrix row count */
514 MXLAPACK_INT cols, /* [in] matrix column count */
515 realT *matrix, /* [in,out] input matrix */
516 MXLAPACK_INT lda, /* [in] input leading dimension */
517 realT *singular, /* [out] descending singular values */
518 realT *left, /* [out] left singular vectors when requested */
519 MXLAPACK_INT ldu, /* [in] left-vector leading dimension */
520 realT *rightT, /* [out] transposed right singular vectors */
521 MXLAPACK_INT ldvt, /* [in] right-vector leading dimension */
522 realT *work, /* [in,out] floating workspace */
523 MXLAPACK_INT lwork /* [in] floating workspace size */ )
524{
525 auto &hooks = svdDeletionHooks<realT>();
526 if( hooks.gesvd )
527 {
528 return hooks.gesvd( jobu, jobvt, rows, cols, matrix, lda, singular, left, ldu, rightT, ldvt, work, lwork );
529 }
530
531 return math::gesvd<realT>( jobu, jobvt, rows, cols, matrix, lda, singular, left, ldu, rightT, ldvt, work, lwork );
532}
533
534// Invoke the injected or production LAED9 structured rank-one eigensolver.
535template <typename realT>
536MXLAPACK_INT callLaed9( realT *eigenvalues, /* [out] ascending updated eigenvalues */
537 realT *delta, /* [out] LAED9 secular workspace */
538 realT *eigenvectors, /* [out] updated eigenvectors in columns */
539 MXLAPACK_INT rank, /* [in] active secular-system dimension */
540 MXLAPACK_INT leadingDimension, /* [in] matrix leading dimension */
541 realT rho, /* [in] positive rank-one update weight */
542 realT *poles, /* [in,out] strictly ascending diagonal poles */
543 realT *update /* [in,out] normalized update components */ )
544{
545 auto &hooks = svdDeletionHooks<realT>();
546 if( hooks.laed9 )
547 {
548 return hooks.laed9( eigenvalues, delta, eigenvectors, rank, leadingDimension, rho, poles, update );
549 }
550
551 return math::laed9<realT>( eigenvalues,
552 delta,
553 eigenvectors,
554 rank,
555 1,
556 rank,
557 rank,
558 leadingDimension,
559 rho,
560 poles,
561 update,
562 leadingDimension );
563}
564
565// Convert a finite LAPACK floating workspace query to an integer size.
566template <typename realT>
567bool querySize( MXLAPACK_INT &size, /* [out] validated integer workspace size */
568 realT query /* [in] floating-point LAPACK query result */ )
569{
570 if( !math::isFinite( query ) || query < realT( 1 ) ||
571 static_cast<long double>( query ) > static_cast<long double>( std::numeric_limits<MXLAPACK_INT>::max() ) )
572 {
573 return false;
574 }
575
576 size = static_cast<MXLAPACK_INT>( std::ceil( query ) );
577 return size >= 1;
578}
579
580// Return the construction-scale tolerance for a theoretically PSD core.
581template <typename realT>
582realT psdTolerance( Eigen::Index dimension, /* [in] core dimension */
583 realT scale /* [in] nonnegative core construction scale */ )
584{
585 return realT( 64 ) * std::numeric_limits<realT>::epsilon() *
586 static_cast<realT>( std::max<Eigen::Index>( 1, dimension ) ) * scale;
587}
588
589} // namespace
590
591template <typename realT>
592svdDeletionTestHooks<realT> &svdDeletionHooks()
593{
594 static svdDeletionTestHooks<realT> hooks;
595 return hooks;
596}
597
598// Implementation access shared by the public deletion entry points.
599template <typename realT>
600struct svdDeletionImplementation
601{
602 using resultT = svdDeletionResult<realT>;
603 using workspaceT = svdDeletionWorkspace<realT>;
604
605 // Publish a failure and its optional underlying LAPACK status.
606 static svdDeletionStatus fail( resultT &result, /* [out] result status to invalidate */
607 svdDeletionStatus status, /* [in] failure status */
608 MXLAPACK_INT lapackInfo = 0 /* [in] underlying LAPACK status */ )
609 {
610 if( !result.ensureStorage() )
611 {
613 }
614 result.m_storage->m_status = status;
615 result.m_storage->m_lapackInfo = lapackInfo;
616 result.m_storage->m_clampedEigenvalues = 0;
618 {
619 result.m_storage->m_minimumPSDValue = realT( 0 );
620 }
621 return status;
622 }
623
624 // Return true for a supported numerical backend value.
625 static bool validBackend( svdDeletionBackend backend /* [in] backend value to validate */ )
626 {
629 }
630
631 // Validate singular values and requested rank without checking factor rows.
632 static bool validSingularInputs( svdDeletionConstVectorRef<realT> singularValues, /* [in] base singular values */
633 Eigen::Index outputRank /* [in] requested output rank */ )
634 {
635 const Eigen::Index rank = singularValues.size();
636 if( rank <= 0 || !validLapackDimension( rank ) || outputRank <= 0 || outputRank > rank ||
637 !allFinite( singularValues ) )
638 {
639 return false;
640 }
641
642 for( Eigen::Index index = 0; index < rank; ++index )
643 {
644 if( singularValues( index ) < realT( 0 ) ||
645 ( index > 0 && singularValues( index ) > singularValues( index - 1 ) ) )
646 {
647 return false;
648 }
649 }
650 return true;
651 }
652
653 // Validate common core inputs without checking global factor orthonormality.
654 static bool validCoreInputs( svdDeletionConstVectorRef<realT> singularValues, /* [in] base singular values */
655 svdDeletionConstMatrixRef<realT> deletedRows, /* [in] deleted factor rows */
656 Eigen::Index outputRank /* [in] requested output rank */ )
657 {
658 const Eigen::Index rank = singularValues.size();
659 return validSingularInputs( singularValues, outputRank ) && deletedRows.cols() == rank &&
660 validLapackDimension( deletedRows.rows() ) &&
661 deletedRows.rows() <= std::numeric_limits<MXLAPACK_INT>::max() - rank && allFinite( deletedRows );
662 }
663
664 // Normalize base singular values and return their common scale.
665 static realT normalizeSingularValues( workspaceT &workspace, /* [out] normalized values */
666 svdDeletionConstVectorRef<realT> singularValues /* [in] base values */ )
667 {
668 const realT scale = singularValues( 0 );
669 if( scale == realT( 0 ) )
670 {
671 workspace.m_storage->m_normalizedSingularValues.setZero();
672 return scale;
673 }
674
675 int scaleExponent{ 0 };
676 const realT scaleMantissa = std::frexp( scale, &scaleExponent );
677 for( Eigen::Index index = 0; index < singularValues.size(); ++index )
678 {
679 int valueExponent{ 0 };
680 const realT valueMantissa = std::frexp( singularValues( index ), &valueExponent );
681 workspace.m_storage->m_normalizedSingularValues( index ) =
682 std::ldexp( valueMantissa / scaleMantissa, valueExponent - scaleExponent );
683 }
684 return scale;
685 }
686
687 // Return a ratio without forming an underflowed reciprocal under fast-math builds.
688 static realT normalizedRatio( realT value, /* [in] nonnegative numerator */
689 realT scale /* [in] positive denominator */ )
690 {
691 int scaleExponent{ 0 };
692 int valueExponent{ 0 };
693 const realT scaleMantissa = std::frexp( scale, &scaleExponent );
694 const realT valueMantissa = std::frexp( value, &valueExponent );
695 return std::ldexp( valueMantissa / scaleMantissa, valueExponent - scaleExponent );
696 }
697
698 // Rescale one normalized singular value without squaring before restoring input scale.
699 static bool rescaleSingularValue( realT &singularValue, /* [out] rescaled singular value */
700 realT &squaredSingularValue, /* [out] rescaled squared value */
701 realT normalizedValue, /* [in] normalized singular value */
702 realT scale /* [in] base singular-value scale */ )
703 {
704 const realT maximum = std::numeric_limits<realT>::max();
705 singularValue = normalizedValue * scale;
706 if( !math::isFinite( singularValue ) || singularValue > std::sqrt( maximum ) )
707 {
708 return false;
709 }
710 squaredSingularValue = singularValue * singularValue;
711 return math::isFinite( squaredSingularValue );
712 }
713
714 // Rescale one normalized squared singular value without overflowing either public representation.
715 static bool rescaleValue( realT &singularValue, /* [out] rescaled singular value */
716 realT &squaredSingularValue, /* [out] rescaled squared singular value */
717 realT normalizedSquaredValue, /* [in] normalized squared value */
718 realT scale /* [in] base singular-value scale */ )
719 {
720 return rescaleSingularValue( singularValue, squaredSingularValue, std::sqrt( normalizedSquaredValue ), scale );
721 }
722
723 // Ensure result and worker storage can execute one core operation.
724 static svdDeletionStatus prepare( resultT &result, /* [in,out] output storage */
725 workspaceT &workspace, /* [in,out] reusable scratch */
726 Eigen::Index rank, /* [in] base rank */
727 Eigen::Index deletedCount, /* [in] actual deleted-row count */
728 Eigen::Index outputRank, /* [in] requested output rank */
729 svdDeletionBackend backend /* [in] selected backend */ )
730 {
731 svdDeletionStatus status = result.prepare( rank, outputRank );
732 if( status != svdDeletionStatus::success )
733 {
734 return status;
735 }
736
737 if( !workspace.prepared() || workspace.baseRank() != rank || workspace.maximumDeleted() < deletedCount ||
738 workspace.backend() != backend )
739 {
740 status = workspace.prepare( rank, deletedCount, backend );
741 if( status != svdDeletionStatus::success )
742 {
743 return fail( result, status, workspace.lapackInfo() );
744 }
745 }
746
748 }
749
750 // Copy deleted factor rows into the active portion of a prepared workspace.
751 static void loadDeletedRows( workspaceT &workspace, /* [out] gathered workspace rows */
752 svdDeletionConstMatrixRef<realT> deletedRows /* [in] rows to copy */ )
753 {
754 if( deletedRows.rows() > 0 )
755 {
756 workspace.m_storage->m_deletedRows.matrix().topRows( deletedRows.rows() ) = deletedRows.matrix();
757 }
758 }
759
760 // Publish the unchanged represented singular system for an empty deletion.
761 static svdDeletionStatus identity( resultT &result, /* [out] identity update */
762 svdDeletionConstVectorRef<realT> singularValues, /* [in] base singular values */
763 Eigen::Index outputRank, /* [in] requested output rank */
764 svdDeletionBackend backend /* [in] reported backend */ )
765 {
766 const svdDeletionStatus status = result.prepare( singularValues.size(), outputRank );
767 if( status != svdDeletionStatus::success )
768 {
769 return status;
770 }
771
772 result.m_storage->m_rotation.setZero();
773 for( Eigen::Index index = 0; index < singularValues.size(); ++index )
774 {
775 result.m_storage->m_singularValues( index ) = singularValues( index );
776 if( singularValues( index ) > realT( 0 ) &&
777 singularValues( index ) > std::sqrt( std::numeric_limits<realT>::max() ) )
778 {
779 return fail( result, svdDeletionStatus::rescalingOverflow );
780 }
781 result.m_storage->m_squaredSingularValues( index ) = singularValues( index ) * singularValues( index );
782 if( index < outputRank )
783 {
784 result.m_storage->m_rotation( index, index ) = realT( 1 );
785 }
786 }
787 result.m_storage->m_backend = backend;
788 result.m_storage->m_status = svdDeletionStatus::success;
789 result.m_storage->m_clampedEigenvalues = 0;
791 {
792 const realT scale = singularValues( 0 );
793 realT minimumNormalized{ 0 };
794 if( scale != realT( 0 ) )
795 {
796 minimumNormalized = normalizedRatio( singularValues( singularValues.size() - 1 ), scale );
797 }
798 result.m_storage->m_minimumPSDValue = minimumNormalized * minimumNormalized;
799 }
800 else
801 {
802 result.m_storage->m_minimumPSDValue = realT( 1 );
803 }
804 result.m_storage->m_lapackInfo = 0;
805 return result.m_storage->m_status;
806 }
807
808 // Publish an arbitrary orthonormal system for an identically zero represented matrix.
809 static svdDeletionStatus zeroSystem( resultT &result, /* [out] zero-valued result */
810 Eigen::Index outputRank, /* [in] requested output rank */
811 svdDeletionBackend backend /* [in] reported backend */ )
812 {
813 result.m_storage->m_rotation.setZero();
814 result.m_storage->m_singularValues.setZero();
815 result.m_storage->m_squaredSingularValues.setZero();
816 for( Eigen::Index index = 0; index < outputRank; ++index )
817 {
818 result.m_storage->m_rotation( index, index ) = realT( 1 );
819 }
820 result.m_storage->m_backend = backend;
821 result.m_storage->m_status = svdDeletionStatus::success;
822 result.m_storage->m_clampedEigenvalues = 0;
823 result.m_storage->m_minimumPSDValue = realT( 0 );
824 result.m_storage->m_lapackInfo = 0;
825 return result.m_storage->m_status;
826 }
827
828 // Execute a normalized symmetric covariance solve with deleted rows already loaded.
829 static svdDeletionStatus leadingLoaded( resultT &result, /* [out] updated system */
830 svdDeletionConstVectorRef<realT> singularValues, /* [in] base values */
831 Eigen::Index deletedCount, /* [in] active rows */
832 Eigen::Index outputRank, /* [in] requested rank */
833 workspaceT &workspace /* [in,out] scratch */ )
834 {
835 const Eigen::Index rank = singularValues.size();
836 const realT scale = normalizeSingularValues( workspace, singularValues );
837 if( scale == realT( 0 ) )
838 {
839 return zeroSystem( result, outputRank, svdDeletionBackend::leadingCovariance );
840 }
841
842 for( Eigen::Index column = 0; column < deletedCount; ++column )
843 {
844 for( Eigen::Index row = 0; row < rank; ++row )
845 {
846 workspace.m_storage->m_scaledDeletedTranspose( row, column ) =
847 workspace.m_storage->m_normalizedSingularValues( row ) *
848 workspace.m_storage->m_deletedRows( column, row );
849 }
850 }
851
852 const auto scaledDeleted = workspace.m_storage->m_scaledDeletedTranspose.matrix().leftCols( deletedCount );
853 workspace.m_storage->m_psdCore.matrix().noalias() = -scaledDeleted * scaledDeleted.transpose();
854 for( Eigen::Index index = 0; index < rank; ++index )
855 {
856 workspace.m_storage->m_psdCore( index, index ) += workspace.m_storage->m_normalizedSingularValues( index ) *
857 workspace.m_storage->m_normalizedSingularValues( index );
858 }
859 MXLAPACK_INT found{ 0 };
860 const MXLAPACK_INT lapackRank = static_cast<MXLAPACK_INT>( rank );
861 const MXLAPACK_INT info =
862 callSyevr<realT>( 'V',
863 'A',
864 'L',
865 lapackRank,
866 workspace.m_storage->m_psdCore.data(),
867 lapackRank,
868 realT( 0 ),
869 realT( 0 ),
870 1,
871 lapackRank,
872 math::lamch<realT>( 'S' ),
873 &found,
874 workspace.m_storage->m_eigenvalues.data(),
875 workspace.m_storage->m_eigenvectors.data(),
876 lapackRank,
877 workspace.m_storage->m_support.data(),
878 workspace.m_storage->m_syevrWork.data(),
879 static_cast<MXLAPACK_INT>( workspace.m_storage->m_syevrWork.size() ),
880 workspace.m_storage->m_syevrIWork.data(),
881 static_cast<MXLAPACK_INT>( workspace.m_storage->m_syevrIWork.size() ) );
882 if( info != 0 || found != lapackRank )
883 {
884 return fail( result, svdDeletionStatus::solverFailure, info );
885 }
886 if( !allFinite( workspace.m_storage->m_eigenvalues ) || !allFinite( workspace.m_storage->m_eigenvectors ) )
887 {
888 return fail( result, svdDeletionStatus::nonFiniteOutput );
889 }
890 if( !validAscendingSpectrum( workspace.m_storage->m_eigenvalues ) )
891 {
892 return fail( result, svdDeletionStatus::invalidSolverOutput );
893 }
894
895 result.m_storage->m_minimumPSDValue = workspace.m_storage->m_eigenvalues( 0 );
896 const realT tolerance =
897 psdTolerance<realT>( rank, realT( 1 ) + static_cast<realT>( scaledDeleted.squaredNorm() ) );
898 if( result.m_storage->m_minimumPSDValue < -tolerance )
899 {
900 return fail( result, svdDeletionStatus::nonPositiveSemidefinite );
901 }
902
903 result.m_storage->m_clampedEigenvalues = 0;
904 for( Eigen::Index index = 0; index < rank; ++index )
905 {
906 if( workspace.m_storage->m_eigenvalues( index ) < realT( 0 ) )
907 {
908 workspace.m_storage->m_eigenvalues( index ) = realT( 0 );
909 ++result.m_storage->m_clampedEigenvalues;
910 }
911 }
912
913 for( Eigen::Index output = 0; output < rank; ++output )
914 {
915 const Eigen::Index source = rank - 1 - output;
916 if( !rescaleValue( result.m_storage->m_singularValues( output ),
917 result.m_storage->m_squaredSingularValues( output ),
918 workspace.m_storage->m_eigenvalues( source ),
919 scale ) )
920 {
921 return fail( result, svdDeletionStatus::rescalingOverflow );
922 }
923 if( output < outputRank )
924 {
925 result.m_storage->m_rotation.matrix().col( output ) =
926 workspace.m_storage->m_eigenvectors.matrix().col( source );
927 }
928 }
929
930 result.m_storage->m_backend = svdDeletionBackend::leadingCovariance;
931 result.m_storage->m_lapackInfo = 0;
932 result.m_storage->m_status = result.m_storage->m_clampedEigenvalues > 0 ? svdDeletionStatus::successWithClamping
934 return result.m_storage->m_status;
935 }
936
937 // Execute the symmetric covariance deletion core after workspace loading.
938 static svdDeletionStatus leading( resultT &result, /* [out] updated system */
939 svdDeletionConstVectorRef<realT> singularValues, /* [in] base values */
940 svdDeletionConstMatrixRef<realT> deletedRows, /* [in] deleted rows */
941 Eigen::Index outputRank, /* [in] requested rank */
942 workspaceT &workspace /* [in,out] scratch */ )
943 {
944 if( !validCoreInputs( singularValues, deletedRows, outputRank ) )
945 {
946 return fail( result, svdDeletionStatus::invalidInput );
947 }
948 if( deletedRows.rows() == 0 )
949 {
950 return identity( result, singularValues, outputRank, svdDeletionBackend::leadingCovariance );
951 }
952
953 const Eigen::Index rank = singularValues.size();
954 svdDeletionStatus status =
955 prepare( result, workspace, rank, deletedRows.rows(), outputRank, svdDeletionBackend::leadingCovariance );
956 if( status != svdDeletionStatus::success )
957 {
958 return status;
959 }
960 loadDeletedRows( workspace, deletedRows );
961 return leadingLoaded( result, singularValues, deletedRows.rows(), outputRank, workspace );
962 }
963
964 // Execute the normalized diagonal-minus-rank-one secular solve with one deleted row already loaded.
965 static svdDeletionStatus rankOneLoaded( resultT &result, /* [out] updated system */
966 svdDeletionConstVectorRef<realT> singularValues, /* [in] base values */
967 Eigen::Index outputRank, /* [in] requested rank */
968 workspaceT &workspace /* [in,out] scratch */ )
969 {
970 const Eigen::Index rank = singularValues.size();
971 auto &stored = *workspace.m_storage;
972 const realT scale = normalizeSingularValues( workspace, singularValues );
973 if( scale == realT( 0 ) )
974 {
975 return zeroSystem( result, outputRank, svdDeletionBackend::rankOneSecular );
976 }
977
978 realT updateNorm{ 0 };
979 realT maximumPole{ 0 };
980 for( Eigen::Index index = 0; index < rank; ++index )
981 {
982 const realT normalized = stored.m_normalizedSingularValues( index );
983 const realT update = normalized * stored.m_deletedRows( 0, index );
984 const realT pole = -( normalized * normalized );
985 if( !math::isFinite( update ) || !math::isFinite( pole ) )
986 {
987 return fail( result, svdDeletionStatus::invalidInput ); // LCOV_EXCL_LINE -- finite normalized products.
988 }
989 stored.m_scaledDeletedTranspose( index, 0 ) = update;
990 stored.m_secularPoles( index ) = pole;
991 updateNorm = std::hypot( updateNorm, update );
992 maximumPole = std::max( maximumPole, std::abs( pole ) );
993 }
994 if( updateNorm == realT( 0 ) )
995 {
996 return identity( result, singularValues, outputRank, svdDeletionBackend::rankOneSecular );
997 }
998 if( !math::isFinite( updateNorm ) || updateNorm > std::sqrt( std::numeric_limits<realT>::max() ) )
999 {
1000 return fail( result, svdDeletionStatus::invalidInput );
1001 }
1002
1003 const realT rho = updateNorm * updateNorm;
1004 if( !math::isFinite( rho ) )
1005 {
1006 // LCOV_EXCL_START -- updateNorm is bounded before squaring.
1007 return fail( result, svdDeletionStatus::invalidInput );
1008 // LCOV_EXCL_STOP
1009 }
1010 realT maximumUpdate{ 0 };
1011 for( Eigen::Index index = 0; index < rank; ++index )
1012 {
1013 stored.m_secularUpdate( index ) = stored.m_scaledDeletedTranspose( index, 0 ) / updateNorm;
1014 maximumUpdate = std::max( maximumUpdate, std::abs( stored.m_secularUpdate( index ) ) );
1015 }
1016 const realT deflationTolerance =
1017 realT( 8 ) * std::numeric_limits<realT>::epsilon() * std::max( maximumPole, maximumUpdate );
1018 if( rho * maximumUpdate <= deflationTolerance )
1019 {
1020 return identity( result, singularValues, outputRank, svdDeletionBackend::rankOneSecular );
1021 }
1022
1023 Eigen::Index activeCount{ 0 };
1024 Eigen::Index deflatedCount{ 0 };
1025 Eigen::Index rotationCount{ 0 };
1026 Eigen::Index previousActive{ -1 };
1027 for( Eigen::Index next = 0; next < rank; ++next )
1028 {
1029 if( rho * std::abs( stored.m_secularUpdate( next ) ) <= deflationTolerance )
1030 {
1031 stored.m_secularDeflatedIndices[deflatedCount++] = next;
1032 continue;
1033 }
1034 if( previousActive < 0 )
1035 {
1036 previousActive = next;
1037 continue;
1038 }
1039
1040 const realT previousUpdate = stored.m_secularUpdate( previousActive );
1041 const realT nextUpdate = stored.m_secularUpdate( next );
1042 const realT rotationNorm = std::hypot( nextUpdate, previousUpdate );
1043 const realT cosine = nextUpdate / rotationNorm;
1044 const realT sine = -previousUpdate / rotationNorm;
1045 const realT poleDifference = stored.m_secularPoles( next ) - stored.m_secularPoles( previousActive );
1046 if( std::abs( poleDifference * cosine * sine ) <= deflationTolerance )
1047 {
1048 stored.m_secularRotationFirst[rotationCount] = previousActive;
1049 stored.m_secularRotationSecond[rotationCount] = next;
1050 stored.m_secularRotationCosine[rotationCount] = cosine;
1051 stored.m_secularRotationSine[rotationCount] = sine;
1052 ++rotationCount;
1053
1054 stored.m_secularUpdate( next ) = rotationNorm;
1055 stored.m_secularUpdate( previousActive ) = realT( 0 );
1056 const realT previousPole = stored.m_secularPoles( previousActive );
1057 const realT nextPole = stored.m_secularPoles( next );
1058 stored.m_secularPoles( previousActive ) = previousPole * cosine * cosine + nextPole * sine * sine;
1059 stored.m_secularPoles( next ) = previousPole * sine * sine + nextPole * cosine * cosine;
1060 stored.m_secularDeflatedIndices[deflatedCount++] = previousActive;
1061 previousActive = next;
1062 }
1063 else
1064 {
1065 stored.m_secularActiveIndices[activeCount++] = previousActive;
1066 previousActive = next;
1067 }
1068 }
1069 if( previousActive >= 0 )
1070 {
1071 stored.m_secularActiveIndices[activeCount++] = previousActive;
1072 }
1073 if( activeCount <= 0 || activeCount + deflatedCount != rank )
1074 {
1075 // LCOV_EXCL_START -- the deflation loop partitions every index and retains an active component.
1076 return fail( result, svdDeletionStatus::invalidSolverOutput );
1077 // LCOV_EXCL_STOP
1078 }
1079
1080 const auto poleIndexLess = [&]( Eigen::Index left, Eigen::Index right )
1081 {
1082 const realT leftPole = stored.m_secularPoles( left );
1083 const realT rightPole = stored.m_secularPoles( right );
1084 return leftPole < rightPole || ( leftPole == rightPole && left < right );
1085 };
1086 std::sort( stored.m_secularActiveIndices.begin(),
1087 stored.m_secularActiveIndices.begin() + activeCount,
1088 poleIndexLess );
1089 std::sort( stored.m_secularDeflatedIndices.begin(),
1090 stored.m_secularDeflatedIndices.begin() + deflatedCount,
1091 poleIndexLess );
1092
1093 realT activeUpdateNorm{ 0 };
1094 for( Eigen::Index active = 0; active < activeCount; ++active )
1095 {
1096 const Eigen::Index source = stored.m_secularActiveIndices[active];
1097 stored.m_secularActivePoles( active ) = stored.m_secularPoles( source );
1098 activeUpdateNorm = std::hypot( activeUpdateNorm, stored.m_secularUpdate( source ) );
1099 if( active > 0 && !( stored.m_secularActivePoles( active - 1 ) < stored.m_secularActivePoles( active ) ) )
1100 {
1101 // LCOV_EXCL_START -- the preceding pass deflates clustered poles.
1102 return fail( result, svdDeletionStatus::invalidSolverOutput );
1103 // LCOV_EXCL_STOP
1104 }
1105 }
1106 if( activeUpdateNorm == realT( 0 ) || !math::isFinite( activeUpdateNorm ) )
1107 {
1108 // LCOV_EXCL_START -- active updates have already passed the finite nonzero threshold.
1109 return fail( result, svdDeletionStatus::invalidSolverOutput );
1110 // LCOV_EXCL_STOP
1111 }
1112
1113 const realT activeRho = rho * activeUpdateNorm * activeUpdateNorm;
1114 if( !math::isFinite( activeRho ) )
1115 {
1116 // LCOV_EXCL_START -- orthogonal deflation preserves an active update norm no greater than one.
1117 return fail( result, svdDeletionStatus::invalidInput );
1118 // LCOV_EXCL_STOP
1119 }
1120 if( activeRho == realT( 0 ) )
1121 {
1122 // LCOV_EXCL_START -- the whole-update threshold handles tiny rho before deflation.
1123 return identity( result, singularValues, outputRank, svdDeletionBackend::rankOneSecular );
1124 // LCOV_EXCL_STOP
1125 }
1126 for( Eigen::Index active = 0; active < activeCount; ++active )
1127 {
1128 const Eigen::Index source = stored.m_secularActiveIndices[active];
1129 stored.m_secularActiveUpdate( active ) = stored.m_secularUpdate( source ) / activeUpdateNorm;
1130 }
1131
1132 const MXLAPACK_INT lapackActive = static_cast<MXLAPACK_INT>( activeCount );
1133 const MXLAPACK_INT leadingDimension = static_cast<MXLAPACK_INT>( rank );
1134 const MXLAPACK_INT info = callLaed9<realT>( stored.m_eigenvalues.data(),
1135 stored.m_psdCore.data(),
1136 stored.m_eigenvectors.data(),
1137 lapackActive,
1138 leadingDimension,
1139 activeRho,
1140 stored.m_secularActivePoles.data(),
1141 stored.m_secularActiveUpdate.data() );
1142 if( info != 0 )
1143 {
1144 return fail( result, svdDeletionStatus::solverFailure, info );
1145 }
1146 for( Eigen::Index active = 0; active < activeCount; ++active )
1147 {
1148 if( !math::isFinite( stored.m_eigenvalues( active ) ) )
1149 {
1150 return fail( result, svdDeletionStatus::nonFiniteOutput );
1151 }
1152 for( Eigen::Index row = 0; row < activeCount; ++row )
1153 {
1154 if( !math::isFinite( stored.m_eigenvectors( row, active ) ) )
1155 {
1156 return fail( result, svdDeletionStatus::nonFiniteOutput );
1157 }
1158 }
1159 }
1160 if( !validAscendingSpectrum( stored.m_eigenvalues.head( activeCount ) ) )
1161 {
1162 return fail( result, svdDeletionStatus::invalidSolverOutput );
1163 }
1164
1165 const realT secularTolerance = realT( 64 ) * std::numeric_limits<realT>::epsilon() *
1166 static_cast<realT>( std::max<Eigen::Index>( 1, rank ) ) *
1167 ( realT( 1 ) + activeRho );
1168 for( Eigen::Index active = 0; active < activeCount; ++active )
1169 {
1170 const realT lower = stored.m_secularActivePoles( active );
1171 const realT upper = active + 1 < activeCount ? stored.m_secularActivePoles( active + 1 )
1172 : stored.m_secularActivePoles( active ) + activeRho;
1173 if( stored.m_eigenvalues( active ) < lower - secularTolerance ||
1174 stored.m_eigenvalues( active ) > upper + secularTolerance )
1175 {
1176 return fail( result, svdDeletionStatus::invalidSolverOutput );
1177 }
1178 stored.m_secularCandidateValues( active ) = stored.m_eigenvalues( active );
1179 }
1180 for( Eigen::Index deflated = 0; deflated < deflatedCount; ++deflated )
1181 {
1182 stored.m_secularCandidateValues( activeCount + deflated ) =
1183 stored.m_secularPoles( stored.m_secularDeflatedIndices[deflated] );
1184 }
1185
1186 Eigen::Index activePosition{ 0 };
1187 Eigen::Index deflatedPosition{ 0 };
1188 Eigen::Index mergedPosition{ 0 };
1189 while( activePosition < activeCount || deflatedPosition < deflatedCount )
1190 {
1191 const bool takeActive = deflatedPosition >= deflatedCount ||
1192 ( activePosition < activeCount &&
1193 stored.m_secularCandidateValues( activePosition ) <=
1194 stored.m_secularCandidateValues( activeCount + deflatedPosition ) );
1195 stored.m_secularOrder[mergedPosition++] = takeActive ? activePosition++ : activeCount + deflatedPosition++;
1196 }
1197 if( mergedPosition != rank )
1198 {
1199 // LCOV_EXCL_START -- the merge consumes each member of the checked partition once.
1200 return fail( result, svdDeletionStatus::invalidSolverOutput );
1201 // LCOV_EXCL_STOP
1202 }
1203
1204 result.m_storage->m_minimumPSDValue = -stored.m_secularCandidateValues( stored.m_secularOrder[rank - 1] );
1205 const realT tolerance = psdTolerance<realT>( rank, realT( 1 ) + rho );
1206 if( result.m_storage->m_minimumPSDValue < -tolerance )
1207 {
1208 return fail( result, svdDeletionStatus::nonPositiveSemidefinite );
1209 }
1210
1211 result.m_storage->m_clampedEigenvalues = 0;
1212 const realT vectorTolerance = realT( 256 ) * std::numeric_limits<realT>::epsilon() *
1213 static_cast<realT>( std::max<Eigen::Index>( 1, rank ) );
1214 const realT residualTolerance = vectorTolerance * ( realT( 1 ) + rho );
1215 for( Eigen::Index output = 0; output < rank; ++output )
1216 {
1217 const Eigen::Index candidate = stored.m_secularOrder[output];
1218 realT normalizedSquaredValue = -stored.m_secularCandidateValues( candidate );
1219 if( normalizedSquaredValue < realT( 0 ) )
1220 {
1221 normalizedSquaredValue = realT( 0 );
1222 ++result.m_storage->m_clampedEigenvalues;
1223 }
1224 if( !rescaleValue( result.m_storage->m_singularValues( output ),
1225 result.m_storage->m_squaredSingularValues( output ),
1226 normalizedSquaredValue,
1227 scale ) )
1228 {
1229 return fail( result, svdDeletionStatus::rescalingOverflow );
1230 }
1231 if( output >= outputRank )
1232 {
1233 continue;
1234 }
1235
1236 stored.m_secularVector.setZero();
1237 if( candidate < activeCount )
1238 {
1239 for( Eigen::Index active = 0; active < activeCount; ++active )
1240 {
1241 stored.m_secularVector( stored.m_secularActiveIndices[active] ) =
1242 stored.m_eigenvectors( active, candidate );
1243 }
1244 }
1245 else
1246 {
1247 const Eigen::Index deflated = candidate - activeCount;
1248 stored.m_secularVector( stored.m_secularDeflatedIndices[deflated] ) = realT( 1 );
1249 }
1250 for( Eigen::Index rotation = rotationCount; rotation-- > 0; )
1251 {
1252 const Eigen::Index first = stored.m_secularRotationFirst[rotation];
1253 const Eigen::Index second = stored.m_secularRotationSecond[rotation];
1254 const realT cosine = stored.m_secularRotationCosine[rotation];
1255 const realT sine = stored.m_secularRotationSine[rotation];
1256 const realT firstValue = stored.m_secularVector( first );
1257 const realT secondValue = stored.m_secularVector( second );
1258 stored.m_secularVector( first ) = cosine * firstValue - sine * secondValue;
1259 stored.m_secularVector( second ) = sine * firstValue + cosine * secondValue;
1260 }
1261
1262 realT vectorNorm{ 0 };
1263 Eigen::Index signIndex{ 0 };
1264 realT signMagnitude{ 0 };
1265 for( Eigen::Index row = 0; row < rank; ++row )
1266 {
1267 const realT value = stored.m_secularVector( row );
1268 if( !math::isFinite( value ) )
1269 {
1270 // LCOV_EXCL_START -- finite bounded Givens rotations preserve finite vector entries.
1271 return fail( result, svdDeletionStatus::nonFiniteOutput );
1272 // LCOV_EXCL_STOP
1273 }
1274 vectorNorm = std::hypot( vectorNorm, value );
1275 if( std::abs( value ) > signMagnitude )
1276 {
1277 signMagnitude = std::abs( value );
1278 signIndex = row;
1279 }
1280 }
1281 if( !math::isFinite( vectorNorm ) || std::abs( vectorNorm - realT( 1 ) ) > vectorTolerance )
1282 {
1283 return fail( result, svdDeletionStatus::invalidSolverOutput );
1284 }
1285 if( stored.m_secularVector( signIndex ) < realT( 0 ) )
1286 {
1287 stored.m_secularVector = -stored.m_secularVector;
1288 }
1289
1290 realT updateDot{ 0 };
1291 for( Eigen::Index row = 0; row < rank; ++row )
1292 {
1293 updateDot += stored.m_scaledDeletedTranspose( row, 0 ) * stored.m_secularVector( row );
1294 }
1295 realT residualNorm{ 0 };
1296 for( Eigen::Index row = 0; row < rank; ++row )
1297 {
1298 const realT normalized = stored.m_normalizedSingularValues( row );
1299 const realT residual = normalized * normalized * stored.m_secularVector( row ) -
1300 stored.m_scaledDeletedTranspose( row, 0 ) * updateDot -
1301 normalizedSquaredValue * stored.m_secularVector( row );
1302 residualNorm = std::hypot( residualNorm, residual );
1303 }
1304 if( !math::isFinite( residualNorm ) || residualNorm > residualTolerance )
1305 {
1306 return fail( result, svdDeletionStatus::invalidSolverOutput );
1307 }
1308 result.m_storage->m_rotation.matrix().col( output ) = stored.m_secularVector.matrix();
1309 }
1310
1311 result.m_storage->m_backend = svdDeletionBackend::rankOneSecular;
1312 result.m_storage->m_lapackInfo = 0;
1313 result.m_storage->m_status = result.m_storage->m_clampedEigenvalues > 0 ? svdDeletionStatus::successWithClamping
1315 return result.m_storage->m_status;
1316 }
1317
1318 // Execute the structured diagonal-minus-rank-one deletion core.
1319 static svdDeletionStatus rankOne( resultT &result, /* [out] updated system */
1320 svdDeletionConstVectorRef<realT> singularValues, /* [in] base values */
1321 svdDeletionConstMatrixRef<realT> deletedRows, /* [in] deleted rows */
1322 Eigen::Index outputRank, /* [in] requested rank */
1323 workspaceT &workspace /* [in,out] scratch */ )
1324 {
1325 if( !validCoreInputs( singularValues, deletedRows, outputRank ) )
1326 {
1327 return fail( result, svdDeletionStatus::invalidInput );
1328 }
1329 if( deletedRows.rows() == 0 )
1330 {
1331 return identity( result, singularValues, outputRank, svdDeletionBackend::rankOneSecular );
1332 }
1333 if( deletedRows.rows() != 1 )
1334 {
1335 return fail( result, svdDeletionStatus::unsupportedDeletionCount );
1336 }
1337
1338 const Eigen::Index rank = singularValues.size();
1339 const svdDeletionStatus status =
1340 prepare( result, workspace, rank, 1, outputRank, svdDeletionBackend::rankOneSecular );
1341 if( status != svdDeletionStatus::success )
1342 {
1343 return status;
1344 }
1345 loadDeletedRows( workspace, deletedRows );
1346 return rankOneLoaded( result, singularValues, outputRank, workspace );
1347 }
1348
1349 // Execute a normalized complement-preserving SVD with deleted rows already loaded.
1350 static svdDeletionStatus stableLoaded( resultT &result, /* [out] updated system */
1351 svdDeletionConstVectorRef<realT> singularValues, /* [in] base values */
1352 Eigen::Index deletedCount, /* [in] active rows */
1353 Eigen::Index outputRank, /* [in] requested rank */
1354 workspaceT &workspace /* [in,out] scratch */ )
1355 {
1356 const Eigen::Index rank = singularValues.size();
1357 const realT scale = normalizeSingularValues( workspace, singularValues );
1358 if( scale == realT( 0 ) )
1359 {
1360 return zeroSystem( result, outputRank, svdDeletionBackend::stableCore );
1361 }
1362
1363 const auto factorRows = workspace.m_storage->m_deletedRows.matrix().topRows( deletedCount );
1364 auto complement = workspace.m_storage->m_psdCore.matrix().topLeftCorner( deletedCount, deletedCount );
1365 complement.setIdentity();
1366 complement.noalias() -= factorRows * factorRows.transpose();
1367 MXLAPACK_INT found{ 0 };
1368 const MXLAPACK_INT lapackComplement = static_cast<MXLAPACK_INT>( deletedCount );
1369 MXLAPACK_INT info = callSyevr<realT>( 'V',
1370 'A',
1371 'L',
1372 lapackComplement,
1373 workspace.m_storage->m_psdCore.data(),
1374 static_cast<MXLAPACK_INT>( workspace.m_storage->m_psdCore.rows() ),
1375 realT( 0 ),
1376 realT( 0 ),
1377 1,
1378 lapackComplement,
1379 math::lamch<realT>( 'S' ),
1380 &found,
1381 workspace.m_storage->m_eigenvalues.data(),
1382 workspace.m_storage->m_eigenvectors.data(),
1383 static_cast<MXLAPACK_INT>( workspace.m_storage->m_eigenvectors.rows() ),
1384 workspace.m_storage->m_support.data(),
1385 workspace.m_storage->m_syevrWork.data(),
1386 static_cast<MXLAPACK_INT>( workspace.m_storage->m_syevrWork.size() ),
1387 workspace.m_storage->m_syevrIWork.data(),
1388 static_cast<MXLAPACK_INT>( workspace.m_storage->m_syevrIWork.size() ) );
1389 if( info != 0 || found != lapackComplement )
1390 {
1391 return fail( result, svdDeletionStatus::solverFailure, info );
1392 }
1393 for( Eigen::Index index = 0; index < deletedCount; ++index )
1394 {
1395 if( !math::isFinite( workspace.m_storage->m_eigenvalues( index ) ) )
1396 {
1397 return fail( result, svdDeletionStatus::nonFiniteOutput );
1398 }
1399 for( Eigen::Index row = 0; row < deletedCount; ++row )
1400 {
1401 if( !math::isFinite( workspace.m_storage->m_eigenvectors( row, index ) ) )
1402 {
1403 return fail( result, svdDeletionStatus::nonFiniteOutput );
1404 }
1405 }
1406 }
1407 if( !validAscendingSpectrum( workspace.m_storage->m_eigenvalues.head( deletedCount ) ) )
1408 {
1409 return fail( result, svdDeletionStatus::invalidSolverOutput );
1410 }
1411
1412 result.m_storage->m_minimumPSDValue = workspace.m_storage->m_eigenvalues( 0 );
1413 const realT tolerance =
1414 psdTolerance<realT>( deletedCount, realT( 1 ) + static_cast<realT>( factorRows.squaredNorm() ) );
1415 if( result.m_storage->m_minimumPSDValue < -tolerance )
1416 {
1417 return fail( result, svdDeletionStatus::nonPositiveSemidefinite );
1418 }
1419
1420 result.m_storage->m_clampedEigenvalues = 0;
1421 for( Eigen::Index index = 0; index < deletedCount; ++index )
1422 {
1423 if( workspace.m_storage->m_eigenvalues( index ) < realT( 0 ) )
1424 {
1425 workspace.m_storage->m_eigenvalues( index ) = realT( 0 );
1426 ++result.m_storage->m_clampedEigenvalues;
1427 }
1428 }
1429
1430 auto complementRoot =
1431 workspace.m_storage->m_complementRoot.matrix().topLeftCorner( deletedCount, deletedCount );
1432 for( Eigen::Index row = 0; row < deletedCount; ++row )
1433 {
1434 const realT root = std::sqrt( workspace.m_storage->m_eigenvalues( row ) );
1435 for( Eigen::Index column = 0; column < deletedCount; ++column )
1436 {
1437 complementRoot( row, column ) = root * workspace.m_storage->m_eigenvectors( column, row );
1438 }
1439 }
1440
1441 auto topCore = workspace.m_storage->m_stableCore.matrix().topRows( rank );
1442 topCore.setIdentity();
1443 topCore.noalias() -= factorRows.transpose() * factorRows;
1444 workspace.m_storage->m_stableCore.matrix().middleRows( rank, deletedCount ).noalias() =
1445 complementRoot * factorRows;
1446 for( Eigen::Index column = 0; column < rank; ++column )
1447 {
1448 workspace.m_storage->m_stableCore.matrix().col( column ).head( rank + deletedCount ) *=
1449 workspace.m_storage->m_normalizedSingularValues( column );
1450 }
1451
1452 realT unusedLeft{ 0 };
1453 const MXLAPACK_INT coreRows = static_cast<MXLAPACK_INT>( rank + deletedCount );
1454 const MXLAPACK_INT lapackRank = static_cast<MXLAPACK_INT>( rank );
1455 info = callGesvd<realT>( 'N',
1456 'S',
1457 coreRows,
1458 lapackRank,
1459 workspace.m_storage->m_stableCore.data(),
1460 static_cast<MXLAPACK_INT>( workspace.m_storage->m_stableCore.rows() ),
1461 workspace.m_storage->m_stableSingularValues.data(),
1462 &unusedLeft,
1463 1,
1464 workspace.m_storage->m_rightTranspose.data(),
1465 lapackRank,
1466 workspace.m_storage->m_gesvdWork.data(),
1467 static_cast<MXLAPACK_INT>( workspace.m_storage->m_gesvdWork.size() ) );
1468 if( info != 0 )
1469 {
1470 return fail( result, svdDeletionStatus::solverFailure, info );
1471 }
1472 if( !allFinite( workspace.m_storage->m_stableSingularValues ) ||
1473 !allFinite( workspace.m_storage->m_rightTranspose ) )
1474 {
1475 return fail( result, svdDeletionStatus::nonFiniteOutput );
1476 }
1477 if( !validDescendingSpectrum( workspace.m_storage->m_stableSingularValues ) )
1478 {
1479 return fail( result, svdDeletionStatus::invalidSolverOutput );
1480 }
1481
1482 for( Eigen::Index output = 0; output < rank; ++output )
1483 {
1484 const realT normalizedValue = workspace.m_storage->m_stableSingularValues( output );
1485 if( !rescaleSingularValue( result.m_storage->m_singularValues( output ),
1486 result.m_storage->m_squaredSingularValues( output ),
1487 normalizedValue,
1488 scale ) )
1489 {
1490 return fail( result, svdDeletionStatus::rescalingOverflow );
1491 }
1492 if( output < outputRank )
1493 {
1494 result.m_storage->m_rotation.matrix().col( output ) =
1495 workspace.m_storage->m_rightTranspose.matrix().row( output ).transpose();
1496 }
1497 }
1498
1499 result.m_storage->m_backend = svdDeletionBackend::stableCore;
1500 result.m_storage->m_lapackInfo = 0;
1501 result.m_storage->m_status = result.m_storage->m_clampedEigenvalues > 0 ? svdDeletionStatus::successWithClamping
1503 return result.m_storage->m_status;
1504 }
1505
1506 // Execute the complement-preserving small-SVD deletion core after workspace loading.
1507 static svdDeletionStatus stable( resultT &result, /* [out] updated system */
1508 svdDeletionConstVectorRef<realT> singularValues, /* [in] base values */
1509 svdDeletionConstMatrixRef<realT> deletedRows, /* [in] deleted rows */
1510 Eigen::Index outputRank, /* [in] requested rank */
1511 workspaceT &workspace /* [in,out] scratch */ )
1512 {
1513 if( !validCoreInputs( singularValues, deletedRows, outputRank ) )
1514 {
1515 return fail( result, svdDeletionStatus::invalidInput );
1516 }
1517 if( deletedRows.rows() == 0 )
1518 {
1519 return identity( result, singularValues, outputRank, svdDeletionBackend::stableCore );
1520 }
1521
1522 const Eigen::Index rank = singularValues.size();
1523 svdDeletionStatus status =
1524 prepare( result, workspace, rank, deletedRows.rows(), outputRank, svdDeletionBackend::stableCore );
1525 if( status != svdDeletionStatus::success )
1526 {
1527 return status;
1528 }
1529 loadDeletedRows( workspace, deletedRows );
1530 return stableLoaded( result, singularValues, deletedRows.rows(), outputRank, workspace );
1531 }
1532
1533 // Gather arbitrary factor rows and dispatch to the selected core.
1534 static svdDeletionStatus remove( resultT &result, /* [out] updated system */
1535 svdDeletionConstVectorRef<realT> singularValues, /* [in] base values */
1536 svdDeletionConstMatrixRef<realT> factor, /* [in] deleted-side factor */
1537 svdDeletionConstIndexViewV2 indices, /* [in] rows to remove */
1538 Eigen::Index outputRank, /* [in] requested rank */
1539 workspaceT &workspace, /* [in,out] scratch */
1540 svdDeletionBackend backend /* [in] selected backend */ )
1541 {
1542 const Eigen::Index rank = singularValues.size();
1543 if( !validSingularInputs( singularValues, outputRank ) || !validBackend( backend ) || factor.rows() <= 0 ||
1544 factor.rows() < rank || factor.cols() != rank || !validLapackDimension( factor.rows() ) ||
1545 !validAbiIndexView( indices ) || indices.size >= static_cast<std::int64_t>( factor.rows() ) ||
1546 indices.size > static_cast<std::int64_t>( std::numeric_limits<MXLAPACK_INT>::max() - rank ) )
1547 {
1548 return fail( result, svdDeletionStatus::invalidInput );
1549 }
1550
1551 Eigen::Index previous = -1;
1552 for( std::int64_t offset = 0; offset < indices.size; ++offset )
1553 {
1554 const std::int64_t rawIndex = abiIndexAt( indices, offset );
1555 if( !validAbiDimension( rawIndex ) )
1556 {
1557 return fail( result, svdDeletionStatus::invalidInput );
1558 }
1559 const Eigen::Index index = static_cast<Eigen::Index>( rawIndex );
1560 if( index < 0 || index >= factor.rows() || index <= previous )
1561 {
1562 return fail( result, svdDeletionStatus::invalidInput );
1563 }
1564 previous = index;
1565 }
1566
1567 if( indices.size == 0 )
1568 {
1569 return identity( result, singularValues, outputRank, backend );
1570 }
1571
1572 if( backend == svdDeletionBackend::rankOneSecular && indices.size != 1 )
1573 {
1574 return fail( result, svdDeletionStatus::unsupportedDeletionCount );
1575 }
1576
1577 const Eigen::Index deletedCount = static_cast<Eigen::Index>( indices.size );
1578 const svdDeletionStatus status = prepare( result, workspace, rank, deletedCount, outputRank, backend );
1579 if( status != svdDeletionStatus::success )
1580 {
1581 return status;
1582 }
1583
1584 for( Eigen::Index row = 0; row < deletedCount; ++row )
1585 {
1586 const Eigen::Index source = static_cast<Eigen::Index>( abiIndexAt( indices, row ) );
1587 for( Eigen::Index column = 0; column < rank; ++column )
1588 {
1589 const realT value = factor( source, column );
1590 if( !math::isFinite( value ) )
1591 {
1592 return fail( result, svdDeletionStatus::invalidInput );
1593 }
1594 workspace.m_storage->m_deletedRows( row, column ) = value;
1595 }
1596 }
1597
1598 switch( backend )
1599 {
1601 return leadingLoaded( result, singularValues, deletedCount, outputRank, workspace );
1603 return stableLoaded( result, singularValues, deletedCount, outputRank, workspace );
1605 return rankOneLoaded( result, singularValues, outputRank, workspace );
1606 }
1607 // LCOV_EXCL_START -- the backend is validated before the exhaustive switch.
1608 return fail( result, svdDeletionStatus::invalidInput );
1609 // LCOV_EXCL_STOP
1610 }
1611};
1612
1613} // namespace detail
1614
1616{
1617 switch( backend )
1618 {
1620 return "leadingCovariance";
1622 return "stableCore";
1624 return "rankOneSecular";
1625 }
1626 return "unknown";
1627}
1628
1630{
1631 switch( status )
1632 {
1634 return "notComputed";
1636 return "success";
1638 return "successWithClamping";
1640 return "invalidInput";
1642 return "allocationFailure";
1644 return "workspaceQueryFailure";
1646 return "solverFailure";
1648 return "nonFiniteOutput";
1650 return "invalidSolverOutput";
1652 return "rescalingOverflow";
1654 return "nonPositiveSemidefinite";
1656 return "factorNotOrthonormal";
1658 return "unsupportedDeletionCount";
1659 }
1660 return "unknown";
1661}
1662
1664{
1666}
1667
1668template <typename realT, typename abiT>
1669svdDeletionResult<realT, abiT>::svdDeletionResult() : m_storage( std::make_unique<storage>() )
1670{
1671}
1672
1673template <typename realT, typename abiT>
1675
1676template <typename realT, typename abiT>
1678
1679template <typename realT, typename abiT>
1682
1683template <typename realT, typename abiT>
1684bool svdDeletionResult<realT, abiT>::ensureStorage() noexcept
1685{
1686 if( m_storage )
1687 {
1688 return true;
1689 }
1690
1691 try
1692 {
1693 detail::callOperationHook<realT>( detail::svdDeletionTestOperation::prepareResult );
1694 m_storage = std::make_unique<storage>();
1695 }
1696 catch( const std::bad_alloc & )
1697 {
1698 return false;
1699 }
1700 catch( const std::length_error & )
1701 {
1702 return false;
1703 }
1704 return true;
1705}
1706
1707template <typename realT, typename abiT>
1708svdDeletionStatus svdDeletionResult<realT, abiT>::prepareAbiV2( std::int64_t abiBaseRank, std::int64_t abiOutputRank )
1709{
1710 if( !ensureStorage() )
1711 {
1713 }
1714 auto &stored = *m_storage;
1715
1716 if( !detail::validAbiDimension( abiBaseRank ) || !detail::validAbiDimension( abiOutputRank ) )
1717 {
1718 stored.m_status = svdDeletionStatus::invalidInput;
1719 return stored.m_status;
1720 }
1721 const Eigen::Index baseRank = static_cast<Eigen::Index>( abiBaseRank );
1722 const Eigen::Index outputRank = static_cast<Eigen::Index>( abiOutputRank );
1723
1724 if( baseRank <= 0 || outputRank <= 0 || outputRank > baseRank || !detail::validLapackDimension( baseRank ) ||
1725 !detail::validArrayShape<realT>( baseRank, outputRank ) || !detail::validArrayShape<realT>( outputRank, 1 ) )
1726 {
1727 stored.m_status = svdDeletionStatus::invalidInput;
1728 return stored.m_status;
1729 }
1730
1731 try
1732 {
1733 if( stored.m_baseRank != baseRank || stored.m_maximumOutputRank < outputRank )
1734 {
1735 detail::callOperationHook<realT>( detail::svdDeletionTestOperation::prepareResult );
1736 stored.m_singularValues.resize( baseRank );
1737 stored.m_squaredSingularValues.resize( baseRank );
1738 stored.m_rotation.resize( baseRank, outputRank );
1739 stored.m_maximumOutputRank = outputRank;
1740 }
1741 }
1742 catch( const std::bad_alloc & )
1743 {
1744 stored.m_status = svdDeletionStatus::allocationFailure;
1745 return stored.m_status;
1746 }
1747 catch( const std::length_error & )
1748 {
1749 stored.m_status = svdDeletionStatus::allocationFailure;
1750 return stored.m_status;
1751 }
1752
1753 stored.m_baseRank = baseRank;
1754 stored.m_outputRank = outputRank;
1755 stored.m_status = svdDeletionStatus::notComputed;
1756 stored.m_lapackInfo = 0;
1757 stored.m_clampedEigenvalues = 0;
1758 stored.m_minimumPSDValue = realT( 0 );
1760}
1761
1762template <typename realT, typename abiT>
1764{
1765 return m_storage ? m_storage->m_status : svdDeletionStatus::notComputed;
1766}
1767
1768template <typename realT, typename abiT>
1770{
1771 return m_storage ? m_storage->m_backend : svdDeletionBackend::stableCore;
1772}
1773
1774template <typename realT, typename abiT>
1775svdDeletionConstVectorViewV2<realT> svdDeletionResult<realT, abiT>::singularValuesViewAbiV2() const noexcept
1776{
1777 return m_storage
1778 ? svdDeletionConstVectorViewV2<realT>{ m_storage->m_singularValues.data(),
1779 static_cast<std::int64_t>( m_storage->m_singularValues.size() ) }
1780 : svdDeletionConstVectorViewV2<realT>{};
1781}
1782
1783template <typename realT, typename abiT>
1784svdDeletionConstVectorViewV2<realT> svdDeletionResult<realT, abiT>::squaredSingularValuesViewAbiV2() const noexcept
1785{
1786 return m_storage ? svdDeletionConstVectorViewV2<realT>{ m_storage->m_squaredSingularValues.data(),
1787 static_cast<std::int64_t>(
1788 m_storage->m_squaredSingularValues.size() ) }
1790}
1791
1792template <typename realT, typename abiT>
1793svdDeletionConstMatrixViewV2<realT> svdDeletionResult<realT, abiT>::rotationViewAbiV2() const noexcept
1794{
1795 if( !m_storage )
1796 {
1797 return {};
1798 }
1799 return { m_storage->m_rotation.data(),
1800 static_cast<std::int64_t>( m_storage->m_rotation.rows() ),
1801 static_cast<std::int64_t>( m_storage->m_outputRank ),
1802 static_cast<std::int64_t>( m_storage->m_rotation.outerStride() ) };
1803}
1804
1805template <typename realT, typename abiT>
1806std::int64_t svdDeletionResult<realT, abiT>::baseRank() const noexcept
1807{
1808 return m_storage ? m_storage->m_baseRank : 0;
1809}
1810
1811template <typename realT, typename abiT>
1813{
1814 return m_storage ? m_storage->m_outputRank : 0;
1815}
1816
1817template <typename realT, typename abiT>
1819{
1820 return m_storage ? m_storage->m_maximumOutputRank : 0;
1821}
1822
1823template <typename realT, typename abiT>
1825{
1826 return m_storage ? m_storage->m_clampedEigenvalues : 0;
1827}
1828
1829template <typename realT, typename abiT>
1831{
1832 return m_storage ? m_storage->m_minimumPSDValue : realT( 0 );
1833}
1834
1835template <typename realT, typename abiT>
1837{
1838 return m_storage ? m_storage->m_lapackInfo : 0;
1839}
1840
1841template <typename realT, typename abiT>
1842svdDeletionWorkspace<realT, abiT>::svdDeletionWorkspace() : m_storage( std::make_unique<storage>() )
1843{
1844}
1845
1846template <typename realT, typename abiT>
1850
1851template <typename realT, typename abiT>
1853
1854template <typename realT, typename abiT>
1857
1858template <typename realT, typename abiT>
1859bool svdDeletionWorkspace<realT, abiT>::ensureStorage() noexcept
1860{
1861 if( m_storage )
1862 {
1863 return true;
1864 }
1865
1866 try
1867 {
1868 detail::callOperationHook<realT>( detail::svdDeletionTestOperation::prepareWorkspace );
1869 m_storage = std::make_unique<storage>();
1870 }
1871 catch( const std::bad_alloc & )
1872 {
1873 return false;
1874 }
1875 catch( const std::length_error & )
1876 {
1877 return false;
1878 }
1879 return true;
1880}
1881
1882template <typename realT, typename abiT>
1883svdDeletionStatus svdDeletionWorkspace<realT, abiT>::prepareAbiV2( std::int64_t abiBaseRank,
1884 std::int64_t abiMaximumDeleted,
1885 svdDeletionBackend backend )
1886{
1887 if( !ensureStorage() )
1888 {
1890 }
1891 auto &stored = *m_storage;
1892 stored.m_lapackInfo = 0;
1893
1894 if( !detail::validAbiDimension( abiBaseRank ) || !detail::validAbiDimension( abiMaximumDeleted ) )
1895 {
1897 }
1898 const Eigen::Index baseRank = static_cast<Eigen::Index>( abiBaseRank );
1899 const Eigen::Index maximumDeleted = static_cast<Eigen::Index>( abiMaximumDeleted );
1900 if( baseRank <= 0 || maximumDeleted < 0 || !detail::svdDeletionImplementation<realT>::validBackend( backend ) ||
1901 !detail::validLapackDimension( baseRank ) || !detail::validLapackDimension( maximumDeleted ) ||
1902 maximumDeleted > std::numeric_limits<MXLAPACK_INT>::max() - baseRank )
1903 {
1905 }
1906 if( backend == svdDeletionBackend::rankOneSecular && maximumDeleted > 1 )
1907 {
1909 }
1910 if( stored.m_prepared && stored.m_baseRank == baseRank && stored.m_maximumDeleted >= maximumDeleted &&
1911 stored.m_backend == backend )
1912 {
1914 }
1915
1916 const Eigen::Index eigenSize = backend == svdDeletionBackend::stableCore ? maximumDeleted : baseRank;
1917 const Eigen::Index syevrSize = backend == svdDeletionBackend::rankOneSecular ? 0 : eigenSize;
1918 if( syevrSize > std::numeric_limits<Eigen::Index>::max() / 2 )
1919 {
1920 return svdDeletionStatus::invalidInput; // LCOV_EXCL_LINE -- requires an Eigen index narrower than LAPACK.
1921 }
1922 const Eigen::Index supportSize = syevrSize > 0 ? 2 * std::max<Eigen::Index>( 1, syevrSize ) : 0;
1923 Eigen::Index minimumGesvdWork{ 0 };
1924 bool validShapes =
1925 detail::validArrayShape<realT>( maximumDeleted, baseRank ) && detail::validArrayShape<realT>( baseRank, 1 ) &&
1926 detail::validArrayShape<realT>( eigenSize, eigenSize ) && detail::validArrayShape<realT>( eigenSize, 1 ) &&
1927 detail::validVectorSize<MXLAPACK_INT>( supportSize );
1929 {
1930 validShapes = validShapes && detail::validArrayShape<realT>( baseRank, maximumDeleted );
1931 }
1932 else if( backend == svdDeletionBackend::stableCore )
1933 {
1934 validShapes = validShapes && detail::validArrayShape<realT>( maximumDeleted, maximumDeleted ) &&
1935 detail::validArrayShape<realT>( baseRank + maximumDeleted, baseRank ) &&
1936 detail::validArrayShape<realT>( baseRank, baseRank );
1937 const unsigned long long rank = static_cast<unsigned long long>( baseRank );
1938 const unsigned long long deleted = static_cast<unsigned long long>( maximumDeleted );
1939 const unsigned long long lapackMaximum =
1940 static_cast<unsigned long long>( std::numeric_limits<MXLAPACK_INT>::max() );
1941 validShapes = validShapes && rank <= lapackMaximum / 5 && rank <= ( lapackMaximum - deleted ) / 4;
1942 if( validShapes )
1943 {
1944 minimumGesvdWork = std::max<Eigen::Index>( 1, std::max( 5 * baseRank, 4 * baseRank + maximumDeleted ) );
1945 validShapes = detail::validVectorSize<realT>( minimumGesvdWork );
1946 }
1947 }
1948 else
1949 {
1950 validShapes = validShapes && detail::validArrayShape<realT>( baseRank, baseRank ) &&
1951 detail::validVectorSize<realT>( baseRank ) && detail::validVectorSize<Eigen::Index>( baseRank );
1952 }
1953 if( !validShapes )
1954 {
1956 }
1957
1958 try
1959 {
1960 detail::callOperationHook<realT>( detail::svdDeletionTestOperation::prepareWorkspace );
1961
1962 stored.m_deletedRows.resize( maximumDeleted, baseRank );
1963 stored.m_normalizedSingularValues.resize( baseRank );
1964 stored.m_scaledDeletedTranspose.resize( 0, 0 );
1965 stored.m_complementRoot.resize( 0, 0 );
1966 stored.m_stableCore.resize( 0, 0 );
1967 stored.m_rightTranspose.resize( 0, 0 );
1968 stored.m_stableSingularValues.resize( 0 );
1969 stored.m_secularPoles.resize( 0 );
1970 stored.m_secularUpdate.resize( 0 );
1971 stored.m_secularActivePoles.resize( 0 );
1972 stored.m_secularActiveUpdate.resize( 0 );
1973 stored.m_secularCandidateValues.resize( 0 );
1974 stored.m_secularVector.resize( 0 );
1975 stored.m_secularActiveIndices.clear();
1976 stored.m_secularDeflatedIndices.clear();
1977 stored.m_secularOrder.clear();
1978 stored.m_secularRotationFirst.clear();
1979 stored.m_secularRotationSecond.clear();
1980 stored.m_secularRotationCosine.clear();
1981 stored.m_secularRotationSine.clear();
1982
1984 {
1985 stored.m_scaledDeletedTranspose.resize( baseRank, maximumDeleted );
1986 stored.m_psdCore.resize( baseRank, baseRank );
1987 }
1988 else if( backend == svdDeletionBackend::stableCore )
1989 {
1990 stored.m_psdCore.resize( maximumDeleted, maximumDeleted );
1991 stored.m_complementRoot.resize( maximumDeleted, maximumDeleted );
1992 stored.m_stableCore.resize( baseRank + maximumDeleted, baseRank );
1993 stored.m_rightTranspose.resize( baseRank, baseRank );
1994 stored.m_stableSingularValues.resize( baseRank );
1995 }
1996 else
1997 {
1998 stored.m_scaledDeletedTranspose.resize( baseRank, 1 );
1999 stored.m_psdCore.resize( baseRank, baseRank );
2000 stored.m_secularPoles.resize( baseRank );
2001 stored.m_secularUpdate.resize( baseRank );
2002 stored.m_secularActivePoles.resize( baseRank );
2003 stored.m_secularActiveUpdate.resize( baseRank );
2004 stored.m_secularCandidateValues.resize( baseRank );
2005 stored.m_secularVector.resize( baseRank );
2006 stored.m_secularActiveIndices.resize( static_cast<std::size_t>( baseRank ) );
2007 stored.m_secularDeflatedIndices.resize( static_cast<std::size_t>( baseRank ) );
2008 stored.m_secularOrder.resize( static_cast<std::size_t>( baseRank ) );
2009 stored.m_secularRotationFirst.resize( static_cast<std::size_t>( baseRank ) );
2010 stored.m_secularRotationSecond.resize( static_cast<std::size_t>( baseRank ) );
2011 stored.m_secularRotationCosine.resize( static_cast<std::size_t>( baseRank ) );
2012 stored.m_secularRotationSine.resize( static_cast<std::size_t>( baseRank ) );
2013 }
2014
2015 stored.m_eigenvectors.resize( eigenSize, eigenSize );
2016 stored.m_eigenvalues.resize( eigenSize );
2017 stored.m_support.resize( static_cast<std::size_t>( supportSize ) );
2018 stored.m_syevrWork.clear();
2019 stored.m_syevrIWork.clear();
2020 stored.m_gesvdWork.clear();
2021
2022 if( syevrSize > 0 )
2023 {
2024 stored.m_psdCore.matrix().setIdentity();
2025 stored.m_eigenvectors.setZero();
2026 stored.m_eigenvalues.setZero();
2027 realT workQuery{ 0 };
2028 MXLAPACK_INT integerWorkQuery{ 0 };
2029 MXLAPACK_INT found{ 0 };
2030 const MXLAPACK_INT lapackEigenSize = static_cast<MXLAPACK_INT>( syevrSize );
2031 const MXLAPACK_INT info = detail::callSyevr<realT>( 'V',
2032 'A',
2033 'L',
2034 lapackEigenSize,
2035 stored.m_psdCore.data(),
2036 lapackEigenSize,
2037 realT( 0 ),
2038 realT( 0 ),
2039 1,
2040 lapackEigenSize,
2041 math::lamch<realT>( 'S' ),
2042 &found,
2043 stored.m_eigenvalues.data(),
2044 stored.m_eigenvectors.data(),
2045 lapackEigenSize,
2046 stored.m_support.data(),
2047 &workQuery,
2048 -1,
2049 &integerWorkQuery,
2050 -1 );
2051 MXLAPACK_INT realWorkSize{ 0 };
2052 if( info != 0 || !detail::querySize( realWorkSize, workQuery ) || integerWorkQuery < 1 ||
2053 !detail::validVectorSize<realT>( realWorkSize ) ||
2054 !detail::validVectorSize<MXLAPACK_INT>( integerWorkQuery ) )
2055 {
2056 clear();
2057 stored.m_lapackInfo = info;
2059 }
2060 stored.m_syevrWork.resize( static_cast<std::size_t>( realWorkSize ) );
2061 stored.m_syevrIWork.resize( static_cast<std::size_t>( integerWorkQuery ) );
2062 }
2063
2064 if( backend == svdDeletionBackend::stableCore )
2065 {
2066 stored.m_stableCore.setZero();
2067 stored.m_rightTranspose.setZero();
2068 stored.m_stableSingularValues.setZero();
2069 realT unusedLeft{ 0 };
2070 realT workQuery{ 0 };
2071 const MXLAPACK_INT rows = static_cast<MXLAPACK_INT>( baseRank + maximumDeleted );
2072 const MXLAPACK_INT cols = static_cast<MXLAPACK_INT>( baseRank );
2073 const MXLAPACK_INT info = detail::callGesvd<realT>( 'N',
2074 'S',
2075 rows,
2076 cols,
2077 stored.m_stableCore.data(),
2078 rows,
2079 stored.m_stableSingularValues.data(),
2080 &unusedLeft,
2081 1,
2082 stored.m_rightTranspose.data(),
2083 cols,
2084 &workQuery,
2085 -1 );
2086 MXLAPACK_INT workSize{ 0 };
2087 if( info != 0 || !detail::querySize( workSize, workQuery ) )
2088 {
2089 clear();
2090 stored.m_lapackInfo = info;
2092 }
2093 workSize = std::max( workSize, static_cast<MXLAPACK_INT>( minimumGesvdWork ) );
2094 if( !detail::validVectorSize<realT>( workSize ) )
2095 {
2096 clear(); // LCOV_EXCL_LINE -- requires size_t narrower than the configured LAPACK integer ABI.
2097 return svdDeletionStatus::workspaceQueryFailure; // LCOV_EXCL_LINE
2098 }
2099 stored.m_gesvdWork.resize( static_cast<std::size_t>( workSize ) );
2100 }
2101 }
2102 catch( const std::bad_alloc & )
2103 {
2104 clear();
2106 }
2107 catch( const std::length_error & )
2108 {
2109 clear();
2111 }
2112
2113 stored.m_baseRank = baseRank;
2114 stored.m_maximumDeleted = maximumDeleted;
2115 stored.m_backend = backend;
2116 stored.m_prepared = true;
2118}
2119
2120template <typename realT, typename abiT>
2122{
2123 if( !m_storage )
2124 {
2125 return;
2126 }
2127
2128 m_storage->m_deletedRows.resize( 0, 0 );
2129 m_storage->m_normalizedSingularValues.resize( 0 );
2130 m_storage->m_scaledDeletedTranspose.resize( 0, 0 );
2131 m_storage->m_psdCore.resize( 0, 0 );
2132 m_storage->m_eigenvectors.resize( 0, 0 );
2133 m_storage->m_eigenvalues.resize( 0 );
2134 m_storage->m_complementRoot.resize( 0, 0 );
2135 m_storage->m_stableCore.resize( 0, 0 );
2136 m_storage->m_rightTranspose.resize( 0, 0 );
2137 m_storage->m_stableSingularValues.resize( 0 );
2138 m_storage->m_secularPoles.resize( 0 );
2139 m_storage->m_secularUpdate.resize( 0 );
2140 m_storage->m_secularActivePoles.resize( 0 );
2141 m_storage->m_secularActiveUpdate.resize( 0 );
2142 m_storage->m_secularCandidateValues.resize( 0 );
2143 m_storage->m_secularVector.resize( 0 );
2144 m_storage->m_secularActiveIndices.clear();
2145 m_storage->m_secularDeflatedIndices.clear();
2146 m_storage->m_secularOrder.clear();
2147 m_storage->m_secularRotationFirst.clear();
2148 m_storage->m_secularRotationSecond.clear();
2149 m_storage->m_secularRotationCosine.clear();
2150 m_storage->m_secularRotationSine.clear();
2151 m_storage->m_support.clear();
2152 m_storage->m_syevrWork.clear();
2153 m_storage->m_syevrIWork.clear();
2154 m_storage->m_gesvdWork.clear();
2155 m_storage->m_baseRank = 0;
2156 m_storage->m_maximumDeleted = 0;
2157 m_storage->m_backend = svdDeletionBackend::stableCore;
2158 m_storage->m_prepared = false;
2159 m_storage->m_lapackInfo = 0;
2160}
2161
2162template <typename realT, typename abiT>
2164{
2165 return m_storage && m_storage->m_prepared;
2166}
2167
2168template <typename realT, typename abiT>
2170{
2171 return m_storage ? m_storage->m_baseRank : 0;
2172}
2173
2174template <typename realT, typename abiT>
2176{
2177 return m_storage ? m_storage->m_maximumDeleted : 0;
2178}
2179
2180template <typename realT, typename abiT>
2182{
2183 return m_storage ? m_storage->m_backend : svdDeletionBackend::stableCore;
2184}
2185
2186template <typename realT, typename abiT>
2188{
2189 return m_storage ? m_storage->m_lapackInfo : 0;
2190}
2191
2192namespace
2193{
2194
2195template <typename realT>
2196svdDeletionStatus validateSvdDeletionFactorImpl( svdDeletionConstMatrixRef<realT> factor, realT tolerance )
2197{
2198 if( factor.rows() < factor.cols() || factor.cols() <= 0 || !detail::validLapackDimension( factor.rows() ) ||
2199 !detail::validLapackDimension( factor.cols() ) || !math::isFinite( tolerance ) || tolerance < realT( 0 ) ||
2200 !detail::validArrayShape<realT>( factor.cols(), factor.cols() ) || !detail::allFinite( factor ) )
2201 {
2203 }
2204
2205 try
2206 {
2207 detail::callOperationHook<realT>( detail::svdDeletionTestOperation::validateFactor );
2208 svdDeletionStorageMatrix<realT> gram( factor.cols(), factor.cols() );
2209 gram.matrix().noalias() = factor.matrix().transpose() * factor.matrix();
2210 if( !detail::allFinite( gram ) )
2211 {
2213 }
2214 for( Eigen::Index index = 0; index < gram.rows(); ++index )
2215 {
2216 gram( index, index ) -= realT( 1 );
2217 }
2218
2219 if( tolerance == realT( 0 ) )
2220 {
2221 tolerance = realT( 64 ) * std::numeric_limits<realT>::epsilon() *
2222 static_cast<realT>( std::max( factor.rows(), factor.cols() ) );
2223 }
2224 if( gram.abs().maxCoeff() > tolerance )
2225 {
2227 }
2228 }
2229 catch( const std::bad_alloc & )
2230 {
2232 }
2233 catch( const std::length_error & )
2234 {
2236 }
2237
2239}
2240
2241} // namespace
2242
2243svdDeletionStatus detail::validateSvdDeletionFactorAbiV2( svdDeletionConstMatrixViewV2<float> factor, float tolerance )
2244{
2245 if( !detail::validAbiMatrixView( factor ) )
2246 {
2248 }
2249 return validateSvdDeletionFactorImpl<float>( detail::mapAbiMatrix( factor ), tolerance );
2250}
2251
2252svdDeletionStatus detail::validateSvdDeletionFactorAbiV2( svdDeletionConstMatrixViewV2<double> factor,
2253 double tolerance )
2254{
2255 if( !detail::validAbiMatrixView( factor ) )
2256 {
2258 }
2259 return validateSvdDeletionFactorImpl<double>( detail::mapAbiMatrix( factor ), tolerance );
2260}
2261
2262template <typename realT>
2263svdDeletionStatus detail::svdDeletionLeadingCoreAbiV2( svdDeletionResult<realT> &result,
2266 std::int64_t outputRank,
2267 svdDeletionWorkspace<realT> &workspace )
2268{
2269 if( !detail::validAbiVectorView( singularValues ) || !detail::validAbiMatrixView( deletedRows ) ||
2270 !detail::validAbiDimension( outputRank ) )
2271 {
2272 return detail::svdDeletionImplementation<realT>::fail( result, svdDeletionStatus::invalidInput );
2273 }
2274 return detail::svdDeletionImplementation<realT>::leading( result,
2275 detail::mapAbiVector( singularValues ),
2276 detail::mapAbiMatrix( deletedRows ),
2277 static_cast<Eigen::Index>( outputRank ),
2278 workspace );
2279}
2280
2281template <typename realT>
2282svdDeletionStatus detail::svdDeletionStableCoreAbiV2( svdDeletionResult<realT> &result,
2285 std::int64_t outputRank,
2286 svdDeletionWorkspace<realT> &workspace )
2287{
2288 if( !detail::validAbiVectorView( singularValues ) || !detail::validAbiMatrixView( deletedRows ) ||
2289 !detail::validAbiDimension( outputRank ) )
2290 {
2291 return detail::svdDeletionImplementation<realT>::fail( result, svdDeletionStatus::invalidInput );
2292 }
2293 return detail::svdDeletionImplementation<realT>::stable( result,
2294 detail::mapAbiVector( singularValues ),
2295 detail::mapAbiMatrix( deletedRows ),
2296 static_cast<Eigen::Index>( outputRank ),
2297 workspace );
2298}
2299
2300template <typename realT>
2301svdDeletionStatus detail::svdDeletionCoreAbiV2( svdDeletionResult<realT> &result,
2304 std::int64_t outputRank,
2305 svdDeletionWorkspace<realT> &workspace,
2306 svdDeletionBackend backend )
2307{
2308 if( !detail::validAbiVectorView( singularValues ) || !detail::validAbiMatrixView( deletedRows ) ||
2309 !detail::validAbiDimension( outputRank ) || !detail::svdDeletionImplementation<realT>::validBackend( backend ) )
2310 {
2311 return detail::svdDeletionImplementation<realT>::fail( result, svdDeletionStatus::invalidInput );
2312 }
2313 switch( backend )
2314 {
2316 return detail::svdDeletionImplementation<realT>::leading( result,
2317 detail::mapAbiVector( singularValues ),
2318 detail::mapAbiMatrix( deletedRows ),
2319 static_cast<Eigen::Index>( outputRank ),
2320 workspace );
2322 return detail::svdDeletionImplementation<realT>::stable( result,
2323 detail::mapAbiVector( singularValues ),
2324 detail::mapAbiMatrix( deletedRows ),
2325 static_cast<Eigen::Index>( outputRank ),
2326 workspace );
2328 return detail::svdDeletionImplementation<realT>::rankOne( result,
2329 detail::mapAbiVector( singularValues ),
2330 detail::mapAbiMatrix( deletedRows ),
2331 static_cast<Eigen::Index>( outputRank ),
2332 workspace );
2333 }
2334 // LCOV_EXCL_START -- the backend is validated before the exhaustive switch.
2335 return detail::svdDeletionImplementation<realT>::fail( result, svdDeletionStatus::invalidInput );
2336 // LCOV_EXCL_STOP
2337}
2338
2339template <typename realT>
2340svdDeletionStatus detail::svdRemoveRowsAbiV2( svdDeletionResult<realT> &result,
2343 svdDeletionConstIndexViewV2 deletedIndices,
2344 std::int64_t outputRank,
2345 svdDeletionWorkspace<realT> &workspace,
2346 svdDeletionBackend backend )
2347{
2348 if( !detail::validAbiVectorView( singularValues ) || !detail::validAbiMatrixView( leftFactor ) ||
2349 !detail::validAbiIndexView( deletedIndices ) || !detail::validAbiDimension( outputRank ) )
2350 {
2351 return detail::svdDeletionImplementation<realT>::fail( result, svdDeletionStatus::invalidInput );
2352 }
2353 return detail::svdDeletionImplementation<realT>::remove( result,
2354 detail::mapAbiVector( singularValues ),
2355 detail::mapAbiMatrix( leftFactor ),
2356 deletedIndices,
2357 static_cast<Eigen::Index>( outputRank ),
2358 workspace,
2359 backend );
2360}
2361
2362template <typename realT>
2363svdDeletionStatus detail::svdRemoveColumnsAbiV2( svdDeletionResult<realT> &result,
2366 svdDeletionConstIndexViewV2 deletedIndices,
2367 std::int64_t outputRank,
2368 svdDeletionWorkspace<realT> &workspace,
2369 svdDeletionBackend backend )
2370{
2371 if( !detail::validAbiVectorView( singularValues ) || !detail::validAbiMatrixView( rightFactor ) ||
2372 !detail::validAbiIndexView( deletedIndices ) || !detail::validAbiDimension( outputRank ) )
2373 {
2374 return detail::svdDeletionImplementation<realT>::fail( result, svdDeletionStatus::invalidInput );
2375 }
2376 return detail::svdDeletionImplementation<realT>::remove( result,
2377 detail::mapAbiVector( singularValues ),
2378 detail::mapAbiMatrix( rightFactor ),
2379 deletedIndices,
2380 static_cast<Eigen::Index>( outputRank ),
2381 workspace,
2382 backend );
2383}
2384
2385template class svdDeletionResult<float>;
2386template class svdDeletionResult<double>;
2387template class svdDeletionWorkspace<float>;
2388template class svdDeletionWorkspace<double>;
2389
2390template svdDeletionStatus detail::svdDeletionLeadingCoreAbiV2<float>( svdDeletionResult<float> &,
2393 std::int64_t,
2395template svdDeletionStatus detail::svdDeletionLeadingCoreAbiV2<double>( svdDeletionResult<double> &,
2398 std::int64_t,
2400
2401template svdDeletionStatus detail::svdDeletionStableCoreAbiV2<float>( svdDeletionResult<float> &,
2404 std::int64_t,
2406template svdDeletionStatus detail::svdDeletionStableCoreAbiV2<double>( svdDeletionResult<double> &,
2409 std::int64_t,
2411
2412template svdDeletionStatus detail::svdDeletionCoreAbiV2<float>( svdDeletionResult<float> &,
2415 std::int64_t,
2418template svdDeletionStatus detail::svdDeletionCoreAbiV2<double>( svdDeletionResult<double> &,
2421 std::int64_t,
2424
2425template svdDeletionStatus detail::svdRemoveRowsAbiV2<float>( svdDeletionResult<float> &,
2429 std::int64_t,
2432template svdDeletionStatus detail::svdRemoveRowsAbiV2<double>( svdDeletionResult<double> &,
2436 std::int64_t,
2439
2440template svdDeletionStatus detail::svdRemoveColumnsAbiV2<float>( svdDeletionResult<float> &,
2444 std::int64_t,
2447template svdDeletionStatus detail::svdRemoveColumnsAbiV2<double>( svdDeletionResult<double> &,
2451 std::int64_t,
2454
2455template detail::svdDeletionTestHooks<float> &detail::svdDeletionHooks<float>();
2456template detail::svdDeletionTestHooks<double> &detail::svdDeletionHooks<double>();
2457
2458} // namespace math
2459} // namespace mx
std::string singularValues(const std::string &dmName, bool create=false)
The path for the deformable mirror (DM) influence function pseudo-inverse singular values.
Definition aoPaths.hpp:160
Result of deleting rows or columns from a represented thin SVD.
svdDeletionResult & operator=(const svdDeletionResult &other)=delete
Results cannot be copy-assigned across the mxlib ABI boundary.
svdDeletionStatus status() const noexcept
Return the most recent operation status.
realT minimumPSDValue() const noexcept
Return the smallest pre-clamp eigenvalue from the normalized backend PSD validation core.
svdDeletionResult()
Construct an empty result.
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.
~svdDeletionResult()
Release result storage using mxlib's Eigen allocation configuration.
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.
svdDeletionWorkspace()
Construct an empty workspace.
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.
std::int64_t maximumDeleted() const noexcept
Return the prepared maximum deletion count.
~svdDeletionWorkspace()
Release owned scratch storage.
svdDeletionWorkspace & operator=(const svdDeletionWorkspace &other)=delete
Workspaces cannot be copy-assigned.
Floating-point classification utilities that remain reliable under fast-math optimization.
bool isFinite(realT value)
Test whether a floating-point value is finite, including under finite-math-only optimization.
std::string root(const std::string &basisName, bool create=false)
The root path for basis files.
Definition aoPaths.hpp:42
const char * svdDeletionStatusName(svdDeletionStatus status)
Return a stable text representation of an SVD deletion status.
svdDeletionStatus
Completion status for an SVD deletion operation.
svdDeletionBackend
Numerical backend used to delete rows or columns from thin-SVD factors.
const char * svdDeletionBackendName(svdDeletionBackend backend)
Return a stable text representation of an SVD deletion backend.
bool svdDeletionSucceeded(svdDeletionStatus status) noexcept
Return true when a status represents usable numerical output.
Eigen::Ref< const svdDeletionVector< realT > > svdDeletionConstVectorRef
Non-owning read-only reference to a compatible SVD deletion vector.
Eigen::Ref< const svdDeletionMatrix< realT > > svdDeletionConstMatrixRef
Non-owning read-only reference to a compatible column-major SVD deletion matrix.
@ unsupportedDeletionCount
The selected backend cannot process the requested number of deleted rows.
@ success
The operation completed without numerical clamping.
@ rescalingOverflow
A finite normalized result cannot be represented after restoring input scale.
@ nonFiniteOutput
LAPACK returned a non-finite singular system.
@ notComputed
No operation has published a result.
@ invalidInput
Dimensions, values, indices, or requested output rank are invalid.
@ factorNotOrthonormal
A requested singular-factor validation failed.
@ nonPositiveSemidefinite
A theoretically PSD core has a materially negative eigenvalue.
@ successWithClamping
The operation completed after clamping roundoff-scale negative eigenvalues.
@ allocationFailure
Result or workspace allocation failed.
@ workspaceQueryFailure
LAPACK returned an invalid or failed workspace query.
@ invalidSolverOutput
LAPACK returned a finite spectrum with invalid ordering or sign.
@ solverFailure
LAPACK failed during the numerical solve.
@ stableCore
Complement-preserving small SVD; avoids squaring singular-value conditioning.
@ leadingCovariance
Symmetric leading-spectrum core; fastest when small singular values are not required.
@ rankOneSecular
Structured covariance eigensolve for deleting exactly one singular-factor row.
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.
MXLAPACK_INT gesvd(char JOBU, char JOBVT, 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)
Compute the singular value decomposition (SVD) of a real matrix.
MXLAPACK_INT laed9(dataT *D, dataT *Q, dataT *S, MXLAPACK_INT K, MXLAPACK_INT KSTART, MXLAPACK_INT KSTOP, MXLAPACK_INT N, MXLAPACK_INT LDQ, dataT RHO, dataT *DLAMDA, dataT *W, MXLAPACK_INT LDS)
Solve selected roots of a diagonal-plus-rank-one secular equation and form its eigenvectors.
The mxlib c++ namespace.
Definition mxlib.hpp:37
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.