version 3.11-dev
Loading...
Searching...
No Matches
mumpssolver.hh
Go to the documentation of this file.
1// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2// vi: set et ts=4 sw=4 sts=4:
3//
4// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder
5// SPDX-License-Identifier: GPL-3.0-or-later
6//
39#ifndef DUMUX_LINEAR_MUMPS_SOLVER_HH
40#define DUMUX_LINEAR_MUMPS_SOLVER_HH
41
42#include <string>
43#include <vector>
44#include <tuple>
45#include <numeric>
46#include <limits>
47#include <algorithm>
48#include <functional>
49#include <chrono>
50#include <iostream>
51#include <cstring>
52#include <cmath>
53#include <memory>
54#include <optional>
55
56#include <dune/common/exceptions.hh>
57#include <dune/common/indices.hh>
58#include <dune/common/hybridutilities.hh>
59#include <dune/common/parallel/mpihelper.hh>
60#include <dune/istl/bvector.hh>
61#include <dune/istl/bcrsmatrix.hh>
62#include <dune/istl/solver.hh>
63#include <dune/istl/solvercategory.hh>
64
74
75#if DUMUX_HAVE_MUMPS
76
77#include <mpi.h>
78#include <dmumps_c.h>
79
80namespace Dumux::Detail {
81
82// Provides GridView and DofMapper from LSTraits if the traits belong to a grid,
83// or inert dummy types otherwise (SeqLinearSolverTraits).
84template<class LSTraits>
85struct MumpsGridTypes {
86 static constexpr bool hasGrid = false;
87 struct GridView {};
88 struct DofMapper { std::size_t size() const { return 0; } };
89};
90
91template<class LSTraits>
92requires requires { typename LSTraits::GridView; typename LSTraits::DofMapper; }
93struct MumpsGridTypes<LSTraits> {
94 static constexpr bool hasGrid = true;
95 using GridView = typename LSTraits::GridView;
96 using DofMapper = typename LSTraits::DofMapper;
97};
98
99// Dereference pointer-like objects (e.g. std::shared_ptr<GridGeometry>) and pass through
100// references/values unchanged. Used so the multidomain constructor accepts a tuple of either
101// (shared) pointers to grid geometries or the grid geometries themselves.
102template<class T>
103decltype(auto) mumpsDeref(const T& t)
104{
105 if constexpr (requires (const T& x) { *x; })
106 return (*t);
107 else
108 return (t);
109}
110
111} // end namespace Dumux::Detail
112
113namespace Dumux {
114
119template<class LSTraits, class LATraits>
120class DirectSolverMumps : public LinearSolver
121{
122 using Matrix = typename LATraits::Matrix;
123 using XVector = typename LATraits::Vector;
124 using BVector = typename LATraits::Vector;
125
126 using Scalar = double;
127 // Global scalar-DOF index. MUMPS itself uses 32-bit MUMPS_INT for the matrix
128 // entries; we keep a wider type internally and cast (with a check) at the boundary.
129 using GlobalIndex = long long;
130
131 using GridTypes_ = Detail::MumpsGridTypes<LSTraits>;
132 using GridView = typename GridTypes_::GridView;
133 using DofMapper = typename GridTypes_::DofMapper;
134 static constexpr bool hasGrid = GridTypes_::hasGrid;
135
136 // True when XVector is a MultiTypeBlockVector (multidomain assembler).
137 static constexpr bool isMultiType = isMultiTypeBlockVector<XVector>::value;
138 // Scalar DOFs per block DOF. MultiType is always converted to scalar (blockSize=1).
139 static constexpr std::size_t blockSize = []() constexpr -> std::size_t {
140 if constexpr (isMultiTypeBlockVector<XVector>::value) return 1;
141 else return XVector::block_type::size();
142 }();
143
144 // Flattened scalar block vector type produced by VectorConverter (1 scalar per block).
145 // The per-subdomain RHS/ghost closures operate on slices of a vector of this type.
146 using ScalarBlockVector = Dune::BlockVector<Dune::FieldVector<Scalar, 1>>;
147
148 // Per-subdomain parallel bookkeeping (MultiType only). One entry per subdomain, in the
149 // same order as the MultiTypeBlockVector subvectors / the flattened scalar layout.
150 struct SubdomainInfo
151 {
152 std::size_t scalarBS = 1; // scalar DOFs per block DOF in this subdomain
153 std::size_t nBlockLocal = 0; // local block DOFs of this subdomain on this rank
154 std::size_t scalarOffset = 0; // start index of this subdomain in the flattened scalar vector
155 bool rowsComplete = true; // owner border rows already complete after local assembly?
156 // Sum partial border-DOF RHS contributions onto the owner (non-overlapping incomplete rows).
157 // Empty when not needed (overlapping or complete rows). Operates on its scalar slice.
158 std::function<void(ScalarBlockVector&)> rhsPreprocess;
159 // Broadcast the owner's solution value to all copies (zero non-owned beforehand).
160 // Operates on its scalar slice.
161 std::function<void(ScalarBlockVector&)> ghostBroadcast;
162 };
163
164public:
165 struct SolverResult : public Dune::InverseOperatorResult
166 {
167 SolverResult() = default;
168 SolverResult(const Dune::InverseOperatorResult& o) : InverseOperatorResult(o) {}
169 operator bool() const { return this->converged; }
170 };
171
181 explicit DirectSolverMumps(const std::string& paramGroup = "")
182 : LinearSolver(paramGroup)
183 , isParallel_(false)
184 {
185 if constexpr (hasGrid)
186 if (Dune::MPIHelper::getCommunication().size() > 1)
187 DUNE_THROW(Dune::InvalidStateException, "Using sequential constructor for parallel run. Use signature with gridView and dofMapper!");
188
189 mpiComm_ = MPI_COMM_SELF;
190 initMumps_();
191 readSolverParams_();
192 }
193
201 DirectSolverMumps(const GridView& gridView,
202 const DofMapper& dofMapper,
203 const std::string& paramGroup = "")
204 requires (hasGrid)
205 : LinearSolver(paramGroup)
206 , gridView_(gridView)
207 , mapper_(&dofMapper)
208 , isParallel_(Detail::solverCategory<LSTraits>(gridView) != Dune::SolverCategory::sequential)
209 {
210 if constexpr (LSTraits::canCommunicate)
211 mpiComm_ = static_cast<MPI_Comm>(gridView.comm());
212 else
213 mpiComm_ = MPI_COMM_SELF;
214 initMumps_();
215 readSolverParams_();
216
217 buildGlobalDofIndices_(gridView, dofMapper);
218 }
219
226 template<class GGTuple>
227 explicit DirectSolverMumps(const GGTuple& ggTuple, const std::string& paramGroup = "")
228 requires (isMultiType && LSTraits::canCommunicate && requires { std::tuple_size<GGTuple>::value; })
229 : LinearSolver(paramGroup)
230 , gridView_(Detail::mumpsDeref(std::get<0>(ggTuple)).gridView())
231 , isParallel_(Detail::mumpsDeref(std::get<0>(ggTuple)).gridView().comm().size() > 1)
232 {
233 constexpr std::size_t numSubDomains = std::tuple_size_v<GGTuple>;
234 static_assert(numSubDomains == std::tuple_size_v<XVector>,
235 "Number of grid geometries must match the number of MultiTypeBlockVector subdomains");
236
237 mpiComm_ = static_cast<MPI_Comm>(Detail::mumpsDeref(std::get<0>(ggTuple)).gridView().comm());
238 initMumps_();
239 readSolverParams_();
240
241 buildMultiTypeMaps_(ggTuple);
242 }
243
244 ~DirectSolverMumps()
245 {
246 if (initialized_)
247 {
248 id_.job = -2; // terminate the MUMPS instance
249 dmumps_c(&id_);
250 }
251 }
252
253 // Non-copyable: owns a MUMPS instance and raw pointers into our buffers.
254 DirectSolverMumps(const DirectSolverMumps&) = delete;
255 DirectSolverMumps& operator=(const DirectSolverMumps&) = delete;
256
262 void updateAfterGridAdaption(const GridView& gridView, const DofMapper& dofMapper)
263 requires (hasGrid)
264 {
265 gridView_ = gridView;
266 mapper_ = &dofMapper;
267 buildGlobalDofIndices_(gridView, dofMapper);
268 analyzed_ = false;
269 factorized_ = false;
270 }
271
278 template<class GGTuple>
279 void updateAfterGridAdaption(const GGTuple& ggTuple)
280 requires (isMultiType && LSTraits::canCommunicate && requires { std::tuple_size<GGTuple>::value; })
281 {
282 gridView_ = Detail::mumpsDeref(std::get<0>(ggTuple)).gridView();
283 buildMultiTypeMaps_(ggTuple);
284 analyzed_ = false;
285 factorized_ = false;
286 }
287
292 SolverResult solve(const Matrix& A, XVector& x, const BVector& b)
293 {
294 setMatrix(A);
295 return solve(x, b);
296 }
297
303 void setMatrix(const Matrix& A)
304 {
305 factorized_ = false;
306 prepareMaps_(A);
307 factorize_(A);
308 }
309
311 void setMatrix(std::shared_ptr<Matrix> A)
312 { setMatrix(*A); }
313
317 SolverResult solve(XVector& x, const BVector& b)
318 { return solveColumns_({&x}, {&b}); }
319
324 SolverResult solve(std::vector<XVector>& x, const std::vector<BVector>& b)
325 {
326 std::vector<XVector*> xColumns;
327 std::vector<const BVector*> bColumns;
328 for (auto& xj : x)
329 xColumns.push_back(&xj);
330 for (const auto& bj : b)
331 bColumns.push_back(&bj);
332 return solveColumns_(xColumns, bColumns);
333 }
334
335 const Dune::InverseOperatorResult& result() const
336 { return result_; }
337
338 Scalar norm(const XVector& x) const
339 {
340 if constexpr (isMultiType)
341 {
342 if (!isParallel_ || localToGlobal_.empty())
343 {
344 auto y = VectorConverter<XVector>::multiTypeToBlockVector(x);
345 return y.two_norm();
346 }
347 // Parallel: sum border-DOF partial contributions before the owned-DOF global norm.
348 auto v = VectorConverter<XVector>::multiTypeToBlockVector(x);
349 for (const auto& sub : subs_)
350 if (sub.rhsPreprocess)
351 sub.rhsPreprocess(v);
352
353 double localSumSq = 0.0;
354 for (std::size_t r = 0; r < v.size(); ++r)
355 if (isOwned_[r])
356 localSumSq += v[r][0] * v[r][0];
357
358 double globalSumSq = 0.0;
359 MPI_Allreduce(&localSumSq, &globalSumSq, 1, MPI_DOUBLE, MPI_SUM, mpiComm_);
360 using std::sqrt;
361 return sqrt(globalSumSq);
362 }
363 else
364 {
365 if (!isParallel_ || localToGlobal_.empty())
366 return x.two_norm();
367
368 // Parallel single-type: true global residual norm over owned DOFs.
369 if constexpr (LSTraits::canCommunicate)
370 {
371 if (LSTraits::isNonOverlapping(*gridView_))
372 {
373 auto xcopy = x;
374 if constexpr (requires { LSTraits::dofCodims; })
375 {
376 MultiCodimParallelVectorHelper<GridView, DofMapper> vecHelper(*gridView_, *mapper_);
377 vecHelper.makeNonOverlappingConsistent(xcopy, LSTraits::dofCodims);
378 }
379 else
380 {
381 ParallelVectorHelper<GridView, DofMapper, LSTraits::dofCodim> vecHelper(*gridView_, *mapper_);
382 vecHelper.makeNonOverlappingConsistent(xcopy);
383 }
384 return ownedGlobalNorm_(xcopy);
385 }
386 }
387 return ownedGlobalNorm_(x);
388 }
389 }
390
391 std::string name() const { return "DuMux direct MUMPS solver"; }
392
393private:
394 // Accessor for MUMPS control parameters using the manual's 1-based ICNTL(i) numbering.
395 int& icntl_(int i) { return id_.icntl[i - 1]; }
396 const int& infog_(int i) const { return id_.infog[i - 1]; }
397
403 void initMumps_()
404 {
405 // Zero the whole control struct so distributed-RHS/solution fields MUMPS may read
406 // (nz_rhs, nloc_rhs, pointers, ...) start well-defined rather than holding stack garbage.
407 std::memset(&id_, 0, sizeof(id_));
408 id_.comm_fortran = static_cast<int>(MPI_Comm_c2f(mpiComm_));
409 id_.par = 1;
410 id_.sym = 0;
411 id_.job = -1;
412 dmumps_c(&id_);
413 initialized_ = true;
414 checkError_("initialization");
415
416 icntl_(1) = -1; // suppress error output stream
417 icntl_(2) = -1; // suppress diagnostic/warning output stream
418 icntl_(3) = -1; // suppress global information output stream
419 icntl_(4) = 1; // verbosity: errors only
420 icntl_(5) = 0; // assembled matrix (not elemental)
421 icntl_(18) = 3; // distributed assembled input: each rank supplies its local triplets
422 icntl_(20) = 0; // dense, centralized RHS on the host
423 icntl_(21) = 0; // centralized solution on the host
424 }
425
431 void readSolverParams_()
432 {
433 const int ordering = getParamFromGroup<int>(this->paramGroup(), "LinearSolver.MumpsOrdering", -1);
434 if (ordering >= 0)
435 icntl_(7) = ordering;
436
437 // ICNTL(14): percentage increase of the estimated workspace, needed if numerical pivoting
438 // creates more fill-in than the symbolic analysis predicted
439 const int workspaceIncrease = getParamFromGroup<int>(this->paramGroup(), "LinearSolver.MumpsWorkspaceIncrease", -1);
440 if (workspaceIncrease >= 0)
441 icntl_(14) = workspaceIncrease;
442
443 // ICNTL(28): symbolic analysis mode. 0=automatic, 1=sequential (default), 2=parallel.
444 // Parallel analysis requires MUMPS built with a parallel ordering tool (PT-SCOTCH or
445 // ParMETIS); without one MUMPS silently falls back to sequential analysis. ICNTL(29)
446 // selects that tool (0=auto, 1=PT-SCOTCH, 2=ParMETIS).
447 const int parAnalysis = getParamFromGroup<int>(this->paramGroup(), "LinearSolver.MumpsParallelAnalysis", -1);
448 if (parAnalysis >= 0)
449 icntl_(28) = parAnalysis;
450 const int parOrderingTool = getParamFromGroup<int>(this->paramGroup(), "LinearSolver.MumpsParallelOrderingTool", -1);
451 if (parOrderingTool >= 0)
452 icntl_(29) = parOrderingTool;
453 }
454
459 void buildGlobalDofIndices_(const GridView& gridView, const DofMapper& mapper)
460 {
461 const std::size_t nLocal = mapper.size();
462 localToGlobal_.assign(nLocal, std::numeric_limits<GlobalIndex>::max());
463 isOwned_.assign(nLocal, false);
464 isGhost_.assign(nLocal, false);
465
466 if constexpr (LSTraits::canCommunicate)
467 {
468 if (isParallel_)
469 {
470 ParallelISTLHelper<LSTraits> pHelper(gridView, mapper);
471 for (std::size_t i = 0; i < nLocal; ++i)
472 {
473 isOwned_[i] = pHelper.isOwned(i);
474 isGhost_[i] = pHelper.isGhost(i);
475 }
476
477 GlobalIndex numOwned = static_cast<GlobalIndex>(
478 std::count(isOwned_.begin(), isOwned_.end(), true));
479 const int commSize = gridView.comm().size();
480 const int myRank = gridView.comm().rank();
481 std::vector<GlobalIndex> counts(commSize);
482 gridView.comm().allgather(&numOwned, 1, counts.data());
483
484 GlobalIndex offset = 0;
485 for (int p = 0; p < myRank; ++p)
486 offset += counts[p];
487 numBlocksGlobal_ = 0;
488 for (auto c : counts) numBlocksGlobal_ += static_cast<std::size_t>(c);
489
490 for (std::size_t i = 0; i < nLocal; ++i)
491 if (isOwned_[i])
492 localToGlobal_[i] = offset++;
493
494 // Propagate global IDs from owners to ghost DOFs via min communication.
495 if constexpr (requires { LSTraits::dofCodims; }) {
496 MultiCodimVectorCommDataHandleMin<DofMapper, std::vector<GlobalIndex>, GridView::dimension, GlobalIndex>
497 handle(mapper, localToGlobal_, LSTraits::dofCodims);
498 gridView.communicate(handle, Dune::InteriorBorder_All_Interface, Dune::ForwardCommunication);
499 } else {
500 VectorCommDataHandleMin<DofMapper, std::vector<GlobalIndex>, LSTraits::dofCodim, GlobalIndex>
501 handle(mapper, localToGlobal_);
502 gridView.communicate(handle, Dune::InteriorBorder_All_Interface, Dune::ForwardCommunication);
503 }
504 return;
505 }
506 }
507
508 // Sequential: all DOFs owned with consecutive IDs.
509 numBlocksGlobal_ = nLocal;
510 std::fill(isOwned_.begin(), isOwned_.end(), true);
511 for (std::size_t i = 0; i < nLocal; ++i)
512 localToGlobal_[i] = static_cast<GlobalIndex>(i);
513 }
514
515 // Obtain a subdomain's DOF mapper discretization-generically: most grid geometries expose
516 // dofMapper(); FCStaggered only provides one through its linear-solver traits (a lightweight
517 // by-value wrapper around the grid view).
518 template<class SubTraits, class SubGG>
519 static decltype(auto) subDofMapper_(const SubGG& gg)
520 {
521 if constexpr (requires { gg.dofMapper(); })
522 return (gg.dofMapper());
523 else
524 return SubTraits::dofMapper(gg);
525 }
526
527 // True if a subdomain's DOF mapper can drive the generic codim-based grid communication
528 // (it must expose indices(entity)); the minimal FCStaggered mapper wrapper cannot, so
529 // FCStaggered subdomains are only supported sequentially.
530 template<class SubTraits, class GV>
531 static constexpr bool subCanComm_()
532 {
533 using Mapper = typename SubTraits::DofMapper;
534 using Element = typename GV::template Codim<0>::Entity;
535 return SubTraits::canCommunicate
536 && requires (const Mapper& m, const Element& e) { m.indices(e); };
537 }
538
539 // Build global block indices for one subdomain (MultiType). Returns the subdomain's
540 // total number of global block DOFs. gids[i] = global block ID (offset to be globally unique).
541 // CanComm gates the parallel branch so it is not instantiated for mappers that cannot
542 // communicate (e.g. FCStaggered); such subdomains fall back to sequential numbering.
543 template<class SubLSTraits, bool CanComm, class GV, class Mapper>
544 GlobalIndex buildSubdomainGIDs_(const GV& gridView, const Mapper& mapper,
545 GlobalIndex globalOffset,
546 std::vector<GlobalIndex>& gids,
547 std::vector<bool>& owned,
548 std::vector<bool>& ghost)
549 {
550 const std::size_t nLocal = mapper.size();
551 gids.assign(nLocal, std::numeric_limits<GlobalIndex>::max());
552 owned.assign(nLocal, false);
553 ghost.assign(nLocal, false);
554
555 if constexpr (CanComm)
556 {
557 if (isParallel_)
558 {
559 ParallelISTLHelper<SubLSTraits> pHelper(gridView, mapper);
560 for (std::size_t i = 0; i < nLocal; ++i)
561 {
562 owned[i] = pHelper.isOwned(i);
563 ghost[i] = pHelper.isGhost(i);
564 }
565
566 GlobalIndex numOwned = static_cast<GlobalIndex>(
567 std::count(owned.begin(), owned.end(), true));
568 const int commSize = gridView.comm().size();
569 const int myRank = gridView.comm().rank();
570 std::vector<GlobalIndex> counts(commSize);
571 gridView.comm().allgather(&numOwned, 1, counts.data());
572
573 GlobalIndex localOffset = globalOffset;
574 for (int p = 0; p < myRank; ++p)
575 localOffset += counts[p];
576
577 GlobalIndex nGlobal = 0;
578 for (auto c : counts) nGlobal += c;
579
580 for (std::size_t i = 0; i < nLocal; ++i)
581 if (owned[i])
582 gids[i] = localOffset++;
583
584 if constexpr (requires { SubLSTraits::dofCodims; }) {
585 MultiCodimVectorCommDataHandleMin<Mapper, std::vector<GlobalIndex>, GV::dimension, GlobalIndex>
586 handle(mapper, gids, SubLSTraits::dofCodims);
587 gridView.communicate(handle, Dune::InteriorBorder_All_Interface, Dune::ForwardCommunication);
588 } else {
589 constexpr int codim = SubLSTraits::dofCodim;
590 VectorCommDataHandleMin<Mapper, std::vector<GlobalIndex>, codim, GlobalIndex>
591 handle(mapper, gids);
592 gridView.communicate(handle, Dune::InteriorBorder_All_Interface, Dune::ForwardCommunication);
593 }
594 return nGlobal;
595 }
596 }
597
598 // Sequential subdomain: all DOFs owned with consecutive IDs.
599 std::fill(owned.begin(), owned.end(), true);
600 for (std::size_t i = 0; i < nLocal; ++i)
601 gids[i] = globalOffset + static_cast<GlobalIndex>(i);
602 return static_cast<GlobalIndex>(nLocal);
603 }
604
609 template<class GGTuple>
610 void buildMultiTypeMaps_(const GGTuple& ggTuple)
611 {
612 constexpr std::size_t numSubDomains = std::tuple_size_v<GGTuple>;
613 localToGlobal_.clear();
614 isOwned_.clear();
615 isGhost_.clear();
616 rowComplete_.clear();
617 subs_.assign(numSubDomains, SubdomainInfo{});
618
619 // Running offsets while we append the subdomains' scalar DOFs in order.
620 GlobalIndex scalarGlobalOffset = 0; // global scalar index where the current subdomain starts
621 std::size_t scalarLocalOffset = 0; // local scalar index where the current subdomain starts
622
623 using Dune::Hybrid::forEach;
624 forEach(std::make_index_sequence<numSubDomains>{}, [&](auto I)
625 {
626 constexpr std::size_t i = decltype(I)::value;
627 const auto& gg = Detail::mumpsDeref(std::get<i>(ggTuple));
628 using SubGG = std::decay_t<decltype(gg)>;
629 using SubTraits = LinearSolverTraits<SubGG>;
630 using SubBlockVec = std::tuple_element_t<i, XVector>;
631 constexpr std::size_t scalarBS = SubBlockVec::block_type::size();
632 using GV = typename SubGG::GridView;
633 constexpr bool canComm = subCanComm_<SubTraits, GV>();
634
635 // Parallel runs require every subdomain's DOF mapper to support the generic grid
636 // communication; bail out with a clear message otherwise (e.g. FCStaggered momentum).
637 if (isParallel_ && !canComm)
638 DUNE_THROW(Dune::NotImplemented,
639 "DirectSolverMumps: parallel multidomain solve is not supported for subdomain "
640 << i << " — its discretization's DOF mapper (e.g. FaceCenteredStaggered) cannot "
641 "drive the generic grid communication. This combination works sequentially; for "
642 "parallel runs use a momentum discretization with a full mapper (CVFE/FCDiamond).");
643
644 // Block-level global IDs for this subdomain (numbered within the subdomain, [0, nGlobalBlock)).
645 std::vector<GlobalIndex> gids;
646 std::vector<bool> owned, ghost;
647 const GlobalIndex nGlobalBlock = buildSubdomainGIDs_<SubTraits, canComm>(
648 gg.gridView(), subDofMapper_<SubTraits>(gg), 0, gids, owned, ghost);
649 const std::size_t nBlockLocal = gids.size();
650
651 // In overlapping decompositions the overlap supplies the neighbour contributions to the
652 // owner's border rows. In non-overlapping ones every process assembles only its own
653 // elements, so a border row is split among the processes sharing the dof.
654 const bool rowsComplete = !SubTraits::isNonOverlapping(gg.gridView());
655
656 // Append this subdomain's scalar DOFs to the global bookkeeping (in subvector order,
657 // matching MatrixConverter/VectorConverter's flattened scalar layout).
658 for (std::size_t b = 0; b < nBlockLocal; ++b)
659 for (std::size_t k = 0; k < scalarBS; ++k)
660 {
661 localToGlobal_.push_back(scalarGlobalOffset + scalarBS * gids[b] + static_cast<GlobalIndex>(k));
662 isOwned_.push_back(owned[b]);
663 isGhost_.push_back(ghost[b]);
664 rowComplete_.push_back(rowsComplete);
665 }
666
667 subs_[i].scalarBS = scalarBS;
668 subs_[i].nBlockLocal = nBlockLocal;
669 subs_[i].scalarOffset = scalarLocalOffset;
670 subs_[i].rowsComplete = rowsComplete;
671 if (isParallel_)
672 if constexpr (canComm)
673 setupSubdomainClosures_<i>(gg);
674
675 scalarLocalOffset += scalarBS * nBlockLocal;
676 scalarGlobalOffset += static_cast<GlobalIndex>(scalarBS) * nGlobalBlock;
677 });
678
679 numBlocksGlobal_ = static_cast<std::size_t>(scalarGlobalOffset);
680 }
681
693 template<std::size_t i, class SubGG>
694 void setupSubdomainClosures_(const SubGG& gg)
695 {
696 using SubTraits = LinearSolverTraits<SubGG>;
697 using SubBlockVec = std::tuple_element_t<i, XVector>;
698 using GV = typename SubGG::GridView;
699 using Mapper = typename SubTraits::DofMapper;
700 constexpr std::size_t scalarBS = SubBlockVec::block_type::size();
701
702 const GV gv = gg.gridView();
703 // Store our own copy of the DOF mapper so the captured closures own a stable instance
704 // regardless of whether the grid geometry hands it out by reference or by value.
705 auto mapper = std::make_shared<Mapper>(subDofMapper_<SubTraits>(gg));
706 const std::size_t offset = subs_[i].scalarOffset;
707 const std::size_t nBlock = subs_[i].nBlockLocal;
708
709 // Pack/unpack helpers between the scalar slice and a typed subdomain block vector.
710 auto unpack = [offset, nBlock](const ScalarBlockVector& v, SubBlockVec& block) {
711 block.resize(nBlock);
712 for (std::size_t b = 0; b < nBlock; ++b)
713 for (std::size_t k = 0; k < scalarBS; ++k)
714 block[b][k] = v[offset + scalarBS * b + k][0];
715 };
716 auto pack = [offset, nBlock](const SubBlockVec& block, ScalarBlockVector& v) {
717 for (std::size_t b = 0; b < nBlock; ++b)
718 for (std::size_t k = 0; k < scalarBS; ++k)
719 v[offset + scalarBS * b + k] = block[b][k];
720 };
721
722 // Ghost broadcast: sum each DOF's copies to the owner's value (callers zero non-owned first).
723 subs_[i].ghostBroadcast = [gv, mapper, unpack, pack](ScalarBlockVector& v) {
724 SubBlockVec block;
725 unpack(v, block);
726 if constexpr (requires { SubTraits::dofCodims; }) {
727 MultiCodimVectorCommDataHandleSum<Mapper, SubBlockVec, GV::dimension>
728 handle(*mapper, block, SubTraits::dofCodims);
729 gv.communicate(handle, Dune::All_All_Interface, Dune::ForwardCommunication);
730 } else {
731 VectorCommDataHandleSum<Mapper, SubBlockVec, SubTraits::dofCodim>
732 handle(*mapper, block);
733 gv.communicate(handle, Dune::All_All_Interface, Dune::ForwardCommunication);
734 }
735 pack(block, v);
736 };
737
738 // RHS preprocessing: only needed for incomplete owner rows (sum partial border
739 // contributions onto the owner before the centralized gather).
740 if (!subs_[i].rowsComplete) {
741 subs_[i].rhsPreprocess = [gv, mapper, unpack, pack](ScalarBlockVector& v) {
742 SubBlockVec block;
743 unpack(v, block);
744 if constexpr (requires { SubTraits::dofCodims; })
745 {
746 MultiCodimParallelVectorHelper<GV, Mapper> vecHelper(gv, *mapper);
747 vecHelper.makeNonOverlappingConsistent(block, SubTraits::dofCodims);
748 }
749 else
750 {
751 ParallelVectorHelper<GV, Mapper, SubTraits::dofCodim> vecHelper(gv, *mapper);
752 vecHelper.makeNonOverlappingConsistent(block);
753 }
754 pack(block, v);
755 };
756 }
757 }
758
760 template<class MTMatrix>
761 void initSeqDomainOffsets_(const MTMatrix& A)
762 {
763 constexpr std::size_t numSubDomains = MTMatrix::N();
764 if (subs_.size() < numSubDomains)
765 subs_.resize(numSubDomains);
766 std::size_t offset = 0;
767 using namespace Dune::Hybrid;
768 forEach(std::make_index_sequence<numSubDomains>{}, [&](auto I)
769 {
770 constexpr std::size_t i = decltype(I)::value;
771 using Block = typename std::decay_t<decltype(A[Dune::index_constant<i>{}][Dune::index_constant<i>{}])>::block_type;
772 subs_[i].scalarBS = Block::rows;
773 subs_[i].nBlockLocal = A[Dune::index_constant<i>{}][Dune::index_constant<i>{}].N();
774 subs_[i].scalarOffset = offset;
775 offset += subs_[i].scalarBS * subs_[i].nBlockLocal;
776 });
777 }
778
779 template<class MTMatrix>
780 bool subdomainSizesMatch_(const MTMatrix& A) const
781 {
782 bool match = true;
783 using namespace Dune::Hybrid;
784 forEach(std::make_index_sequence<MTMatrix::N()>{}, [&](auto I)
785 {
786 constexpr std::size_t i = decltype(I)::value;
787 match = match && A[I][I].N() == subs_[i].nBlockLocal;
788 });
789 return match;
790 }
791
792 // Initialize DOF maps for sequential use from a known scalar DOF count.
793 void initSeqMaps_(std::size_t nDofs)
794 {
795 numBlocksGlobal_ = nDofs;
796 localToGlobal_.resize(nDofs);
797 isOwned_.assign(nDofs, true);
798 isGhost_.assign(nDofs, false);
799 rowComplete_.assign(nDofs, true);
800 for (std::size_t i = 0; i < nDofs; ++i)
801 localToGlobal_[i] = static_cast<GlobalIndex>(i);
802 }
803
812 void buildIsSrcRow_()
813 {
814 const std::size_t n = isOwned_.size();
815 isSrcRow_.assign(n, false);
816 if constexpr (isMultiType)
817 {
818 for (std::size_t r = 0; r < n; ++r)
819 isSrcRow_[r] = rowComplete_[r] ? isOwned_[r] : !isGhost_[r];
820 return;
821 }
822 else if constexpr (LSTraits::canCommunicate)
823 {
824 if (isParallel_)
825 {
826 const bool nonOverlapping = LSTraits::isNonOverlapping(*gridView_);
827 for (std::size_t r = 0; r < n; ++r)
828 isSrcRow_[r] = nonOverlapping ? !isGhost_[r] : isOwned_[r];
829 return;
830 }
831 }
832 isSrcRow_ = isOwned_;
833 }
834
840 auto tripletWriter_(bool firstCall, std::size_t& k, bool& samePattern)
841 {
842 if (firstCall)
843 {
844 irnLoc_.clear();
845 jcnLoc_.clear();
846 aLoc_.clear();
847 }
848
849 return [this, firstCall, &k, &samePattern](GlobalIndex row, GlobalIndex col, double value)
850 {
851 if (firstCall)
852 {
853 irnLoc_.push_back(toMumpsInt_(row + 1));
854 jcnLoc_.push_back(toMumpsInt_(col + 1));
855 aLoc_.push_back(value);
856 }
857 else if (k >= aLoc_.size() || irnLoc_[k] != row + 1 || jcnLoc_[k] != col + 1)
858 samePattern = false;
859 else if (samePattern)
860 aLoc_[k] = value;
861 ++k;
862 };
863 }
864
871 template<class AMatrix>
872 bool assembleTriplets_(const AMatrix& A, bool firstCall)
873 requires (isMultiTypeBlockMatrix<AMatrix>::value)
874 {
875 using namespace Dune::Hybrid;
876 constexpr std::size_t numSubDomains = AMatrix::N();
877 std::size_t k = 0;
878 bool samePattern = true;
879 auto addTriplet = tripletWriter_(firstCall, k, samePattern);
880 forEach(std::make_index_sequence<numSubDomains>{}, [&](auto I)
881 {
882 forEach(std::make_index_sequence<numSubDomains>{}, [&](auto J)
883 {
884 const auto& block = A[I][J];
885 using BT = typename std::decay_t<decltype(block)>::block_type;
886 const std::size_t offI = subs_[decltype(I)::value].scalarOffset;
887 const std::size_t offJ = subs_[decltype(J)::value].scalarOffset;
888 for (auto rowIt = block.begin(); rowIt != block.end(); ++rowIt)
889 {
890 for (int ki = 0; ki < BT::rows; ++ki)
891 {
892 const std::size_t r = offI + BT::rows*rowIt.index() + ki;
893 if (!isSrcRow_[r]) continue;
894 const GlobalIndex gRow = localToGlobal_[r];
895 for (auto colIt = rowIt->begin(); colIt != rowIt->end(); ++colIt)
896 for (int kj = 0; kj < BT::cols; ++kj)
897 addTriplet(gRow, localToGlobal_[offJ + BT::cols*colIt.index() + kj], (*colIt)[ki][kj]);
898 }
899 }
900 });
901 });
902 return samePattern && k == aLoc_.size();
903 }
904
905 template<class AMatrix>
906 bool assembleTriplets_(const AMatrix& A, bool firstCall)
907 requires (!isMultiTypeBlockMatrix<AMatrix>::value)
908 {
909 using BlockType = typename AMatrix::block_type;
910 constexpr int BS_I = BlockType::rows;
911 constexpr int BS_J = BlockType::cols;
912
913 std::size_t k = 0;
914 bool samePattern = true;
915 auto addTriplet = tripletWriter_(firstCall, k, samePattern);
916 for (auto rowIt = A.begin(); rowIt != A.end(); ++rowIt)
917 {
918 const std::size_t r = rowIt.index();
919 if (!isSrcRow_[r]) continue;
920 const GlobalIndex gBlockRow = localToGlobal_[r];
921
922 for (int ki = 0; ki < BS_I; ++ki)
923 {
924 const GlobalIndex gRow = gBlockRow * BS_I + ki;
925 for (auto colIt = rowIt->begin(); colIt != rowIt->end(); ++colIt)
926 {
927 const GlobalIndex gBlockCol = localToGlobal_[colIt.index()];
928 for (int kj = 0; kj < BS_J; ++kj)
929 addTriplet(gRow, gBlockCol * BS_J + kj, (*colIt)[ki][kj]);
930 }
931 }
932 }
933 return samePattern && k == aLoc_.size();
934 }
935
942 void setupRhsGather_()
943 {
944 // MPI counts and displacements are int
945 const std::size_t nScalar = numBlocksGlobal_ * blockSize;
946 if (nScalar > static_cast<std::size_t>(std::numeric_limits<int>::max()))
947 DUNE_THROW(Dune::NotImplemented, "DirectSolverMumps: gathering the right-hand side of "
948 << nScalar << " unknowns");
949
950 // Owned scalar global indices in the (ascending-local) packing order.
951 std::vector<int> myIdx;
952 for (std::size_t i = 0; i < localToGlobal_.size(); ++i)
953 if (isOwned_[i])
954 for (std::size_t k = 0; k < blockSize; ++k)
955 myIdx.push_back(static_cast<int>(localToGlobal_[i] * blockSize + k));
956 nOwnedScalar_ = static_cast<int>(myIdx.size());
957
958 int commSize = 1, myRank = 0;
959 MPI_Comm_size(mpiComm_, &commSize);
960 MPI_Comm_rank(mpiComm_, &myRank);
961 isHost_ = (myRank == 0);
962
963 recvCounts_.assign(isHost_ ? commSize : 0, 0);
964 MPI_Gather(&nOwnedScalar_, 1, MPI_INT,
965 isHost_ ? recvCounts_.data() : nullptr, 1, MPI_INT, 0, mpiComm_);
966
967 if (isHost_)
968 {
969 displs_.assign(commSize, 0);
970 std::partial_sum(recvCounts_.begin(), recvCounts_.end() - 1, displs_.begin() + 1);
971 allGlobalIdx_.assign(nScalar, 0);
972 rhsHost_.assign(nScalar, 0.0); // natural (MUMPS) order, length n per right-hand side
973 tmpHost_.assign(nScalar, 0.0); // gather order
974 }
975 // Gather the owned global indices (constant across solves).
976 MPI_Gatherv(myIdx.data(), nOwnedScalar_, MPI_INT,
977 isHost_ ? allGlobalIdx_.data() : nullptr,
978 isHost_ ? recvCounts_.data() : nullptr,
979 isHost_ ? displs_.data() : nullptr,
980 MPI_INT, 0, mpiComm_);
981 sendBuf_.resize(static_cast<std::size_t>(nOwnedScalar_));
982 }
983
989 template<class BVec, class XVec>
990 void solveCentralized_(const std::vector<const BVec*>& b, const std::vector<XVec*>& x)
991 {
992 const auto start = std::chrono::steady_clock::now();
993 const std::size_t numColumns = b.size();
994 if (numColumns == 0)
995 return;
996
997 const std::size_t n = tmpHost_.size();
998 if (isHost_)
999 rhsHost_.resize(n*numColumns);
1000
1001 for (std::size_t j = 0; j < numColumns; ++j)
1002 {
1003 // Pack owned scalar RHS values in the same order as setupRhsGather_'s indices.
1004 const auto& bj = *b[j];
1005 std::size_t s = 0;
1006 for (std::size_t i = 0; i < bj.size(); ++i)
1007 if (isOwned_[i])
1008 for (std::size_t k = 0; k < blockSize; ++k)
1009 sendBuf_[s++] = bj[i][k];
1010
1011 MPI_Gatherv(sendBuf_.data(), nOwnedScalar_, MPI_DOUBLE,
1012 isHost_ ? tmpHost_.data() : nullptr,
1013 isHost_ ? recvCounts_.data() : nullptr,
1014 isHost_ ? displs_.data() : nullptr,
1015 MPI_DOUBLE, 0, mpiComm_);
1016
1017 if (isHost_)
1018 for (std::size_t p = 0; p < n; ++p)
1019 rhsHost_[j*n + static_cast<std::size_t>(allGlobalIdx_[p])] = tmpHost_[p];
1020 }
1021
1022 if (isHost_)
1023 {
1024 id_.nrhs = static_cast<MUMPS_INT>(numColumns);
1025 id_.lrhs = id_.n;
1026 id_.rhs = rhsHost_.data();
1027 }
1028 id_.job = 3; // solve
1029 dmumps_c(&id_);
1030 checkError_("solve");
1031
1032 for (std::size_t j = 0; j < numColumns; ++j)
1033 {
1034 // The solutions are centralized on the host (natural order); permute back to gather order.
1035 if (isHost_)
1036 for (std::size_t p = 0; p < n; ++p)
1037 tmpHost_[p] = rhsHost_[j*n + static_cast<std::size_t>(allGlobalIdx_[p])];
1038
1039 MPI_Scatterv(isHost_ ? tmpHost_.data() : nullptr,
1040 isHost_ ? recvCounts_.data() : nullptr,
1041 isHost_ ? displs_.data() : nullptr,
1042 MPI_DOUBLE, sendBuf_.data(), nOwnedScalar_, MPI_DOUBLE, 0, mpiComm_);
1043
1044 auto& xj = *x[j];
1045 std::size_t s = 0;
1046 for (std::size_t i = 0; i < xj.size(); ++i)
1047 if (isOwned_[i])
1048 for (std::size_t k = 0; k < blockSize; ++k)
1049 xj[i][k] = sendBuf_[s++];
1050 }
1051
1052 if (this->verbosity() > 0 && isHost_)
1053 std::cout << "[MUMPS] solve+gather "
1054 << std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count()
1055 << " ms" << (numColumns > 1 ? " for " + std::to_string(numColumns) + " right-hand sides" : std::string{})
1056 << std::endl;
1057 }
1058
1059 // Symbolic analysis (repeated only if the matrix structure changed) and numeric factorization
1060 void factorize_(const Matrix& A)
1061 {
1062 using Clock = std::chrono::steady_clock;
1063 const bool time = this->verbosity() > 0;
1064 auto ms = [](Clock::time_point a, Clock::time_point z) {
1065 return std::chrono::duration<double, std::milli>(z - a).count();
1066 };
1067
1068 double tAssemble = 0.0, tAnalysis = 0.0;
1069 auto t0 = Clock::now();
1070 // the analysis is collective, so all processes redo it if one process' structure changed
1071 const bool analyze = !analyzed_ || !onAllProcesses_(assembleTriplets_(A, /*firstCall=*/false));
1072 if (analyze)
1073 {
1074 buildIsSrcRow_();
1075 setupRhsGather_();
1076
1077 assembleTriplets_(A, /*firstCall=*/true);
1078 id_.n = toMumpsInt_(static_cast<GlobalIndex>(numBlocksGlobal_ * blockSize));
1079 id_.nz_loc = toMumpsInt_(static_cast<GlobalIndex>(aLoc_.size()));
1080 id_.irn_loc = irnLoc_.data();
1081 id_.jcn_loc = jcnLoc_.data();
1082 id_.a_loc = aLoc_.data();
1083 auto t1 = Clock::now(); tAssemble = ms(t0, t1);
1084
1085 id_.job = 1; // symbolic analysis
1086 dmumps_c(&id_);
1087 checkError_("symbolic analysis");
1088 analyzed_ = true;
1089 tAnalysis = ms(t1, Clock::now());
1090 }
1091 else
1092 tAssemble = ms(t0, Clock::now());
1093
1094 auto t2 = Clock::now();
1095 id_.job = 2; // numeric factorization
1096 dmumps_c(&id_);
1097 checkError_("numeric factorization");
1098 factorized_ = true;
1099 const double tNumeric = ms(t2, Clock::now());
1100
1101 if (time && isHost_)
1102 std::cout << "[MUMPS] assemble " << tAssemble << " ms"
1103 << (tAnalysis > 0.0 ? (", analysis " + std::to_string(tAnalysis) + " ms") : std::string{})
1104 << ", numeric factorization " << tNumeric << " ms" << std::endl;
1105 }
1106
1107 // Sequential dof maps follow the size of the system of A, parallel ones are checked against it.
1108 void prepareMaps_(const Matrix& A)
1109 {
1110 if constexpr (isMultiType)
1111 {
1112 if (isParallel_)
1113 updateMapsToSystemSize_(numScalarRows_(A), subdomainSizesMatch_(A));
1114 else
1115 {
1116 updateMapsToSystemSize_(numScalarRows_(A));
1117 initSeqDomainOffsets_(A);
1118 }
1119 }
1120 else
1121 updateMapsToSystemSize_(A.N());
1122 }
1123
1124 template<class MTMatrix>
1125 static std::size_t numScalarRows_(const MTMatrix& A)
1126 {
1127 std::size_t numRows = 0;
1128 using namespace Dune::Hybrid;
1129 forEach(std::make_index_sequence<MTMatrix::N()>{}, [&](auto I)
1130 {
1131 const auto& block = A[I][I];
1132 numRows += block.N()*std::decay_t<decltype(block)>::block_type::rows;
1133 });
1134 return numRows;
1135 }
1136
1137 SolverResult solveColumns_(const std::vector<XVector*>& x, const std::vector<const BVector*>& b)
1138 {
1139 if (!factorized_)
1140 DUNE_THROW(Dune::InvalidStateException, "Called solve(x, b) but no matrix has been set");
1141
1142 checkColumns_(x, b);
1143 result_.clear();
1144 if constexpr (isMultiType)
1145 solveMultiType_(x, b);
1146 else
1147 solveSingleType_(x, b);
1148 result_.converged = true;
1149 return result_;
1150 }
1151
1152 // The vectors have to match the factorized system and the number of right-hand sides has to be
1153 // the same on all processes, which take part in every gather; otherwise all processes throw.
1154 void checkColumns_(const std::vector<XVector*>& x, const std::vector<const BVector*>& b) const
1155 {
1156 bool match = x.size() == b.size();
1157 for (std::size_t j = 0; match && j < x.size(); ++j)
1158 match = sizeInDofMaps_(*x[j]) == localToGlobal_.size() && sizeInDofMaps_(*b[j]) == localToGlobal_.size();
1159
1160 if (isParallel_)
1161 {
1162 long long counts[2] = { static_cast<long long>(b.size()), -static_cast<long long>(b.size()) };
1163 MPI_Allreduce(MPI_IN_PLACE, counts, 2, MPI_LONG_LONG, MPI_MIN, mpiComm_);
1164 match = match && counts[0] == -counts[1];
1165 }
1166
1167 if (!onAllProcesses_(match))
1168 DUNE_THROW(Dune::InvalidStateException,
1169 "DirectSolverMumps: the solutions and right-hand sides do not match each other or the "
1170 "factorized system on at least one process (here " << x.size() << " solutions and "
1171 << b.size() << " right-hand sides, the system has " << localToGlobal_.size() << " rows)");
1172 }
1173
1174 // The size of a vector in the entries of the dof maps (blocks for single-type, scalars for MultiType)
1175 template<class V>
1176 static std::size_t sizeInDofMaps_(const V& v)
1177 {
1178 if constexpr (isMultiTypeBlockVector<V>::value)
1179 {
1180 std::size_t size = 0;
1181 Dune::Hybrid::forEach(std::make_index_sequence<V::N()>{}, [&](auto i)
1182 {
1183 using Block = typename std::decay_t<decltype(v[i])>::block_type;
1184 size += v[i].size()*Block::size();
1185 });
1186 return size;
1187 }
1188 else
1189 return v.size();
1190 }
1191
1192 bool onAllProcesses_(bool value) const
1193 {
1194 if (!isParallel_)
1195 return value;
1196 int local = value ? 1 : 0;
1197 int global = 0;
1198 MPI_Allreduce(&local, &global, 1, MPI_INT, MPI_MIN, mpiComm_);
1199 return global == 1;
1200 }
1201
1202 // The sequential dof maps follow the size of the system; the parallel ones are rebuilt by
1203 // updateAfterGridAdaption. In parallel, a mismatch on one process makes all processes throw.
1204 void updateMapsToSystemSize_(std::size_t numRows, bool subdomainSizesMatch = true)
1205 {
1206 const bool sizesMatch = numRows == localToGlobal_.size() && subdomainSizesMatch;
1207 if (isParallel_)
1208 {
1209 if (!onAllProcesses_(sizesMatch))
1210 DUNE_THROW(Dune::InvalidStateException,
1211 "DirectSolverMumps: the dof distribution of the system differs from the one of "
1212 "the solver on at least one process (here " << numRows << " rows, the solver "
1213 "expects " << localToGlobal_.size() << "). Call updateAfterGridAdaption after "
1214 "the grid changed.");
1215 }
1216 else if (!sizesMatch)
1217 {
1218 initSeqMaps_(numRows);
1219 analyzed_ = false;
1220 }
1221 }
1222
1223 void solveSingleType_(const std::vector<XVector*>& x, const std::vector<const BVector*>& b)
1224 {
1225 std::vector<BVector> bConsistent;
1226 bConsistent.reserve(b.size());
1227 for (const auto* bj : b)
1228 {
1229 bConsistent.push_back(*bj);
1230 makeRhsConsistent_(bConsistent.back());
1231 }
1232
1233 std::vector<const BVector*> bColumns;
1234 for (const auto& bj : bConsistent)
1235 bColumns.push_back(&bj);
1236 solveCentralized_(bColumns, x);
1237
1238 for (auto* xj : x)
1239 distributeSolution_(*xj);
1240 }
1241
1242 // Non-overlapping parallel: accumulate partial border RHS onto owners before the gather.
1243 void makeRhsConsistent_(BVector& b) const
1244 {
1245 if constexpr (LSTraits::canCommunicate)
1246 {
1247 if (isParallel_ && LSTraits::isNonOverlapping(*gridView_))
1248 {
1249 if constexpr (requires { LSTraits::dofCodims; })
1250 {
1251 MultiCodimParallelVectorHelper<GridView, DofMapper> vecHelper(*gridView_, *mapper_);
1252 vecHelper.makeNonOverlappingConsistent(b, LSTraits::dofCodims);
1253 }
1254 else
1255 {
1256 ParallelVectorHelper<GridView, DofMapper, LSTraits::dofCodim> vecHelper(*gridView_, *mapper_);
1257 vecHelper.makeNonOverlappingConsistent(b);
1258 }
1259 }
1260 }
1261 }
1262
1263 // Distribute the solution: zero non-owned, then sum-communicate to the owner's value.
1264 void distributeSolution_(XVector& x) const
1265 {
1266 if constexpr (LSTraits::canCommunicate)
1267 {
1268 if (isParallel_)
1269 {
1270 for (std::size_t i = 0; i < x.size(); ++i)
1271 if (!isOwned_[i])
1272 x[i] = 0;
1273 if constexpr (requires { LSTraits::dofCodims; }) {
1274 MultiCodimVectorCommDataHandleSum<DofMapper, XVector, GridView::dimension> handle(*mapper_, x, LSTraits::dofCodims);
1275 gridView_->communicate(handle, Dune::All_All_Interface, Dune::ForwardCommunication);
1276 } else {
1277 VectorCommDataHandleSum<DofMapper, XVector, LSTraits::dofCodim> handle(*mapper_, x);
1278 gridView_->communicate(handle, Dune::All_All_Interface, Dune::ForwardCommunication);
1279 }
1280 }
1281 }
1282 }
1283
1284 void solveMultiType_(const std::vector<XVector*>& x, const std::vector<const BVector*>& b)
1285 {
1286 // The flattened vectors have the scalar layout of the system factorized by MUMPS.
1287 std::vector<ScalarBlockVector> bScalar;
1288 bScalar.reserve(b.size());
1289 for (const auto* bj : b)
1290 {
1291 bScalar.push_back(VectorConverter<BVector>::multiTypeToBlockVector(*bj));
1292
1293 // RHS preprocessing per subdomain: sum partial border-DOF contributions onto the owner.
1294 if (isParallel_)
1295 for (const auto& sub : subs_)
1296 if (sub.rhsPreprocess)
1297 sub.rhsPreprocess(bScalar.back());
1298 }
1299
1300 auto xScalar = bScalar; // same layout; owned entries overwritten by the solve
1301 std::vector<const ScalarBlockVector*> bColumns;
1302 std::vector<ScalarBlockVector*> xColumns;
1303 for (std::size_t j = 0; j < bScalar.size(); ++j)
1304 {
1305 bColumns.push_back(&bScalar[j]);
1306 xColumns.push_back(&xScalar[j]);
1307 }
1308 solveCentralized_(bColumns, xColumns);
1309
1310 for (std::size_t j = 0; j < x.size(); ++j)
1311 {
1312 // Ghost broadcast per subdomain: zero non-owned DOFs, sum-communicate to the owner's value.
1313 if (isParallel_)
1314 {
1315 for (std::size_t r = 0; r < xScalar[j].size(); ++r)
1316 if (!isOwned_[r]) xScalar[j][r] = 0.0;
1317 for (const auto& sub : subs_)
1318 if (sub.ghostBroadcast)
1319 sub.ghostBroadcast(xScalar[j]);
1320 }
1321
1322 VectorConverter<XVector>::retrieveValues(*x[j], xScalar[j]);
1323 }
1324 }
1325
1326 Scalar ownedGlobalNorm_(const XVector& x) const
1327 {
1328 double localSumSq = 0.0;
1329 for (std::size_t i = 0; i < x.size(); ++i)
1330 if (isOwned_[i])
1331 for (std::size_t k = 0; k < blockSize; ++k)
1332 localSumSq += x[i][k] * x[i][k];
1333 double globalSumSq = 0.0;
1334 MPI_Allreduce(&localSumSq, &globalSumSq, 1, MPI_DOUBLE, MPI_SUM, mpiComm_);
1335 using std::sqrt;
1336 return sqrt(globalSumSq);
1337 }
1338
1339 static MUMPS_INT toMumpsInt_(GlobalIndex v)
1340 {
1341 if (v > static_cast<GlobalIndex>(std::numeric_limits<MUMPS_INT>::max()))
1342 DUNE_THROW(Dune::RangeError, "Index " << v << " exceeds MUMPS_INT range; "
1343 "rebuild MUMPS with 64-bit integers (-DINTSIZE64)");
1344 return static_cast<MUMPS_INT>(v);
1345 }
1346
1347 void checkError_(const char* phase) const
1348 {
1349 const int error = infog_(1);
1350 if (error < 0)
1351 {
1352 // the errors that the MUMPS user guide resolves by increasing ICNTL(14)
1353 const bool workspaceTooSmall = error == -8 || error == -9 || error == -14
1354 || error == -15 || error == -17 || error == -20;
1355 DUNE_THROW(Dune::Exception, "MUMPS " << phase << " failed: INFOG(1)=" << error
1356 << ", INFOG(2)=" << infog_(2)
1357 << (workspaceTooSmall ? ". Increase LinearSolver.MumpsWorkspaceIncrease (ICNTL(14))" : ""));
1358 }
1359 }
1360
1361 std::optional<GridView> gridView_;
1362 const DofMapper* mapper_ = nullptr;
1363 bool isParallel_;
1364
1365 MPI_Comm mpiComm_;
1366 DMUMPS_STRUC_C id_;
1367 bool initialized_ = false;
1368 bool analyzed_ = false;
1369 bool factorized_ = false;
1370 SolverResult result_;
1371
1372 // Global DOF bookkeeping (block level for single-type, scalar level for MultiType).
1373 std::size_t numBlocksGlobal_ = 0;
1374 std::vector<GlobalIndex> localToGlobal_;
1375 std::vector<bool> isOwned_;
1376 std::vector<bool> isGhost_;
1377 std::vector<bool> isSrcRow_; // block rows this rank contributes to the distributed assembly
1378 std::vector<bool> rowComplete_; // per (scalar) row: owner row already complete? (MultiType)
1379
1380 // Distributed assembled input (ICNTL(18)=3). irn_loc/jcn_loc hold the structure of the last
1381 // symbolic analysis; a_loc is refilled every solve.
1382 std::vector<MUMPS_INT> irnLoc_;
1383 std::vector<MUMPS_INT> jcnLoc_;
1384 std::vector<double> aLoc_;
1385
1386 // Centralized dense RHS/solution gather (host = rank 0). The assembled RHS is gathered to the
1387 // host, solved, and the solution scattered back.
1388 bool isHost_ = true;
1389 int nOwnedScalar_ = 0;
1390 std::vector<int> recvCounts_; // host only
1391 std::vector<int> displs_; // host only
1392 std::vector<int> allGlobalIdx_; // host only, gather-order -> natural global index
1393 std::vector<double> rhsHost_; // host only, natural (MUMPS) order, length n per right-hand side
1394 std::vector<double> tmpHost_; // host only, gather order
1395 std::vector<double> sendBuf_; // per-rank owned scalar values / solution
1396
1397 // MultiType (multidomain) bookkeeping: one entry per subdomain, in subvector order.
1398 std::vector<SubdomainInfo> subs_;
1399};
1400
1401} // end namespace Dumux
1402
1403#endif // DUMUX_HAVE_MUMPS
1404
1405#endif
Base class for linear solvers.
Definition solver.hh:27
constexpr BJ bj
Tag for the Beavers-Joseph slip condition.
Definition momentum/slipcondition.hh:42
Define traits for linear solvers.
Type traits to be used with matrix types.
A helper class that converts a Dune::MultiTypeBlockMatrix into a plain Dune::BCRSMatrix.
constexpr std::size_t blockSize()
Definition dunevectors.hh:27
Definition cvfelocalresidual.hh:25
Dune::SolverCategory::Category solverCategory(const GridView &gridView)
Definition solvercategory.hh:20
bool solve(const Matrix &matrix, Vector &x, const Vector &b)
Solve the local linear system .
Definition odesolver.hh:468
auto norm(const Vector &vector)
Compute the Euclidean norm of a scalar or vector-like object.
Definition odesolver.hh:435
typename ScalarType< X >::Type Scalar
Definition odesolver.hh:52
Definition adapt.hh:17
Provides a helper class for nonoverlapping decomposition.
The infrastructure to retrieve run-time parameters from Dune::ParameterTrees.
Base class for linear solvers.
Solver category.
Type traits to be used with vector types.
Contains a class to exchange entries of a vector.