Uni20
Asynchronous tensor and runtime library.
Loading...
Searching...
No Matches
ops/truncated_svd.hpp
Go to the documentation of this file.
1#pragma once
2
13#include <uni20/core/math.hpp>
16
17#include <algorithm>
18#include <cstddef>
19#include <optional>
20#include <span>
21#include <type_traits>
22#include <utility>
23#include <vector>
24
25namespace uni20::linalg
26{
27
33template <uni20::Real Real> struct SvdTruncationPolicy
34{
35 std::size_t minimum_retained_extent = 0;
37 std::optional<Real> singular_value_cutoff = std::nullopt;
38 std::optional<Real> normalized_squared_singular_value_cutoff = std::nullopt;
39 std::optional<Real> maximum_discarded_weight = std::nullopt;
40};
41
43template <uni20::Real Real> struct SvdTruncationInfo
44{
45 std::size_t available_rank;
46 std::size_t retained_rank;
51};
52
56template <class LeftTensor, class SingularValueTensor, class RightAdjointTensor, class TruncationInfo>
58{
60 using singular_value_tensor_type = SingularValueTensor;
62 using truncation_info_type = TruncationInfo;
63
65 SingularValueTensor singular_values;
67 TruncationInfo truncation;
68};
69
70namespace detail
71{
72template <uni20::Real Real> class CompensatedNonnegativeSum {
73 public:
74 void add(Real value)
75 {
76 Real const next = sum_ + value;
77 if (sum_ >= value)
78 correction_ += (sum_ - next) + value;
79 else
80 correction_ += (value - next) + sum_;
81 sum_ = next;
82 }
83
84 [[nodiscard]] Real value() const { return sum_ + correction_; }
85
86 private:
87 Real sum_{};
88 Real correction_{};
89};
90
91template <uni20::Real Real> void validate_svd_truncation_policy(SvdTruncationPolicy<Real> const& policy)
92{
93 ERROR_IF(policy.minimum_retained_extent > policy.maximum_retained_extent,
94 "SVD minimum retained extent exceeds maximum retained extent", policy.minimum_retained_extent,
95 policy.maximum_retained_extent);
96
97 auto validate_nonnegative = [](std::optional<Real> const& value, char const* name) {
98 ERROR_IF(value && (!uni20::isfinite(*value) || *value < Real{}), name, "must be finite and nonnegative");
99 };
100 validate_nonnegative(policy.singular_value_cutoff, "SVD singular-value cutoff");
101 validate_nonnegative(policy.normalized_squared_singular_value_cutoff, "SVD normalized squared singular-value cutoff");
102 validate_nonnegative(policy.maximum_discarded_weight, "SVD maximum discarded weight");
103
104 ERROR_IF(
105 policy.normalized_squared_singular_value_cutoff && *policy.normalized_squared_singular_value_cutoff > Real{1},
106 "SVD normalized squared singular-value cutoff exceeds one", *policy.normalized_squared_singular_value_cutoff);
107 ERROR_IF(policy.maximum_discarded_weight && *policy.maximum_discarded_weight > Real{1},
108 "SVD maximum discarded weight exceeds one", *policy.maximum_discarded_weight);
109}
110
111template <uni20::Real Real, class ValueAt>
112[[nodiscard]] auto select_svd_truncation_values(std::size_t available_rank, ValueAt&& value_at,
113 SvdTruncationPolicy<Real> const& policy)
114{
115 using real_type = Real;
116 validate_svd_truncation_policy(policy);
117
118 std::vector<real_type> scaled_squares(available_rank);
119 std::vector<real_type> discarded_scaled_squares(available_rank + 1, real_type{});
120
121 real_type scale{};
122 if (available_rank > 0)
123 {
124 scale = value_at(std::size_t{0});
125 CHECK(uni20::isfinite(scale), scale);
126 CHECK(scale >= real_type{}, scale);
127
128 for (std::size_t index = 1; index < available_rank; ++index)
129 {
130 real_type const previous = value_at(index - 1);
131 real_type const value = value_at(index);
133 CHECK(value >= real_type{}, value);
134 CHECK(previous >= value, previous, value);
135 }
136 }
137
138 if (scale != real_type{})
139 {
140 CompensatedNonnegativeSum<real_type> sum;
141 for (std::size_t index = available_rank; index > 0; --index)
142 {
143 std::size_t const value_index = index - 1;
144 real_type const ratio = value_at(value_index) / scale;
145 real_type const square = ratio * ratio;
146 scaled_squares[value_index] = square;
147 sum.add(square);
148 discarded_scaled_squares[value_index] = sum.value();
149 }
150 }
151
152 real_type const total_scaled_squared_norm = discarded_scaled_squares[0];
153 bool const has_accuracy_criterion = policy.singular_value_cutoff || policy.normalized_squared_singular_value_cutoff ||
154 policy.maximum_discarded_weight;
155 std::size_t retained_rank = has_accuracy_criterion ? 0 : available_rank;
156
157 if (policy.singular_value_cutoff)
158 {
159 std::size_t cutoff_rank = 0;
160 while (cutoff_rank < available_rank && value_at(cutoff_rank) >= *policy.singular_value_cutoff)
161 ++cutoff_rank;
162 retained_rank = std::max(retained_rank, cutoff_rank);
163 }
164
165 if (policy.normalized_squared_singular_value_cutoff)
166 {
167 std::size_t cutoff_rank = 0;
168 if (total_scaled_squared_norm == real_type{})
169 {
170 if (*policy.normalized_squared_singular_value_cutoff == real_type{}) cutoff_rank = available_rank;
171 }
172 else
173 {
174 real_type const threshold = *policy.normalized_squared_singular_value_cutoff * total_scaled_squared_norm;
175 while (cutoff_rank < available_rank && scaled_squares[cutoff_rank] >= threshold)
176 ++cutoff_rank;
177 }
178 retained_rank = std::max(retained_rank, cutoff_rank);
179 }
180
181 if (policy.maximum_discarded_weight)
182 {
183 std::size_t error_rank = 0;
184 if (total_scaled_squared_norm != real_type{})
185 {
186 real_type const maximum_discarded = *policy.maximum_discarded_weight * total_scaled_squared_norm;
187 while (error_rank < available_rank && discarded_scaled_squares[error_rank] > maximum_discarded)
188 ++error_rank;
189 }
190 retained_rank = std::max(retained_rank, error_rank);
191 }
192
193 retained_rank = std::max(retained_rank, std::min(policy.minimum_retained_extent, available_rank));
194 retained_rank = std::min(retained_rank, std::min(policy.maximum_retained_extent, available_rank));
195
196 real_type original_squared_norm{};
197 real_type discarded_weight{};
198 if (scale != real_type{})
199 {
200 original_squared_norm = scale * scale * total_scaled_squared_norm;
201 discarded_weight = discarded_scaled_squares[retained_rank] / total_scaled_squared_norm;
202 }
203
204 return SvdTruncationInfo<real_type>{
205 .available_rank = available_rank,
206 .retained_rank = retained_rank,
207 .original_squared_norm = original_squared_norm,
208 .discarded_weight = discarded_weight,
209 .smallest_retained_singular_value =
210 retained_rank == 0 ? std::nullopt : std::optional<real_type>{value_at(retained_rank - 1)},
211 .largest_discarded_singular_value =
212 retained_rank == available_rank ? std::nullopt : std::optional<real_type>{value_at(retained_rank)}};
213}
214
215template <uni20::RankedImmediateTensorView<1> SingularValueTensor>
216[[nodiscard]] auto
217select_svd_truncation(SingularValueTensor const& singular_values,
218 SvdTruncationPolicy<uni20::tensor_element_t<SingularValueTensor>> const& policy)
219{
221 return select_svd_truncation_values<real_type>(
222 static_cast<std::size_t>(singular_values.extent(0)),
223 [&](std::size_t index) { return singular_values[static_cast<uni20::index_type>(index)]; }, policy);
224}
225
226template <class ExactResult>
227[[nodiscard]] auto truncate_svd_result(
228 ExactResult&& exact,
229 SvdTruncationPolicy<
230 uni20::tensor_element_t<typename std::remove_cvref_t<ExactResult>::singular_value_tensor_type>> const& policy)
231{
232 using exact_type = std::remove_cvref_t<ExactResult>;
235
236 auto const p = precision_of(exact.singular_values);
237 auto truncation = select_svd_truncation(exact.singular_values, policy);
238 auto const retained_rank = static_cast<uni20::index_type>(truncation.retained_rank);
239 auto const rows = exact.left_singular_vectors.extent(0);
240 auto const cols = exact.right_singular_vectors_adjoint.extent(1);
241
242 uni20::Tensor<scalar_type, 2> left_singular_vectors(uni20::uninitialized, rows, retained_rank, p);
244 uni20::Tensor<scalar_type, 2> right_singular_vectors_adjoint(uni20::uninitialized, retained_rank, cols, p);
245
246 for (uni20::index_type column = 0; column < retained_rank; ++column)
247 {
248 singular_values[column] = exact.singular_values[column];
249 for (uni20::index_type row = 0; row < rows; ++row)
250 left_singular_vectors[row, column] = exact.left_singular_vectors[row, column];
251 for (uni20::index_type output_column = 0; output_column < cols; ++output_column)
252 right_singular_vectors_adjoint[column, output_column] =
253 exact.right_singular_vectors_adjoint[column, output_column];
254 }
255
256 return TruncatedSvdResult<decltype(left_singular_vectors), decltype(singular_values),
257 decltype(right_singular_vectors_adjoint), decltype(truncation)>{
258 .left_singular_vectors = std::move(left_singular_vectors),
259 .singular_values = std::move(singular_values),
260 .right_singular_vectors_adjoint = std::move(right_singular_vectors_adjoint),
261 .truncation = std::move(truncation)};
262}
263} // namespace detail
264
272template <uni20::Real Real>
273[[nodiscard]] auto select_svd_truncation(std::span<Real const> singular_values,
274 SvdTruncationPolicy<Real> const& policy = {}) -> SvdTruncationInfo<Real>
275{
276 return detail::select_svd_truncation_values<Real>(
277 singular_values.size(), [&](std::size_t index) { return singular_values[index]; }, policy);
278}
279
281template <KernelBackendSelector BackendSelector, uni20::RankedTensorView<2> MatrixTensor>
282[[nodiscard]] auto
283truncated_svd(BackendSelector&& selector, MatrixTensor const& matrix,
285 std::optional<Precision> requested = {})
286{
287 auto exact = svd(std::forward<BackendSelector>(selector), matrix, SvdOptions{}, requested);
288 return detail::truncate_svd_result(std::move(exact), policy);
289}
290
292template <uni20::RankedTensorView<2> MatrixTensor>
293[[nodiscard]] auto
294truncated_svd(MatrixTensor const& matrix,
296 std::optional<Precision> requested = {})
297{
298 auto exact = svd(matrix, SvdOptions{}, requested);
299 return detail::truncate_svd_result(std::move(exact), policy);
300}
301
306template <KernelBackendSelector BackendSelector, class MatrixTensor>
308 (!std::is_lvalue_reference_v<MatrixTensor>) && (!std::is_const_v<std::remove_reference_t<MatrixTensor>>)
309[[nodiscard]] auto truncated_svd(
310 BackendSelector&& selector, MatrixTensor&& matrix,
312 std::optional<Precision> requested = {})
313{
314 auto exact =
315 svd(std::forward<BackendSelector>(selector), std::forward<MatrixTensor>(matrix), SvdOptions{}, requested);
316 return detail::truncate_svd_result(std::move(exact), policy);
317}
318
320template <class MatrixTensor>
322 (!std::is_lvalue_reference_v<MatrixTensor>) && (!std::is_const_v<std::remove_reference_t<MatrixTensor>>)
323[[nodiscard]] auto truncated_svd(
325 std::optional<Precision> requested = {})
326{
327 auto exact = svd(std::forward<MatrixTensor>(matrix), SvdOptions{}, requested);
328 return detail::truncate_svd_result(std::move(exact), policy);
329}
330
332template <RankedTensorView<2> Matrix>
333[[nodiscard]] auto truncated_svd(Matrix&& matrix, Precision p,
335{
336 return uni20::linalg::truncated_svd(std::forward<Matrix>(matrix), std::move(policy), std::optional<Precision>{p});
337}
338
340template <KernelBackendSelector Selector, RankedTensorView<2> Matrix>
341[[nodiscard]] auto truncated_svd(Selector&& selector, Matrix&& matrix, Precision p,
343{
344 return uni20::linalg::truncated_svd(std::forward<Selector>(selector), std::forward<Matrix>(matrix), std::move(policy),
345 std::optional<Precision>{p});
346}
347
348} // namespace uni20::linalg
AsyncTask square(ReadBuffer< int > in, WriteBuffer< int > out)
Definition async_tbb_reduction_example.cpp:39
Exact arithmetic or an explicit binary working precision.
Definition core/precision.hpp:13
General-purpose owning tensor with immediate or deferred mdspan metadata.
Definition basic_tensor.hpp:109
MutableImmediateTensorView whose writable mdspan has a specified static rank.
Definition tensor/concepts.hpp:209
Tensor-level object that owns the storage described by its multidimensional view.
Definition tensor/concepts.hpp:116
Concept for real scalar types (float, double, etc.).
Definition scalar_concepts.hpp:69
locale_t value
Definition data_table.cpp:49
typename make_real< T >::type make_real_t
Alias for the underlying real-valued type of T.
Definition scalar_traits.hpp:168
constexpr bool isfinite(I const &x) noexcept
Returns whether an integer scalar is finite.
Definition math.hpp:121
Scalar math helper utilities.
int rows(std::FILE *stream)
Return the terminal row count associated with a C stream.
Definition terminal.cpp:482
R ratio(DiagnosticMagnitude< R > const &a, DiagnosticMagnitude< R > const &b)
Definition gmres_diagnostics.hpp:125
Definition linalg/async.hpp:28
auto truncated_svd(BackendSelector selector, async::Async< MatrixTensor > const &matrix, detail::svd_truncation_policy_t< MatrixTensor > policy={}, std::optional< Precision > requested={})
Schedule a preserving truncated SVD through an explicit selector.
Definition async/truncated_svd.hpp:144
auto select_svd_truncation(std::span< Real const > singular_values, SvdTruncationPolicy< Real > const &policy={}) -> SvdTruncationInfo< Real >
Select a retained prefix from descending singular values.
Definition ops/truncated_svd.hpp:273
auto svd(BackendSelector selector, async::Async< MatrixTensor > const &matrix, SvdOptions options={}, std::optional< Precision > requested={})
Schedule a preserving exact SVD through an explicit selector.
Definition async/svd.hpp:531
auto singular_values(BackendSelector selector, async::Async< MatrixTensor > const &matrix, std::optional< Precision > requested={})
Schedule preserving exact singular values through an explicit selector.
Definition async/svd.hpp:416
Definition data_table.cpp:8
constexpr uninitialized_t uninitialized
Explicit allocation tag for overwrite outputs and scratch space.
Definition common/initialization.hpp:22
void sum(BackendSelector selector, async::Async< OutputTensor > &output, async::Async< InputTensor > const &input)
Schedule a full sum into an explicit async scalar tensor.
Definition linalg/async/reductions.hpp:309
constexpr Precision precision_of(T const &value)
Read scalar precision without changing any scalar or provider state.
Definition runtime_precision.hpp:53
std::remove_cv_t< typename tensor_mdspec_t< T >::element_type > tensor_element_t
Scalar value type exposed by a tensor-level object.
Definition tensor/concepts.hpp:126
std::ptrdiff_t index_type
Definition types.hpp:19
Destructive and value-producing exact dense singular value decompositions.
Statistics describing a completed SVD truncation.
Definition ops/truncated_svd.hpp:44
std::size_t available_rank
Definition ops/truncated_svd.hpp:45
std::optional< Real > largest_discarded_singular_value
Definition ops/truncated_svd.hpp:50
std::optional< Real > smallest_retained_singular_value
Definition ops/truncated_svd.hpp:49
Real discarded_weight
Definition ops/truncated_svd.hpp:48
Real original_squared_norm
Definition ops/truncated_svd.hpp:47
std::size_t retained_rank
Definition ops/truncated_svd.hpp:46
Rank-selection policy for a truncated singular value decomposition.
Definition ops/truncated_svd.hpp:34
std::optional< Real > maximum_discarded_weight
Definition ops/truncated_svd.hpp:39
std::size_t minimum_retained_extent
Definition ops/truncated_svd.hpp:35
std::size_t maximum_retained_extent
Definition ops/truncated_svd.hpp:36
std::optional< Real > singular_value_cutoff
Definition ops/truncated_svd.hpp:37
std::optional< Real > normalized_squared_singular_value_cutoff
Definition ops/truncated_svd.hpp:38
Owning factors and statistics returned by truncated_svd.
Definition ops/truncated_svd.hpp:58
TruncationInfo truncation
Definition ops/truncated_svd.hpp:67
TruncationInfo truncation_info_type
Definition ops/truncated_svd.hpp:62
LeftTensor left_singular_vectors
Definition ops/truncated_svd.hpp:64
RightAdjointTensor right_singular_vector_adjoint_tensor_type
Definition ops/truncated_svd.hpp:61
SingularValueTensor singular_values
Definition ops/truncated_svd.hpp:65
LeftTensor left_singular_vector_tensor_type
Definition ops/truncated_svd.hpp:59
SingularValueTensor singular_value_tensor_type
Definition ops/truncated_svd.hpp:60
RightAdjointTensor right_singular_vectors_adjoint
Definition ops/truncated_svd.hpp:66
Return the sum of two element values.
Definition elementwise_functions.hpp:102
Project-level numeric limits customization point.
Definition numeric_limits.hpp:16
General-purpose owning tensors with runtime extents.
#define CHECK(cond,...)
Definition trace.hpp:299
#define ERROR_IF(cond,...)
Definition trace.hpp:389