PhysicsBasedAnimationToolkit 0.0.10
Cross-platform C++20 library of algorithms and data structures commonly used in computer graphics research on physically-based simulation.
Loading...
Searching...
No Matches
SparsityPattern.h
1#ifndef PBAT_MATH_LINALG_SPARSITYPATTERN_H
2#define PBAT_MATH_LINALG_SPARSITYPATTERN_H
3
4#include "PhysicsBasedAnimationToolkitExport.h"
5#include "pbat/Aliases.h"
8
9#include <exception>
10#include <fmt/core.h>
11#include <range/v3/action/sort.hpp>
12#include <range/v3/range/conversion.hpp>
13#include <range/v3/view/iota.hpp>
14#include <range/v3/view/unique.hpp>
15#include <ranges>
16#include <type_traits>
17#include <utility>
18#include <vector>
19
20namespace pbat {
21namespace math {
22namespace linalg {
23
30template <typename TIndex = Index, Eigen::StorageOptions Options = Eigen::ColMajor>
31class SparsityPattern
32{
33 public:
34 using IndexType = TIndex;
36 Eigen::SparseMatrix<std::uint8_t, Options, TIndex>;
37
38 PBAT_API SparsityPattern() = default;
39
40 template <
41 common::CContiguousIndexRange TRowIndexRange,
42 common::CContiguousIndexRange TColIndexRange>
43 SparsityPattern(
44 IndexType nRows,
45 IndexType nCols,
46 TRowIndexRange&& rowIndices,
47 TColIndexRange&& colIndices);
48
49 template <
50 common::CContiguousIndexRange TRowIndexRange,
51 common::CContiguousIndexRange TColIndexRange>
52 void Compute(
53 IndexType nRows,
54 IndexType nCols,
55 TRowIndexRange&& rowIndices,
56 TColIndexRange&& colIndices);
57
58 template <common::CArithmeticRange TNonZeroRange>
59 auto ToMatrix(TNonZeroRange&& nonZeros) const
60 -> Eigen::SparseMatrix<std::ranges::range_value_t<TNonZeroRange>, Options, TIndex>;
61
62 template <common::CArithmeticRange TNonZeroRange, class TDerived>
63 void To(TNonZeroRange&& nonZeros, Eigen::SparseCompressedBase<TDerived>& Ain) const;
64
65 template <common::CArithmeticRange TNonZeroRange, class TDerived>
66 void AddTo(TNonZeroRange&& nonZeros, Eigen::SparseCompressedBase<TDerived>& Ain) const;
67
68 PBAT_API bool IsEmpty() const { return mNonZeroIndexToValueIndex.empty(); }
69
70 template <common::CFloatingPoint TScalar>
71 auto Pattern() const -> Eigen::SparseMatrix<TScalar, Options, TIndex>;
72
73 private:
74 std::vector<IndexType>
75 mNonZeroIndexToValueIndex;
76 InnerPatternType mInnerPattern;
77};
78
79template <typename TIndex, Eigen::StorageOptions Options>
80template <
81 common::CContiguousIndexRange TRowIndexRange,
82 common::CContiguousIndexRange TColIndexRange>
83SparsityPattern<TIndex, Options>::SparsityPattern(
84 IndexType nRows,
85 IndexType nCols,
86 TRowIndexRange&& rowIndices,
87 TColIndexRange&& colIndices)
88{
89 Compute(
90 nRows,
91 nCols,
92 std::forward<TRowIndexRange>(rowIndices),
93 std::forward<TColIndexRange>(colIndices));
94}
95
96template <typename TIndex, Eigen::StorageOptions Options>
97template <
98 common::CContiguousIndexRange TRowIndexRange,
99 common::CContiguousIndexRange TColIndexRange>
100inline void SparsityPattern<TIndex, Options>::Compute(
101 IndexType nRows,
102 IndexType nCols,
103 TRowIndexRange&& rowIndices,
104 TColIndexRange&& colIndices)
105{
106 PBAT_PROFILE_NAMED_SCOPE("pbat.math.linalg.SparsityPattern.Compute");
107 namespace srng = std::ranges;
108 namespace sviews = std::views;
109 namespace rng = ranges;
110 namespace views = rng::views;
111
112 auto const nRowIndices = srng::size(rowIndices);
113 auto const nColIndices = srng::size(colIndices);
114 if (nRowIndices != nColIndices)
115 {
116 std::string const what = fmt::format(
117 "Expected same number of row indices and column indices, but got {} row indices "
118 "and {} column indices",
119 nRowIndices,
120 nColIndices);
121 throw std::invalid_argument(what);
122 }
123 auto const [rowMin, rowMax] = srng::minmax_element(rowIndices);
124 auto const [colMin, colMax] = srng::minmax_element(colIndices);
125 bool const bRowsInBounds = (*rowMin >= 0) && (*rowMax < nRows);
126 bool const bColsInBounds = (*colMin >= 0) && (*colMax < nCols);
127 if (not(bRowsInBounds and bColsInBounds))
128 {
129 std::string const what = fmt::format(
130 "Out of bounds min (row,col)=({},{}) and max (row,col)=({},{}), while "
131 "(nRows,nCols)=({},{})",
132 *rowMin,
133 *colMin,
134 *rowMax,
135 *colMax,
136 nRows,
137 nCols);
138 throw std::invalid_argument(what);
139 }
140
141 auto const nnz = nRowIndices;
142 mNonZeroIndexToValueIndex.resize(nnz, IndexType{0});
143
144 auto rows = srng::data(rowIndices);
145 auto cols = srng::data(colIndices);
146
147 bool constexpr bIsRowMajor = (Options == Eigen::RowMajor);
148 auto const indexPair = [&](Index s) {
149 if constexpr (bIsRowMajor)
150 return std::make_pair(rows[s], cols[s]);
151 else
152 return std::make_pair(cols[s], rows[s]);
153 };
154 auto const less = [&](IndexType lhs, IndexType rhs) {
155 return indexPair(lhs) < indexPair(rhs);
156 };
157 auto const equal = [&](IndexType lhs, IndexType rhs) {
158 return indexPair(lhs) == indexPair(rhs);
159 };
160 auto const numNonZeroIndices = static_cast<IndexType>(nnz);
161 auto const sortedNonZeroIndices = views::iota(IndexType{0}, numNonZeroIndices) |
162 rng::to<std::vector>() | rng::actions::sort(less);
163 auto const sortedUniqueNonZeroIndices =
164 sortedNonZeroIndices | views::unique(equal) | rng::to<std::vector>();
165
166 Eigen::Vector<IndexType, Eigen::Dynamic> innerSizes;
167 innerSizes.setZero(bIsRowMajor ? nRows : nCols);
168 if constexpr (bIsRowMajor)
169 {
170 for (auto u : sortedUniqueNonZeroIndices)
171 ++innerSizes[rows[u]];
172 }
173 else
174 {
175 for (auto u : sortedUniqueNonZeroIndices)
176 ++innerSizes[cols[u]];
177 }
178
179 for (auto k = 0, u = 0; k < numNonZeroIndices; ++k)
180 {
181 auto const s = sortedNonZeroIndices[static_cast<std::size_t>(k)];
182 auto const uu = sortedUniqueNonZeroIndices[static_cast<std::size_t>(u)];
183 bool const bIsSameEntry = equal(s, uu);
184 mNonZeroIndexToValueIndex[static_cast<std::size_t>(s)] =
185 static_cast<IndexType>(bIsSameEntry ? u : ++u);
186 }
187 mInnerPattern.resize(nRows, nCols);
188 mInnerPattern.reserve(innerSizes);
189 for (auto s : sortedUniqueNonZeroIndices)
190 mInnerPattern.insert(rows[s], cols[s]) = 0;
191 mInnerPattern.makeCompressed();
192}
193
194template <typename TIndex, Eigen::StorageOptions Options>
195template <common::CArithmeticRange TNonZeroRange>
196inline auto SparsityPattern<TIndex, Options>::ToMatrix(TNonZeroRange&& nonZeros) const
197 -> Eigen::SparseMatrix<std::ranges::range_value_t<TNonZeroRange>, Options, TIndex>
198{
199 PBAT_PROFILE_NAMED_SCOPE("pbat.math.linalg.SparsityPattern.ToMatrix");
200 using ScalarType = std::ranges::range_value_t<TNonZeroRange>;
201 auto Acpy = Pattern<ScalarType>();
202 AddTo(std::forward<TNonZeroRange>(nonZeros), Acpy);
203 return Acpy;
204}
205
206template <typename TIndex, Eigen::StorageOptions Options>
207template <common::CArithmeticRange TNonZeroRange, class TDerived>
208inline void SparsityPattern<TIndex, Options>::AddTo(
209 TNonZeroRange&& nonZeros,
210 Eigen::SparseCompressedBase<TDerived>& Ain) const
211{
212 PBAT_PROFILE_NAMED_SCOPE("pbat.math.linalg.SparsityPattern.AddTo");
213 static_assert(
214 std::is_same_v<Scalar, std::ranges::range_value_t<TNonZeroRange>>,
215 "Only Scalar non-zero values are accepted");
216
217 namespace rng = std::ranges;
218 auto const nnz = rng::size(nonZeros);
219 if (nnz != mNonZeroIndexToValueIndex.size())
220 {
221 std::string const what =
222 fmt::format("Expected {} non zeros, got {}", mNonZeroIndexToValueIndex.size(), nnz);
223 throw std::invalid_argument(what);
224 }
225
226 Scalar* values = Ain.valuePtr();
227 for (auto k = 0ULL; k < nnz; ++k)
228 values[mNonZeroIndexToValueIndex[k]] += nonZeros[k];
229}
230
231template <typename TIndex, Eigen::StorageOptions Options>
232template <common::CArithmeticRange TNonZeroRange, class TDerived>
233inline void SparsityPattern<TIndex, Options>::To(
234 TNonZeroRange&& nonZeros,
235 Eigen::SparseCompressedBase<TDerived>& Ain) const
236{
237 Ain.coeffs().setZero();
238 AddTo(std::forward<TNonZeroRange>(nonZeros), Ain);
239}
240
241template <typename TIndex, Eigen::StorageOptions Options>
242template <common::CFloatingPoint TScalar>
243inline auto SparsityPattern<TIndex, Options>::Pattern() const
244 -> Eigen::SparseMatrix<TScalar, Options, TIndex>
245{
246 return mInnerPattern.template cast<TScalar>();
247}
248
249} // namespace linalg
250} // namespace math
251} // namespace pbat
252
253#endif // PBAT_MATH_LINALG_SPARSITYPATTERN_H
Sparsity pattern precomputer to accelerate sparse matrix assembly.
Definition SparsityPattern.h:32
TIndex IndexType
Index type for the matrix indices.
Definition SparsityPattern.h:34
Eigen::SparseMatrix< std::uint8_t, Options, TIndex > InnerPatternType
Type of the inner pattern.
Definition SparsityPattern.h:35
Concepts for common types.
Contiguous range of integer types.
Definition Concepts.h:73
Common functionality.
Definition ArgSort.h:20
Linear Algebra related functionality.
Definition FilterEigenvalues.h:7
Math related functionality.
Definition Concepts.h:19
The main namespace of the library.
Definition Aliases.h:15
std::ptrdiff_t Index
Index type.
Definition Aliases.h:17
double Scalar
Scalar type.
Definition Aliases.h:18
Profiling utilities for the Physics-Based Animation Toolkit (PBAT)