alpaqa sparse
Nonconvex constrained optimization
Loading...
Searching...
No Matches
sparsity-conversions.hpp
Go to the documentation of this file.
1#pragma once
2
6
7#include <algorithm>
8#include <cassert>
9#include <concepts>
10#include <numeric>
11#include <optional>
12#include <stdexcept>
13
15
16/// Converts one matrix storage format to another.
17/// @tparam From
18/// The input sparsity pattern type.
19/// @tparam To
20/// The output sparsity pattern type.
21template <class From, class To>
23
24/// Additional options for the conversion performed by @ref SparsityConverter.
25template <class To>
27
28template <Config Conf>
30
31template <Config Conf>
38 if (from.symmetry != Symmetry::Unsymmetric && from.rows != from.cols)
39 throw std::invalid_argument("Nonsquare matrix cannot be symmetric");
40 }
42 operator const to_sparsity_t &() const & { return sparsity; }
43 const to_sparsity_t &get_sparsity() const { return *this; }
45 if (to.data() != from.data())
46 to = from;
47 }
48};
49
50template <Config Conf, class StorageIndex>
57#if ALPAQA_HAVE_COO_CSC_CONVERSIONS
58 assert(util::check_uniqueness_csc(from.outer_ptr, from.inner_idx));
59#endif
60 if (from.symmetry != Symmetry::Unsymmetric && from.rows != from.cols)
61 throw std::invalid_argument("Nonsquare matrix cannot be symmetric");
62 return {
63 .rows = from.rows,
64 .cols = from.cols,
65 .symmetry = from.symmetry,
66 };
67 }
69 : from_sparsity(from), sparsity(convert_sparsity(from, request)) {}
72 operator const to_sparsity_t &() const & { return sparsity; }
73 const to_sparsity_t &get_sparsity() const { return *this; }
75 to.setZero();
76 auto &&T = to.reshaped(sparsity.rows, sparsity.cols);
77 index_t l = 0;
78 for (index_t c = 0; c < from_sparsity.cols; ++c) {
79 auto inner_start = from_sparsity.outer_ptr(c);
80 auto inner_end = from_sparsity.outer_ptr(c + 1);
81 for (auto i = inner_start; i < inner_end; ++i) {
82 auto r = from_sparsity.inner_idx(i);
83 switch (from_sparsity.symmetry) {
84 case Symmetry::Unsymmetric: T(r, c) = from(l); break;
85 case Symmetry::Upper:
86 if (r > c)
87 throw std::invalid_argument(
88 "Invalid symmetric CSC matrix: upper-triangular matrix should not "
89 "have elements below the diagonal");
90 T(c, r) = T(r, c) = from(l);
91 break;
92 case Symmetry::Lower:
93 if (r < c)
94 throw std::invalid_argument(
95 "Invalid symmetric CSC matrix: lower-triangular matrix should not "
96 "have elements above the diagonal");
97 T(c, r) = T(r, c) = from(l);
98 break;
99 default: throw std::invalid_argument("Invalid symmetry");
100 }
101 ++l;
102 }
103 }
104 }
105};
106
107template <Config Conf, class StorageIndex>
114#if ALPAQA_HAVE_COO_CSC_CONVERSIONS
115 assert(util::check_uniqueness_triplets(from.row_indices, from.col_indices));
116#endif
117 if (from.symmetry != Symmetry::Unsymmetric && from.rows != from.cols)
118 throw std::invalid_argument("Nonsquare matrix cannot be symmetric");
119 return {
120 .rows = from.rows,
121 .cols = from.cols,
122 .symmetry = from.symmetry,
123 };
124 }
126 : from_sparsity(from), sparsity(convert_sparsity(from, request)) {}
129 operator const to_sparsity_t &() const & { return sparsity; }
130 const to_sparsity_t &get_sparsity() const { return *this; }
132 to.setZero();
133 auto &&T = to.reshaped(sparsity.rows, sparsity.cols);
134 for (index_t l = 0; l < from_sparsity.nnz(); ++l) {
135 auto r = from_sparsity.row_indices(l) - from_sparsity.first_index;
136 auto c = from_sparsity.col_indices(l) - from_sparsity.first_index;
137 switch (from_sparsity.symmetry) {
138 case Symmetry::Unsymmetric: T(r, c) = from(l); break;
139 case Symmetry::Upper:
140 if (r > c)
141 throw std::invalid_argument(
142 "Invalid symmetric COO matrix: upper-triangular matrix should not "
143 "have elements below the diagonal");
144 T(c, r) = T(r, c) = from(l);
145 break;
146 case Symmetry::Lower:
147 if (r < c)
148 throw std::invalid_argument(
149 "Invalid symmetric COO matrix: lower-triangular matrix should not "
150 "have elements above the diagonal");
151 T(c, r) = T(r, c) = from(l);
152 break;
153 default: throw std::invalid_argument("Invalid symmetry");
154 }
155 }
156 }
157};
158
159template <Config Conf, class StorageIndex>
161 /// Convert the index offset (zero for C/C++, one for Fortran).
162 std::optional<StorageIndex> first_index = std::nullopt;
163};
164
165template <Config Conf, class StorageIndex>
174 storage_index_t Δ = 0;
175 if (request.first_index)
176 Δ = *request.first_index;
177 switch (from.symmetry) {
179 length_t nnz = from.rows * from.cols;
180 row_indices.resize(nnz);
181 col_indices.resize(nnz);
182 index_t l = 0;
183 for (index_t c = 0; c < from.cols; ++c) {
184 for (index_t r = 0; r < from.rows; ++r) {
185 row_indices[l] = static_cast<storage_index_t>(r) + Δ;
186 col_indices[l] = static_cast<storage_index_t>(c) + Δ;
187 ++l;
188 }
189 }
190 } break;
191 case Symmetry::Upper: {
192 if (from.rows != from.cols)
193 throw std::invalid_argument("Nonsquare matrix cannot be symmetric");
194 length_t nnz = from.rows * (from.rows + 1) / 2;
195 row_indices.resize(nnz);
196 col_indices.resize(nnz);
197 index_t l = 0;
198 for (index_t c = 0; c < from.cols; ++c) {
199 for (index_t r = 0; r <= c; ++r) {
200 row_indices[l] = static_cast<storage_index_t>(r) + Δ;
201 col_indices[l] = static_cast<storage_index_t>(c) + Δ;
202 ++l;
203 }
204 }
205 } break;
206 case Symmetry::Lower:
207 throw std::invalid_argument("Lower-triangular symmetry currently not supported");
208 default: throw std::invalid_argument("Invalid symmetry");
209 }
210 return {
211 .rows = from.rows,
212 .cols = from.cols,
213 .symmetry = from.symmetry,
214 .row_indices = row_indices,
215 .col_indices = col_indices,
216 .order = to_sparsity_t::SortedByColsAndRows,
217 .first_index = request.first_index ? *request.first_index : 0,
218 };
219 }
221 : sparsity(convert_sparsity(from, request)) {}
224 operator const to_sparsity_t &() const & { return sparsity; }
225 const to_sparsity_t &get_sparsity() const { return *this; }
227 if (sparsity.symmetry == Symmetry::Unsymmetric) {
228 if (to.data() != from.data())
229 to = from;
230 } else if (sparsity.symmetry == Symmetry::Upper) {
231 auto &&F = from.reshaped(sparsity.rows, sparsity.cols);
232 auto t = to.begin();
233 for (index_t c = 0; c < sparsity.cols; ++c)
234 std::ranges::copy_backward(F.col(c).topRows(c + 1), t += c + 1);
235 }
236 }
237};
238
239template <Config Conf, class StorageIndexFrom, class StorageIndexTo>
248 storage_index_t Δ = 0;
249 if (request.first_index)
250 Δ = *request.first_index;
251 row_indices.resize(from.nnz());
252 col_indices.resize(from.nnz());
253 index_t l = 0;
254 for (index_t c = 0; c < from.cols; ++c) {
255 auto inner_start = from.outer_ptr(c);
256 auto inner_end = from.outer_ptr(c + 1);
257 for (auto i = inner_start; i < inner_end; ++i) {
258 auto r = from.inner_idx(i);
259 row_indices[l] = static_cast<storage_index_t>(r) + Δ;
260 col_indices[l] = static_cast<storage_index_t>(c) + Δ;
261 ++l;
262 }
263 }
264 return {
265 .rows = from.rows,
266 .cols = from.cols,
267 .symmetry = from.symmetry,
268 .row_indices = row_indices,
269 .col_indices = col_indices,
270 .order = from.order == from_sparsity_t::SortedRows ? to_sparsity_t::SortedByColsAndRows
271 : to_sparsity_t::SortedByColsOnly,
272 .first_index = request.first_index ? *request.first_index : 0,
273 };
274 }
276 : sparsity(convert_sparsity(from, request)) {
277#if ALPAQA_HAVE_COO_CSC_CONVERSIONS
278 assert(util::check_uniqueness_triplets(sparsity.row_indices, sparsity.col_indices));
279#endif
280 }
283 operator const to_sparsity_t &() const & { return sparsity; }
284 const to_sparsity_t &get_sparsity() const { return *this; }
286 if (to.data() != from.data())
287 to = from;
288 }
289};
290
291template <Config Conf, class StorageIndexFrom, class StorageIndexTo>
300 storage_index_t Δ = 0;
301 if (request.first_index)
302 Δ = *request.first_index - static_cast<storage_index_t>(from.first_index);
303 // Check if we can fully reuse the indices without changes
304 if constexpr (std::is_same_v<StorageIndexFrom, StorageIndexTo>)
305 if (Δ == 0)
306 return from;
307 // Otherwise, allocate space for shifted or converted indices
308 row_indices.resize(from.nnz());
309 col_indices.resize(from.nnz());
310 auto cvt_idx = [Δ](auto i) { return static_cast<storage_index_t>(i) + Δ; };
311 std::ranges::transform(from.row_indices, row_indices.begin(), cvt_idx);
312 std::ranges::transform(from.col_indices, col_indices.begin(), cvt_idx);
313 return {
314 .rows = from.rows,
315 .cols = from.cols,
316 .symmetry = from.symmetry,
317 .row_indices = row_indices,
318 .col_indices = col_indices,
319 .order = static_cast<typename to_sparsity_t::Order>(from.order),
320 .first_index = request.first_index ? *request.first_index
321 : static_cast<storage_index_t>(from.first_index),
322 };
323 }
325 : sparsity(convert_sparsity(from, request)) {
326#if ALPAQA_HAVE_COO_CSC_CONVERSIONS
327 assert(util::check_uniqueness_triplets(sparsity.row_indices, sparsity.col_indices));
328#endif
329 }
332 operator const to_sparsity_t &() const & { return sparsity; }
333 const to_sparsity_t &get_sparsity() const { return *this; }
335 if (to.data() != from.data())
336 to = from;
337 }
338};
339
340template <Config Conf, class StorageIndex>
342 /// Sort the indices.
343 std::optional<typename SparseCSC<Conf, StorageIndex>::Order> order = std::nullopt;
344};
345
346template <Config Conf, class StorageIndexFrom, class StorageIndexFromTo>
356#if ALPAQA_HAVE_COO_CSC_CONVERSIONS
357 // Convert the indices
358 // TODO: using indexvec here could be suboptimal if the original storage
359 // index was int
360 index_vector_t row_indices(from.nnz()), col_indices(from.nnz());
361 auto cvt_idx = [](auto i) { return static_cast<storage_index_t>(i); };
362 std::ranges::transform(from.row_indices, row_indices.begin(), cvt_idx);
363 std::ranges::transform(from.col_indices, col_indices.begin(), cvt_idx);
364 // Sort the indices
365 typename to_sparsity_t::Order order;
366 if (request.order && *request.order == to_sparsity_t::SortedRows) {
367 order = to_sparsity_t::SortedRows;
368 switch (from.order) {
369 case from_sparsity_t::SortedByColsAndRows:
370 // No sorting necessary
371 break;
372 case from_sparsity_t::Unsorted: [[fallthrough]];
373 case from_sparsity_t::SortedByColsOnly: [[fallthrough]];
374 case from_sparsity_t::SortedByRowsAndCols: [[fallthrough]];
375 case from_sparsity_t::SortedByRowsOnly:
376 permutation.resize(from.nnz());
377 std::iota(permutation.begin(), permutation.end(), index_t{0});
378 util::sort_triplets(row_indices, col_indices, permutation);
379 break;
380 default: throw std::invalid_argument("Invalid order");
381 }
382 } else {
383 switch (from.order) {
384 case from_sparsity_t::SortedByColsAndRows:
385 order = to_sparsity_t::SortedRows;
386 // No sorting necessary
387 break;
388 case from_sparsity_t::SortedByColsOnly:
389 order = to_sparsity_t::Unsorted;
390 // No sorting necessary
391 break;
392 case from_sparsity_t::Unsorted: [[fallthrough]];
393 case from_sparsity_t::SortedByRowsAndCols: [[fallthrough]];
394 case from_sparsity_t::SortedByRowsOnly:
395 order = to_sparsity_t::Unsorted;
396 permutation.resize(from.nnz());
397 std::iota(permutation.begin(), permutation.end(), index_t{0});
398 util::sort_triplets_col(row_indices, col_indices, permutation);
399 break;
400 default: throw std::invalid_argument("Invalid order");
401 }
402 }
403 assert(!request.order || *request.order == order);
404 if (std::ranges::is_sorted(permutation))
405 permutation = indexvec{};
406 // Convert the sorted COO format to CSC
407 inner_idx.resize(from.nnz());
408 outer_ptr.resize(from.cols + 1);
409 util::convert_triplets_to_ccs(row_indices, col_indices, inner_idx, outer_ptr,
410 cvt_idx(from.first_index));
411 return {
412 .rows = from.rows,
413 .cols = from.cols,
414 .symmetry = from.symmetry,
415 .inner_idx = inner_idx,
416 .outer_ptr = outer_ptr,
417 .order = order,
418 };
419#else
420 throw std::runtime_error(
421 "This build of alpaqa does not support conversions from sparse COO format to CSC "
422 "format. Please recompile with a C++23-compliant compiler.");
423#endif
424 }
426 : sparsity(convert_sparsity(from, request)) {
427#if ALPAQA_HAVE_COO_CSC_CONVERSIONS
428 assert(util::check_uniqueness_csc(sparsity.outer_ptr, sparsity.inner_idx));
429#endif
430 }
434 operator const to_sparsity_t &() const & { return sparsity; }
435 const to_sparsity_t &get_sparsity() const { return *this; }
437 if (permutation.size() > 0) {
438 assert(to.data() != from.data());
439 to = from(permutation);
440 } else {
441 if (to.data() != from.data())
442 to = from;
443 }
444 }
445};
446
447template <Config Conf, class StorageIndexFrom, class StorageIndexTo>
457 auto cvt_idx = [](auto i) { return static_cast<storage_index_t>(i); };
458 auto convert_indices = [&] {
459 inner_idx.resize(from.inner_idx.size());
460 std::ranges::transform(from.inner_idx, inner_idx.begin(), cvt_idx);
461 outer_ptr.resize(from.outer_ptr.size());
462 std::ranges::transform(from.outer_ptr, outer_ptr.begin(), cvt_idx);
463 };
464 auto sort_indices = [&] {
465#if ALPAQA_HAVE_COO_CSC_CONVERSIONS
467 permutation.resize(from.nnz());
468 std::iota(permutation.begin(), permutation.end(), index_t{0});
469 util::sort_rows_csc(outer_ptr, inner_idx, permutation);
470#else
471 throw std::runtime_error(
472 "This build of alpaqa does not support sorting matrices in CSC format. "
473 "Please recompile with a C++23-compliant compiler.");
474#endif
475 };
476 using Order = typename to_sparsity_t::Order;
477 bool need_sorting = false;
478 auto order = static_cast<Order>(from.order);
479 // Check whether the user requested the indices to be sorted
480 if (request.order && *request.order == to_sparsity_t::SortedRows) {
481 order = to_sparsity_t::SortedRows;
482 switch (from.order) {
483 case from_sparsity_t::Unsorted: need_sorting = true; break;
484 case from_sparsity_t::SortedRows: need_sorting = false; break;
485 default: throw std::invalid_argument("Invalid order");
486 }
487 }
488 // Convert and sort, or only convert the indices
489 if (need_sorting)
490 sort_indices();
491 else if (!std::is_same_v<StorageIndexFrom, StorageIndexTo>)
493 // Remove unnecessary permutations
494 if (std::ranges::is_sorted(permutation))
495 permutation = indexvec{};
496 // If the index types are the same, we may be able to reuse the storage
497 if constexpr (std::is_same_v<StorageIndexFrom, StorageIndexTo>)
498 return {
499 .rows = from.rows,
500 .cols = from.cols,
501 .symmetry = from.symmetry,
502 .inner_idx = need_sorting ? index_vector_view_t{inner_idx} : from.inner_idx,
503 .outer_ptr = need_sorting ? index_vector_view_t{outer_ptr} : from.outer_ptr,
504 .order = order,
505 };
506 // If the index types are not the same, we have to copy regardless
507 else
508 return {
509 .rows = from.rows,
510 .cols = from.cols,
511 .symmetry = from.symmetry,
512 .inner_idx = inner_idx,
513 .outer_ptr = outer_ptr,
514 .order = order,
515 };
516 }
518 : sparsity(convert_sparsity(from, request)) {
519#if ALPAQA_HAVE_COO_CSC_CONVERSIONS
520 assert(util::check_uniqueness_csc(sparsity.outer_ptr, sparsity.inner_idx));
521#endif
522 }
526 operator const to_sparsity_t &() const & { return sparsity; }
527 const to_sparsity_t &get_sparsity() const { return *this; }
529 if (permutation.size() > 0) {
530 assert(to.data() != from.data());
531 to = from(permutation);
532 } else {
533 if (to.data() != from.data())
534 to = from;
535 }
536 }
537};
538
539template <Config Conf, class StorageIndex>
549 auto cvt_idx = [](auto i) { return static_cast<storage_index_t>(i); };
550 switch (from.symmetry) {
552 inner_idx.resize(from.rows * from.cols);
553 outer_ptr.resize(from.cols + 1);
554 index_t l = 0;
555 for (index_t c = 0; c < from.cols; ++c) {
556 outer_ptr[c] = cvt_idx(l);
557 for (index_t r = 0; r < from.rows; ++r) {
558 inner_idx[l] = cvt_idx(r);
559 ++l;
560 }
561 }
562 outer_ptr[from.cols] = cvt_idx(l);
563 } break;
564 case Symmetry::Upper: {
565 if (from.rows != from.cols)
566 throw std::invalid_argument("Nonsquare matrix cannot be symmetric");
567 inner_idx.resize(from.rows * (from.rows + 1) / 2);
568 outer_ptr.resize(from.cols + 1);
569 index_t l = 0;
570 for (index_t c = 0; c < from.cols; ++c) {
571 outer_ptr[c] = cvt_idx(l);
572 for (index_t r = 0; r <= c; ++r) {
573 inner_idx[l] = cvt_idx(r);
574 ++l;
575 }
576 }
577 outer_ptr[from.cols] = cvt_idx(l);
578 } break;
579 case Symmetry::Lower:
580 throw std::invalid_argument("Lower-triangular symmetry currently not supported");
581 default: throw std::invalid_argument("Invalid symmetry");
582 }
583 return {
584 .rows = from.rows,
585 .cols = from.cols,
586 .symmetry = from.symmetry,
587 .inner_idx = inner_idx,
588 .outer_ptr = outer_ptr,
589 .order = to_sparsity_t::SortedRows,
590 };
591 }
593 : sparsity(convert_sparsity(from, request)) {}
596 operator const to_sparsity_t &() const & { return sparsity; }
597 const to_sparsity_t &get_sparsity() const { return *this; }
599 if (sparsity.symmetry == Symmetry::Unsymmetric) {
600 if (to.data() != from.data())
601 to = from;
602 } else if (sparsity.symmetry == Symmetry::Upper) {
603 auto &&F = from.reshaped(sparsity.rows, sparsity.cols);
604 auto t = to.begin();
605 for (index_t c = 0; c < sparsity.cols; ++c)
606 std::ranges::copy_backward(F.col(c).topRows(c + 1), t += c + 1);
607 }
608 }
609};
610
611namespace detail {
612template <class To, class>
614
615template <class To, class... Froms>
616struct ConverterVariantHelper<To, std::variant<Froms...>> {
617 using type = std::variant<SparsityConverter<Froms, To>...>;
618};
619} // namespace detail
620
621template <class To>
624
625/// Converts any supported matrix storage format to the given format.
626/// @see @ref Sparsity
627template <class Conf, class To>
628 requires std::same_as<Conf, typename To::config_t>
635 : converter{std::visit(wrap_converter(std::move(request)), from)} {}
637 operator const to_sparsity_t &() const {
638 return std::visit([](const auto &c) -> const to_sparsity_t & { return c; }, converter);
639 }
640 const to_sparsity_t &get_sparsity() const { return *this; }
642 std::visit([&](const auto &c) { c.convert_values(std::move(from), to); }, converter);
643 }
644
645 private:
646 template <class... Args>
647 static auto wrap_converter(Args &&...args) {
648 return [&args...]<class From>(const From &from) -> ConverterVariant<To> {
649 return SparsityConverter<From, To>{from, std::forward<Args>(args)...};
650 };
651 }
652};
653
654} // namespace alpaqa::sparsity
#define USING_ALPAQA_CONFIG(Conf)
Definition config.hpp:56
@ Upper
Symmetric, upper-triangular part is stored.
@ Lower
Symmetric, lower-triangular part is stored.
detail::ConverterVariantHelper< To, SparsityVariant< typename To::config_t > >::type ConverterVariant
Dense matrix structure.
Definition sparsity.hpp:20
Additional options for the conversion performed by SparsityConverter.
Converts one matrix storage format to another.
typename Conf::indexvec indexvec
Definition config.hpp:78
typename Conf::index_t index_t
Definition config.hpp:77
typename Conf::length_t length_t
Definition config.hpp:76
constexpr const auto inf
Definition config.hpp:85
typename Conf::rvec rvec
Definition config.hpp:69
typename Conf::crvec crvec
Definition config.hpp:70
Sparse coordinate list structure (COO).
Definition sparsity.hpp:55
index_vector_view_t col_indices
Definition sparsity.hpp:64
storage_index_t first_index
Zero for C/C++, one for Fortran.
Definition sparsity.hpp:82
length_t nnz() const
Get the number of structurally nonzero elements.
Definition sparsity.hpp:85
Eigen::VectorX< storage_index_t > index_vector_t
Definition sparsity.hpp:58
index_vector_view_t row_indices
Definition sparsity.hpp:63
Sparse compressed-column structure (CCS or CSC).
Definition sparsity.hpp:28
index_vector_view_t outer_ptr
Definition sparsity.hpp:37
Eigen::Ref< const index_vector_t > index_vector_view_t
Definition sparsity.hpp:32
Eigen::VectorX< storage_index_t > index_vector_t
Definition sparsity.hpp:31
index_vector_view_t inner_idx
Definition sparsity.hpp:36
SparsityConverter(Sparsity< config_t > from, Request request={})
Stores any of the supported sparsity patterns.
Definition sparsity.hpp:105