alpaqa guanaqo
Nonconvex constrained optimization
Loading...
Searching...
No Matches
nuclear-norm.hpp
Go to the documentation of this file.
1#pragma once
2
5#include <Eigen/SVD>
6
7namespace alpaqa::functions {
8
9#if EIGEN_VERSION_AT_LEAST(3, 4, 1)
10template <Config Conf>
11using DefaultSVD = Eigen::BDCSVD<typename Conf::mat,
12 Eigen::ComputeThinU | Eigen::ComputeThinV>;
13#else
14template <Config Conf>
15using DefaultSVD = Eigen::BDCSVD<typename Conf::mat>;
16#endif
17
18/// Nuclear norm (ℓ₁-norm of singular values).
19/// @ingroup grp_Functions
20template <Config Conf, class SVD = DefaultSVD<Conf>>
23
24 /// Construct without pre-allocation.
26 if (λ < 0 || !std::isfinite(λ))
27 throw std::invalid_argument("NuclearNorm::λ must be nonnegative");
28 }
29 /// Construct with pre-allocation.
31 : λ{λ}, rows{rows}, cols{cols},
32#if EIGEN_VERSION_AT_LEAST(3, 4, 1)
33 svd{rows, cols},
34#else
35 svd{rows, cols, Eigen::ComputeThinU | Eigen::ComputeThinV},
36#endif
37 singular_values{std::min(rows, cols)} {
38 if (λ < 0 || !std::isfinite(λ))
39 throw std::invalid_argument("NuclearNorm::λ must be nonnegative");
40 }
41
43 length_t rows = 0, cols = 0;
44 SVD svd;
46
47 real_t prox(crmat in, rmat out, real_t γ = 1) {
48 if (λ == 0) {
49 out = in;
50 return 0;
51 }
52 if (rows == 0 || cols == 0) { // dynamic size
53 assert(in.rows() == out.rows());
54 assert(in.cols() == out.cols());
55#if EIGEN_VERSION_AT_LEAST(3, 4, 1)
56 svd.compute(in);
57#else
58 svd.compute(in, Eigen::ComputeThinU | Eigen::ComputeThinV);
59#endif
60 } else { // fixed size
61 assert(in.size() == rows * cols);
62 assert(out.size() == rows * cols);
63#if EIGEN_VERSION_AT_LEAST(3, 4, 1)
64 svd.compute(in.reshaped(rows, cols));
65#else
66 svd.compute(in.reshaped(rows, cols),
67 Eigen::ComputeThinU | Eigen::ComputeThinV);
68#endif
69 }
70 const length_t n = svd.singularValues().size();
71 auto step = vec::Constant(n, λ * γ);
72 singular_values = vec::Zero(n).cwiseMax(svd.singularValues() - step);
73 using vec_util::norm_1;
74 real_t value = λ * norm_1(singular_values);
75 auto it0 = std::find(singular_values.begin(), singular_values.end(), 0);
76 index_t rank = it0 - singular_values.begin();
77 using Eigen::placeholders::all, Eigen::seqN;
78 auto sel = seqN(0, rank);
79 auto &&U = svd.matrixU(), &&V = svd.matrixV();
80 auto &&U1 = U(all, sel);
81 auto &&Σ1 = singular_values(sel).asDiagonal();
82 auto &&V1T = V.transpose()(sel, all);
83 out.reshaped().noalias() = (U1 * Σ1 * V1T).reshaped();
84 return value;
85 }
86
87 friend real_t guanaqo_tag_invoke(tag_t<alpaqa::prox>, NuclearNorm &self,
88 crmat in, rmat out, real_t γ) {
89 return self.prox(std::move(in), std::move(out), γ);
90 }
91};
92
93} // namespace alpaqa::functions
#define USING_ALPAQA_CONFIG(Conf)
Definition config.hpp:77
Eigen::BDCSVD< typename Conf::mat > DefaultSVD
auto norm_1(const Eigen::MatrixBase< Derived > &v)
Get the 1-norm of the given vector.
Definition config.hpp:212
typename Conf::crmat crmat
Definition config.hpp:97
typename Conf::rmat rmat
Definition config.hpp:96
typename Conf::real_t real_t
Definition config.hpp:86
typename Conf::index_t index_t
Definition config.hpp:104
typename Conf::length_t length_t
Definition config.hpp:103
typename Conf::vec vec
Definition config.hpp:88
real_t prox(crmat in, rmat out, real_t γ=1)
NuclearNorm(real_t λ=1)
Construct without pre-allocation.
friend real_t guanaqo_tag_invoke(tag_t< alpaqa::prox >, NuclearNorm &self, crmat in, rmat out, real_t γ)
NuclearNorm(real_t λ, length_t rows, length_t cols)
Construct with pre-allocation.