alpaqa develop
Nonconvex constrained optimization
Loading...
Searching...
No Matches
sparse-ops.hpp
Go to the documentation of this file.
1#pragma once
2
8
9#include <algorithm>
10#include <cassert>
11#include <chrono>
12#include <iostream>
13#include <numeric>
14#include <ranges>
15#include <span>
16
17#include <Eigen/Sparse>
18
19namespace alpaqa::util {
20
21namespace detail {
22
23/// Returns a range over the row indices in the given column of @p sp_mat that
24/// are also in @p mask.
25/// The range consists of the full Eigen InnerIterators (row, column, value).
26template <class SpMat, class MaskVec>
28 // Make a range that iterates over all matrix elements in the given column:
29 using row_iter_t = typename SpMat::InnerIterator;
31 // Projector that extracts the row index from an element of that range:
32 static constexpr auto proj_row = [](const row_iter_t &it) {
33 return static_cast<typename MaskVec::value_type>(it.row());
34 };
35 // Compute the intersection between the matrix elements and the mask:
37 std::ranges::ref_view{mask},
38 std::less{}, proj_row);
39 // Extract just the iterator to the matrix element (dropping the mask):
40 auto extract_eigen_iter = []<class T>(T &&tup) -> decltype(auto) {
41 return std::get<0>(std::forward<T>(tup));
42 };
43 return std::views::transform(std::move(intersection), extract_eigen_iter);
44}
45
46/// Like @ref select_rows_in_col, but returns a range of tuples containing the
47/// Eigen InnerIterator and a linear index into the mask.
48template <class SpMat, class MaskVec>
50 // Make a range that iterates over all matrix elements in the given column:
51 using row_iter_t = typename SpMat::InnerIterator;
53 // Projector that extracts the row index from an element of that range:
54 static constexpr auto proj_row = [](const row_iter_t &it) {
55 return static_cast<typename MaskVec::value_type>(it.row());
56 };
57 // Make a range of tuples of the index into the mask and the mask value:
58 auto iota_mask = util::enumerate(std::ranges::ref_view{mask});
59 // Projector that extracts the mask value from such a tuple:
60 static constexpr auto proj_mask = [](const auto &tup) -> decltype(auto) {
61 return std::get<1>(tup);
62 };
63 // Compute the intersection between the matrix elements and the mask:
64 auto intersection =
65 util::iter_set_intersection(std::move(col_range), std::move(iota_mask),
66 std::less{}, proj_row, proj_mask);
67 // Extract the iterator to the matrix element and the index into the mask:
68 auto extract_eigen_iter_and_index = []<class T>(T &&tup)
69 requires(std::is_rvalue_reference_v<T &&>)
70 {
71 auto &[eigen_iter, enum_tup] = tup;
72 auto &mask_index = std::get<0>(enum_tup);
73 return std::tuple{std::move(eigen_iter), std::move(mask_index)};
74 };
75 return std::views::transform(std::move(intersection),
77}
78
79} // namespace detail
80
81/// R += R_full(mask,mask)
82template <class SpMat, class Mat, class MaskVec>
83void sparse_add_masked(const SpMat &R_full, Mat &&R, const MaskVec &mask) {
84 // Iterate over all columns in the mask
85 for (auto [ci, c] : util::enumerate(mask))
86 // Iterate over rows in intersection of mask and sparse column
87 for (auto [r, ri] : detail::select_rows_in_col_iota(R_full, mask, c))
88 R(ri, ci) += r.value();
89}
90
91/// S += S_full(mask,:)
92template <class SpMat, class Mat, class MaskVec>
94 using index_t = typename SpMat::Index;
95 // Iterate over all columns
96 for (index_t c = 0; c < S_full.cols(); ++c)
97 // Iterate over rows in intersection of mask and sparse column
98 for (auto [r, ri] : detail::select_rows_in_col_iota(S_full, mask, c))
99 S(ri, c) += r.value();
100}
101
102/// out += R(mask_J,mask_K) * v(mask_K);
103template <class SpMat, class CVec, class Vec, class MaskVec>
105 Vec &&out, const MaskVec &mask_J,
106 const MaskVec &mask_K) {
108 // Iterate over all columns in the mask K
109 for (auto c : mask_K)
110 // Iterate over rows in intersection of mask J and sparse column
111 for (auto &&[r, ri] : select_rows_in_col_iota(R, mask_J, c))
112 out(ri) += r.value() * v(c);
113}
114
115/// out += S(mask,:)ᵀ * v(mask);
116template <class SpMat, class CVec, class Vec, class MaskVec>
118 Vec &&out, const MaskVec &mask) {
119 using index_t = typename SpMat::Index;
120 // Iterate over all rows of Sᵀ
121 for (index_t c = 0; c < S.cols(); ++c)
122 // Iterate over columns in intersection of mask K and sparse row
123 for (auto r : detail::select_rows_in_col(S, mask, c))
124 out(c) += r.value() * v(r.row());
125}
126
127#if __cpp_lib_ranges_zip >= 202110L && __cpp_lib_ranges_enumerate >= 202302L
128#define ALPAQA_HAVE_COO_CSC_CONVERSIONS 1
129
130template <class I>
131void convert_triplets_to_ccs(const auto &rows, const auto &cols,
132 auto &&inner_idx, auto &&outer_ptr, I idx_0 = 0) {
133 // Inner indices: simply the row indices
134 assert(std::size(rows) == std::size(inner_idx));
135 auto cvt_indices = [&](auto i) { return static_cast<I>(i) - idx_0; };
136 std::ranges::ref_view rows_vw = rows;
137 std::ranges::transform(rows_vw, std::begin(inner_idx), cvt_indices);
138 // Outer indices: need to count the number of nonzeros per column
139 auto cols_iter = std::begin(cols);
140 for (auto &&[i, outer] : std::views::enumerate(outer_ptr)) {
141 cols_iter = std::lower_bound(cols_iter, std::end(cols), i + idx_0);
142 outer = static_cast<I>(cols_iter - std::begin(cols));
143 }
144}
145
146template <auto cmp, class... Ts>
147void sort_triplets_impl(Ts &&...triplets) {
148 auto indices = std::views::zip(std::ranges::ref_view{triplets}...);
149 auto t0 = std::chrono::steady_clock::now();
150 std::ranges::sort(indices, cmp);
151 auto t1 = std::chrono::steady_clock::now();
152 std::cout << "Sorting took: "
153 << std::chrono::duration<double>{t1 - t0}.count() * 1e6
154 << " µs\n";
155}
156
157/// Sort the (row, column, value) triplets, by column index first, then row.
158template <class... Ts>
159void sort_triplets(Ts &&...triplets) {
160 // Sort the indices (by column first, then row)
161 auto cmp = [](const auto &a, const auto &b) {
162 return std::tie(std::get<1>(a), std::get<0>(a)) <
163 std::tie(std::get<1>(b), std::get<0>(b));
164 };
165 sort_triplets_impl<cmp>(std::forward<Ts>(triplets)...);
166}
167
168/// Sort the (row, column, value) triplets by column index.
169template <class... Ts>
170void sort_triplets_col(Ts &&...triplets) {
171 // Sort the indices (by column)
172 auto cmp = [](const auto &a, const auto &b) {
173 return std::get<1>(a) < std::get<1>(b);
174 };
175 sort_triplets_impl<cmp>(std::forward<Ts>(triplets)...);
176}
177
178/// Given a sparse compressed-column storage matrix, sort all row indices
179/// within each column.
180template <class Outer, class... Inners>
181void sort_rows_csc(const Outer &outer_ptr, Inners &&...inners) {
182 if (outer_ptr.size() == 0)
183 return;
184 // Sort the indices (by row)
185 auto cmp = [](const auto &a, const auto &b) {
186 return std::get<0>(a) < std::get<0>(b);
187 };
188 for (decltype(outer_ptr.size()) c = 0; c < outer_ptr.size() - 1; ++c) {
189 auto inner_start = outer_ptr(c);
190 auto inner_end = outer_ptr(c + 1);
191 auto indices = std::views::zip(
192 std::ranges::subrange{std::ranges::begin(inners) + inner_start,
193 std::ranges::begin(inners) + inner_end}...);
194 std::ranges::sort(indices, cmp);
195 }
196}
197
198/// Check that no two entries with the same row and column index exist in
199/// the given sparse coordinate list matrix. Assumes sorted indices.
200template <class... Ts>
202 auto indices = std::views::zip(std::ranges::ref_view{triplets}...);
203 return std::ranges::adjacent_find(indices, std::equal_to<>{}) ==
204 std::ranges::end(indices);
205}
206
207/// Check that no two entries with the same row and column index exist in
208/// the given sparse compressed-column storage matrix. Assumes sorted indices.
209template <class Outer, class Inner>
210bool check_uniqueness_csc(const Outer &outer_ptr, const Inner inner) {
211 if (outer_ptr.size() == 0)
212 return true;
213 auto is_unique_col = [&](auto &inner_start) {
214 auto inner_end = (&inner_start)[1];
215 auto indices =
216 std::ranges::subrange{std::ranges::begin(inner) + inner_start,
217 std::ranges::begin(inner) + inner_end};
218 return std::ranges::adjacent_find(indices, std::equal_to<>{}) ==
219 std::ranges::end(indices);
220 };
221 return std::transform_reduce(std::begin(outer_ptr), std::end(outer_ptr) - 1,
222 true, std::logical_and<>{}, is_unique_col);
223}
224
225#endif
226
227} // namespace alpaqa::util
auto select_rows_in_col(const SpMat &sp_mat, MaskVec &mask, auto column)
Returns a range over the row indices in the given column of sp_mat that are also in mask.
auto select_rows_in_col_iota(const SpMat &sp_mat, MaskVec &mask, auto column)
Like select_rows_in_col, but returns a range of tuples containing the Eigen InnerIterator and a linea...
void sparse_matvec_add_masked_rows_cols(const SpMat &R, const CVec &v, Vec &&out, const MaskVec &mask_J, const MaskVec &mask_K)
out += R(mask_J,mask_K) * v(mask_K);
void sparse_add_masked_rows(const SpMat &S_full, Mat &&S, const MaskVec &mask)
S += S_full(mask,:)
auto enumerate(Rng &&rng)
Definition enumerate.hpp:66
void sparse_add_masked(const SpMat &R_full, Mat &&R, const MaskVec &mask)
R += R_full(mask,mask)
void sparse_matvec_add_transpose_masked_rows(const SpMat &S, const CVec &v, Vec &&out, const MaskVec &mask)
out += S(mask,:)ᵀ * v(mask);
set_intersection_iterable< std::ranges::views::all_t< R1 >, std::ranges::views::all_t< R2 >, Comp, Proj1, Proj2 > iter_set_intersection(R1 &&r1, R2 &&r2, Comp comp={}, Proj1 proj1={}, Proj2 proj2={})
typename Conf::index_t index_t
Definition config.hpp:104
constexpr const auto inf
Definition config.hpp:112