MueLu Version of the Day
Loading...
Searching...
No Matches
MueLu_RebalanceTransferFactory_def.hpp
Go to the documentation of this file.
1// @HEADER
2// *****************************************************************************
3// MueLu: A package for multigrid based preconditioning
4//
5// Copyright 2012 NTESS and the MueLu contributors.
6// SPDX-License-Identifier: BSD-3-Clause
7// *****************************************************************************
8// @HEADER
9
10#ifndef MUELU_REBALANCETRANSFERFACTORY_DEF_HPP
11#define MUELU_REBALANCETRANSFERFACTORY_DEF_HPP
12
13#include <sstream>
14#include <Teuchos_Tuple.hpp>
15
16#include "Xpetra_MultiVector.hpp"
17#include "Xpetra_MultiVectorFactory.hpp"
18#include "Xpetra_Vector.hpp"
19#include "Xpetra_VectorFactory.hpp"
20#include <Xpetra_Matrix.hpp>
21#include <Xpetra_MapFactory.hpp>
22#include <Xpetra_MatrixFactory.hpp>
23#include <Xpetra_Import.hpp>
24#include <Xpetra_ImportFactory.hpp>
25#include <Xpetra_IO.hpp>
26
28
29#include "MueLu_AmalgamationFactory.hpp"
30#include "MueLu_Level.hpp"
31#include "MueLu_MasterList.hpp"
32#include "MueLu_Monitor.hpp"
33#include "MueLu_PerfUtils.hpp"
34
35namespace MueLu {
36
37template <class Scalar, class LocalOrdinal, class GlobalOrdinal, class Node>
39
40template <class Scalar, class LocalOrdinal, class GlobalOrdinal, class Node>
42
43template <class Scalar, class LocalOrdinal, class GlobalOrdinal, class Node>
45 RCP<ParameterList> validParamList = rcp(new ParameterList());
46
47#define SET_VALID_ENTRY(name) validParamList->setEntry(name, MasterList::getEntry(name))
48 SET_VALID_ENTRY("repartition: rebalance P and R");
49 SET_VALID_ENTRY("repartition: explicit via new copy rebalance P and R");
50 SET_VALID_ENTRY("repartition: rebalance Nullspace");
51 SET_VALID_ENTRY("transpose: use implicit");
52 SET_VALID_ENTRY("repartition: use subcommunicators");
53 SET_VALID_ENTRY("repartition: send type");
54#undef SET_VALID_ENTRY
55
56 {
57 typedef Teuchos::StringValidator validatorType;
58 RCP<validatorType> typeValidator = rcp(new validatorType(Teuchos::tuple<std::string>("Interpolation", "Restriction")));
59 validParamList->set("type", "Interpolation", "Type of the transfer operator that need to be rebalanced (Interpolation or Restriction)", typeValidator);
60 }
61
62 validParamList->set<RCP<const FactoryBase> >("P", null, "Factory of the prolongation operator that need to be rebalanced (only used if type=Interpolation)");
63 validParamList->set<RCP<const FactoryBase> >("R", null, "Factory of the restriction operator that need to be rebalanced (only used if type=Restriction)");
64 validParamList->set<RCP<const FactoryBase> >("Nullspace", null, "Factory of the nullspace that need to be rebalanced (only used if type=Interpolation)");
65 validParamList->set<RCP<const FactoryBase> >("Coordinates", null, "Factory of the coordinates that need to be rebalanced (only used if type=Interpolation)");
66 validParamList->set<RCP<const FactoryBase> >("Material", null, "Factory of the material that need to be rebalanced (only used if type=Interpolation)");
67 validParamList->set<RCP<const FactoryBase> >("BlockNumber", null, "Factory of the block ids that need to be rebalanced (only used if type=Interpolation)");
68 validParamList->set<RCP<const FactoryBase> >("Importer", null, "Factory of the importer object used for the rebalancing");
69 validParamList->set<int>("write start", -1, "First level at which coordinates should be written to file");
70 validParamList->set<int>("write end", -1, "Last level at which coordinates should be written to file");
71
72 // TODO validation: "P" parameter valid only for type="Interpolation" and "R" valid only for type="Restriction". Like so:
73 // if (paramList.isEntry("type") && paramList.get("type) == "Interpolation) {
74 // validParamList->set< RCP<const FactoryBase> >("P", Teuchos::null, "Factory of the prolongation operator that need to be rebalanced (only used if type=Interpolation)");
75
76 return validParamList;
77}
78
79template <class Scalar, class LocalOrdinal, class GlobalOrdinal, class Node>
81 const ParameterList& pL = GetParameterList();
82
83 if (pL.get<std::string>("type") == "Interpolation") {
84 Input(coarseLevel, "P");
85 if (pL.get<bool>("repartition: rebalance Nullspace"))
86 Input(coarseLevel, "Nullspace");
87 if (pL.get<RCP<const FactoryBase> >("Coordinates") != Teuchos::null)
88 Input(coarseLevel, "Coordinates");
89 if (pL.get<RCP<const FactoryBase> >("Material") != Teuchos::null)
90 Input(coarseLevel, "Material");
91 if (pL.get<RCP<const FactoryBase> >("BlockNumber") != Teuchos::null)
92 Input(coarseLevel, "BlockNumber");
93
94 } else {
95 if (pL.get<bool>("transpose: use implicit") == false)
96 Input(coarseLevel, "R");
97 }
98
99 Input(coarseLevel, "Importer");
100}
101
102template <class Scalar, class LocalOrdinal, class GlobalOrdinal, class Node>
104 FactoryMonitor m(*this, "Build", coarseLevel);
105 typedef Xpetra::MultiVector<typename Teuchos::ScalarTraits<Scalar>::magnitudeType, LO, GO, NO> xdMV;
106
107 const ParameterList& pL = GetParameterList();
108
109 RCP<Matrix> originalP = Get<RCP<Matrix> >(coarseLevel, "P");
110 // If we don't have a valid P (e.g., # global aggregates is 0), skip this rebalancing. This level will
111 // ultimately be removed in MueLu_Hierarchy_defs.h via a resize()
112 if (originalP == Teuchos::null) {
113 Set(coarseLevel, "P", originalP);
114 return;
115 }
116 int implicit = !pL.get<bool>("repartition: rebalance P and R");
117 int reallyExplicit = pL.get<bool>("repartition: explicit via new copy rebalance P and R");
118 int writeStart = pL.get<int>("write start");
119 int writeEnd = pL.get<int>("write end");
120
121 if (writeStart == 0 && fineLevel.GetLevelID() == 0 && writeStart <= writeEnd && IsAvailable(fineLevel, "Coordinates")) {
122 std::string fileName = "coordinates_level_0.m";
123 RCP<xdMV> fineCoords = fineLevel.Get<RCP<xdMV> >("Coordinates");
124 if (fineCoords != Teuchos::null)
125 Xpetra::IO<typename Teuchos::ScalarTraits<Scalar>::magnitudeType, LO, GO, NO>::Write(fileName, *fineCoords);
126 }
127
128 if (writeStart == 0 && fineLevel.GetLevelID() == 0 && writeStart <= writeEnd && IsAvailable(fineLevel, "BlockNumber")) {
129 std::string fileName = "BlockNumber_level_0.m";
130 RCP<LocalOrdinalVector> fineBlockNumber = fineLevel.Get<RCP<LocalOrdinalVector> >("BlockNumber");
131 if (fineBlockNumber != Teuchos::null)
132 Xpetra::IO<SC, LO, GO, NO>::WriteLOMV(fileName, *fineBlockNumber);
133 }
134
135 RCP<const Import> importer = Get<RCP<const Import> >(coarseLevel, "Importer");
136 if (implicit) {
137 // Save the importer, we'll need it for solve
138 coarseLevel.Set("Importer", importer, NoFactory::get());
139 }
140
141 RCP<ParameterList> params = rcp(new ParameterList());
142 if (IsPrint(Statistics2)) {
143 params->set("printLoadBalancingInfo", true);
144 params->set("printCommInfo", true);
145 }
146
147 std::string transferType = pL.get<std::string>("type");
148 if (transferType == "Interpolation") {
149 originalP = Get<RCP<Matrix> >(coarseLevel, "P");
150
151 {
152 // This line must be after the Get call
153 SubFactoryMonitor m1(*this, "Rebalancing prolongator", coarseLevel);
154
155 if (implicit || importer.is_null()) {
156 GetOStream(Runtime0) << "Using original prolongator" << std::endl;
157 Set(coarseLevel, "P", originalP);
158
159 } else {
160 // There are two version of an explicit rebalanced P and R.
161 // The !reallyExplicit way, is sufficient for all MueLu purposes
162 // with the exception of the CombinePFactory that needs true domain
163 // and column maps.
164 // !reallyExplicit:
165 // Rather than calling fillComplete (which would entail creating a new
166 // column map), it's sufficient to replace the domain map and importer.
167 // Note that this potentially violates the assumption that in the
168 // column map, local IDs appear before any off-rank IDs.
169 //
170 // reallyExplicit:
171 // P transfers from coarse grid to the fine grid. Here, we change
172 // the domain map (coarse) of Paccording to the new partition. The
173 // range map (fine) is kept unchanged.
174 //
175 // The domain map of P must match the range map of R. To change the
176 // domain map of P, P needs to be fillCompleted again with the new
177 // domain map. To achieve this, P is copied into a new matrix that
178 // is not fill-completed. The doImport() operation is just used
179 // here to make a copy of P: the importer is trivial and there is
180 // no data movement involved. The reordering actually happens during
181 // fillComplete() with domainMap == importer->getTargetMap().
182
183 RCP<Matrix> rebalancedP;
184 if (reallyExplicit) {
185 size_t totalMaxPerRow = 0;
186 ArrayRCP<size_t> nnzPerRow(originalP->getRowMap()->getLocalNumElements(), 0);
187 for (size_t i = 0; i < originalP->getRowMap()->getLocalNumElements(); ++i) {
188 nnzPerRow[i] = originalP->getNumEntriesInLocalRow(i);
189 if (nnzPerRow[i] > totalMaxPerRow) totalMaxPerRow = nnzPerRow[i];
190 }
191
192 rebalancedP = MatrixFactory::Build(originalP->getRowMap(), totalMaxPerRow);
193
194 {
195 RCP<Import> trivialImporter = ImportFactory::Build(originalP->getRowMap(), originalP->getRowMap());
196
197 std::string monitorLabel{"Rebalancing prolongator -- import only"};
198 if (auto sendType = pL.get<std::string>("repartition: send type"); sendType != "") {
199 auto sendTypeList = Teuchos::rcp(new Teuchos::ParameterList());
200 sendTypeList->set("Send type", sendType);
201 trivialImporter->setDistributorParameters(sendTypeList);
202 monitorLabel += " [" + sendType + "]";
203 }
204
205 SubFactoryMonitor m2(*this, monitorLabel.c_str(), coarseLevel);
206 rebalancedP->doImport(*originalP, *trivialImporter, Xpetra::INSERT);
207 }
208 rebalancedP->fillComplete(importer->getTargetMap(), originalP->getRangeMap());
209
210 } else {
211 rebalancedP = originalP;
212 RCP<const CrsMatrixWrap> crsOp = rcp_dynamic_cast<const CrsMatrixWrap>(originalP);
213 TEUCHOS_TEST_FOR_EXCEPTION(crsOp == Teuchos::null, Exceptions::BadCast, "Cast from Xpetra::Matrix to Xpetra::CrsMatrixWrap failed");
214
215 RCP<CrsMatrix> rebalancedP2 = crsOp->getCrsMatrix();
216 TEUCHOS_TEST_FOR_EXCEPTION(rebalancedP2 == Teuchos::null, std::runtime_error, "Xpetra::CrsMatrixWrap doesn't have a CrsMatrix");
217
218 {
219 SubFactoryMonitor subM(*this, "Rebalancing prolongator -- fast map replacement", coarseLevel);
220
221 RCP<const Import> newImporter;
222 {
223 SubFactoryMonitor subM2(*this, "Import construction", coarseLevel);
224 newImporter = ImportFactory::Build(importer->getTargetMap(), rebalancedP->getColMap());
225 }
226 rebalancedP2->replaceDomainMapAndImporter(importer->getTargetMap(), newImporter);
227 }
228 }
230 // TODO FIXME somehow we have to transfer the striding information of the permuted domain/range maps.
231 // That is probably something for an external permutation factory
232 // if (originalP->IsView("stridedMaps"))
233 // rebalancedP->CreateView("stridedMaps", originalP);
235 if (!rebalancedP.is_null()) {
236 std::ostringstream oss;
237 oss << "P_" << coarseLevel.GetLevelID();
238 rebalancedP->setObjectLabel(oss.str());
239 }
240 Set(coarseLevel, "P", rebalancedP);
241
242 if (IsPrint(Statistics2))
243 GetOStream(Statistics2) << PerfUtils::PrintMatrixInfo(*rebalancedP, "P (rebalanced)", params);
244 }
245 }
246
247 if (importer.is_null()) {
248 if (IsAvailable(coarseLevel, "Nullspace"))
249 Set(coarseLevel, "Nullspace", Get<RCP<MultiVector> >(coarseLevel, "Nullspace"));
250
251 if (pL.isParameter("Coordinates") && pL.get<RCP<const FactoryBase> >("Coordinates") != Teuchos::null)
252 if (IsAvailable(coarseLevel, "Coordinates"))
253 Set(coarseLevel, "Coordinates", Get<RCP<xdMV> >(coarseLevel, "Coordinates"));
254
255 if (pL.isParameter("Material") && pL.get<RCP<const FactoryBase> >("Material") != Teuchos::null)
256 if (IsAvailable(coarseLevel, "Material"))
257 Set(coarseLevel, "Material", Get<RCP<MultiVector> >(coarseLevel, "Material"));
258
259 if (pL.isParameter("BlockNumber") && pL.get<RCP<const FactoryBase> >("BlockNumber") != Teuchos::null)
260 if (IsAvailable(coarseLevel, "BlockNumber"))
261 Set(coarseLevel, "BlockNumber", Get<RCP<LocalOrdinalVector> >(coarseLevel, "BlockNumber"));
262
263 return;
264 }
265
266 if (pL.isParameter("Coordinates") &&
267 pL.get<RCP<const FactoryBase> >("Coordinates") != Teuchos::null &&
268 IsAvailable(coarseLevel, "Coordinates")) {
269 RCP<xdMV> coords = Get<RCP<xdMV> >(coarseLevel, "Coordinates");
270
271 // This line must be after the Get call
272 SubFactoryMonitor subM(*this, "Rebalancing coordinates", coarseLevel);
273
274 // If a process has no matrix rows, then we can't calculate blocksize using the formula below.
275 LO nodeNumElts = coords->getMap()->getLocalNumElements();
276 LO myBlkSize = 0, blkSize = 0;
277 if (nodeNumElts > 0)
278 myBlkSize = importer->getSourceMap()->getLocalNumElements() / nodeNumElts;
279 MueLu_maxAll(coords->getMap()->getComm(), myBlkSize, blkSize);
280
281 RCP<const Import> coordImporter;
282
283 if (blkSize == 1) {
284 coordImporter = importer;
285 } else {
286 RCP<const Map> origMap = coords->getMap();
287 std::vector<size_t> stridingInfo{Teuchos::as<size_t>(blkSize)};
288 RCP<const Map> targetMap = StridedMapFactory::Build(origMap->lib(), Teuchos::OrdinalTraits<Xpetra::global_size_t>::invalid(),
289 importer->getTargetMap()->getLocalElementList(), origMap->getIndexBase(), stridingInfo, origMap->getComm());
290 RCP<const Map> targetVectorMap;
291
292 AmalgamationFactory<SC, LO, GO, NO>::AmalgamateMap(rcp_dynamic_cast<const StridedMap>(targetMap), targetVectorMap);
293 coordImporter = ImportFactory::Build(origMap, targetVectorMap);
294 }
295
296 RCP<xdMV> permutedCoords = Xpetra::MultiVectorFactory<typename Teuchos::ScalarTraits<Scalar>::magnitudeType, LO, GO, NO>::Build(coordImporter->getTargetMap(), coords->getNumVectors());
297 permutedCoords->doImport(*coords, *coordImporter, Xpetra::INSERT);
298
299 if (pL.isParameter("repartition: use subcommunicators") == true && pL.get<bool>("repartition: use subcommunicators") == true)
300 permutedCoords->replaceMap(permutedCoords->getMap()->removeEmptyProcesses());
301
302 if (permutedCoords->getMap() == Teuchos::null)
303 permutedCoords = Teuchos::null;
304
305 Set(coarseLevel, "Coordinates", permutedCoords);
306
307 std::string fileName = "rebalanced_coordinates_level_" + toString(coarseLevel.GetLevelID()) + ".m";
308 if (writeStart <= coarseLevel.GetLevelID() && coarseLevel.GetLevelID() <= writeEnd && permutedCoords->getMap() != Teuchos::null)
309 Xpetra::IO<typename Teuchos::ScalarTraits<Scalar>::magnitudeType, LO, GO, NO>::Write(fileName, *permutedCoords);
310 }
311
312 if (IsAvailable(coarseLevel, "Material")) {
313 RCP<MultiVector> material = Get<RCP<MultiVector> >(coarseLevel, "Material");
314
315 // This line must be after the Get call
316 SubFactoryMonitor subM(*this, "Rebalancing material", coarseLevel);
317
318 // If a process has no matrix rows, then we can't calculate blocksize using the formula below.
319 LO nodeNumElts = material->getMap()->getLocalNumElements();
320 LO myBlkSize = 0, blkSize = 0;
321 if (nodeNumElts > 0)
322 myBlkSize = importer->getSourceMap()->getLocalNumElements() / nodeNumElts;
323 MueLu_maxAll(material->getMap()->getComm(), myBlkSize, blkSize);
324
325 RCP<const Import> materialImporter;
326
327 if (blkSize == 1) {
328 materialImporter = importer;
329 } else {
330 RCP<const Map> origMap = material->getMap();
331 std::vector<size_t> stridingInfo{Teuchos::as<size_t>(blkSize)};
332 RCP<const Map> targetMap = StridedMapFactory::Build(origMap->lib(), Teuchos::OrdinalTraits<Xpetra::global_size_t>::invalid(),
333 importer->getTargetMap()->getLocalElementList(), origMap->getIndexBase(), stridingInfo, origMap->getComm());
334 RCP<const Map> targetVectorMap;
335
336 AmalgamationFactory<SC, LO, GO, NO>::AmalgamateMap(rcp_dynamic_cast<const StridedMap>(targetMap), targetVectorMap);
337 materialImporter = ImportFactory::Build(origMap, targetVectorMap);
338 }
339
340 RCP<MultiVector> permutedMaterial = MultiVectorFactory::Build(materialImporter->getTargetMap(), material->getNumVectors());
341 permutedMaterial->doImport(*material, *materialImporter, Xpetra::INSERT);
342
343 if (pL.get<bool>("repartition: use subcommunicators") == true)
344 permutedMaterial->replaceMap(permutedMaterial->getMap()->removeEmptyProcesses());
345
346 if (permutedMaterial->getMap() == Teuchos::null)
347 permutedMaterial = Teuchos::null;
348
349 Set(coarseLevel, "Material", permutedMaterial);
350 }
351
352 if (pL.isParameter("BlockNumber") &&
353 pL.get<RCP<const FactoryBase> >("BlockNumber") != Teuchos::null &&
354 IsAvailable(coarseLevel, "BlockNumber")) {
355 RCP<LocalOrdinalVector> BlockNumber = Get<RCP<LocalOrdinalVector> >(coarseLevel, "BlockNumber");
356
357 // This line must be after the Get call
358 SubFactoryMonitor subM(*this, "Rebalancing BlockNumber", coarseLevel);
359
360 RCP<LocalOrdinalVector> permutedBlockNumber = LocalOrdinalVectorFactory::Build(importer->getTargetMap(), false);
361 permutedBlockNumber->doImport(*BlockNumber, *importer, Xpetra::INSERT);
362
363 if (pL.isParameter("repartition: use subcommunicators") == true && pL.get<bool>("repartition: use subcommunicators") == true)
364 permutedBlockNumber->replaceMap(permutedBlockNumber->getMap()->removeEmptyProcesses());
365
366 if (permutedBlockNumber->getMap() == Teuchos::null)
367 permutedBlockNumber = Teuchos::null;
368
369 Set(coarseLevel, "BlockNumber", permutedBlockNumber);
370
371 std::string fileName = "rebalanced_BlockNumber_level_" + toString(coarseLevel.GetLevelID()) + ".m";
372 if (writeStart <= coarseLevel.GetLevelID() && coarseLevel.GetLevelID() <= writeEnd && permutedBlockNumber->getMap() != Teuchos::null)
373 Xpetra::IO<SC, LO, GO, NO>::WriteLOMV(fileName, *permutedBlockNumber);
374 }
375
376 if (IsAvailable(coarseLevel, "Nullspace")) {
377 RCP<MultiVector> nullspace = Get<RCP<MultiVector> >(coarseLevel, "Nullspace");
378
379 // This line must be after the Get call
380 SubFactoryMonitor subM(*this, "Rebalancing nullspace", coarseLevel);
381
382 RCP<MultiVector> permutedNullspace = MultiVectorFactory::Build(importer->getTargetMap(), nullspace->getNumVectors());
383 permutedNullspace->doImport(*nullspace, *importer, Xpetra::INSERT);
384
385 if (pL.get<bool>("repartition: use subcommunicators") == true)
386 permutedNullspace->replaceMap(permutedNullspace->getMap()->removeEmptyProcesses());
387
388 if (permutedNullspace->getMap() == Teuchos::null)
389 permutedNullspace = Teuchos::null;
390
391 Set(coarseLevel, "Nullspace", permutedNullspace);
392 }
393
394 } else {
395 if (pL.get<bool>("transpose: use implicit") == false) {
396 RCP<Matrix> originalR = Get<RCP<Matrix> >(coarseLevel, "R");
397
398 SubFactoryMonitor m2(*this, "Rebalancing restrictor", coarseLevel);
399
400 if (implicit || importer.is_null()) {
401 GetOStream(Runtime0) << "Using original restrictor" << std::endl;
402 Set(coarseLevel, "R", originalR);
403
404 } else {
405 RCP<Matrix> rebalancedR;
406 {
407 SubFactoryMonitor subM(*this, "Rebalancing restriction -- fusedImport", coarseLevel);
408
409 RCP<Map> dummy; // meaning: use originalR's domain map.
410 Teuchos::ParameterList listLabel;
411 listLabel.set("Timer Label", "MueLu::RebalanceR-" + Teuchos::toString(coarseLevel.GetLevelID()));
412 rebalancedR = MatrixFactory::Build(originalR, *importer, dummy, importer->getTargetMap(), Teuchos::rcp(&listLabel, false));
413 }
414 if (!rebalancedR.is_null()) {
415 std::ostringstream oss;
416 oss << "R_" << coarseLevel.GetLevelID();
417 rebalancedR->setObjectLabel(oss.str());
418 }
419 Set(coarseLevel, "R", rebalancedR);
420
422 // TODO FIXME somehow we have to transfer the striding information of the permuted domain/range maps.
423 // That is probably something for an external permutation factory
424 // if (originalR->IsView("stridedMaps"))
425 // rebalancedR->CreateView("stridedMaps", originalR);
427
428 if (IsPrint(Statistics2))
429 GetOStream(Statistics2) << PerfUtils::PrintMatrixInfo(*rebalancedR, "R (rebalanced)", params);
430 }
431 }
432 }
433}
434
435} // namespace MueLu
436
437#endif // MUELU_REBALANCETRANSFERFACTORY_DEF_HPP
#define SET_VALID_ENTRY(name)
#define MueLu_maxAll(rcpComm, in, out)
static void AmalgamateMap(const Map &sourceMap, const Matrix &A, RCP< const Map > &amalgamatedMap, Array< LO > &translation)
Method to create merged map for systems of PDEs.
Exception indicating invalid cast attempted.
Timer to be used in factories. Similar to Monitor but with additional timers.
Class that holds all level-specific information.
int GetLevelID() const
Return level number.
T & Get(const std::string &ename, const FactoryBase *factory=NoFactory::get())
Get data without decrementing associated storage counter (i.e., read-only access)....
void Set(const std::string &ename, const T &entry, const FactoryBase *factory=NoFactory::get())
static const NoFactory * get()
static std::string PrintMatrixInfo(const Matrix &A, const std::string &msgTag, RCP< const Teuchos::ParameterList > params=Teuchos::null)
RCP< const ParameterList > GetValidParameterList() const
Return a const parameter list of valid parameters that setParameterList() will accept.
void DeclareInput(Level &fineLevel, Level &coarseLevel) const
Specifies the data that this class needs, and the factories that generate that data.
void Build(Level &fineLevel, Level &coarseLevel) const
Build an object with this factory.
virtual ~RebalanceTransferFactory()
Destructor.
RebalanceTransferFactory()
Constructor.
Timer to be used in factories. Similar to SubMonitor but adds a timer level by level.
Namespace for MueLu classes and methods.
@ Statistics2
Print even more statistics.
@ Runtime0
One-liner description of what is happening.
std::string toString(const T &what)
Little helper function to convert non-string types to strings.