MueLu Version of the Day
Loading...
Searching...
No Matches
MueLu_TentativePFactory_kokkos_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_TENTATIVEPFACTORY_KOKKOS_DEF_HPP
11#define MUELU_TENTATIVEPFACTORY_KOKKOS_DEF_HPP
12
13#include "Kokkos_UnorderedMap.hpp"
14#include "Xpetra_CrsGraphFactory.hpp"
15
17
18#include "MueLu_Aggregates.hpp"
19#include "MueLu_AmalgamationInfo.hpp"
20#include "MueLu_AmalgamationFactory.hpp"
21
22#include "MueLu_MasterList.hpp"
23#include "MueLu_PerfUtils.hpp"
24#include "MueLu_Monitor.hpp"
25
26#include "Xpetra_IO.hpp"
27
28namespace MueLu {
29
30namespace { // anonymous
31
32template <class LocalOrdinal, class View>
33class ReduceMaxFunctor {
34 public:
35 ReduceMaxFunctor(View view)
36 : view_(view) {}
37
38 KOKKOS_INLINE_FUNCTION
39 void operator()(const LocalOrdinal& i, LocalOrdinal& vmax) const {
40 if (vmax < view_(i))
41 vmax = view_(i);
42 }
43
44 KOKKOS_INLINE_FUNCTION
45 void join(LocalOrdinal& dst, const LocalOrdinal& src) const {
46 if (dst < src) {
47 dst = src;
48 }
49 }
50
51 KOKKOS_INLINE_FUNCTION
52 void init(LocalOrdinal& dst) const {
53 dst = 0;
54 }
55
56 private:
57 View view_;
58};
59
60// local QR decomposition
61template <class LOType, class GOType, class SCType, class DeviceType, class NspType, class aggRowsType, class maxAggDofSizeType, class agg2RowMapLOType, class statusType, class rowsType, class rowsAuxType, class colsAuxType, class valsAuxType>
62class LocalQRDecompFunctor {
63 private:
64 typedef LOType LO;
65 typedef GOType GO;
66 typedef SCType SC;
67
68 typedef typename DeviceType::execution_space execution_space;
69 typedef typename KokkosKernels::ArithTraits<SC>::val_type impl_SC;
70 typedef KokkosKernels::ArithTraits<impl_SC> impl_ATS;
71 typedef typename impl_ATS::magnitudeType Magnitude;
72
73 public:
74 typedef Kokkos::View<impl_SC**, typename execution_space::scratch_memory_space, Kokkos::MemoryUnmanaged> shared_matrix;
75 typedef Kokkos::View<impl_SC*, typename execution_space::scratch_memory_space, Kokkos::MemoryUnmanaged> shared_vector;
76
77 private:
78 NspType fineNS;
79 NspType coarseNS;
80 aggRowsType aggRows;
81 maxAggDofSizeType maxAggDofSize; //< maximum number of dofs in aggregate (max size of aggregate * numDofsPerNode)
82 agg2RowMapLOType agg2RowMapLO;
83 statusType statusAtomic;
84 rowsType rows;
85 rowsAuxType rowsAux;
86 colsAuxType colsAux;
87 valsAuxType valsAux;
90
91 public:
92 LocalQRDecompFunctor(NspType fineNS_, NspType coarseNS_, aggRowsType aggRows_, maxAggDofSizeType maxAggDofSize_, agg2RowMapLOType agg2RowMapLO_, statusType statusAtomic_, rowsType rows_, rowsAuxType rowsAux_, colsAuxType colsAux_, valsAuxType valsAux_, bool doQRStep_, int scratchLevel_)
93 : fineNS(fineNS_)
94 , coarseNS(coarseNS_)
95 , aggRows(aggRows_)
96 , maxAggDofSize(maxAggDofSize_)
97 , agg2RowMapLO(agg2RowMapLO_)
98 , statusAtomic(statusAtomic_)
99 , rows(rows_)
100 , rowsAux(rowsAux_)
101 , colsAux(colsAux_)
102 , valsAux(valsAux_)
103 , doQRStep(doQRStep_)
104 , scratchLevel(scratchLevel_) {}
105
106 KOKKOS_INLINE_FUNCTION
107 void operator()(const typename Kokkos::TeamPolicy<execution_space>::member_type& thread, size_t& nnz) const {
108 auto agg = thread.league_rank();
109
110 // size of aggregate: number of DOFs in aggregate
111 auto aggSize = aggRows(agg + 1) - aggRows(agg);
112
113 const impl_SC one = impl_ATS::one();
114 const impl_SC two = one + one;
115 const impl_SC zero = impl_ATS::zero();
116 const auto zeroM = impl_ATS::magnitude(zero);
117
118 int m = aggSize;
119 int n = fineNS.extent(1);
120
121 // calculate row offset for coarse nullspace
122 Xpetra::global_size_t offset = agg * n;
123
124 if (doQRStep) {
125 // Extract the piece of the nullspace corresponding to the aggregate
126 shared_matrix r(thread.team_scratch(scratchLevel), m, n); // A (initially), R (at the end)
127 for (int j = 0; j < n; j++)
128 for (int k = 0; k < m; k++)
129 r(k, j) = fineNS(agg2RowMapLO(aggRows(agg) + k), j);
130#if 0
131 printf("A\n");
132 for (int i = 0; i < m; i++) {
133 for (int j = 0; j < n; j++)
134 printf(" %5.3lf ", r(i,j));
135 printf("\n");
136 }
137#endif
138
139 // Calculate QR decomposition (standard)
140 shared_matrix q(thread.team_scratch(scratchLevel), m, m); // Q
141 if (m >= n) {
142 bool isSingular = false;
143
144 // Initialize Q^T
145 auto qt = q;
146 for (int i = 0; i < m; i++) {
147 for (int j = 0; j < m; j++)
148 qt(i, j) = zero;
149 qt(i, i) = one;
150 }
151
152 for (int k = 0; k < n; k++) { // we ignore "n" instead of "n-1" to normalize
153 // FIXME_KOKKOS: use team
154 Magnitude s = zeroM, norm, norm_x;
155 for (int i = k + 1; i < m; i++)
156 s += pow(impl_ATS::magnitude(r(i, k)), 2);
157 norm = sqrt(pow(impl_ATS::magnitude(r(k, k)), 2) + s);
158
159 if (norm == zero) {
160 isSingular = true;
161 break;
162 }
163
164 r(k, k) -= norm * one;
165
166 norm_x = sqrt(pow(impl_ATS::magnitude(r(k, k)), 2) + s);
167 if (norm_x == zeroM) {
168 // We have a single diagonal element in the column.
169 // No reflections required. Just need to restor r(k,k).
170 r(k, k) = norm * one;
171 continue;
172 }
173
174 // FIXME_KOKKOS: use team
175 for (int i = k; i < m; i++)
176 r(i, k) /= norm_x;
177
178 // Update R(k:m,k+1:n)
179 for (int j = k + 1; j < n; j++) {
180 // FIXME_KOKKOS: use team in the loops
181 impl_SC si = zero;
182 for (int i = k; i < m; i++)
183 si += r(i, k) * r(i, j);
184 for (int i = k; i < m; i++)
185 r(i, j) -= two * si * r(i, k);
186 }
187
188 // Update Q^T (k:m,k:m)
189 for (int j = k; j < m; j++) {
190 // FIXME_KOKKOS: use team in the loops
191 impl_SC si = zero;
192 for (int i = k; i < m; i++)
193 si += r(i, k) * qt(i, j);
194 for (int i = k; i < m; i++)
195 qt(i, j) -= two * si * r(i, k);
196 }
197
198 // Fix R(k:m,k)
199 r(k, k) = norm * one;
200 for (int i = k + 1; i < m; i++)
201 r(i, k) = zero;
202 }
203
204#if 0
205 // Q = (Q^T)^T
206 for (int i = 0; i < m; i++)
207 for (int j = 0; j < i; j++) {
208 impl_SC tmp = qt(i,j);
209 qt(i,j) = qt(j,i);
210 qt(j,i) = tmp;
211 }
212#endif
213
214 // Build coarse nullspace using the upper triangular part of R
215 for (int j = 0; j < n; j++)
216 for (int k = 0; k <= j; k++)
217 coarseNS(offset + k, j) = r(k, j);
218
219 if (isSingular) {
220 statusAtomic(1) = true;
221 return;
222 }
223
224 } else {
225 // Special handling for m < n (i.e. single node aggregates in structural mechanics)
226
227 // The local QR decomposition is not possible in the "overconstrained"
228 // case (i.e. number of columns in qr > number of rowsAux), which
229 // corresponds to #DOFs in Aggregate < n. For usual problems this
230 // is only possible for single node aggregates in structural mechanics.
231 // (Similar problems may arise in discontinuous Galerkin problems...)
232 // We bypass the QR decomposition and use an identity block in the
233 // tentative prolongator for the single node aggregate and transfer the
234 // corresponding fine level null space information 1-to-1 to the coarse
235 // level null space part.
236
237 // NOTE: The resulting tentative prolongation operator has
238 // (m*DofsPerNode-n) zero columns leading to a singular
239 // coarse level operator A. To deal with that one has the following
240 // options:
241 // - Use the "RepairMainDiagonal" flag in the RAPFactory (default:
242 // false) to add some identity block to the diagonal of the zero rowsAux
243 // in the coarse level operator A, such that standard level smoothers
244 // can be used again.
245 // - Use special (projection-based) level smoothers, which can deal
246 // with singular matrices (very application specific)
247 // - Adapt the code below to avoid zero columns. However, we do not
248 // support a variable number of DOFs per node in MueLu/Xpetra which
249 // makes the implementation really hard.
250 //
251 // FIXME: do we need to check for singularity here somehow? Zero
252 // columns would be easy but linear dependency would require proper QR.
253
254 // R = extended (by adding identity rowsAux) qr
255 for (int j = 0; j < n; j++)
256 for (int k = 0; k < n; k++)
257 if (k < m)
258 coarseNS(offset + k, j) = r(k, j);
259 else
260 coarseNS(offset + k, j) = (k == j ? one : zero);
261
262 // Q = I (rectangular)
263 for (int i = 0; i < m; i++)
264 for (int j = 0; j < n; j++)
265 q(i, j) = (j == i ? one : zero);
266 }
267
268 // Process each row in the local Q factor and fill helper arrays to assemble P
269 for (int j = 0; j < m; j++) {
270 LO localRow = agg2RowMapLO(aggRows(agg) + j);
271 size_t rowStart = rowsAux(localRow);
272 size_t lnnz = 0;
273 for (int k = 0; k < n; k++) {
274 // skip zeros
275 if (q(j, k) != zero) {
276 colsAux(rowStart + lnnz) = offset + k;
277 valsAux(rowStart + lnnz) = q(j, k);
278 lnnz++;
279 }
280 }
281 rows(localRow + 1) = lnnz;
282 nnz += lnnz;
283 }
284
285#if 0
286 printf("R\n");
287 for (int i = 0; i < m; i++) {
288 for (int j = 0; j < n; j++)
289 printf(" %5.3lf ", coarseNS(i,j));
290 printf("\n");
291 }
292
293 printf("Q\n");
294 for (int i = 0; i < aggSize; i++) {
295 for (int j = 0; j < aggSize; j++)
296 printf(" %5.3lf ", q(i,j));
297 printf("\n");
298 }
299#endif
300 } else {
302 // "no-QR" option //
304 // Local Q factor is just the fine nullspace support over the current aggregate.
305 // Local R factor is the identity.
306 // TODO I have not implemented any special handling for aggregates that are too
307 // TODO small to locally support the nullspace, as is done in the standard QR
308 // TODO case above.
309
310 for (int j = 0; j < m; j++) {
311 LO localRow = agg2RowMapLO(aggRows(agg) + j);
312 size_t rowStart = rowsAux(localRow);
313 size_t lnnz = 0;
314 for (int k = 0; k < n; k++) {
315 const impl_SC qr_jk = fineNS(localRow, k);
316 // skip zeros
317 if (qr_jk != zero) {
318 colsAux(rowStart + lnnz) = offset + k;
319 valsAux(rowStart + lnnz) = qr_jk;
320 lnnz++;
321 }
322 }
323 rows(localRow + 1) = lnnz;
324 nnz += lnnz;
325 }
326
327 for (int j = 0; j < n; j++)
328 coarseNS(offset + j, j) = one;
329 }
330 }
331};
332
333} // namespace
334
335template <class Scalar, class LocalOrdinal, class GlobalOrdinal, class Node>
337 RCP<ParameterList> validParamList = rcp(new ParameterList());
338
339#define SET_VALID_ENTRY(name) validParamList->setEntry(name, MasterList::getEntry(name))
340 SET_VALID_ENTRY("tentative: calculate qr");
341 SET_VALID_ENTRY("tentative: build coarse coordinates");
342 SET_VALID_ENTRY("sa: keep tentative prolongator");
343#undef SET_VALID_ENTRY
344
345 validParamList->set<RCP<const FactoryBase>>("A", Teuchos::null, "Generating factory of the matrix A");
346 validParamList->set<RCP<const FactoryBase>>("Aggregates", Teuchos::null, "Generating factory of the aggregates");
347 validParamList->set<RCP<const FactoryBase>>("Nullspace", Teuchos::null, "Generating factory of the nullspace");
348 validParamList->set<RCP<const FactoryBase>>("Scaled Nullspace", Teuchos::null, "Generating factory of the scaled nullspace");
349 validParamList->set<RCP<const FactoryBase>>("UnAmalgamationInfo", Teuchos::null, "Generating factory of UnAmalgamationInfo");
350 validParamList->set<RCP<const FactoryBase>>("CoarseMap", Teuchos::null, "Generating factory of the coarse map");
351 validParamList->set<RCP<const FactoryBase>>("Coordinates", Teuchos::null, "Generating factory of the coordinates");
352 validParamList->set<RCP<const FactoryBase>>("Node Comm", Teuchos::null, "Generating factory of the node level communicator");
353
354 // Make sure we don't recursively validate options for the matrixmatrix kernels
355 ParameterList norecurse;
356 norecurse.disableRecursiveValidation();
357 validParamList->set<ParameterList>("matrixmatrix: kernel params", norecurse, "MatrixMatrix kernel parameters");
358
359 return validParamList;
360}
361
362template <class Scalar, class LocalOrdinal, class GlobalOrdinal, class Node>
364 const ParameterList& pL = GetParameterList();
365 // NOTE: This guy can only either be 'Nullspace' or 'Scaled Nullspace' or else the validator above will cause issues
366 std::string nspName = "Nullspace";
367 if (pL.isParameter("Nullspace name")) nspName = pL.get<std::string>("Nullspace name");
368
369 Input(fineLevel, "A");
370 Input(fineLevel, "Aggregates");
371 Input(fineLevel, nspName);
372 Input(fineLevel, "UnAmalgamationInfo");
373 Input(fineLevel, "CoarseMap");
374 if (fineLevel.GetLevelID() == 0 &&
375 fineLevel.IsAvailable("Coordinates", NoFactory::get()) && // we have coordinates (provided by user app)
376 pL.get<bool>("tentative: build coarse coordinates")) { // and we want coordinates on other levels
377 bTransferCoordinates_ = true; // then set the transfer coordinates flag to true
378 Input(fineLevel, "Coordinates");
379 } else if (bTransferCoordinates_) {
380 Input(fineLevel, "Coordinates");
381 }
382}
383
384template <class Scalar, class LocalOrdinal, class GlobalOrdinal, class Node>
386 return BuildP(fineLevel, coarseLevel);
387}
388
389template <class Scalar, class LocalOrdinal, class GlobalOrdinal, class Node>
391 FactoryMonitor m(*this, "Build", coarseLevel);
392
393 typedef typename Teuchos::ScalarTraits<Scalar>::coordinateType coordinate_type;
394 typedef Xpetra::MultiVectorFactory<coordinate_type, LO, GO, NO> RealValuedMultiVectorFactory;
395 const ParameterList& pL = GetParameterList();
396 std::string nspName = "Nullspace";
397 if (pL.isParameter("Nullspace name")) nspName = pL.get<std::string>("Nullspace name");
398
399 auto A = Get<RCP<Matrix>>(fineLevel, "A");
400 auto aggregates = Get<RCP<Aggregates>>(fineLevel, "Aggregates");
401 auto amalgInfo = Get<RCP<AmalgamationInfo>>(fineLevel, "UnAmalgamationInfo");
402 auto fineNullspace = Get<RCP<MultiVector>>(fineLevel, nspName);
403 auto coarseMap = Get<RCP<const Map>>(fineLevel, "CoarseMap");
404 RCP<RealValuedMultiVector> fineCoords;
405 if (bTransferCoordinates_) {
406 fineCoords = Get<RCP<RealValuedMultiVector>>(fineLevel, "Coordinates");
407 }
408
409 RCP<Matrix> Ptentative;
410 // No coarse DoFs so we need to bail by setting Ptentattive to null and returning
411 // This level will ultimately be removed in MueLu_Hierarchy_defs.h via a resize()
412 if (aggregates->GetNumGlobalAggregatesComputeIfNeeded() == 0) {
413 Ptentative = Teuchos::null;
414 Set(coarseLevel, "P", Ptentative);
415 return;
416 }
417 RCP<MultiVector> coarseNullspace;
418 RCP<RealValuedMultiVector> coarseCoords;
419
420 if (bTransferCoordinates_) {
421 RCP<const Map> coarseCoordMap;
422
423 LO blkSize = 1;
424 if (rcp_dynamic_cast<const StridedMap>(coarseMap) != Teuchos::null)
425 blkSize = rcp_dynamic_cast<const StridedMap>(coarseMap)->getFixedBlockSize();
426
427 if (blkSize == 1) {
428 // Scalar system
429 // No amalgamation required, we can use the coarseMap
430 coarseCoordMap = coarseMap;
431 } else {
432 // Vector system
433 AmalgamationFactory<SC, LO, GO, NO>::AmalgamateMap(rcp_dynamic_cast<const StridedMap>(coarseMap), coarseCoordMap);
434 }
435
436 coarseCoords = RealValuedMultiVectorFactory::Build(coarseCoordMap, fineCoords->getNumVectors(), false);
437
438 // Create overlapped fine coordinates to reduce global communication
439 auto uniqueMap = fineCoords->getMap();
440 RCP<RealValuedMultiVector> ghostedCoords = fineCoords;
441 if (aggregates->AggregatesCrossProcessors()) {
442 auto nonUniqueMap = aggregates->GetMap();
443 auto importer = ImportFactory::Build(uniqueMap, nonUniqueMap);
444
445 ghostedCoords = RealValuedMultiVectorFactory::Build(nonUniqueMap, fineCoords->getNumVectors(), false);
446 ghostedCoords->doImport(*fineCoords, *importer, Xpetra::INSERT);
447 }
448
449 // The good new is that his graph has already been constructed for the
450 // TentativePFactory and was cached in Aggregates. So this is a no-op.
451 auto aggGraph = aggregates->GetGraph();
452 auto numAggs = aggGraph.numRows();
453
454 auto fineCoordsView = fineCoords->getLocalViewDevice(Tpetra::Access::ReadOnly);
455 auto coarseCoordsView = coarseCoords->getLocalViewDevice(Tpetra::Access::OverwriteAll);
456
457 // Fill in coarse coordinates
458 {
459 SubFactoryMonitor m2(*this, "AverageCoords", coarseLevel);
460
461 const auto dim = fineCoords->getNumVectors();
462
463 typename AppendTrait<decltype(fineCoordsView), Kokkos::RandomAccess>::type fineCoordsRandomView = fineCoordsView;
464 for (size_t j = 0; j < dim; j++) {
465 Kokkos::parallel_for(
466 "MueLu::TentativeP::BuildCoords", Kokkos::RangePolicy<local_ordinal_type, execution_space>(0, numAggs),
467 KOKKOS_LAMBDA(const LO i) {
468 // A row in this graph represents all node ids in the aggregate
469 // Therefore, averaging is very easy
470
471 auto aggregate = aggGraph.rowConst(i);
472
473 coordinate_type sum = 0.0; // do not use Scalar here (Stokhos)
474 for (size_t colID = 0; colID < static_cast<size_t>(aggregate.length); colID++)
475 sum += fineCoordsRandomView(aggregate(colID), j);
476
477 coarseCoordsView(i, j) = sum / aggregate.length;
478 });
479 }
480 }
481 }
482
483 if (!aggregates->AggregatesCrossProcessors()) {
484 if (Xpetra::Helpers<SC, LO, GO, NO>::isTpetraBlockCrs(A)) {
485 BuildPuncoupledBlockCrs(coarseLevel, A, aggregates, amalgInfo, fineNullspace, coarseMap, Ptentative, coarseNullspace,
486 coarseLevel.GetLevelID());
487 } else {
488 BuildPuncoupled(coarseLevel, A, aggregates, amalgInfo, fineNullspace, coarseMap, Ptentative, coarseNullspace, coarseLevel.GetLevelID());
489 }
490 } else
491 BuildPcoupled(A, aggregates, amalgInfo, fineNullspace, coarseMap, Ptentative, coarseNullspace);
492
493 // If available, use striding information of fine level matrix A for range
494 // map and coarseMap as domain map; otherwise use plain range map of
495 // Ptent = plain range map of A for range map and coarseMap as domain map.
496 // NOTE:
497 // The latter is not really safe, since there is no striding information
498 // for the range map. This is not really a problem, since striding
499 // information is always available on the intermedium levels and the
500 // coarsest levels.
501 if (A->IsView("stridedMaps") == true)
502 Ptentative->CreateView("stridedMaps", A->getRowMap("stridedMaps"), coarseMap);
503
504 if (bTransferCoordinates_) {
505 Set(coarseLevel, "Coordinates", coarseCoords);
506 }
507
508 // FIXME: We should remove the NodeComm on levels past the threshold
509 if (fineLevel.IsAvailable("Node Comm")) {
510 RCP<const Teuchos::Comm<int>> nodeComm = Get<RCP<const Teuchos::Comm<int>>>(fineLevel, "Node Comm");
511 Set<RCP<const Teuchos::Comm<int>>>(coarseLevel, "Node Comm", nodeComm);
512 }
513
514 Set(coarseLevel, "Nullspace", coarseNullspace);
515 Set(coarseLevel, "P", Ptentative);
516
517 if (pL.get<bool>("sa: keep tentative prolongator")) {
518 coarseLevel.Set("Ptent", Ptentative, NoFactory::get());
519 coarseLevel.AddKeepFlag("Ptent", NoFactory::get(), MueLu::Final);
520 }
521
522 if (IsPrint(Statistics2)) {
523 RCP<ParameterList> params = rcp(new ParameterList());
524 params->set("printLoadBalancingInfo", true);
525 GetOStream(Statistics2) << PerfUtils::PrintMatrixInfo(*Ptentative, "Ptent", params);
526 }
527}
528
529template <class Scalar, class LocalOrdinal, class GlobalOrdinal, class Node>
531 BuildPuncoupled(Level& coarseLevel, RCP<Matrix> A, RCP<Aggregates> aggregates,
532 RCP<AmalgamationInfo> amalgInfo, RCP<MultiVector> fineNullspace,
533 RCP<const Map> coarseMap, RCP<Matrix>& Ptentative,
534 RCP<MultiVector>& coarseNullspace, const int levelID) const {
535 auto rowMap = A->getRowMap();
536 auto colMap = A->getColMap();
537
538 const size_t numRows = rowMap->getLocalNumElements();
539 const size_t NSDim = fineNullspace->getNumVectors();
540
541 typedef KokkosKernels::ArithTraits<SC> ATS;
542 using impl_SC = typename ATS::val_type;
543 using impl_ATS = KokkosKernels::ArithTraits<impl_SC>;
544 const impl_SC zero = impl_ATS::zero(), one = impl_ATS::one();
545
546 const LO INVALID = Teuchos::OrdinalTraits<LO>::invalid();
547
548 typename Aggregates::local_graph_type aggGraph;
549 {
550 SubFactoryMonitor m2(*this, "Get Aggregates graph", coarseLevel);
551 aggGraph = aggregates->GetGraph();
552 }
553 auto aggRows = aggGraph.row_map;
554 auto aggCols = aggGraph.entries;
555
556 // Aggregates map is based on the amalgamated column map
557 // We can skip global-to-local conversion if LIDs in row map are
558 // same as LIDs in column map
559 bool goodMap;
560 {
561 SubFactoryMonitor m2(*this, "Check good map", coarseLevel);
562 goodMap = isGoodMap(*rowMap, *colMap);
563 }
564 // FIXME_KOKKOS: need to proofread later code for bad maps
565 TEUCHOS_TEST_FOR_EXCEPTION(!goodMap, Exceptions::RuntimeError,
566 "MueLu: TentativePFactory_kokkos: for now works only with good maps "
567 "(i.e. \"matching\" row and column maps)");
568
569 // STEP 1: do unamalgamation
570 // The non-kokkos version uses member functions from the AmalgamationInfo
571 // container class to unamalgamate the data. In contrast, the kokkos
572 // version of TentativePFactory does the unamalgamation here and only uses
573 // the data of the AmalgamationInfo container class
574
575 // Extract information for unamalgamation
576 LO fullBlockSize, blockID, stridingOffset, stridedBlockSize;
577 GO indexBase;
578 amalgInfo->GetStridingInformation(fullBlockSize, blockID, stridingOffset, stridedBlockSize, indexBase);
579 GO globalOffset = amalgInfo->GlobalOffset();
580
581 // Extract aggregation info (already in Kokkos host views)
582 auto procWinner = aggregates->GetProcWinner()->getLocalViewDevice(Tpetra::Access::ReadOnly);
583 auto vertex2AggId = aggregates->GetVertex2AggId()->getLocalViewDevice(Tpetra::Access::ReadOnly);
584 const size_t numAggregates = aggregates->GetNumAggregates();
585
586 int myPID = aggregates->GetMap()->getComm()->getRank();
587
588 // Create Kokkos::View (on the device) to store the aggreate dof sizes
589 // Later used to get aggregate dof offsets
590 // NOTE: This zeros itself on construction
591 typedef typename Aggregates::aggregates_sizes_type::non_const_type AggSizeType;
592 AggSizeType aggDofSizes;
593
594 if (stridedBlockSize == 1) {
595 SubFactoryMonitor m2(*this, "Calc AggSizes", coarseLevel);
596
597 // FIXME_KOKKOS: use ViewAllocateWithoutInitializing + set a single value
598 aggDofSizes = AggSizeType("agg_dof_sizes", numAggregates + 1);
599
600 auto sizesConst = aggregates->ComputeAggregateSizes();
601 Kokkos::deep_copy(Kokkos::subview(aggDofSizes, Kokkos::make_pair(static_cast<size_t>(1), numAggregates + 1)), sizesConst);
602
603 } else {
604 SubFactoryMonitor m2(*this, "Calc AggSizes", coarseLevel);
605
606 // FIXME_KOKKOS: use ViewAllocateWithoutInitializing + set a single value
607 aggDofSizes = AggSizeType("agg_dof_sizes", numAggregates + 1);
608
609 auto nodeMap = aggregates->GetMap()->getLocalMap();
610 auto dofMap = colMap->getLocalMap();
611
612 Kokkos::parallel_for(
613 "MueLu:TentativePF:Build:compute_agg_sizes", range_type(0, numAggregates),
614 KOKKOS_LAMBDA(const LO agg) {
615 auto aggRowView = aggGraph.rowConst(agg);
616
617 size_t size = 0;
618 for (LO colID = 0; colID < aggRowView.length; colID++) {
619 GO nodeGID = nodeMap.getGlobalElement(aggRowView(colID));
620
621 for (LO k = 0; k < stridedBlockSize; k++) {
622 GO dofGID = (nodeGID - indexBase) * fullBlockSize + k + indexBase + globalOffset + stridingOffset;
623
624 if (dofMap.getLocalElement(dofGID) != INVALID)
625 size++;
626 }
627 }
628 aggDofSizes(agg + 1) = size;
629 });
630 }
631
632 // Find maximum dof size for aggregates
633 // Later used to reserve enough scratch space for local QR decompositions
634 LO maxAggSize = 0;
635 ReduceMaxFunctor<LO, decltype(aggDofSizes)> reduceMax(aggDofSizes);
636 Kokkos::parallel_reduce("MueLu:TentativePF:Build:max_agg_size", range_type(0, aggDofSizes.extent(0)), reduceMax, maxAggSize);
637
638 // parallel_scan (exclusive)
639 // The aggDofSizes View then contains the aggregate dof offsets
640 Kokkos::parallel_scan(
641 "MueLu:TentativePF:Build:aggregate_sizes:stage1_scan", range_type(0, numAggregates + 1),
642 KOKKOS_LAMBDA(const LO i, LO& update, const bool& final_pass) {
643 update += aggDofSizes(i);
644 if (final_pass)
645 aggDofSizes(i) = update;
646 });
647
648 // Create Kokkos::View on the device to store mapping
649 // between (local) aggregate id and row map ids (LIDs)
650 Kokkos::View<LO*, DeviceType> agg2RowMapLO(Kokkos::ViewAllocateWithoutInitializing("agg2row_map_LO"), numRows);
651 {
652 SubFactoryMonitor m2(*this, "Create Agg2RowMap", coarseLevel);
653
654 AggSizeType aggOffsets(Kokkos::ViewAllocateWithoutInitializing("aggOffsets"), numAggregates);
655 Kokkos::deep_copy(aggOffsets, Kokkos::subview(aggDofSizes, Kokkos::make_pair(static_cast<size_t>(0), numAggregates)));
656
657 Kokkos::parallel_for(
658 "MueLu:TentativePF:Build:createAgg2RowMap", range_type(0, vertex2AggId.extent(0)),
659 KOKKOS_LAMBDA(const LO lnode) {
660 if (procWinner(lnode, 0) == myPID) {
661 // No need for atomics, it's one-to-one
662 auto aggID = vertex2AggId(lnode, 0);
663
664 auto offset = Kokkos::atomic_fetch_add(&aggOffsets(aggID), stridedBlockSize);
665 // FIXME: I think this may be wrong
666 // We unconditionally add the whole block here. When we calculated
667 // aggDofSizes, we did the isLocalElement check. Something's fishy.
668 for (LO k = 0; k < stridedBlockSize; k++)
669 agg2RowMapLO(offset + k) = lnode * stridedBlockSize + k;
670 }
671 });
672 }
673
674 // STEP 2: prepare local QR decomposition
675 // Reserve memory for tentative prolongation operator
676 coarseNullspace = MultiVectorFactory::Build(coarseMap, NSDim, true);
677
678 // Pull out the nullspace vectors so that we can have random access (on the device)
679 auto fineNS = fineNullspace->getLocalViewDevice(Tpetra::Access::ReadWrite);
680 auto coarseNS = coarseNullspace->getLocalViewDevice(Tpetra::Access::ReadWrite);
681
682 size_t nnz = 0; // actual number of nnz
683
684 typedef typename Xpetra::Matrix<SC, LO, GO, NO>::local_matrix_device_type local_matrix_type;
685 typedef typename local_matrix_type::row_map_type::non_const_type rows_type;
686 typedef typename local_matrix_type::index_type::non_const_type cols_type;
687 typedef typename local_matrix_type::values_type::non_const_type vals_type;
688
689 // Device View for status (error messages...)
690 typedef Kokkos::View<int[10], DeviceType> status_type;
691 status_type status("status");
692
693 typename AppendTrait<decltype(fineNS), Kokkos::RandomAccess>::type fineNSRandom = fineNS;
695
696 const ParameterList& pL = GetParameterList();
697 const bool& doQRStep = pL.get<bool>("tentative: calculate qr");
698 if (!doQRStep) {
699 GetOStream(Runtime1) << "TentativePFactory : bypassing local QR phase" << std::endl;
700 if (NSDim > 1)
701 GetOStream(Warnings0) << "TentativePFactor : for nontrivial nullspace, this may degrade performance" << std::endl;
702 }
703
704 size_t nnzEstimate = numRows * NSDim;
705 rows_type rowsAux(Kokkos::ViewAllocateWithoutInitializing("Ptent_aux_rows"), numRows + 1);
706 cols_type colsAux(Kokkos::ViewAllocateWithoutInitializing("Ptent_aux_cols"), nnzEstimate);
707 vals_type valsAux("Ptent_aux_vals", nnzEstimate);
708 rows_type rows("Ptent_rows", numRows + 1);
709 {
710 // Stage 0: fill in views.
711 SubFactoryMonitor m2(*this, "Stage 0 (InitViews)", coarseLevel);
712
713 // The main thing to notice is initialization of vals with INVALID. These
714 // values will later be used to compress the arrays
715 Kokkos::parallel_for(
716 "MueLu:TentativePF:BuildPuncoupled:for1", range_type(0, numRows + 1),
717 KOKKOS_LAMBDA(const LO row) {
718 rowsAux(row) = row * NSDim;
719 });
720 Kokkos::parallel_for(
721 "MueLu:TentativePF:BuildUncoupled:for2", range_type(0, nnzEstimate),
722 KOKKOS_LAMBDA(const LO j) {
723 colsAux(j) = INVALID;
724 });
725 }
726
727 if (NSDim == 1) {
728 // 1D is special, as it is the easiest. We don't even need to the QR,
729 // just normalize an array. Plus, no worries abot small aggregates. In
730 // addition, we do not worry about compression. It is unlikely that
731 // nullspace will have zeros. If it does, a prolongator row would be
732 // zero and we'll get singularity anyway.
733 SubFactoryMonitor m2(*this, "Stage 1 (LocalQR)", coarseLevel);
734
735 // Set up team policy with numAggregates teams and one thread per team.
736 // Each team handles a slice of the data associated with one aggregate
737 // and performs a local QR decomposition (in this case real QR is
738 // unnecessary).
739 const Kokkos::TeamPolicy<execution_space> policy(numAggregates, 1);
740
741 if (doQRStep) {
742 Kokkos::parallel_for(
743 "MueLu:TentativePF:BuildUncoupled:main_loop", policy,
744 KOKKOS_LAMBDA(const typename Kokkos::TeamPolicy<execution_space>::member_type& thread) {
745 auto agg = thread.league_rank();
746
747 // size of the aggregate (number of DOFs in aggregate)
748 LO aggSize = aggRows(agg + 1) - aggRows(agg);
749
750 // Extract the piece of the nullspace corresponding to the aggregate, and
751 // put it in the flat array, "localQR" (in column major format) for the
752 // QR routine. Trivial in 1D.
753 auto norm = impl_ATS::magnitude(zero);
754
755 // Calculate QR by hand
756 // FIXME: shouldn't there be stridedblock here?
757 // FIXME_KOKKOS: shouldn't there be stridedblock here?
758 for (decltype(aggSize) k = 0; k < aggSize; k++) {
759 auto dnorm = impl_ATS::magnitude(fineNSRandom(agg2RowMapLO(aggRows(agg) + k), 0));
760 norm += dnorm * dnorm;
761 }
762 norm = sqrt(norm);
763
764 if (norm == zero) {
765 // zero column; terminate the execution
766 statusAtomic(1) = true;
767 return;
768 }
769
770 // R = norm
771 coarseNS(agg, 0) = norm;
772
773 // Q = localQR(:,0)/norm
774 for (decltype(aggSize) k = 0; k < aggSize; k++) {
775 LO localRow = agg2RowMapLO(aggRows(agg) + k);
776 impl_SC localVal = fineNSRandom(agg2RowMapLO(aggRows(agg) + k), 0) / norm;
777
778 rows(localRow + 1) = 1;
779 colsAux(localRow) = agg;
780 valsAux(localRow) = localVal;
781 }
782 });
783
784 typename status_type::host_mirror_type statusHost = Kokkos::create_mirror_view(status);
785 Kokkos::deep_copy(statusHost, status);
786 for (decltype(statusHost.size()) i = 0; i < statusHost.size(); i++)
787 if (statusHost(i)) {
788 std::ostringstream oss;
789 oss << "MueLu::TentativePFactory::MakeTentative: ";
790 switch (i) {
791 case 0: oss << "!goodMap is not implemented"; break;
792 case 1: oss << "fine level NS part has a zero column"; break;
793 }
794 throw Exceptions::RuntimeError(oss.str());
795 }
796
797 } else {
798 Kokkos::parallel_for(
799 "MueLu:TentativePF:BuildUncoupled:main_loop_noqr", policy,
800 KOKKOS_LAMBDA(const typename Kokkos::TeamPolicy<execution_space>::member_type& thread) {
801 auto agg = thread.league_rank();
802
803 // size of the aggregate (number of DOFs in aggregate)
804 LO aggSize = aggRows(agg + 1) - aggRows(agg);
805
806 // R = norm
807 coarseNS(agg, 0) = one;
808
809 // Q = localQR(:,0)/norm
810 for (decltype(aggSize) k = 0; k < aggSize; k++) {
811 LO localRow = agg2RowMapLO(aggRows(agg) + k);
812 impl_SC localVal = fineNSRandom(agg2RowMapLO(aggRows(agg) + k), 0);
813
814 rows(localRow + 1) = 1;
815 colsAux(localRow) = agg;
816 valsAux(localRow) = localVal;
817 }
818 });
819 }
820
821 Kokkos::parallel_reduce(
822 "MueLu:TentativeP:CountNNZ", range_type(0, numRows + 1),
823 KOKKOS_LAMBDA(const LO i, size_t& nnz_count) {
824 nnz_count += rows(i);
825 },
826 nnz);
827
828 } else { // NSdim > 1
829 // FIXME_KOKKOS: This code branch is completely unoptimized.
830 // Work to do:
831 // - Optimize QR decomposition
832 // - Remove INVALID usage similarly to CoalesceDropFactory_kokkos by
833 // packing new values in the beginning of each row
834 // We do use auxilary view in this case, so keep a second rows view for
835 // counting nonzeros in rows
836
837 {
838 SubFactoryMonitor m2 = SubFactoryMonitor(*this, doQRStep ? "Stage 1 (LocalQR)" : "Stage 1 (Fill coarse nullspace and tentative P)", coarseLevel);
839 // Set up team policy with numAggregates teams and one thread per team.
840 // Each team handles a slice of the data associated with one aggregate
841 // and performs a local QR decomposition
842 Kokkos::TeamPolicy<execution_space> policy(numAggregates, 1); // numAggregates teams a 1 thread
843 using LocalQrFunctorType = LocalQRDecompFunctor<LocalOrdinal, GlobalOrdinal, Scalar, DeviceType, decltype(fineNSRandom),
844 decltype(aggDofSizes /*aggregate sizes in dofs*/), decltype(maxAggSize), decltype(agg2RowMapLO),
845 decltype(statusAtomic), decltype(rows), decltype(rowsAux), decltype(colsAux),
846 decltype(valsAux)>;
847 int scratchLevel = 0;
848 if (doQRStep) {
849 using shared_matrix = LocalQrFunctorType::shared_matrix;
850 int m = maxAggSize;
851 int n = fineNSRandom.extent(1);
852 int size = shared_matrix::shmem_size(m, n) + // r
853 shared_matrix::shmem_size(m, m); // q
854
855 if (size < policy.scratch_size_max(/*level=*/(int)0))
856 scratchLevel = 0;
857 else if (size < policy.scratch_size_max(/*level=*/(int)1))
858 scratchLevel = 1;
859 else
860 throw Exceptions::RuntimeError("Neither L0 scratch memory (max size " + std::to_string(policy.scratch_size_max((int)0)) +
861 "), nor L1 scratch memory (max size " + std::to_string(policy.scratch_size_max((int)1)) +
862 ") is large enough for requested allocation of size " + std::to_string(size));
863 policy.set_scratch_size(scratchLevel, Kokkos::PerTeam(size));
864 }
865 LocalQrFunctorType localQRFunctor(fineNSRandom, coarseNS, aggDofSizes, maxAggSize, agg2RowMapLO, statusAtomic,
867
868 Kokkos::parallel_reduce("MueLu:TentativePF:BuildUncoupled:main_qr_loop", policy, localQRFunctor, nnz);
869 }
870
871 typename status_type::host_mirror_type statusHost = Kokkos::create_mirror_view(status);
872 Kokkos::deep_copy(statusHost, status);
873 for (decltype(statusHost.size()) i = 0; i < statusHost.size(); i++)
874 if (statusHost(i)) {
875 std::ostringstream oss;
876 oss << "MueLu::TentativePFactory::MakeTentative: ";
877 switch (i) {
878 case 0: oss << "!goodMap is not implemented"; break;
879 case 1: oss << "fine level NS part has a zero column"; break;
880 }
881 throw Exceptions::RuntimeError(oss.str());
882 }
883 }
884
885 // Compress the cols and vals by ignoring INVALID column entries that correspond
886 // to 0 in QR.
887
888 // The real cols and vals are constructed using calculated (not estimated) nnz
889 cols_type cols;
890 vals_type vals;
891
892 if (nnz != nnzEstimate) {
893 {
894 // Stage 2: compress the arrays
895 SubFactoryMonitor m2(*this, "Stage 2 (CompressRows)", coarseLevel);
896
897 Kokkos::parallel_scan(
898 "MueLu:TentativePF:Build:compress_rows", range_type(0, numRows + 1),
899 KOKKOS_LAMBDA(const LO i, LO& upd, const bool& final) {
900 upd += rows(i);
901 if (final)
902 rows(i) = upd;
903 });
904 }
905
906 {
907 SubFactoryMonitor m2(*this, "Stage 2 (CompressCols)", coarseLevel);
908
909 cols = cols_type("Ptent_cols", nnz);
910 vals = vals_type("Ptent_vals", nnz);
911
912 // FIXME_KOKKOS: this can be spedup by moving correct cols and vals values
913 // to the beginning of rows. See CoalesceDropFactory_kokkos for
914 // example.
915 Kokkos::parallel_for(
916 "MueLu:TentativePF:Build:compress_cols_vals", range_type(0, numRows),
917 KOKKOS_LAMBDA(const LO i) {
918 LO rowStart = rows(i);
919
920 size_t lnnz = 0;
921 for (auto j = rowsAux(i); j < rowsAux(i + 1); j++)
922 if (colsAux(j) != INVALID) {
923 cols(rowStart + lnnz) = colsAux(j);
924 vals(rowStart + lnnz) = valsAux(j);
925 lnnz++;
926 }
927 });
928 }
929
930 } else {
931 rows = rowsAux;
932 cols = colsAux;
933 vals = valsAux;
934 }
935
936 GetOStream(Runtime1) << "TentativePFactory : aggregates do not cross process boundaries" << std::endl;
937
938 {
939 // Stage 3: construct Xpetra::Matrix
940 SubFactoryMonitor m2(*this, "Stage 3 (LocalMatrix+FillComplete)", coarseLevel);
941
942 local_matrix_type lclMatrix = local_matrix_type("A", numRows, coarseMap->getLocalNumElements(), nnz, vals, rows, cols);
943
944 // Managing labels & constants for ESFC
945 RCP<ParameterList> FCparams;
946 if (pL.isSublist("matrixmatrix: kernel params"))
947 FCparams = rcp(new ParameterList(pL.sublist("matrixmatrix: kernel params")));
948 else
949 FCparams = rcp(new ParameterList);
950
951 // By default, we don't need global constants for TentativeP
952 FCparams->set("compute global constants", FCparams->get("compute global constants", false));
953 FCparams->set("Timer Label", std::string("MueLu::TentativeP-") + toString(levelID));
954
955 auto PtentCrs = CrsMatrixFactory::Build(lclMatrix, rowMap, coarseMap, coarseMap, A->getDomainMap());
956 Ptentative = rcp(new CrsMatrixWrap(PtentCrs));
957 }
958}
959
960template <class Scalar, class LocalOrdinal, class GlobalOrdinal, class Node>
962 BuildPuncoupledBlockCrs(Level& coarseLevel, RCP<Matrix> A, RCP<Aggregates> aggregates,
963 RCP<AmalgamationInfo> amalgInfo, RCP<MultiVector> fineNullspace,
964 RCP<const Map> coarsePointMap, RCP<Matrix>& Ptentative,
965 RCP<MultiVector>& coarseNullspace, const int levelID) const {
966 /* This routine generates a BlockCrs P for a BlockCrs A. There are a few assumptions here, which meet the use cases we care about, but could
967 be generalized later, if we ever need to do so:
968 1) Null space dimension === block size of matrix: So no elasticity right now
969 2) QR is not supported: Under assumption #1, this shouldn't cause problems.
970 3) Maps are "good": Aka the first chunk of the ColMap is the RowMap.
971
972 These assumptions keep our code way simpler and still support the use cases we actually care about.
973 */
974
975 RCP<const Map> rowMap = A->getRowMap();
976 RCP<const Map> rangeMap = A->getRangeMap();
977 RCP<const Map> colMap = A->getColMap();
978 // const size_t numFinePointRows = rangeMap->getLocalNumElements();
979 const size_t numFineBlockRows = rowMap->getLocalNumElements();
980
981 // typedef Teuchos::ScalarTraits<SC> STS;
982 // typedef typename STS::magnitudeType Magnitude;
983 const LO INVALID = Teuchos::OrdinalTraits<LO>::invalid();
984
985 typedef KokkosKernels::ArithTraits<SC> ATS;
986 using impl_SC = typename ATS::val_type;
987 using impl_ATS = KokkosKernels::ArithTraits<impl_SC>;
988 const impl_SC one = impl_ATS::one();
989
990 // const GO numAggs = aggregates->GetNumAggregates();
991 const size_t NSDim = fineNullspace->getNumVectors();
992 auto aggSizes = aggregates->ComputeAggregateSizes();
993
994 typename Aggregates::local_graph_type aggGraph;
995 {
996 SubFactoryMonitor m2(*this, "Get Aggregates graph", coarseLevel);
997 aggGraph = aggregates->GetGraph();
998 }
999 auto aggRows = aggGraph.row_map;
1000 auto aggCols = aggGraph.entries;
1001
1002 // Need to generate the coarse block map
1003 // NOTE: We assume NSDim == block size here
1004 // NOTE: We also assume that coarseMap has contiguous GIDs
1005 // const size_t numCoarsePointRows = coarsePointMap->getLocalNumElements();
1006 const size_t numCoarseBlockRows = coarsePointMap->getLocalNumElements() / NSDim;
1007 RCP<const Map> coarseBlockMap = MapFactory::Build(coarsePointMap->lib(),
1008 Teuchos::OrdinalTraits<Xpetra::global_size_t>::invalid(),
1009 numCoarseBlockRows,
1010 coarsePointMap->getIndexBase(),
1011 coarsePointMap->getComm());
1012 // Sanity checking
1013 const ParameterList& pL = GetParameterList();
1014 // const bool &doQRStep = pL.get<bool>("tentative: calculate qr");
1015
1016 // The aggregates use the amalgamated column map, which in this case is what we want
1017
1018 // Aggregates map is based on the amalgamated column map
1019 // We can skip global-to-local conversion if LIDs in row map are
1020 // same as LIDs in column map
1021 bool goodMap = MueLu::Utilities<SC, LO, GO, NO>::MapsAreNested(*rowMap, *colMap);
1022 TEUCHOS_TEST_FOR_EXCEPTION(!goodMap, Exceptions::RuntimeError,
1023 "MueLu: TentativePFactory_kokkos: for now works only with good maps "
1024 "(i.e. \"matching\" row and column maps)");
1025
1026 // STEP 1: do unamalgamation
1027 // The non-kokkos version uses member functions from the AmalgamationInfo
1028 // container class to unamalgamate the data. In contrast, the kokkos
1029 // version of TentativePFactory does the unamalgamation here and only uses
1030 // the data of the AmalgamationInfo container class
1031
1032 // Extract information for unamalgamation
1033 LO fullBlockSize, blockID, stridingOffset, stridedBlockSize;
1034 GO indexBase;
1035 amalgInfo->GetStridingInformation(fullBlockSize, blockID, stridingOffset, stridedBlockSize, indexBase);
1036 // GO globalOffset = amalgInfo->GlobalOffset();
1037
1038 // Extract aggregation info (already in Kokkos host views)
1039 auto procWinner = aggregates->GetProcWinner()->getLocalViewDevice(Tpetra::Access::ReadOnly);
1040 auto vertex2AggId = aggregates->GetVertex2AggId()->getLocalViewDevice(Tpetra::Access::ReadOnly);
1041 const size_t numAggregates = aggregates->GetNumAggregates();
1042
1043 int myPID = aggregates->GetMap()->getComm()->getRank();
1044
1045 // Create Kokkos::View (on the device) to store the aggreate dof sizes
1046 // Later used to get aggregate dof offsets
1047 // NOTE: This zeros itself on construction
1048 typedef typename Aggregates::aggregates_sizes_type::non_const_type AggSizeType;
1049 AggSizeType aggDofSizes; // This turns into "starts" after the parallel_scan
1050
1051 {
1052 SubFactoryMonitor m2(*this, "Calc AggSizes", coarseLevel);
1053
1054 // FIXME_KOKKOS: use ViewAllocateWithoutInitializing + set a single value
1055 aggDofSizes = AggSizeType("agg_dof_sizes", numAggregates + 1);
1056
1057 Kokkos::deep_copy(Kokkos::subview(aggDofSizes, Kokkos::make_pair(static_cast<size_t>(1), numAggregates + 1)), aggSizes);
1058 }
1059
1060 // Find maximum dof size for aggregates
1061 // Later used to reserve enough scratch space for local QR decompositions
1062 LO maxAggSize = 0;
1063 ReduceMaxFunctor<LO, decltype(aggDofSizes)> reduceMax(aggDofSizes);
1064 Kokkos::parallel_reduce("MueLu:TentativePF:Build:max_agg_size", range_type(0, aggDofSizes.extent(0)), reduceMax, maxAggSize);
1065
1066 // parallel_scan (exclusive)
1067 // The aggDofSizes View then contains the aggregate dof offsets
1068 Kokkos::parallel_scan(
1069 "MueLu:TentativePF:Build:aggregate_sizes:stage1_scan", range_type(0, numAggregates + 1),
1070 KOKKOS_LAMBDA(const LO i, LO& update, const bool& final_pass) {
1071 update += aggDofSizes(i);
1072 if (final_pass)
1073 aggDofSizes(i) = update;
1074 });
1075
1076 // Create Kokkos::View on the device to store mapping
1077 // between (local) aggregate id and row map ids (LIDs)
1078 Kokkos::View<LO*, DeviceType> aggToRowMapLO(Kokkos::ViewAllocateWithoutInitializing("aggtorow_map_LO"), numFineBlockRows);
1079 {
1080 SubFactoryMonitor m2(*this, "Create AggToRowMap", coarseLevel);
1081
1082 AggSizeType aggOffsets(Kokkos::ViewAllocateWithoutInitializing("aggOffsets"), numAggregates);
1083 Kokkos::deep_copy(aggOffsets, Kokkos::subview(aggDofSizes, Kokkos::make_pair(static_cast<size_t>(0), numAggregates)));
1084
1085 Kokkos::parallel_for(
1086 "MueLu:TentativePF:Build:createAgg2RowMap", range_type(0, vertex2AggId.extent(0)),
1087 KOKKOS_LAMBDA(const LO lnode) {
1088 if (procWinner(lnode, 0) == myPID) {
1089 // No need for atomics, it's one-to-one
1090 auto aggID = vertex2AggId(lnode, 0);
1091
1092 auto offset = Kokkos::atomic_fetch_add(&aggOffsets(aggID), stridedBlockSize);
1093 // FIXME: I think this may be wrong
1094 // We unconditionally add the whole block here. When we calculated
1095 // aggDofSizes, we did the isLocalElement check. Something's fishy.
1096 for (LO k = 0; k < stridedBlockSize; k++)
1097 aggToRowMapLO(offset + k) = lnode * stridedBlockSize + k;
1098 }
1099 });
1100 }
1101
1102 // STEP 2: prepare local QR decomposition
1103 // Reserve memory for tentative prolongation operator
1104 coarseNullspace = MultiVectorFactory::Build(coarsePointMap, NSDim, true);
1105
1106 // Pull out the nullspace vectors so that we can have random access (on the device)
1107 auto fineNS = fineNullspace->getLocalViewDevice(Tpetra::Access::ReadWrite);
1108 auto coarseNS = coarseNullspace->getLocalViewDevice(Tpetra::Access::ReadWrite);
1109
1110 typedef typename Xpetra::Matrix<SC, LO, GO, NO>::local_matrix_device_type local_matrix_type;
1111 typedef typename local_matrix_type::row_map_type::non_const_type rows_type;
1112 typedef typename local_matrix_type::index_type::non_const_type cols_type;
1113 // typedef typename local_matrix_type::values_type::non_const_type vals_type;
1114
1115 // Device View for status (error messages...)
1116 typedef Kokkos::View<int[10], DeviceType> status_type;
1117 status_type status("status");
1118
1119 typename AppendTrait<decltype(fineNS), Kokkos::RandomAccess>::type fineNSRandom = fineNS;
1121
1122 // We're going to bypass QR in the BlockCrs version of the code regardless of what the user asks for
1123 GetOStream(Runtime1) << "TentativePFactory : bypassing local QR phase" << std::endl;
1124
1125 // BlockCrs requires that we build the (block) graph first, so let's do that...
1126
1127 // NOTE: Because we're assuming that the NSDim == BlockSize, we only have one
1128 // block non-zero per row in the matrix;
1129 rows_type ia(Kokkos::ViewAllocateWithoutInitializing("BlockGraph_rowptr"), numFineBlockRows + 1);
1130 cols_type ja(Kokkos::ViewAllocateWithoutInitializing("BlockGraph_colind"), numFineBlockRows);
1131
1132 Kokkos::parallel_for(
1133 "MueLu:TentativePF:BlockCrs:graph_init", range_type(0, numFineBlockRows),
1134 KOKKOS_LAMBDA(const LO j) {
1135 ia[j] = j;
1136 ja[j] = INVALID;
1137
1138 if (j == (LO)numFineBlockRows - 1)
1139 ia[numFineBlockRows] = numFineBlockRows;
1140 });
1141
1142 // Fill Graph
1143 const Kokkos::TeamPolicy<execution_space> policy(numAggregates, 1);
1144 Kokkos::parallel_for(
1145 "MueLu:TentativePF:BlockCrs:fillGraph", policy,
1146 KOKKOS_LAMBDA(const typename Kokkos::TeamPolicy<execution_space>::member_type& thread) {
1147 auto agg = thread.league_rank();
1148 Xpetra::global_size_t offset = agg;
1149
1150 // size of the aggregate (number of DOFs in aggregate)
1151 LO aggSize = aggRows(agg + 1) - aggRows(agg);
1152
1153 for (LO j = 0; j < aggSize; j++) {
1154 // FIXME: Allow for bad maps
1155 const LO localRow = aggToRowMapLO[aggDofSizes[agg] + j];
1156 const size_t rowStart = ia[localRow];
1157 ja[rowStart] = offset;
1158 }
1159 });
1160
1161 // Compress storage (remove all INVALID, which happen when we skip zeros)
1162 // We do that in-place
1163 {
1164 // Stage 2: compress the arrays
1165 SubFactoryMonitor m2(*this, "Stage 2 (CompressData)", coarseLevel);
1166 // Fill i_temp with the correct row starts
1167 rows_type i_temp(Kokkos::ViewAllocateWithoutInitializing("BlockGraph_rowptr"), numFineBlockRows + 1);
1168 LO nnz = 0;
1169 Kokkos::parallel_scan(
1170 "MueLu:TentativePF:BlockCrs:compress_rows", range_type(0, numFineBlockRows),
1171 KOKKOS_LAMBDA(const LO i, LO& upd, const bool& final) {
1172 if (final)
1173 i_temp[i] = upd;
1174 for (auto j = ia[i]; j < ia[i + 1]; j++)
1175 if (ja[j] != INVALID)
1176 upd++;
1177 if (final && i == (LO)numFineBlockRows - 1)
1178 i_temp[numFineBlockRows] = upd;
1179 },
1180 nnz);
1181
1182 cols_type j_temp(Kokkos::ViewAllocateWithoutInitializing("BlockGraph_colind"), nnz);
1183
1184 Kokkos::parallel_for(
1185 "MueLu:TentativePF:BlockCrs:compress_cols", range_type(0, numFineBlockRows),
1186 KOKKOS_LAMBDA(const LO i) {
1187 size_t rowStart = i_temp[i];
1188 size_t lnnz = 0;
1189 for (auto j = ia[i]; j < ia[i + 1]; j++)
1190 if (ja[j] != INVALID) {
1191 j_temp[rowStart + lnnz] = ja[j];
1192 lnnz++;
1193 }
1194 });
1195
1196 ia = i_temp;
1197 ja = j_temp;
1198 }
1199
1200 RCP<CrsGraph> BlockGraph = CrsGraphFactory::Build(rowMap, coarseBlockMap, ia, ja);
1201
1202 // Managing labels & constants for ESFC
1203 {
1204 RCP<ParameterList> FCparams;
1205 if (pL.isSublist("matrixmatrix: kernel params"))
1206 FCparams = rcp(new ParameterList(pL.sublist("matrixmatrix: kernel params")));
1207 else
1208 FCparams = rcp(new ParameterList);
1209 // By default, we don't need global constants for TentativeP
1210 FCparams->set("compute global constants", FCparams->get("compute global constants", false));
1211 std::string levelIDs = toString(levelID);
1212 FCparams->set("Timer Label", std::string("MueLu::TentativeP-") + levelIDs);
1213 RCP<const Export> dummy_e;
1214 RCP<const Import> dummy_i;
1215 BlockGraph->expertStaticFillComplete(coarseBlockMap, rowMap, dummy_i, dummy_e, FCparams);
1216 }
1217
1218 // We can't leave the ia/ja pointers floating around, because of host/device view counting, so
1219 // we clear them here
1220 ia = rows_type();
1221 ja = cols_type();
1222
1223 // Now let's make a BlockCrs Matrix
1224 // NOTE: Assumes block size== NSDim
1225 RCP<Xpetra::CrsMatrix<SC, LO, GO, NO>> P_xpetra = Xpetra::CrsMatrixFactory<SC, LO, GO, NO>::BuildBlock(BlockGraph, coarsePointMap, rangeMap, NSDim);
1226 RCP<Xpetra::TpetraBlockCrsMatrix<SC, LO, GO, NO>> P_tpetra = rcp_dynamic_cast<Xpetra::TpetraBlockCrsMatrix<SC, LO, GO, NO>>(P_xpetra);
1227 if (P_tpetra.is_null()) throw std::runtime_error("BuildPUncoupled: Matrix factory did not return a Tpetra::BlockCrsMatrix");
1228 RCP<CrsMatrixWrap> P_wrap = rcp(new CrsMatrixWrap(P_xpetra));
1229
1230 auto values = P_tpetra->getTpetra_BlockCrsMatrix()->getValuesDeviceNonConst();
1231 const LO stride = NSDim * NSDim;
1232
1233 Kokkos::parallel_for(
1234 "MueLu:TentativePF:BlockCrs:main_loop_noqr", policy,
1235 KOKKOS_LAMBDA(const typename Kokkos::TeamPolicy<execution_space>::member_type& thread) {
1236 auto agg = thread.league_rank();
1237
1238 // size of the aggregate (number of DOFs in aggregate)
1239 LO aggSize = aggRows(agg + 1) - aggRows(agg);
1240 Xpetra::global_size_t offset = agg * NSDim;
1241
1242 // Q = localQR(:,0)/norm
1243 for (LO j = 0; j < aggSize; j++) {
1244 LO localBlockRow = aggToRowMapLO(aggRows(agg) + j);
1245 LO rowStart = localBlockRow * stride;
1246 for (LO r = 0; r < (LO)NSDim; r++) {
1247 LO localPointRow = localBlockRow * NSDim + r;
1248 for (LO c = 0; c < (LO)NSDim; c++) {
1249 values[rowStart + r * NSDim + c] = fineNSRandom(localPointRow, c);
1250 }
1251 }
1252 }
1253
1254 // R = norm
1255 for (LO j = 0; j < (LO)NSDim; j++)
1256 coarseNS(offset + j, j) = one;
1257 });
1258
1259 Ptentative = P_wrap;
1260}
1261
1262template <class Scalar, class LocalOrdinal, class GlobalOrdinal, class Node>
1264 BuildPcoupled(RCP<Matrix> /* A */, RCP<Aggregates> /* aggregates */,
1265 RCP<AmalgamationInfo> /* amalgInfo */, RCP<MultiVector> /* fineNullspace */,
1266 RCP<const Map> /* coarseMap */, RCP<Matrix>& /* Ptentative */,
1267 RCP<MultiVector>& /* coarseNullspace */) const {
1268 throw Exceptions::RuntimeError("MueLu: Construction of coupled tentative P is not implemented");
1269}
1270
1271template <class Scalar, class LocalOrdinal, class GlobalOrdinal, class Node>
1273 isGoodMap(const Map& rowMap, const Map& colMap) const {
1274 auto rowLocalMap = rowMap.getLocalMap();
1275 auto colLocalMap = colMap.getLocalMap();
1276
1277 const size_t numRows = rowLocalMap.getLocalNumElements();
1278 const size_t numCols = colLocalMap.getLocalNumElements();
1279
1280 if (numCols < numRows)
1281 return false;
1282
1283 size_t numDiff = 0;
1284 Kokkos::parallel_reduce(
1285 "MueLu:TentativePF:isGoodMap", range_type(0, numRows),
1286 KOKKOS_LAMBDA(const LO i, size_t& diff) {
1287 diff += (rowLocalMap.getGlobalElement(i) != colLocalMap.getGlobalElement(i));
1288 },
1289 numDiff);
1290
1291 return (numDiff == 0);
1292}
1293
1294} // namespace MueLu
1295
1296#define MUELU_TENTATIVEPFACTORY_KOKKOS_SHORT
1297#endif // MUELU_TENTATIVEPFACTORY_KOKKOS_DEF_HPP
#define SET_VALID_ENTRY(name)
maxAggDofSizeType maxAggDofSize
agg2RowMapLOType agg2RowMapLO
MueLu::DefaultLocalOrdinal LocalOrdinal
MueLu::DefaultScalar Scalar
MueLu::DefaultGlobalOrdinal GlobalOrdinal
typename LWGraph_kokkos::local_graph_type local_graph_type
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 throws to report errors in the internal logical of the program.
Timer to be used in factories. Similar to Monitor but with additional timers.
Class that holds all level-specific information.
bool IsAvailable(const std::string &ename, const FactoryBase *factory=NoFactory::get()) const
Test whether a need's value has been saved.
int GetLevelID() const
Return level number.
void AddKeepFlag(const std::string &ename, const FactoryBase *factory=NoFactory::get(), KeepType keep=MueLu::Keep)
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)
Timer to be used in factories. Similar to SubMonitor but adds a timer level by level.
bool isGoodMap(const Map &rowMap, const Map &colMap) const
void BuildP(Level &fineLevel, Level &coarseLevel) const
Abstract Build method.
void BuildPuncoupledBlockCrs(Level &coarseLevel, RCP< Matrix > A, RCP< Aggregates > aggregates, RCP< AmalgamationInfo > amalgInfo, RCP< MultiVector > fineNullspace, RCP< const Map > coarseMap, RCP< Matrix > &Ptentative, RCP< MultiVector > &coarseNullspace, const int levelID) const
Kokkos::RangePolicy< local_ordinal_type, execution_space > range_type
void BuildPuncoupled(Level &coarseLevel, RCP< Matrix > A, RCP< Aggregates > aggregates, RCP< AmalgamationInfo > amalgInfo, RCP< MultiVector > fineNullspace, RCP< const Map > coarseMap, RCP< Matrix > &Ptentative, RCP< MultiVector > &coarseNullspace, const int levelID) const
void Build(Level &fineLevel, Level &coarseLevel) const
Build an object with this factory.
RCP< const ParameterList > GetValidParameterList() const
Return a const parameter list of valid parameters that setParameterList() will accept.
void DeclareInput(Level &fineLevel, Level &coarseLevel) const
Input.
void BuildPcoupled(RCP< Matrix > A, RCP< Aggregates > aggregates, RCP< AmalgamationInfo > amalgInfo, RCP< MultiVector > fineNullspace, RCP< const Map > coarseMap, RCP< Matrix > &Ptentative, RCP< MultiVector > &coarseNullspace) const
static bool MapsAreNested(const Xpetra::Map< LocalOrdinal, GlobalOrdinal, Node > &rowMap, const Xpetra::Map< LocalOrdinal, GlobalOrdinal, Node > &colMap)
Namespace for MueLu classes and methods.
@ Final
Keep data only for this run. Used to keep data useful for Hierarchy::Iterate(). Data will be deleted ...
@ Warnings0
Important warning messages (one line)
@ Statistics2
Print even more statistics.
@ Runtime1
Description of what is happening (more verbose)
std::string toString(const T &what)
Little helper function to convert non-string types to strings.