Dune::Copasi 2.1.0-git79717215+dune.gitlab.629933
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Modules Pages Concepts
umfpack.hh
Go to the documentation of this file.
1#ifndef DUNE_COPASI_SOLVER_ISTL_UMFPACK_HH
2#define DUNE_COPASI_SOLVER_ISTL_UMFPACK_HH
3
4#if !HAVE_SUITESPARSE_UMFPACK
5#error Trying to use umfpack wrapper without including umfpack
6#endif
7
9
10#include <dune/istl/solver.hh>
11
12#include <umfpack.h>
13
14#include <algorithm>
15#include <ranges>
16#include <version>
17
18// disabled by default due to memory leaks: https://github.com/oneapi-src/oneDPL/pull/1589
19#if DUNE_COPASI_ENABLE_PARALLEL_SORT
20#if __cpp_lib_execution >= 201603L
21#include <execution>
22#include <algorithm>
23#elif __has_include(<oneapi/dpl/execution>)
24#include <oneapi/dpl/execution>
25#include <oneapi/dpl/algorithm>
26#endif
27#endif
28
29namespace Dune::Copasi::ISTL {
30
31template<Concept::AssembledLinearOperator O, class Alloc = std::allocator<void>>
32class UMFPackWapper final : public InverseOperator<typename O::domain_type, typename O::range_type>
33{
34 using Matrix = typename O::matrix_type;
35 using X = typename O::domain_type;
36 using Y = typename O::range_type;
37 using MI = PDELab::MultiIndex<std::size_t, blockLevel<Matrix>()>;
38
39 static const inline std::map<std::string, double> umf_strategy_map = {
40 { "AUTO", UMFPACK_STRATEGY_AUTO },
41 { "UNSYMMETRIC", UMFPACK_STRATEGY_UNSYMMETRIC },
42 { "UMFPACK_STRATEGY_SYMMETRIC", UMFPACK_STRATEGY_SYMMETRIC },
43 };
44
45 static const inline std::map<std::string, double> umf_ordering_map = {
46 { "CHOLMOD", UMFPACK_ORDERING_CHOLMOD },
47 { "AMD", UMFPACK_ORDERING_AMD },
48 { "METIS", UMFPACK_ORDERING_METIS },
49 { "BEST", UMFPACK_ORDERING_BEST },
50 { "NONE", UMFPACK_ORDERING_NONE },
51#if defined(UMFPACK_VER) && (UMFPACK >= UMFPACK_VER_CODE(6, 0))
52 { "METIS_GUARD", UMFPACK_ORDERING_METIS_GUARD },
53#endif
54 };
55
56 // rebind allocators to correct types
57 using Int64Alloc = typename std::allocator_traits<Alloc>::template rebind_alloc<int64_t>;
58 using DoubleAlloc = typename std::allocator_traits<Alloc>::template rebind_alloc<double>;
59
60 struct SymbolicDeleter
61 {
62 void operator()(void* ptr) const { umfpack_dl_free_symbolic(&ptr); }
63 };
64 struct NumericDeleter
65 {
66 void operator()(void* ptr) const { umfpack_dl_free_numeric(&ptr); }
67 };
68
69public:
70 // note allocator only allocates data for what UMFPack allows to be externally owned. Otherwise,
71 // umprfack still allocates some memory internally
72 UMFPackWapper(const std::shared_ptr<const O>& op,
73 const ParameterTree& config,
74 const Alloc& alloc = Alloc())
75 : _alloc(alloc)
76 , _umf_offsets(Int64Alloc(_alloc))
77 , _umf_rows(Int64Alloc(_alloc))
78 , _umf_iworkspace(Int64Alloc(_alloc))
79 , _umf_dworkspace(DoubleAlloc(_alloc))
80 , _umf_values(DoubleAlloc(_alloc))
81 , _umf_x(DoubleAlloc(_alloc))
82 , _umf_b(DoubleAlloc(_alloc))
83 , _report_symbolic{config.get("report_control", false)}
84 , _report_numeric{config.get("report_numeric", false)}
85 , _report_info{config.get("report_info", true)}
86 {
87 // set up umfpack configuration options
88 umfpack_di_defaults(_umf_control);
89 auto set_config = [&](auto id, auto key) {
90 _umf_control[id] = config.get(key, _umf_control[id]);
91 };
92 set_config(UMFPACK_PRL, "verbosity");
93 set_config(UMFPACK_DENSE_ROW, "dense_row");
94 set_config(UMFPACK_DENSE_COL, "dense_col");
95 set_config(UMFPACK_PIVOT_TOLERANCE, "pivot_tolerance");
96 set_config(UMFPACK_BLOCK_SIZE, "block_size");
97 set_config(UMFPACK_DENSE_ROW, "dense_row");
98 set_config(UMFPACK_ALLOC_INIT, "alloc_init");
99 set_config(UMFPACK_IRSTEP, "irstep");
100 set_config(UMFPACK_FIXQ, "fixq");
101 set_config(UMFPACK_AMD_DENSE, "amd_dense");
102 set_config(UMFPACK_SYM_PIVOT_TOLERANCE, "sym_pivot_tolerance");
103 set_config(UMFPACK_SCALE, "scale");
104 set_config(UMFPACK_FRONT_ALLOC_INIT, "front_alloc_init");
105 set_config(UMFPACK_DROPTOL, "droptol");
106 set_config(UMFPACK_AGGRESSIVE, "aggressive");
107 set_config(UMFPACK_SINGLETONS, "singletons");
108 _umf_control[UMFPACK_STRATEGY] = umf_strategy_map.at(config.get("strategy", "AUTO"));
109 _umf_control[UMFPACK_ORDERING] = umf_ordering_map.at(config.get("ordering", "CHOLMOD"));
110 // report about umfpack settings
111 if (config.get("report_control", false))
112 umfpack_dl_report_control(_umf_control);
113 // store matrix into a format that unfpack understand
114 storeFlatMatrix(op->getmat());
115 }
116
117 ~UMFPackWapper() override {
119 umfpack_dl_free_numeric(&_umf_numeric_ptr);
120 }
121
122private:
123 template<class M>
124 void forEachMatrixEntry(M&& mat, const auto& call_back, MI row = MI(), MI col = MI())
125 {
126 using MD = std::decay_t<M>;
127 if constexpr (IsNumber<MD>::value) {
128 std::invoke(call_back, mat, row, col);
129 } else { // assume row mayor access!
130 for (auto row_it = mat.begin(); row_it != mat.end(); ++row_it) {
131 auto this_row = push_back(row, row_it.index());
132 for (auto col_it = row_it->begin(); col_it != row_it->end(); ++col_it) {
133 forEachMatrixEntry(*col_it, call_back, this_row, push_back(col, col_it.index()));
134 }
135 }
136 }
137 }
138
139 void storeFlatMatrix(const Matrix& mat)
140 {
141 // matrix entries as a (val, row, col) triplet
142 struct Entry
143 {
144 double val;
145 MI row;
146 MI col;
147 };
148 using EntryAlloc = typename std::allocator_traits<Alloc>::template rebind_alloc<Entry>;
149 EntryAlloc entryalloc(_alloc);
150
151 // step 1: store sparse matrix in coordinate format (value, row, col)
152 std::size_t nnz = 0;
153 if (blockLevel<Matrix>() > 1) {
154 forEachMatrixEntry(mat, [&](auto&&, auto&&, auto&&) { ++nnz; });
155 } else {
156 nnz = mat.nonzeroes();
157 }
158
159 auto entries = std::span<Entry>(std::allocator_traits<EntryAlloc>::allocate(entryalloc, nnz), nnz);
160 nnz = 0;
161 forEachMatrixEntry(mat, [&](double val, MI row, MI col) {
162 std::allocator_traits<EntryAlloc>::construct(entryalloc, &entries[nnz++], val, row, col);
163 });
164
165 // step 2: flattern row multi-index into a single index
166 if (blockLevel<Matrix>() > 1) {
167
168 auto row_compare = [](auto lhs, auto rhs) {
169 // note that sort does not need to order the col entry
170 return std::ranges::lexicographical_compare(lhs.row, rhs.row);
171 };
172 // sort row multi-index lexicographically: block ordering of rows -> natural ordering of rows
173 sort(
174#if DUNE_COPASI_ENABLE_PARALLEL_SORT and (__cpp_lib_execution >= 201603L)
175 std::execution::par,
176#elif DUNE_COPASI_ENABLE_PARALLEL_SORT and __has_include(<oneapi/dpl/execution>)
177 oneapi::dpl::execution::par,
178#endif
179 entries.begin(),
180 entries.end(),
181 row_compare);
182
183 // convert multi-index into a single index
184 MI last_row;
185 std::size_t flat_row = -1;
186 for (auto& [val, row, col] : entries) {
187 // row is new [[pre: rows are sorted]]: increase flat row count
188 if (row != std::exchange(last_row, row))
189 ++flat_row;
190 // store flat row in first index of row multi-index
191 row.resize(1);
192 row[0] = flat_row;
193 }
194 // number of flat rows
195 _umf_n = flat_row + 1;
196 } else {
197 _umf_n = mat.N();
198 }
199
200 auto col_row_compare = [](auto lhs, auto rhs) {
201 return std::ranges::lexicographical_compare(std::array{ lhs.col, lhs.row },
202 std::array{ rhs.col, rhs.row },
203 std::ranges::lexicographical_compare);
204 };
205
206 // order indices by column (note that sort does need to be order on row entry too)
207 sort(
208#if DUNE_COPASI_ENABLE_PARALLEL_SORT and (__cpp_lib_execution >= 201603L)
209 std::execution::par,
210#elif DUNE_COPASI_ENABLE_PARALLEL_SORT and __has_include(<oneapi/dpl/execution>)
211 oneapi::dpl::execution::par,
212#endif
213 entries.begin(),
214 entries.end(),
215 col_row_compare);
216
217 // initialize umfpack data
218 _umf_values.clear();
219 _umf_offsets.clear();
220 _umf_rows.clear();
221 _umf_offsets.reserve(entries.back().col[0]);
222 _umf_rows.reserve(entries.size());
223 _umf_values.reserve(entries.size());
224
225 // transform col multi-index into a flat offset on the row flat indices
226 MI last_col;
227 for (std::size_t i = 0; i != entries.size(); ++i) {
228 auto [val, row, col] = entries[i];
229 // col is new [[pre: cols are sorted]]: store new column offset
230 if (col != std::exchange(last_col, col))
231 _umf_offsets.push_back(i);
232 // store value and flat row index
233 _umf_values.push_back(val);
234 // [[pre: rows are sorted within column]]
235 _umf_rows.push_back(static_cast<int64_t>(row[0]));
236 std::allocator_traits<EntryAlloc>::destroy(entryalloc, &entries[i]);
237 }
238 _umf_m = _umf_offsets.size();
239 _umf_offsets.push_back(entries.size());
240
241 std::allocator_traits<EntryAlloc>::deallocate(entryalloc, entries.data(), entries.size());
242 }
243
244 void factorize()
245 {
246 assert(not _umf_offsets.empty());
247 assert(not _umf_rows.empty());
248 assert(not _umf_values.empty());
249
250 // pre-order matrix columns
251 void* symbolic_ptr = nullptr;
252 umfpack_dl_symbolic(_umf_n,
253 _umf_m,
254 _umf_offsets.data(),
255 _umf_rows.data(),
256 _umf_values.data(),
257 &symbolic_ptr,
259 _umf_info);
261 umfpack_dl_report_symbolic(symbolic_ptr, _umf_control);
262
263 // factorize matrix
265 umfpack_dl_free_symbolic(&_umf_numeric_ptr);
266 _umf_numeric_ptr = nullptr;
267 umfpack_dl_numeric(_umf_offsets.data(),
268 _umf_rows.data(),
269 _umf_values.data(),
270 symbolic_ptr,
273 _umf_info);
274 // report results
275 if (_report_numeric)
276 umfpack_dl_report_numeric(_umf_numeric_ptr, _umf_control);
277 umfpack_dl_free_symbolic(&symbolic_ptr);
278 }
279
280public:
281 void apply(X& x, Y& b, InverseOperatorResult& res) override
282 {
283 // make sure matrix and buffers are setup
284 if (not _umf_numeric_ptr)
285 factorize();
286 _umf_iworkspace.resize(_umf_n);
287 _umf_dworkspace.resize(_umf_n * (_umf_control[UMFPACK_IRSTEP] > 0. ? 5 : 1));
288
289 // assign b to working array
290 _umf_b.resize(_umf_n);
291 std::size_t flat_i = 0;
292 PDELab::forEachContainerEntry(b, [&](auto val) { _umf_b[flat_i++] = val; });
293
294 // reserve data for result
295 _umf_x.resize(_umf_m);
296
297 // actual solve
298 int status = umfpack_dl_wsolve(UMFPACK_A,
299 _umf_offsets.data(),
300 _umf_rows.data(),
301 _umf_values.data(),
302 _umf_x.data(),
303 _umf_b.data(),
306 _umf_info,
307 _umf_iworkspace.data(),
308 _umf_dworkspace.data());
309 // report results
310 if (_report_info)
311 umfpack_dl_report_info(_umf_control, _umf_info);
312
313 // assign x from working array
314 flat_i = 0;
315 PDELab::forEachContainerEntry(x, [&](auto& val) { val = _umf_x[flat_i++]; });
316
317 // store result info
318 res.iterations = 1;
319 res.converged = (status == 0);
320 }
321
322 void apply(X& x, Y& b, [[maybe_unused]] double reduction, InverseOperatorResult& res) override
323 {
324 this->apply(x, b, res);
325 }
326
327 SolverCategory::Category category() const override
328 {
329 return SolverCategory::Category::sequential;
330 }
331
332 static auto factory()
333 {
334 return [](const std::shared_ptr<LinearOperator<X, Y>>& op,
335 const ParameterTree& config,
336 const Alloc& alloc) -> std::shared_ptr<InverseOperator<X, Y>> {
337 // make an allocator for this preconditioner
338 using OpAlloc = typename std::allocator_traits<Alloc>::template rebind_alloc<UMFPackWapper>;
339 OpAlloc opalloc(alloc);
340
341 // cast operator to something the preconditioner expects
342 auto aop = std::dynamic_pointer_cast<O>(op);
343 if (not aop)
344 throw format_exception(InvalidStateException{}, "Linear operator does not hold a matrix!");
345 // construct operator instance
346 return std::allocate_shared<UMFPackWapper>(opalloc, aop, config, alloc);
347 };
348 }
349
351 Alloc _alloc;
352 std::vector<int64_t, Int64Alloc> _umf_offsets;
353 std::vector<int64_t, Int64Alloc> _umf_rows;
354 std::vector<int64_t, Int64Alloc> _umf_iworkspace;
355 std::vector<double, DoubleAlloc> _umf_dworkspace;
356 std::vector<double, DoubleAlloc> _umf_values;
357 std::vector<double, DoubleAlloc> _umf_x;
358 std::vector<double, DoubleAlloc> _umf_b;
359 void* _umf_numeric_ptr = nullptr;
360 double _umf_control[UMFPACK_CONTROL];
361 double _umf_info[UMFPACK_INFO];
365};
366
367} // namespace Dune::Copasi::ISTL
368
369#endif // DUNE_COPASI_SOLVER_ISTL_UMFPACK_HH
Definition: umfpack.hh:33
~UMFPackWapper() override
Definition: umfpack.hh:117
double _umf_info[UMFPACK_INFO]
Definition: umfpack.hh:361
void apply(X &x, Y &b, InverseOperatorResult &res) override
Definition: umfpack.hh:281
double _umf_control[UMFPACK_CONTROL]
Definition: umfpack.hh:360
std::vector< int64_t, Int64Alloc > _umf_offsets
Definition: umfpack.hh:352
int _umf_m
Definition: umfpack.hh:350
std::vector< int64_t, Int64Alloc > _umf_iworkspace
Definition: umfpack.hh:354
bool _report_numeric
Definition: umfpack.hh:363
bool _report_info
Definition: umfpack.hh:364
UMFPackWapper(const std::shared_ptr< const O > &op, const ParameterTree &config, const Alloc &alloc=Alloc())
Definition: umfpack.hh:72
void * _umf_numeric_ptr
Definition: umfpack.hh:359
std::vector< double, DoubleAlloc > _umf_values
Definition: umfpack.hh:356
std::vector< double, DoubleAlloc > _umf_b
Definition: umfpack.hh:358
SolverCategory::Category category() const override
Definition: umfpack.hh:327
static auto factory()
Definition: umfpack.hh:332
Alloc _alloc
Definition: umfpack.hh:351
int _umf_n
Definition: umfpack.hh:350
void apply(X &x, Y &b, double reduction, InverseOperatorResult &res) override
Definition: umfpack.hh:322
std::vector< double, DoubleAlloc > _umf_x
Definition: umfpack.hh:357
std::vector< int64_t, Int64Alloc > _umf_rows
Definition: umfpack.hh:353
bool _report_symbolic
Definition: umfpack.hh:362
std::vector< double, DoubleAlloc > _umf_dworkspace
Definition: umfpack.hh:355
Definition: block_jacobi.hh:14
auto format_exception(Exception &&e, fmt::format_string< Args... > format, Args &&... args)
Definition: exceptions.hh:23