mxlib
c++ tools for analyzing astronomical data and other tasks by Jared R. Males. [git repo]
Loading...
Searching...
No Matches
svdDowndate.hpp
Go to the documentation of this file.
1/** \file
2 * \brief Reusable row and column deletion updates for thin singular value decompositions.
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#ifndef math_svdDowndate_hpp
27#define math_svdDowndate_hpp
28
29#include <Eigen/Dense>
30
31#include <cstddef>
32#include <cstdint>
33#include <memory>
34#include <span>
35#include <type_traits>
36
37#include "templateLapack.hpp"
38
39namespace mx
40{
41namespace math
42{
43
44#if defined( __GNUC__ ) || defined( __clang__ )
45#define MXLIB_SVD_DELETION_HEADER_ADAPTER inline __attribute__( ( visibility( "hidden" ) ) )
46#else
47#define MXLIB_SVD_DELETION_HEADER_ADAPTER inline
48#endif
49
50/** \addtogroup svd_downdate
51 * @{
52 */
53
54/// Numerical backend used to delete rows or columns from thin-SVD factors.
56{
57 leadingCovariance, ///< Symmetric leading-spectrum core; fastest when small singular values are not required.
58 stableCore, ///< Complement-preserving small SVD; avoids squaring singular-value conditioning.
59 rankOneSecular ///< Structured covariance eigensolve for deleting exactly one singular-factor row.
60};
61
62/// Completion status for an SVD deletion operation.
64{
65 notComputed, ///< No operation has published a result.
66 success, ///< The operation completed without numerical clamping.
67 successWithClamping, ///< The operation completed after clamping roundoff-scale negative eigenvalues.
68 invalidInput, ///< Dimensions, values, indices, or requested output rank are invalid.
69 allocationFailure, ///< Result or workspace allocation failed.
70 workspaceQueryFailure, ///< LAPACK returned an invalid or failed workspace query.
71 solverFailure, ///< LAPACK failed during the numerical solve.
72 nonFiniteOutput, ///< LAPACK returned a non-finite singular system.
73 invalidSolverOutput, ///< LAPACK returned a finite spectrum with invalid ordering or sign.
74 rescalingOverflow, ///< A finite normalized result cannot be represented after restoring input scale.
75 nonPositiveSemidefinite, ///< A theoretically PSD core has a materially negative eigenvalue.
76 factorNotOrthonormal, ///< A requested singular-factor validation failed.
77 unsupportedDeletionCount ///< The selected backend cannot process the requested number of deleted rows.
78};
79
80/// Return a stable text representation of an SVD deletion backend.
81const char *svdDeletionBackendName( svdDeletionBackend backend /**< [in] backend to describe */ );
82
83/// Return a stable text representation of an SVD deletion status.
84const char *svdDeletionStatusName( svdDeletionStatus status /**< [in] status to describe */ );
85
86/// Return true when a status represents usable numerical output.
87bool svdDeletionSucceeded( svdDeletionStatus status /**< [in] status to classify */ ) noexcept;
88
89/// Column-major dynamic matrix used by the SVD deletion API.
90template <typename realT>
91using svdDeletionMatrix = Eigen::Array<realT, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor>;
92
93/// Dynamic column vector used by the SVD deletion API.
94template <typename realT>
95using svdDeletionVector = Eigen::Array<realT, Eigen::Dynamic, 1>;
96
97/// Non-owning read-only reference to a compatible column-major SVD deletion matrix.
98template <typename realT>
99using svdDeletionConstMatrixRef = Eigen::Ref<const svdDeletionMatrix<realT>>;
100
101/// Non-owning read-only reference to a compatible SVD deletion vector.
102template <typename realT>
103using svdDeletionConstVectorRef = Eigen::Ref<const svdDeletionVector<realT>>;
104
105static_assert( std::is_integral_v<Eigen::Index>, "The SVD-deletion ABI requires Eigen::Index to be an integral type." );
106static_assert( std::is_signed_v<Eigen::Index>, "The SVD-deletion ABI requires Eigen::Index to be signed." );
107static_assert( sizeof( Eigen::Index ) <= sizeof( std::int64_t ),
108 "The SVD-deletion ABI cannot represent an Eigen::Index wider than int64_t." );
109static_assert( sizeof( Eigen::Index ) == sizeof( std::ptrdiff_t ),
110 "The SVD-deletion ABI requires Eigen::Index and ptrdiff_t to have the same width." );
111
112/// ABI-stable borrowed contiguous-vector storage descriptor.
113template <typename realT>
115{
116 const realT *data{ nullptr }; ///< First scalar, or null for an empty view.
117
118 std::int64_t size{ 0 }; ///< Number of contiguous scalars.
119};
120
121/// ABI-stable borrowed column-major matrix storage descriptor.
122template <typename realT>
124{
125 const realT *data{ nullptr }; ///< First scalar, or null for an empty view.
126
127 std::int64_t rows{ 0 }; ///< Matrix row count.
128
129 std::int64_t columns{ 0 }; ///< Matrix column count.
130
131 std::int64_t outerStride{ 0 }; ///< Scalar stride between successive columns.
132};
133
134/// ABI-stable borrowed signed-index storage descriptor.
136{
137 const void *data{ nullptr }; ///< First signed index, or null for an empty view.
138
139 std::int64_t size{ 0 }; ///< Number of indices.
140
141 std::int64_t elementBytes{ 0 }; ///< Width of each signed integer element.
142};
143
144static_assert( std::is_standard_layout_v<svdDeletionConstVectorViewV2<float>> );
145static_assert( std::is_trivially_copyable_v<svdDeletionConstVectorViewV2<float>> );
146static_assert( std::is_standard_layout_v<svdDeletionConstVectorViewV2<double>> );
147static_assert( std::is_trivially_copyable_v<svdDeletionConstVectorViewV2<double>> );
148static_assert( std::is_standard_layout_v<svdDeletionConstMatrixViewV2<float>> );
149static_assert( std::is_trivially_copyable_v<svdDeletionConstMatrixViewV2<float>> );
150static_assert( std::is_standard_layout_v<svdDeletionConstMatrixViewV2<double>> );
151static_assert( std::is_trivially_copyable_v<svdDeletionConstMatrixViewV2<double>> );
152static_assert( std::is_standard_layout_v<svdDeletionConstIndexViewV2> );
153static_assert( std::is_trivially_copyable_v<svdDeletionConstIndexViewV2> );
154
155/// Type-level ABI tag for the second-generation opaque SVD deletion handles.
157{
158};
159
160template <typename realT, typename abiT = svdDeletionAbiV2Tag>
162
163template <typename realT, typename abiT = svdDeletionAbiV2Tag>
165
166namespace detail
167{
168
169template <typename realT>
170struct svdDeletionImplementation;
171
172/// \cond svdDeletion_abi_detail
173
174// ABI-v2 entry point for factor validation without Eigen types at the shared-library boundary.
175svdDeletionStatus validateSvdDeletionFactorAbiV2( svdDeletionConstMatrixViewV2<float> factor, float tolerance );
176
177// ABI-v2 entry point for double-precision factor validation.
178svdDeletionStatus validateSvdDeletionFactorAbiV2( svdDeletionConstMatrixViewV2<double> factor, double tolerance );
179
180// ABI-v2 entry point for the leading-covariance deletion core.
181template <typename realT>
182svdDeletionStatus svdDeletionLeadingCoreAbiV2( svdDeletionResult<realT> &result,
185 std::int64_t outputRank,
186 svdDeletionWorkspace<realT> &workspace );
187
188// ABI-v2 entry point for the stable deletion core.
189template <typename realT>
190svdDeletionStatus svdDeletionStableCoreAbiV2( svdDeletionResult<realT> &result,
193 std::int64_t outputRank,
194 svdDeletionWorkspace<realT> &workspace );
195
196// ABI-v2 entry point for backend-selected factor deletion.
197template <typename realT>
198svdDeletionStatus svdDeletionCoreAbiV2( svdDeletionResult<realT> &result,
201 std::int64_t outputRank,
203 svdDeletionBackend backend );
204
205// ABI-v2 entry point for physical row deletion.
206template <typename realT>
207svdDeletionStatus svdRemoveRowsAbiV2( svdDeletionResult<realT> &result,
210 svdDeletionConstIndexViewV2 deletedIndices,
211 std::int64_t outputRank,
213 svdDeletionBackend backend );
214
215// ABI-v2 entry point for physical column deletion.
216template <typename realT>
217svdDeletionStatus svdRemoveColumnsAbiV2( svdDeletionResult<realT> &result,
220 svdDeletionConstIndexViewV2 deletedIndices,
221 std::int64_t outputRank,
223 svdDeletionBackend backend );
224
225/// \endcond
226
227/// \cond svdDeletion_test_detail
228
229/// Internal stages exposed only for deterministic failure-path tests.
230enum class svdDeletionTestOperation
231{
232 validateFactor,
233 prepareResult,
234 prepareWorkspace
235};
236
237/// Function signature for deterministic allocation-failure injection.
238using svdDeletionOperationHookT = void ( * )( svdDeletionTestOperation );
239
240/// Function signature matching the LAPACK SYEVR wrapper.
241template <typename realT>
242using svdDeletionSyevrHookT = MXLAPACK_INT ( * )( char,
243 char,
244 char,
245 MXLAPACK_INT,
246 realT *,
247 MXLAPACK_INT,
248 realT,
249 realT,
250 MXLAPACK_INT,
251 MXLAPACK_INT,
252 realT,
253 MXLAPACK_INT *,
254 realT *,
255 realT *,
256 MXLAPACK_INT,
257 MXLAPACK_INT *,
258 realT *,
259 MXLAPACK_INT,
260 MXLAPACK_INT *,
261 MXLAPACK_INT );
262
263/// Function signature matching the LAPACK GESVD wrapper.
264template <typename realT>
265using svdDeletionGesvdHookT = MXLAPACK_INT ( * )( char,
266 char,
267 MXLAPACK_INT,
268 MXLAPACK_INT,
269 realT *,
270 MXLAPACK_INT,
271 realT *,
272 realT *,
273 MXLAPACK_INT,
274 realT *,
275 MXLAPACK_INT,
276 realT *,
277 MXLAPACK_INT );
278
279/// Function signature for the structured rank-one secular eigensolver hook.
280template <typename realT>
281using svdDeletionLaed9HookT =
282 MXLAPACK_INT ( * )( realT *, realT *, realT *, MXLAPACK_INT, MXLAPACK_INT, realT, realT *, realT * );
283
284/// Test hooks for one floating-point specialization.
285template <typename realT>
286struct svdDeletionTestHooks
287{
288 /// Optional operation hook; null performs the production operation.
289 svdDeletionOperationHookT operation{ nullptr };
290
291 /// Optional SYEVR hook; null calls `math::syevr<realT>`.
292 svdDeletionSyevrHookT<realT> syevr{ nullptr };
293
294 /// Optional GESVD hook; null calls `math::gesvd<realT>`.
295 svdDeletionGesvdHookT<realT> gesvd{ nullptr };
296
297 /// Optional LAED9 hook; null calls the LAPACK structured secular eigensolver.
298 svdDeletionLaed9HookT<realT> laed9{ nullptr };
299};
300
301/// Access the process-wide SVD deletion test hooks for one scalar type.
302template <typename realT>
303svdDeletionTestHooks<realT> &svdDeletionHooks();
304
305/// \endcond
306
307} // namespace detail
308
309/// Result of deleting rows or columns from a represented thin SVD.
310/** If \f$A=U\Sigma V^T\f$, the returned rotation applies to the factor on the side that was not deleted. For row
311 * deletion the updated right factor is `V * rotation()`; for column deletion the updated left factor is
312 * `U * rotation()`. Singular values are descending.
313 *
314 * Output arrays remain allocated across calls. Their numerical contents are valid only when status() reports a
315 * successful operation.
316 *
317 * \tparam realT floating-point type; supported explicit instantiations are float and double.
318 * \tparam abiT type-level ABI tag; callers use the default.
319 */
320template <typename realT, typename abiT>
322{
323 static_assert( std::is_same_v<abiT, svdDeletionAbiV2Tag>,
324 "svdDeletionResult does not support a caller-selected ABI tag." );
325
326 public:
327 /// Construct an empty result.
329
330 /// Release result storage using mxlib's Eigen allocation configuration.
332
333 /// Results cannot be copied across the mxlib ABI boundary.
334 svdDeletionResult( const svdDeletionResult &other /**< [in] result that copying is forbidden from */ ) = delete;
335
336 /// Results cannot be copy-assigned across the mxlib ABI boundary.
338 operator=( const svdDeletionResult &other /**< [in] result that copy assignment is forbidden from */ ) = delete;
339
340 /// Move owned result storage from another result.
341 svdDeletionResult( svdDeletionResult &&other /**< [in,out] result to move from */ ) noexcept;
342
343 /// Replace this result by moving owned storage from another result.
344 svdDeletionResult &operator=( svdDeletionResult &&other /**< [in,out] result to move from */ ) noexcept;
345
346 /// Prepare or reuse output storage for a base and requested active output rank.
347 /** Storage grows when required but does not shrink while `baseRank` is unchanged. Calling this before a hot loop
348 * with the largest planned output rank reserves the rotation capacity for later smaller requests.
349 */
350 MXLIB_SVD_DELETION_HEADER_ADAPTER
351 svdDeletionStatus prepare( Eigen::Index baseRank, /**< [in] number of supplied singular triplets */
352 Eigen::Index outputRank /**< [in] number of updated triplets to publish */ )
353 {
354 return prepareAbiV2( static_cast<std::int64_t>( baseRank ), static_cast<std::int64_t>( outputRank ) );
355 }
356
357 /// Return the most recent operation status.
358 svdDeletionStatus status() const noexcept;
359
360 /// Return the backend that produced the current result.
361 svdDeletionBackend backend() const noexcept;
362
363 /// Return an unaligned borrowed view of all `baseRank()` descending updated singular values.
364 /** The view remains valid until this result is prepared, assigned, moved, or destroyed. */
365 MXLIB_SVD_DELETION_HEADER_ADAPTER svdDeletionConstVectorRef<realT> singularValues() const noexcept
366 {
367 const svdDeletionConstVectorViewV2<realT> view = singularValuesViewAbiV2();
368 return Eigen::Map<const svdDeletionVector<realT>, Eigen::Unaligned>( view.data,
369 static_cast<Eigen::Index>( view.size ) );
370 }
371
372 /// Return an unaligned borrowed view of all corresponding descending squared singular values.
373 /** The view remains valid until this result is prepared, assigned, moved, or destroyed. */
374 MXLIB_SVD_DELETION_HEADER_ADAPTER svdDeletionConstVectorRef<realT> squaredSingularValues() const noexcept
375 {
376 const svdDeletionConstVectorViewV2<realT> view = squaredSingularValuesViewAbiV2();
377 return Eigen::Map<const svdDeletionVector<realT>, Eigen::Unaligned>( view.data,
378 static_cast<Eigen::Index>( view.size ) );
379 }
380
381 /// Return the preserved-side rotation, with updated directions in columns.
382 /** The unaligned borrowed view remains valid until this result is prepared, assigned, moved, or destroyed. */
383 MXLIB_SVD_DELETION_HEADER_ADAPTER svdDeletionConstMatrixRef<realT> rotation() const noexcept
384 {
385 const svdDeletionConstMatrixViewV2<realT> view = rotationViewAbiV2();
386 using mapT = Eigen::Map<const svdDeletionMatrix<realT>, Eigen::Unaligned, Eigen::OuterStride<Eigen::Dynamic>>;
387 return mapT( view.data,
388 static_cast<Eigen::Index>( view.rows ),
389 static_cast<Eigen::Index>( view.columns ),
390 Eigen::OuterStride<Eigen::Dynamic>( static_cast<Eigen::Index>( view.outerStride ) ) );
391 }
392
393 /// Return the base factorization rank for which storage is prepared.
394 std::int64_t baseRank() const noexcept;
395
396 /// Return the requested published rank.
397 std::int64_t outputRank() const noexcept;
398
399 /// Return the allocated rotation-column capacity for the current base rank.
400 std::int64_t maximumOutputRank() const noexcept;
401
402 /// Return the number of roundoff-scale negative eigenvalues clamped to zero.
403 std::int64_t clampedEigenvalues() const noexcept;
404
405 /// Return the smallest pre-clamp eigenvalue from the normalized backend PSD validation core.
406 /** A non-positive-semidefinite failure preserves the offending value. An empty stable-core deletion uses one as
407 * the vacuous minimum of its zero-dimensional complement core.
408 */
409 realT minimumPSDValue() const noexcept;
410
411 /// Return the underlying LAPACK status from the most recent failed query or solve.
412 MXLAPACK_INT lapackInfo() const noexcept;
413
414 private:
415 friend struct detail::svdDeletionImplementation<realT>;
416
417 /// Prepare output storage through the ABI-v2 integer-only boundary.
418 svdDeletionStatus prepareAbiV2( std::int64_t baseRank, /**< [in] number of supplied singular triplets */
419 std::int64_t outputRank /**< [in] number of triplets to publish */ );
420
421 /// Return the singular-value storage through the ABI-v2 POD boundary.
422 svdDeletionConstVectorViewV2<realT> singularValuesViewAbiV2() const noexcept;
423
424 /// Return squared-singular-value storage through the ABI-v2 POD boundary.
425 svdDeletionConstVectorViewV2<realT> squaredSingularValuesViewAbiV2() const noexcept;
426
427 /// Return rotation storage through the ABI-v2 POD boundary.
428 svdDeletionConstMatrixViewV2<realT> rotationViewAbiV2() const noexcept;
429
430 /// Allocate private result storage after a move, translating allocation failure to false.
431 bool ensureStorage() noexcept;
432
433 class storage;
434
435 /// Opaque result storage allocated and released with mxlib's Eigen build configuration.
436 std::unique_ptr<storage> m_storage;
437};
438
439/// Reusable, non-shared storage for SVD deletion operations.
440/** Call prepare() before a hot loop to allocate and query LAPACK once. A later operation reuses the storage whenever
441 * its dimensions and backend fit the prepared capacity. The workspace is noncopyable but movable, allowing callers
442 * to own one independent workspace per worker without sharing in-flight numerical state.
443 *
444 * \tparam realT floating-point type; supported explicit instantiations are float and double.
445 * \tparam abiT type-level ABI tag; callers use the default.
446 */
447template <typename realT, typename abiT>
449{
450 static_assert( std::is_same_v<abiT, svdDeletionAbiV2Tag>,
451 "svdDeletionWorkspace does not support a caller-selected ABI tag." );
452
453 public:
454 /// Construct an empty workspace.
456
457 /// Release owned scratch storage.
459
460 /// Workspaces cannot be copied.
461 svdDeletionWorkspace( const svdDeletionWorkspace &other /**< [in] workspace that copying is forbidden from */ ) =
462 delete;
463
464 /// Workspaces cannot be copy-assigned.
466 const svdDeletionWorkspace &other /**< [in] workspace that copy assignment is forbidden from */ ) = delete;
467
468 /// Move owned storage and preparation state from another workspace.
469 svdDeletionWorkspace( svdDeletionWorkspace &&other /**< [in,out] workspace to move from */ ) noexcept;
470
471 /// Replace this workspace by moving owned storage and preparation state.
472 svdDeletionWorkspace &operator=( svdDeletionWorkspace &&other /**< [in,out] workspace to move from */ ) noexcept;
473
474 /// Prepare reusable storage and LAPACK work arrays.
475 MXLIB_SVD_DELETION_HEADER_ADAPTER
476 svdDeletionStatus prepare( Eigen::Index baseRank, /**< [in] maximum supplied singular rank */
477 Eigen::Index maximumDeleted, /**< [in] maximum rows of the deleted-side factor */
478 svdDeletionBackend backend /**< [in] numerical backend to prepare */ )
479 {
480 return prepareAbiV2( static_cast<std::int64_t>( baseRank ),
481 static_cast<std::int64_t>( maximumDeleted ),
482 backend );
483 }
484
485 /// Release all prepared storage and reset dimensions.
486 void clear() noexcept;
487
488 /// Report whether this workspace has completed preparation.
489 bool prepared() const noexcept;
490
491 /// Return the prepared base rank.
492 std::int64_t baseRank() const noexcept;
493
494 /// Return the prepared maximum deletion count.
495 std::int64_t maximumDeleted() const noexcept;
496
497 /// Return the prepared numerical backend.
498 svdDeletionBackend backend() const noexcept;
499
500 /// Return the underlying LAPACK status from the most recent failed workspace query.
501 MXLAPACK_INT lapackInfo() const noexcept;
502
503 private:
504 friend struct detail::svdDeletionImplementation<realT>;
505
506 /// Prepare scratch storage through the ABI-v2 integer-only boundary.
507 svdDeletionStatus prepareAbiV2( std::int64_t baseRank, /**< [in] maximum supplied singular rank */
508 std::int64_t maximumDeleted, /**< [in] maximum deleted-factor rows */
509 svdDeletionBackend backend /**< [in] numerical backend */ );
510
511 /// Allocate private workspace storage after a move, translating allocation failure to false.
512 bool ensureStorage() noexcept;
513
514 class storage;
515
516 /// Opaque scratch storage allocated and released with mxlib's Eigen build configuration.
517 std::unique_ptr<storage> m_storage;
518};
519
520/// Validate that a supplied thin singular-vector factor has orthonormal columns.
521/** This is an optional one-time base-factor check. Hot deletion calls assume the factor contract and do not repeat
522 * this `O(n q^2)` operation. A zero tolerance selects a dimension-scaled default.
523 */
524MXLIB_SVD_DELETION_HEADER_ADAPTER svdDeletionStatus
525validateSvdDeletionFactor( svdDeletionConstMatrixRef<float> factor, /**< [in] thin singular-vector factor to validate */
526 float tolerance = 0 /**< [in] maximum absolute Gram-matrix error, or zero for automatic */ )
527{
528 return detail::validateSvdDeletionFactorAbiV2( { factor.data(),
529 static_cast<std::int64_t>( factor.rows() ),
530 static_cast<std::int64_t>( factor.cols() ),
531 static_cast<std::int64_t>( factor.outerStride() ) },
532 tolerance );
533}
534
535/// Validate that a supplied double-precision thin singular-vector factor has orthonormal columns.
536MXLIB_SVD_DELETION_HEADER_ADAPTER svdDeletionStatus validateSvdDeletionFactor(
537 svdDeletionConstMatrixRef<double> factor, /**< [in] thin singular-vector factor to validate */
538 double tolerance = 0 /**< [in] maximum absolute Gram-matrix error, or zero for automatic */ )
539{
540 return detail::validateSvdDeletionFactorAbiV2( { factor.data(),
541 static_cast<std::int64_t>( factor.rows() ),
542 static_cast<std::int64_t>( factor.cols() ),
543 static_cast<std::int64_t>( factor.outerStride() ) },
544 tolerance );
545}
546
547/// Delete supplied singular-factor rows with the full-spectrum symmetric covariance core.
548/** Given deleted-side rows `F`, this solves
549 *
550 * \f[
551 * H = \Sigma (I-F^T F) \Sigma = W \Lambda W^T.
552 * \f]
553 *
554 * The result rotation is `W` and its singular values are `sqrt(diag(Lambda))`. The complete spectrum is evaluated
555 * for PSD validation even when only `outputRank` leading directions are published. This backend forms normal
556 * equations and therefore does not promise high relative accuracy for the smallest singular values.
557 * The `rankOneSecular` backend evaluates this same core in quadratic time when exactly one row is deleted.
558 */
559template <typename realT>
560MXLIB_SVD_DELETION_HEADER_ADAPTER svdDeletionStatus svdDeletionLeadingCore(
561 svdDeletionResult<realT> &result, /**< [out] updated spectrum, rotation, and diagnostics */
562 std::type_identity_t<svdDeletionConstVectorRef<realT>> singularValues, /**< [in] descending base singular values */
563 std::type_identity_t<svdDeletionConstMatrixRef<realT>> deletedRows, /**< [in] deleted-side factor rows */
564 Eigen::Index outputRank, /**< [in] number of leading updated directions to publish */
565 svdDeletionWorkspace<realT> &workspace /**< [in,out] reusable, worker-private scratch storage */ )
566{
567 return detail::svdDeletionLeadingCoreAbiV2<realT>(
568 result,
569 { singularValues.data(), static_cast<std::int64_t>( singularValues.size() ) },
570 { deletedRows.data(),
571 static_cast<std::int64_t>( deletedRows.rows() ),
572 static_cast<std::int64_t>( deletedRows.cols() ),
573 static_cast<std::int64_t>( deletedRows.outerStride() ) },
574 static_cast<std::int64_t>( outputRank ),
575 workspace );
576}
577
578/// Delete supplied singular-factor rows with the complement-preserving small-SVD core.
579/** Let `F` contain deleted-side singular-factor rows and choose `B` so that
580 * `B^T B = I - F F^T`. The at-most `(q+c) x q` core
581 *
582 * \f[
583 * K = \begin{bmatrix}(I-F^TF)\Sigma \\ BF\Sigma\end{bmatrix}
584 * \f]
585 *
586 * obeys `K^T K = Sigma (I-F^T F) Sigma`. Its right singular vectors are the preserved-side rotation. This is the
587 * default generic backend because it avoids explicitly squaring the represented singular values. See
588 * \cite brand_2006 and \cite long_males_2021.
589 *
590 * This backend and `leadingCovariance` use dense LAPACK solvers and therefore have cubic asymptotic cost in the base
591 * rank. The `rankOneSecular` backend instead uses a structured quadratic-time solve for one-row deletion.
592 */
593template <typename realT>
594MXLIB_SVD_DELETION_HEADER_ADAPTER svdDeletionStatus svdDeletionStableCore(
595 svdDeletionResult<realT> &result, /**< [out] updated spectrum, rotation, and diagnostics */
596 std::type_identity_t<svdDeletionConstVectorRef<realT>> singularValues, /**< [in] descending base singular values */
597 std::type_identity_t<svdDeletionConstMatrixRef<realT>> deletedRows, /**< [in] deleted-side factor rows */
598 Eigen::Index outputRank, /**< [in] number of leading updated directions to publish */
599 svdDeletionWorkspace<realT> &workspace /**< [in,out] reusable, worker-private scratch storage */ )
600{
601 return detail::svdDeletionStableCoreAbiV2<realT>(
602 result,
603 { singularValues.data(), static_cast<std::int64_t>( singularValues.size() ) },
604 { deletedRows.data(),
605 static_cast<std::int64_t>( deletedRows.rows() ),
606 static_cast<std::int64_t>( deletedRows.cols() ),
607 static_cast<std::int64_t>( deletedRows.outerStride() ) },
608 static_cast<std::int64_t>( outputRank ),
609 workspace );
610}
611
612/// Delete supplied singular-factor rows with an explicitly selected backend.
613/** `rankOneSecular` accepts either no deleted rows, which returns the identity update, or exactly one deleted row.
614 * It solves the same covariance core as `leadingCovariance` by transforming the diagonal-minus-rank-one problem to a
615 * positive rank-one secular equation. LAPACK-style deflation handles negligible update components and clustered
616 * poles at a roundoff-scaled tolerance; post-solve validation uses dimension-scaled bounds. See
617 * \cite bunch_nielsen_1978 and \cite gu_eisenstat_1995.
618 */
619template <typename realT>
620MXLIB_SVD_DELETION_HEADER_ADAPTER svdDeletionStatus svdDeletionCore(
621 svdDeletionResult<realT> &result, /**< [out] updated spectrum, rotation, and diagnostics */
622 std::type_identity_t<svdDeletionConstVectorRef<realT>> singularValues, /**< [in] descending base singular values */
623 std::type_identity_t<svdDeletionConstMatrixRef<realT>> deletedRows, /**< [in] deleted-side factor rows */
624 Eigen::Index outputRank, /**< [in] number of leading updated directions to publish */
625 svdDeletionWorkspace<realT> &workspace, /**< [in,out] reusable, worker-private scratch storage */
626 svdDeletionBackend backend = svdDeletionBackend::stableCore /**< [in] numerical backend */ )
627{
628 return detail::svdDeletionCoreAbiV2<realT>(
629 result,
630 { singularValues.data(), static_cast<std::int64_t>( singularValues.size() ) },
631 { deletedRows.data(),
632 static_cast<std::int64_t>( deletedRows.rows() ),
633 static_cast<std::int64_t>( deletedRows.cols() ),
634 static_cast<std::int64_t>( deletedRows.outerStride() ) },
635 static_cast<std::int64_t>( outputRank ),
636 workspace,
637 backend );
638}
639
640/// Delete physical rows from the matrix represented by a thin SVD.
641/** For `A=U Sigma V^T`, this gathers `U[deletedIndices,:]`. The returned rotation applies to `V`. Supplying complete
642 * factors makes the deletion identical to a direct SVD of the physically retained matrix. Supplying truncated
643 * factors deletes rows exactly from that represented low-rank matrix.
644 */
645template <typename realT>
646MXLIB_SVD_DELETION_HEADER_ADAPTER svdDeletionStatus svdRemoveRows(
647 svdDeletionResult<realT> &result, /**< [out] updated spectrum, rotation, and diagnostics */
648 std::type_identity_t<svdDeletionConstVectorRef<realT>> singularValues, /**< [in] descending base singular values */
649 std::type_identity_t<svdDeletionConstMatrixRef<realT>> leftFactor, /**< [in] thin left factor `U` */
650 std::span<const Eigen::Index> deletedIndices, /**< [in] sorted, unique row indices to delete */
651 Eigen::Index outputRank, /**< [in] number of leading updated directions */
652 svdDeletionWorkspace<realT> &workspace, /**< [in,out] reusable, worker-private scratch */
653 svdDeletionBackend backend = svdDeletionBackend::stableCore /**< [in] numerical backend */ )
654{
655 return detail::svdRemoveRowsAbiV2<realT>(
656 result,
657 { singularValues.data(), static_cast<std::int64_t>( singularValues.size() ) },
658 { leftFactor.data(),
659 static_cast<std::int64_t>( leftFactor.rows() ),
660 static_cast<std::int64_t>( leftFactor.cols() ),
661 static_cast<std::int64_t>( leftFactor.outerStride() ) },
662 { deletedIndices.data(),
663 static_cast<std::int64_t>( deletedIndices.size() ),
664 static_cast<std::int64_t>( sizeof( Eigen::Index ) ) },
665 static_cast<std::int64_t>( outputRank ),
666 workspace,
667 backend );
668}
669
670/// Delete physical columns from the matrix represented by a thin SVD.
671/** For `A=U Sigma V^T`, this gathers `V[deletedIndices,:]`. The returned rotation applies to `U`. Supplying complete
672 * factors makes the deletion identical to a direct SVD of the physically retained matrix. Supplying truncated
673 * factors deletes columns exactly from that represented low-rank matrix.
674 */
675template <typename realT>
676MXLIB_SVD_DELETION_HEADER_ADAPTER svdDeletionStatus svdRemoveColumns(
677 svdDeletionResult<realT> &result, /**< [out] updated spectrum, rotation, and diagnostics */
678 std::type_identity_t<svdDeletionConstVectorRef<realT>> singularValues, /**< [in] descending base singular values */
679 std::type_identity_t<svdDeletionConstMatrixRef<realT>> rightFactor, /**< [in] thin right factor `V` */
680 std::span<const Eigen::Index> deletedIndices, /**< [in] sorted, unique column indices to delete */
681 Eigen::Index outputRank, /**< [in] number of leading updated directions */
682 svdDeletionWorkspace<realT> &workspace, /**< [in,out] reusable, worker-private scratch */
683 svdDeletionBackend backend = svdDeletionBackend::stableCore /**< [in] numerical backend */ )
684{
685 return detail::svdRemoveColumnsAbiV2<realT>(
686 result,
687 { singularValues.data(), static_cast<std::int64_t>( singularValues.size() ) },
688 { rightFactor.data(),
689 static_cast<std::int64_t>( rightFactor.rows() ),
690 static_cast<std::int64_t>( rightFactor.cols() ),
691 static_cast<std::int64_t>( rightFactor.outerStride() ) },
692 { deletedIndices.data(),
693 static_cast<std::int64_t>( deletedIndices.size() ),
694 static_cast<std::int64_t>( sizeof( Eigen::Index ) ) },
695 static_cast<std::int64_t>( outputRank ),
696 workspace,
697 backend );
698}
699
700/** @} */
701
702#undef MXLIB_SVD_DELETION_HEADER_ADAPTER
703
704} // namespace math
705} // namespace mx
706
707#endif // math_svdDowndate_hpp
Result of deleting rows or columns from a represented thin SVD.
MXLIB_SVD_DELETION_HEADER_ADAPTER svdDeletionStatus prepare(Eigen::Index baseRank, Eigen::Index outputRank)
Prepare or reuse output storage for a base and requested active output rank.
MXLIB_SVD_DELETION_HEADER_ADAPTER svdDeletionConstMatrixRef< realT > rotation() const noexcept
Return the preserved-side rotation, with updated directions in columns.
svdDeletionResult & operator=(svdDeletionResult &&other) noexcept
Replace this result by moving owned storage from another result.
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.
MXLIB_SVD_DELETION_HEADER_ADAPTER svdDeletionConstVectorRef< realT > singularValues() const noexcept
realT minimumPSDValue() const noexcept
svdDeletionResult()
Construct an empty result.
std::int64_t clampedEigenvalues() const noexcept
std::int64_t outputRank() const noexcept
std::int64_t maximumOutputRank() const noexcept
~svdDeletionResult()
Release result storage using mxlib's Eigen allocation configuration.
MXLIB_SVD_DELETION_HEADER_ADAPTER svdDeletionConstVectorRef< realT > squaredSingularValues() const noexcept
Return an unaligned borrowed view of all corresponding descending squared singular values.
std::int64_t baseRank() const noexcept
MXLAPACK_INT lapackInfo() const noexcept
svdDeletionResult(const svdDeletionResult &other)=delete
Results cannot be copied across the mxlib ABI boundary.
svdDeletionBackend backend() const noexcept
svdDeletionResult(svdDeletionResult &&other) noexcept
Move owned result storage from another result.
Reusable, non-shared storage for SVD deletion operations.
svdDeletionWorkspace()
Construct an empty workspace.
MXLAPACK_INT lapackInfo() const noexcept
void clear() noexcept
Release all prepared storage and reset dimensions.
std::int64_t baseRank() const noexcept
svdDeletionBackend backend() const noexcept
svdDeletionWorkspace & operator=(svdDeletionWorkspace &&other) noexcept
Replace this workspace by moving owned storage and preparation state.
MXLIB_SVD_DELETION_HEADER_ADAPTER svdDeletionStatus prepare(Eigen::Index baseRank, Eigen::Index maximumDeleted, svdDeletionBackend backend)
Prepare reusable storage and LAPACK work arrays.
svdDeletionWorkspace(const svdDeletionWorkspace &other)=delete
Workspaces cannot be copied.
std::int64_t maximumDeleted() const noexcept
svdDeletionWorkspace(svdDeletionWorkspace &&other) noexcept
Move owned storage and preparation state from another workspace.
~svdDeletionWorkspace()
Release owned scratch storage.
svdDeletionWorkspace & operator=(const svdDeletionWorkspace &other)=delete
Workspaces cannot be copy-assigned.
const char * svdDeletionStatusName(svdDeletionStatus status)
Return a stable text representation of an SVD deletion status.
MXLIB_SVD_DELETION_HEADER_ADAPTER svdDeletionStatus svdDeletionCore(svdDeletionResult< realT > &result, std::type_identity_t< svdDeletionConstVectorRef< realT > > singularValues, std::type_identity_t< svdDeletionConstMatrixRef< realT > > deletedRows, Eigen::Index outputRank, svdDeletionWorkspace< realT > &workspace, svdDeletionBackend backend=svdDeletionBackend::stableCore)
Delete supplied singular-factor rows with an explicitly selected backend.
MXLIB_SVD_DELETION_HEADER_ADAPTER svdDeletionStatus svdRemoveColumns(svdDeletionResult< realT > &result, std::type_identity_t< svdDeletionConstVectorRef< realT > > singularValues, std::type_identity_t< svdDeletionConstMatrixRef< realT > > rightFactor, std::span< const Eigen::Index > deletedIndices, Eigen::Index outputRank, svdDeletionWorkspace< realT > &workspace, svdDeletionBackend backend=svdDeletionBackend::stableCore)
Delete physical columns from the matrix represented by a thin SVD.
svdDeletionStatus
Completion status for an SVD deletion operation.
MXLIB_SVD_DELETION_HEADER_ADAPTER svdDeletionStatus validateSvdDeletionFactor(svdDeletionConstMatrixRef< float > factor, float tolerance=0)
Validate that a supplied thin singular-vector factor has orthonormal columns.
MXLIB_SVD_DELETION_HEADER_ADAPTER svdDeletionStatus svdDeletionLeadingCore(svdDeletionResult< realT > &result, std::type_identity_t< svdDeletionConstVectorRef< realT > > singularValues, std::type_identity_t< svdDeletionConstMatrixRef< realT > > deletedRows, Eigen::Index outputRank, svdDeletionWorkspace< realT > &workspace)
Delete supplied singular-factor rows with the full-spectrum symmetric covariance core.
svdDeletionBackend
Numerical backend used to delete rows or columns from thin-SVD factors.
Eigen::Array< realT, Eigen::Dynamic, 1 > svdDeletionVector
Dynamic column vector used by the SVD deletion API.
const char * svdDeletionBackendName(svdDeletionBackend backend)
Return a stable text representation of an SVD deletion backend.
MXLIB_SVD_DELETION_HEADER_ADAPTER svdDeletionStatus svdRemoveRows(svdDeletionResult< realT > &result, std::type_identity_t< svdDeletionConstVectorRef< realT > > singularValues, std::type_identity_t< svdDeletionConstMatrixRef< realT > > leftFactor, std::span< const Eigen::Index > deletedIndices, Eigen::Index outputRank, svdDeletionWorkspace< realT > &workspace, svdDeletionBackend backend=svdDeletionBackend::stableCore)
Delete physical rows from the matrix represented by a thin SVD.
MXLIB_SVD_DELETION_HEADER_ADAPTER svdDeletionStatus svdDeletionStableCore(svdDeletionResult< realT > &result, std::type_identity_t< svdDeletionConstVectorRef< realT > > singularValues, std::type_identity_t< svdDeletionConstMatrixRef< realT > > deletedRows, Eigen::Index outputRank, svdDeletionWorkspace< realT > &workspace)
Delete supplied singular-factor rows with the complement-preserving small-SVD core.
Eigen::Array< realT, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor > svdDeletionMatrix
Column-major dynamic matrix used by the SVD deletion API.
bool svdDeletionSucceeded(svdDeletionStatus status) noexcept
Return true when a status represents usable numerical output.
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 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
Type-level ABI tag for the second-generation opaque SVD deletion handles.
ABI-stable borrowed signed-index storage descriptor.
std::int64_t elementBytes
Width of each signed integer element.
std::int64_t size
Number of indices.
const void * data
First signed index, or null for an empty view.
ABI-stable borrowed column-major matrix storage descriptor.
const realT * data
First scalar, or null for an empty view.
std::int64_t outerStride
Scalar stride between successive columns.
std::int64_t columns
Matrix column count.
std::int64_t rows
Matrix row count.
ABI-stable borrowed contiguous-vector storage descriptor.
const realT * data
First scalar, or null for an empty view.
std::int64_t size
Number of contiguous scalars.
Declares and defines templatized wrappers for the Lapack library.