MueLu Version of the Day
Loading...
Searching...
No Matches
MueLu_InverseApproximationFactory_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_INVERSEAPPROXIMATIONFACTORY_DEF_HPP_
11#define MUELU_INVERSEAPPROXIMATIONFACTORY_DEF_HPP_
12
13#include <Xpetra_BlockedCrsMatrix.hpp>
14#include <Xpetra_CrsGraph.hpp>
15#include <Xpetra_CrsMatrixWrap.hpp>
16#include <Xpetra_CrsMatrix.hpp>
17#include <Xpetra_VectorFactory.hpp>
18#include <Xpetra_MatrixFactory.hpp>
19#include <Xpetra_Matrix.hpp>
20
21#include "Kokkos_Sort.hpp"
22#include "KokkosBlas1_set.hpp"
23#include "KokkosBatched_QR_Decl.hpp"
24#include "KokkosBatched_ApplyQ_Decl.hpp"
25#include "KokkosBatched_Trsv_Decl.hpp"
26#include "KokkosBatched_Util.hpp"
27#include <KokkosKernels_SimpleUtils.hpp>
28
29#include "MueLu_Level.hpp"
30#include "MueLu_Monitor.hpp"
31#include "MueLu_Utilities.hpp"
33
34#if KOKKOSKERNELS_VERSION < 50102
35#include "Teuchos_SerialDenseVector.hpp"
36#include "Teuchos_SerialDenseMatrix.hpp"
37#include "Teuchos_SerialQRDenseSolver.hpp"
38#endif
39
40namespace MueLu {
41
42template <class Scalar, class LocalOrdinal, class GlobalOrdinal, class Node>
44 RCP<ParameterList> validParamList = rcp(new ParameterList());
45 using Magnitude = typename Teuchos::ScalarTraits<Scalar>::magnitudeType;
46
47 validParamList->set<RCP<const FactoryBase>>("A", NoFactory::getRCP(), "Matrix to build the approximate inverse on.\n");
48
49 validParamList->set<std::string>("inverse: approximation type", "diagonal", "Method used to approximate the inverse.");
50 validParamList->set<Magnitude>("inverse: drop tolerance", 0.0, "Values below this threshold are dropped from the matrix (or fixed if diagonal fixing is active).");
51 validParamList->set<bool>("inverse: fixing", false, "Keep diagonal and fix small entries with 1.0");
52
53 return validParamList;
54}
55
56template <class Scalar, class LocalOrdinal, class GlobalOrdinal, class Node>
58 Input(currentLevel, "A");
59}
60
61template <class Scalar, class LocalOrdinal, class GlobalOrdinal, class Node>
63 FactoryMonitor m(*this, "Build", currentLevel);
64
65 using STS = Teuchos::ScalarTraits<SC>;
66 const SC one = STS::one();
67 using Magnitude = typename Teuchos::ScalarTraits<Scalar>::magnitudeType;
68
69 const ParameterList& pL = GetParameterList();
70 const bool fixing = pL.get<bool>("inverse: fixing");
71
72 // check which approximation type to use
73 const std::string method = pL.get<std::string>("inverse: approximation type");
74 TEUCHOS_TEST_FOR_EXCEPTION(method != "diagonal" && method != "lumping" && method != "sparseapproxinverse" && method != "factoredsparseapproxinverse", Exceptions::RuntimeError,
75 "MueLu::InverseApproximationFactory::Build: Approximation type can be 'diagonal' or 'lumping' or "
76 "'sparseapproxinverse' or 'factoredsparseapproxinverse'.");
77
78 RCP<Matrix> A = Get<RCP<Matrix>>(currentLevel, "A");
79 RCP<BlockedCrsMatrix> bA = Teuchos::rcp_dynamic_cast<BlockedCrsMatrix>(A);
80 const bool isBlocked = (bA == Teuchos::null ? false : true);
81
82 // if blocked operator is used, defaults to A(0,0)
83 if (isBlocked) A = bA->getMatrix(0, 0);
84
85 Magnitude tol = pL.get<Magnitude>("inverse: drop tolerance");
86 RCP<Matrix> Ainv = Teuchos::null;
87
88 if (method == "diagonal") {
89 const auto diag = VectorFactory::Build(A->getRangeMap(), true);
90 A->getLocalDiagCopy(*diag);
91 const RCP<const Vector> D = (!fixing ? Utilities::GetInverse(diag) : Utilities::GetInverse(diag, tol, one));
92 Ainv = MatrixFactory::Build(D);
93 } else if (method == "lumping") {
94 const auto diag = Utilities::GetLumpedMatrixDiagonal(*A);
95 const RCP<const Vector> D = (!fixing ? Utilities::GetInverse(diag) : Utilities::GetInverse(diag, tol, one));
96 Ainv = MatrixFactory::Build(D);
97 } else if (method == "sparseapproxinverse") {
98 RCP<CrsGraph> sparsityPattern = Utilities::GetThresholdedGraph(A, tol);
99 if (IsPrint(Statistics1)) {
100 sparsityPattern->computeGlobalConstants();
101 GetOStream(Statistics1) << "NNZ Graph(A): " << A->getCrsGraph()->getGlobalNumEntries() << " , NNZ Tresholded Graph(A): " << sparsityPattern->getGlobalNumEntries() << std::endl;
102 }
103 RCP<Matrix> pAinv = GetSparseInverse(A, sparsityPattern);
104 Ainv = Utilities::GetThresholdedMatrix(pAinv, tol, fixing);
105 if (IsPrint(Statistics1)) {
106 rcp_const_cast<CrsGraph>(Ainv->getCrsGraph())->computeGlobalConstants();
107 GetOStream(Statistics1) << "NNZ Ainv: " << pAinv->getGlobalNumEntries() << ", NNZ Tresholded Ainv (parameter: " << tol << "): " << Ainv->getGlobalNumEntries() << std::endl;
108 }
109 } else if (method == "factoredsparseapproxinverse") {
110 RCP<CrsGraph> sparsityPattern = Utilities::GetThresholdedLowerTriangularGraph(A, tol);
111 if (IsPrint(Statistics1)) {
112 sparsityPattern->computeGlobalConstants();
113 GetOStream(Statistics1) << "NNZ Graph(A): " << A->getCrsGraph()->getGlobalNumEntries() << " , NNZ Tresholded Graph(triLower(A)): " << sparsityPattern->getGlobalNumEntries() << std::endl;
114 }
115 RCP<Matrix> pLinvFactor = GetFactoredSparseInverse(A, sparsityPattern);
116 RCP<Matrix> LinvFactor = Utilities::GetThresholdedMatrix(pLinvFactor, tol, fixing);
117 // To create the inverse from the inverse factor, we need to multiply Linv' * LinvFactor. Of course, we could
118 // save a fair amount of storage by delaying this to when we actually need it as Linv' * LinvFactor has
119 // many more nonzeros than just Linv. One other thing, I'm explicitly using the Transpose computation as opposed to
120 // setting one of the booleans to true in the MatrixMatrix:Multiply. There are some comments about true/false combinations
121 // that don't work, so I decided not to push my luck here.
122
123 RCP<Matrix> LinvTrans = Utilities::Transpose(*LinvFactor, true);
124 Ainv = Xpetra::MatrixMatrix<Scalar, LocalOrdinal, GlobalOrdinal, Node>::Multiply(*LinvTrans, false, *LinvFactor, false, GetOStream(Statistics2), true, true, std::string("Ainv"));
125
126 if (IsPrint(Statistics1)) {
127 rcp_const_cast<CrsGraph>(Ainv->getCrsGraph())->computeGlobalConstants();
128 GetOStream(Statistics1) << "NNZ Linv: " << LinvFactor->getGlobalNumEntries() << ", NNZ Tresholded Linv (parameter: " << tol << "): " << pLinvFactor->getGlobalNumEntries() << std::endl;
129 }
130 }
131
132 GetOStream(Statistics1) << "Approximate inverse calculated by: " << method << "." << std::endl;
133 GetOStream(Statistics1) << "Ainv has " << Ainv->getGlobalNumRows() << "x" << Ainv->getGlobalNumCols() << " rows and columns." << std::endl;
134
135 Set(currentLevel, "Ainv", Ainv);
136}
137
138#if KOKKOSKERNELS_VERSION >= 50102
139
140template <class local_matrix_type>
141class LocalSPAIFunctor {
142 private:
143 using scalar_type = typename local_matrix_type::value_type;
144 using local_ordinal_type = typename local_matrix_type::ordinal_type;
145 using execution_space = typename local_matrix_type::execution_space;
146 using impl_scalar_type = typename KokkosKernels::ArithTraits<scalar_type>::val_type;
147 using impl_ATS = KokkosKernels::ArithTraits<impl_scalar_type>;
148
149 public:
150 using shared_matrix = Kokkos::View<impl_scalar_type**, typename execution_space::scratch_memory_space, Kokkos::MemoryUnmanaged>;
151 using shared_vector = Kokkos::View<impl_scalar_type*, typename execution_space::scratch_memory_space, Kokkos::MemoryUnmanaged>;
152 using shared_lo_vector = Kokkos::View<local_ordinal_type*, typename execution_space::scratch_memory_space, Kokkos::MemoryUnmanaged>;
153
154 private:
155 const local_matrix_type lclA;
156 local_matrix_type lclAinv;
157 const local_ordinal_type maxUniqueColEntries;
158 const int scratchLevel;
159
160 public:
161 LocalSPAIFunctor(const local_matrix_type& lclA_, local_matrix_type& lclAinv_, local_ordinal_type maxUniqueColEntries_, int scratchLevel_)
162 : lclA(lclA_)
163 , lclAinv(lclAinv_)
164 , maxUniqueColEntries(maxUniqueColEntries_)
165 , scratchLevel(scratchLevel_) {}
166
167 KOKKOS_INLINE_FUNCTION
168 void operator()(const typename Kokkos::TeamPolicy<execution_space>::member_type& thread) const {
169 auto rlid = thread.league_rank();
170 auto rowAinv = lclAinv.row(rlid);
171
172 // Loop over entries in row rlid of Ainv and collect all of A's column indices.
173 shared_lo_vector column_indices(thread.team_scratch(scratchLevel), maxUniqueColEntries);
174 local_ordinal_type numColEntries = 0;
175 for (local_ordinal_type ii = 0; ii < rowAinv.length; ++ii) {
176 auto i = rowAinv.colidx(ii);
177 auto rowA = lclA.rowConst(i);
178 for (local_ordinal_type jj = 0; jj < rowA.length; ++jj) {
179 auto j = rowA.colidx(jj);
180 column_indices(numColEntries) = j;
181 ++numColEntries;
182 }
183 }
184
185 // Get merged list of column indices.
186 local_ordinal_type numUniqeColEntries = 0;
187 local_ordinal_type diagOffset = 0;
188 {
189 // Sort
190 Kokkos::Experimental::sort_thread(thread, Kokkos::subview(column_indices, Kokkos::make_pair(0, numColEntries)));
191 // Merge
192 if (numColEntries > 0)
193 ++numUniqeColEntries;
194 local_ordinal_type pos = 0;
195 for (local_ordinal_type m = 1; m < numColEntries; ++m) {
196 if (column_indices(pos) != column_indices(m)) {
197 column_indices(pos + 1) = column_indices(m);
198 ++pos;
199 ++numUniqeColEntries;
200 if (column_indices(pos) == rlid)
201 diagOffset = pos;
202 }
203 }
204 }
205 // create a unique version of the column indices that has the correct length (as opposed
206 // to column_indices). Can we instead resize column_indices with MemoryUnmanaged?
207 // This is so that we can use binary search later on sorted list
208 shared_lo_vector uniqueColIndicies(thread.team_scratch(scratchLevel), numUniqeColEntries);
209 for (local_ordinal_type m = 0; m < numUniqeColEntries; ++m) {
210 uniqueColIndicies(m) = column_indices(m);
211 }
212
213 // Extract local part of A into a dense view.
214 shared_matrix localA(thread.team_scratch(scratchLevel), numUniqeColEntries, rowAinv.length);
215 KokkosBlas::SerialSet::invoke(impl_ATS::zero(), localA);
216
217 // Now fill localA.
218 for (local_ordinal_type ii = 0; ii < rowAinv.length; ++ii) {
219 auto i = rowAinv.colidx(ii);
220 auto rowA = lclA.rowConst(i);
221 for (local_ordinal_type jj = 0; jj < rowA.length; ++jj) {
222 auto j = rowA.colidx(jj);
223 auto v = rowA.value(jj);
224
225 // do binary search to find column in uniqueColIndices
226 auto it = KokkosKernels::lower_bound_thread(uniqueColIndicies, j);
227 localA(it, ii) = v;
228 }
229 }
230
231 shared_matrix ek(thread.team_scratch(scratchLevel), numUniqeColEntries, 1);
232 // set to zero, set diagonal entry to one
233 for (local_ordinal_type i = 0; i < numUniqeColEntries; ++i) {
234 ek(i, 0) = (i == diagOffset) ? impl_ATS::one() : impl_ATS::zero();
235 }
236
237 // QR solve
238 shared_vector tau(thread.team_scratch(scratchLevel), rowAinv.length);
239 shared_vector work(thread.team_scratch(scratchLevel), numUniqeColEntries);
240 // factorize localA = Q*R in-place
241 KokkosBatched::SerialQR<KokkosBatched::Algo::QR::Unblocked>::invoke(localA, tau, work);
242 // ek := Q^T ek
243 KokkosBatched::SerialApplyQ<KokkosBatched::Side::Left, KokkosBatched::Trans::Transpose, KokkosBatched::Algo::ApplyQ::Unblocked>::invoke(localA, tau, ek, work);
244 // ek[:rowLength] := R^{-1} ek[:rowLength]
245 auto sub_A = Kokkos::subview(localA, Kokkos::make_pair(0, rowAinv.length), Kokkos::ALL());
246 auto sub_ek = Kokkos::subview(ek, Kokkos::make_pair(0, rowAinv.length), 0);
247 KokkosBatched::SerialTrsv<KokkosBatched::Uplo::Upper, KokkosBatched::Trans::NoTranspose, KokkosBatched::Diag::NonUnit, KokkosBatched::Algo::Trsv::Unblocked>::invoke(impl_ATS::one(), sub_A, sub_ek);
248
249 // Set entries of Ainv.
250 for (local_ordinal_type i = 0; i < rowAinv.length; ++i) {
251 rowAinv.value(i) = sub_ek(i);
252 }
253 }
254};
255
256template <class Scalar, class LocalOrdinal, class GlobalOrdinal, class Node>
257RCP<Xpetra::Matrix<Scalar, LocalOrdinal, GlobalOrdinal, Node>>
258InverseApproximationFactory<Scalar, LocalOrdinal, GlobalOrdinal, Node>::GetSparseInverse(const RCP<Matrix>& Aorg, const RCP<const CrsGraph>& sparsityPattern) const {
259 using execution_space = typename Node::execution_space;
260
261 // construct the inverse matrix with the given sparsity pattern
262 RCP<Matrix> Ainv = MatrixFactory::Build(sparsityPattern);
263 Ainv->resumeFill();
264
265 // gather missing rows from other procs to generate an overlapping map
266 RCP<Import> rowImport = ImportFactory::Build(sparsityPattern->getRowMap(), sparsityPattern->getColMap());
267 RCP<Matrix> A = MatrixFactory::Build(Aorg, *rowImport);
268
269 auto maxRowEntriesAinv = Ainv->getLocalMaxNumRowEntries();
270 auto maxRowEntriesA = A->getLocalMaxNumRowEntries();
271 auto maxUniqueColEntries = maxRowEntriesAinv * maxRowEntriesA;
272 {
273 auto lclA = A->getLocalMatrixDevice();
274 auto lclAinv = Ainv->getLocalMatrixDevice();
275
276 Kokkos::TeamPolicy<execution_space> policy(lclAinv.numRows(), 1);
277
278 using spai_functor_type = LocalSPAIFunctor<decltype(lclAinv)>;
279 using shared_matrix = typename spai_functor_type::shared_matrix;
280 using shared_vector = typename spai_functor_type::shared_vector;
281 using shared_lo_vector = typename spai_functor_type::shared_lo_vector;
282
283 int size = shared_matrix::shmem_size(maxUniqueColEntries, maxRowEntriesAinv) + shared_matrix::shmem_size(maxUniqueColEntries, 1) + shared_vector::shmem_size(3 * maxUniqueColEntries) + shared_vector::shmem_size(maxRowEntriesAinv) + shared_lo_vector::shmem_size(maxUniqueColEntries);
284
285 int scratchLevel = -1;
286 if (size < policy.scratch_size_max(/*level=*/(int)0)) {
287 policy.set_scratch_size(/*level=*/(int)0, Kokkos::PerTeam(size));
288 scratchLevel = 0;
289 } else if (size < policy.scratch_size_max(/*level=*/(int)1)) {
290 policy.set_scratch_size(/*level=*/(int)1, Kokkos::PerTeam(size));
291 scratchLevel = 1;
292 } else
293 throw Exceptions::RuntimeError("Neither L0 scratch memory (max size " + std::to_string(policy.scratch_size_max((int)0)) +
294 "), nor L1 scratch memory (max size " + std::to_string(policy.scratch_size_max((int)1)) +
295 ") is large enough for requested allocation of size " + std::to_string(size));
296
297 LocalSPAIFunctor spaiFunctor(lclA, lclAinv, maxUniqueColEntries, scratchLevel);
298
299 Kokkos::parallel_for("MueLu::InverseFactory::LocalSpai", policy, spaiFunctor);
300 }
301
302 Ainv->fillComplete();
303
304 // Transpose needed to match published paper algorithms as the inverse is not symmetric
305 // However, non-transposed version seems to work better in row-oriented MinvA algorithm
306 // RCP<Matrix> actualSpai = Utilities::Transpose(*Ainv, true); // , label, Tparams);
307
308 return Ainv;
309}
310
311#else
312
313template <class Scalar, class LocalOrdinal, class GlobalOrdinal, class Node>
314RCP<Xpetra::Matrix<Scalar, LocalOrdinal, GlobalOrdinal, Node>>
315InverseApproximationFactory<Scalar, LocalOrdinal, GlobalOrdinal, Node>::GetSparseInverse(const RCP<Matrix>& Aorg, const RCP<const CrsGraph>& sparsityPattern) const {
316 // construct the inverse matrix with the given sparsity pattern
317 RCP<Matrix> Ainv = MatrixFactory::Build(sparsityPattern);
318 Ainv->resumeFill();
319
320 // gather missing rows from other procs to generate an overlapping map
321 RCP<Import> rowImport = ImportFactory::Build(sparsityPattern->getRowMap(), sparsityPattern->getColMap());
322 RCP<Matrix> A = MatrixFactory::Build(Aorg, *rowImport);
323
324 // loop over all rows of the inverse sparsity pattern (this can be done in parallel)
325 for (size_t k = 0; k < sparsityPattern->getLocalNumRows(); k++) {
326 // 1. get column indices Ik of local row k
327 ArrayView<const LO> Ik;
328 sparsityPattern->getLocalRowView(k, Ik);
329
330 // 2. get all local A(Ik,:) rows
331 Array<ArrayView<const LO>> J(Ik.size());
332 Array<ArrayView<const SC>> Ak(Ik.size());
333 Array<LO> Jk;
334 for (LO i = 0; i < Ik.size(); i++) {
335 A->getLocalRowView(Ik[i], J[i], Ak[i]);
336 for (LO j = 0; j < J[i].size(); j++)
337 Jk.append(J[i][j]);
338 }
339 // set of unique column indices Jk
340 std::sort(Jk.begin(), Jk.end());
341 Jk.erase(std::unique(Jk.begin(), Jk.end()), Jk.end());
342 // create map
343 std::map<LO, LO> G;
344 for (LO i = 0; i < Jk.size(); i++) G.insert(std::pair<LO, LO>(Jk[i], i));
345
346 // 3. merge rows together
347 Teuchos::SerialDenseMatrix<LO, SC> localA(Jk.size(), Ik.size(), true);
348 for (LO i = 0; i < Ik.size(); i++) {
349 for (LO j = 0; j < J[i].size(); j++) {
350 localA(G.at(J[i][j]), i) = Ak[i][j];
351 }
352 }
353
354 // 4. get direction-vector
355 // diagonal needs an entry!
356 Teuchos::SerialDenseVector<LO, SC> ek(Jk.size(), true);
357 ek[std::find(Jk.begin(), Jk.end(), k) - Jk.begin()] = Teuchos::ScalarTraits<Scalar>::one();
358 ;
359
360 // 5. solve linear system for x
361 Teuchos::SerialDenseVector<LO, SC> localX(Ik.size());
362 Teuchos::SerialQRDenseSolver<LO, SC> qrSolver;
363 qrSolver.setMatrix(Teuchos::rcp(&localA, false));
364 qrSolver.setVectors(Teuchos::rcp(&localX, false), Teuchos::rcp(&ek, false));
365 const int err = qrSolver.solve();
366 TEUCHOS_TEST_FOR_EXCEPTION(err != 0, Exceptions::RuntimeError,
367 "MueLu::InverseApproximationFactory::GetSparseInverse: Error in serial QR solve.");
368
369 // 6. set calculated row into Ainv
370 ArrayView<const SC> Mk(localX.values(), localX.length());
371 Ainv->replaceLocalValues(k, Ik, Mk);
372 }
373 Ainv->fillComplete();
374
375 // Transpose needed to match published paper algorithms as the inverse is not symmetric
376 // However, non-transposed version seems to work better in row-oriented MinvA algorithm
377 // RCP<Matrix> actualSpai = Utilities::Transpose(*Ainv, true); // , label, Tparams);
378
379 return Ainv;
380}
381
382#endif
383
384template <class local_matrix_type, typename global_ordinal_type>
386 private:
387 using scalar_type = typename local_matrix_type::value_type;
388 using local_ordinal_type = typename local_matrix_type::ordinal_type;
389 using execution_space = typename local_matrix_type::execution_space;
390 using impl_scalar_type = typename KokkosKernels::ArithTraits<scalar_type>::val_type;
391 using impl_ATS = KokkosKernels::ArithTraits<impl_scalar_type>;
392 using device_type = typename local_matrix_type::device_type;
393 using local_map_type = Tpetra::Details::LocalMap<local_ordinal_type, global_ordinal_type, device_type>;
394
395 public:
396 using shared_matrix = Kokkos::View<impl_scalar_type**, typename execution_space::scratch_memory_space, Kokkos::MemoryUnmanaged>;
397 using shared_vector = Kokkos::View<impl_scalar_type*, typename execution_space::scratch_memory_space, Kokkos::MemoryUnmanaged>;
398 using shared_lo_vector = Kokkos::View<local_ordinal_type*, typename execution_space::scratch_memory_space, Kokkos::MemoryUnmanaged>;
399
400 private:
401 const local_matrix_type lclA;
402 local_matrix_type lclAinv;
407 const int scratchLevel;
408
409 public:
410 LocalFSAIFunctor(const local_matrix_type& lclA_, local_matrix_type& lclAinv_, const local_map_type& lclARowMap_, const local_map_type& lclAColMap_,
411 const local_map_type& lclAinvRowMap_, const local_map_type& lclAinvColMap_, int scratchLevel_)
412 : lclA(lclA_)
413 , lclAinv(lclAinv_)
414 , lclARowMap(lclARowMap_)
415 , lclAColMap(lclAColMap_)
416 , lclAinvRowMap(lclAinvRowMap_)
417 , lclAinvColMap(lclAinvColMap_)
418 , scratchLevel(scratchLevel_) {}
419
420 KOKKOS_INLINE_FUNCTION
421 void operator()(const typename Kokkos::TeamPolicy<execution_space>::member_type& thread) const {
422 auto rlid = thread.league_rank();
423 auto rowAinv = lclAinv.row(rlid);
424
425 // matlab version of algorithm
426 // n = size(A,1); nzs = nnz(S);
427 // newrows = zeros(nzs,1); newcols = zeros(nzs,1); newvals= zeros(nzs,1);
428 // count = 0;
429 // for i=1:n,
430 // [~,subCols,~] = find(S(i,:));
431 // diagLocation = find(subCols == i);
432 // submat = A(subCols,subCols);
433 // subn = size(submat,1);
434 // if diagLocation ~= subn, fprintf('pattern not lower triangular?\n'); keyboard; end;
435 // identCol = zeros(subn,1); identCol(diagLocation) = 1;
436 // AinvFactorRow = submat\identCol;
437 // normalizedFactor = AinvFactorRow/sqrt(AinvFactorRow(diagLocation));
438 // newrows(count+1:count+subn) = i;
439 // newcols(count+1:count+subn) = subCols;
440 // newvals(count+1:count+subn) = normalizedFactor;
441 // count = count + subn;
442 // end;
443 // Lfactor = sparse(newrows,newcols,newvals,n,n);
444 //
445
446 auto A_rowGid = lclARowMap.getGlobalElement(rlid);
447
448 auto numRowEntries = rowAinv.length;
449 local_ordinal_type diagOffset = -1;
450 scalar_type diagValue = 0.0;
451 local_ordinal_type A_lclRowIndForDiag = -1;
452
453 // Loop over entries in row rlid of Ainv and collect all of A's column indices.
454 shared_lo_vector column_indices(thread.team_scratch(scratchLevel), numRowEntries);
455 local_ordinal_type numColEntries = rowAinv.length;
456 for (local_ordinal_type ii = 0; ii < rowAinv.length; ++ii) {
457 auto i = rowAinv.colidx(ii);
458 auto Ainv_colGid = lclAinvColMap.getGlobalElement(i); // for debugging
459 local_ordinal_type A_lclRowInd = lclARowMap.getLocalElement(Ainv_colGid);
460 local_ordinal_type A_lclColInd = lclAColMap.getLocalElement(Ainv_colGid);
461 column_indices(ii) = A_lclColInd;
462 if (A_rowGid == Ainv_colGid) {
463 A_lclRowIndForDiag = A_lclRowInd;
464 }
465 }
466#ifdef HAVE_MUELU_DEBUG // code must also be compiled with -DKokkos_ENABLE_DEBUG=ON
467 KOKKOS_ASSERT(A_lclRowIndForDiag != -1 && "MueLu::InverseApproximationFactory::GetSparseInverse: no diagonal entry found in A.");
468#endif
469
470 Kokkos::Experimental::sort_thread(thread, column_indices); // in order to apply binary search later
471 for (int kkk = 0; kkk < numColEntries; kkk++) {
472 if (column_indices(kkk) == A_lclRowIndForDiag) diagOffset = kkk;
473 }
474#ifdef HAVE_MUELU_DEBUG
475 KOKKOS_ASSERT(diagOffset != -1 && "MueLu::InverseApproximationFactory::GetSparseInverse: no diagonal entry offset found in A.");
476#endif
477
478 // Extract local part of A into a dense view.
479 shared_matrix localA(thread.team_scratch(scratchLevel), numRowEntries, rowAinv.length);
480 KokkosBlas::SerialSet::invoke(impl_ATS::zero(), localA);
481
482 // Now fill localA.
483 for (local_ordinal_type ii = 0; ii < rowAinv.length; ++ii) {
484 auto i = rowAinv.colidx(ii);
485 auto Ainv_colGid = lclAinvColMap.getGlobalElement(i); // for debugging
486 local_ordinal_type A_lclRowInd = lclARowMap.getLocalElement(Ainv_colGid);
487#ifdef HAVE_MUELU_DEBUG
488 KOKKOS_ASSERT(A_lclRowInd != -1 && "MueLu::InverseApproximationFactory: Column global ID in Ainv not found in A rowmap");
489#endif
490 auto rowA = lclA.rowConst(A_lclRowInd);
491
492 for (local_ordinal_type jj = 0; jj < rowA.length; ++jj) {
493 auto j = rowA.colidx(jj);
494 // do binary search to find column in column_indices, but first check that it is
495 // in lower triangular portion of matrix (because this might be faster?)
496 auto A_colGid = lclAColMap.getGlobalElement(j);
497 if (A_colGid <= A_rowGid) {
498 auto newIndex = KokkosKernels::lower_bound_thread(column_indices, j);
499 if ((newIndex < column_indices.extent(0)) && (column_indices(newIndex) == j))
500 localA(newIndex, ii) = rowA.value(jj);
501 }
502 }
503 }
504 shared_matrix ek(thread.team_scratch(scratchLevel), numRowEntries, 1);
505 // set to zero, set diagonal entry to one
506 for (local_ordinal_type i = 0; i < numRowEntries; ++i) {
507 ek(i, 0) = (i == diagOffset) ? impl_ATS::one() : impl_ATS::zero();
508 }
509
510 // QR solve
511 shared_vector tau(thread.team_scratch(scratchLevel), rowAinv.length);
512 shared_vector work(thread.team_scratch(scratchLevel), numRowEntries);
513 // factorize localA = Q*R in-place
514#define QRway
515#ifdef QRway
516 KokkosBatched::SerialQR<KokkosBatched::Algo::QR::Unblocked>::invoke(localA, tau, work);
517#else
518 KokkosBatched::SerialCholesky<KokkosBatched::Uplo::Lower, KokkosBatched::Algo::Cholesky::Unblocked>::invoke(localA); // use Cholesky
519#endif
520 // ek := Q^T ek
521#ifdef QRway
522 KokkosBatched::SerialApplyQ<KokkosBatched::Side::Left, KokkosBatched::Trans::Transpose, KokkosBatched::Algo::ApplyQ::Unblocked>::invoke(localA, tau, ek, work);
523 // ek[:rowLength] := R^{-1} ek[:rowLength]
524 auto sub_A = Kokkos::subview(localA, Kokkos::make_pair(0, rowAinv.length), Kokkos::ALL());
525#else
526 auto sub_A = Kokkos::subview(localA, Kokkos::make_pair(0, rowAinv.length), Kokkos::make_pair(0, rowAinv.length)); // use Cholesky
527#endif
528 auto sub_ek = Kokkos::subview(ek, Kokkos::make_pair(0, rowAinv.length), 0);
529#ifdef QRway
530 KokkosBatched::SerialTrsv<KokkosBatched::Uplo::Upper, KokkosBatched::Trans::NoTranspose, KokkosBatched::Diag::NonUnit, KokkosBatched::Algo::Trsv::Unblocked>::invoke(impl_ATS::one(), sub_A, sub_ek);
531#else
532 KokkosBatched::SerialTrsv<KokkosBatched::Uplo::Lower, KokkosBatched::Trans::NoTranspose, KokkosBatched::Diag::NonUnit, KokkosBatched::Algo::Trsv::Unblocked>::invoke(impl_ATS::one(), sub_A, sub_ek); // use Cholesky
533 KokkosBatched::SerialTrsv<KokkosBatched::Uplo::Lower, KokkosBatched::Trans::Transpose, KokkosBatched::Diag::NonUnit, KokkosBatched::Algo::Trsv::Unblocked>::invoke(impl_ATS::one(), sub_A, sub_ek); // use Cholesky
534#endif
535
536 // Set entries of Ainv.
537
538 diagValue = sub_ek(diagOffset);
539#ifdef HAVE_MUELU_DEBUG
540 KOKKOS_ASSERT(impl_ATS::real(diagValue) > 0.0 && "MueLu::InverseApproximationFactory::GetSparseInverse: non positive diagonal entry.");
541#endif
542 auto scale_factor = impl_ATS::one() / impl_ATS::sqrt(diagValue);
543 for (local_ordinal_type i = 0; i < rowAinv.length; ++i) {
544 typename KokkosKernels::ArithTraits<decltype(diagValue)>::val_type thevalue = sub_ek(i) * scale_factor;
545
546 if (thevalue == impl_ATS::zero()) thevalue = impl_ATS::eps();
547 rowAinv.value(i) = thevalue;
548 }
549 }
550};
551
552template <class Scalar, class LocalOrdinal, class GlobalOrdinal, class Node>
553RCP<Xpetra::Matrix<Scalar, LocalOrdinal, GlobalOrdinal, Node>>
554InverseApproximationFactory<Scalar, LocalOrdinal, GlobalOrdinal, Node>::GetFactoredSparseInverse(const RCP<Matrix>& Aorg, const RCP<const CrsGraph>& sparsityPattern) const {
555 using execution_space = typename Node::execution_space;
556
557 // construct the inverse matrix with factor the given sparsity pattern
558 RCP<Matrix> Ainv = MatrixFactory::Build(sparsityPattern);
559 Ainv->resumeFill();
560
561 // gather missing rows from other procs to generate an overlapping map
562 RCP<Import> rowImport = ImportFactory::Build(sparsityPattern->getRowMap(), sparsityPattern->getColMap());
563 RCP<Matrix> A = MatrixFactory::Build(Aorg, *rowImport);
564
565 auto maxRowEntriesAinv = Ainv->getLocalMaxNumRowEntries();
566 {
567 auto lclA = A->getLocalMatrixDevice();
568 auto lclAinv = Ainv->getLocalMatrixDevice();
569 auto lclARowmap = A->getRowMap()->getLocalMap();
570 auto lclAColmap = A->getColMap()->getLocalMap();
571 auto lclAinvRowmap = Ainv->getRowMap()->getLocalMap();
572 auto lclAinvColmap = Ainv->getColMap()->getLocalMap();
573 auto lclAorgRowmap = Aorg->getRowMap()->getLocalMap();
574
575 Kokkos::TeamPolicy<execution_space> policy(lclAinv.numRows(), 1);
576
577 using fsai_functor_type = LocalFSAIFunctor<decltype(lclAinv), GlobalOrdinal>;
578 using shared_matrix = typename fsai_functor_type::shared_matrix;
579 using shared_vector = typename fsai_functor_type::shared_vector;
580 using shared_lo_vector = typename fsai_functor_type::shared_lo_vector;
581
582 int size = shared_matrix::shmem_size(maxRowEntriesAinv, maxRowEntriesAinv) + shared_matrix::shmem_size(maxRowEntriesAinv, 1) + shared_vector::shmem_size(3 * maxRowEntriesAinv) + shared_vector::shmem_size(maxRowEntriesAinv) + shared_lo_vector::shmem_size(maxRowEntriesAinv);
583
584 int scratchLevel = -1;
585 if (size < policy.scratch_size_max(/*level=*/(int)0)) {
586 policy.set_scratch_size(/*level=*/(int)0, Kokkos::PerTeam(size));
587 scratchLevel = 0;
588 } else if (size < policy.scratch_size_max(/*level=*/(int)1)) {
589 policy.set_scratch_size(/*level=*/(int)1, Kokkos::PerTeam(size));
590 scratchLevel = 1;
591 } else
592 throw Exceptions::RuntimeError("Neither L0 scratch memory (max size " + std::to_string(policy.scratch_size_max((int)0)) +
593 "), nor L1 scratch memory (max size " + std::to_string(policy.scratch_size_max((int)1)) +
594 ") is large enough for requested allocation of size " + std::to_string(size));
595
596 LocalFSAIFunctor fsaiFunctor(lclA, lclAinv, lclARowmap, lclAColmap, lclAinvRowmap, lclAinvColmap, scratchLevel);
597
598 Kokkos::parallel_for("MueLu::InverseFactory::LocalSpai", policy, fsaiFunctor);
599 }
600
601 Ainv->fillComplete();
602
603 return Ainv;
604}
605
606} // namespace MueLu
607
608#endif /* MUELU_INVERSEAPPROXIMATIONFACTORY_DEF_HPP_ */
MueLu::DefaultGlobalOrdinal GlobalOrdinal
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.
RCP< Matrix > GetFactoredSparseInverse(const RCP< Matrix > &A, const RCP< const CrsGraph > &sparsityPattern) const
Sparse factor inverse calculation method.
void Build(Level &currentLevel) const
Build an object with this factory.
RCP< const ParameterList > GetValidParameterList() const
Return a const parameter list of valid parameters that setParameterList() will accept.
RCP< Matrix > GetSparseInverse(const RCP< Matrix > &A, const RCP< const CrsGraph > &sparsityPattern) const
Sparse inverse calculation method.
Class that holds all level-specific information.
KokkosKernels::ArithTraits< impl_scalar_type > impl_ATS
typename KokkosKernels::ArithTraits< scalar_type >::val_type impl_scalar_type
Kokkos::View< local_ordinal_type *, typename execution_space::scratch_memory_space, Kokkos::MemoryUnmanaged > shared_lo_vector
Tpetra::Details::LocalMap< local_ordinal_type, global_ordinal_type, device_type > local_map_type
typename local_matrix_type::value_type scalar_type
typename local_matrix_type::execution_space execution_space
Kokkos::View< impl_scalar_type *, typename execution_space::scratch_memory_space, Kokkos::MemoryUnmanaged > shared_vector
typename local_matrix_type::device_type device_type
typename local_matrix_type::ordinal_type local_ordinal_type
Kokkos::View< impl_scalar_type **, typename execution_space::scratch_memory_space, Kokkos::MemoryUnmanaged > shared_matrix
LocalFSAIFunctor(const local_matrix_type &lclA_, local_matrix_type &lclAinv_, const local_map_type &lclARowMap_, const local_map_type &lclAColMap_, const local_map_type &lclAinvRowMap_, const local_map_type &lclAinvColMap_, int scratchLevel_)
KOKKOS_INLINE_FUNCTION void operator()(const typename Kokkos::TeamPolicy< execution_space >::member_type &thread) const
static const RCP< const NoFactory > getRCP()
Static Get() functions.
static Teuchos::RCP< Vector > GetInverse(Teuchos::RCP< const Vector > v, Magnitude tol=Teuchos::ScalarTraits< Scalar >::eps() *100, Scalar valReplacement=Teuchos::ScalarTraits< Scalar >::zero())
Return vector containing inverse of input vector.
static RCP< Xpetra::CrsGraph< LocalOrdinal, GlobalOrdinal, Node > > GetThresholdedLowerTriangularGraph(const RCP< Matrix > &A, const Magnitude threshold)
Threshold a graph.
static RCP< Xpetra::CrsGraph< LocalOrdinal, GlobalOrdinal, Node > > GetThresholdedGraph(const RCP< Matrix > &A, const Magnitude threshold)
Threshold a graph.
static RCP< Matrix > GetThresholdedMatrix(const RCP< Matrix > &Ain, const Magnitude threshold, const bool keepDiagonal=true)
Threshold a matrix.
static Teuchos::RCP< Vector > GetLumpedMatrixDiagonal(Matrix const &A, const bool doReciprocal=false, Magnitude tol=Teuchos::ScalarTraits< Scalar >::magnitude(Teuchos::ScalarTraits< Scalar >::zero()), Scalar valReplacement=Teuchos::ScalarTraits< Scalar >::zero(), const bool replaceSingleEntryRowWithZero=false, const bool useAverageAbsDiagVal=false)
Extract Matrix Diagonal of lumped matrix.
static RCP< Xpetra::Matrix< Scalar, LocalOrdinal, GlobalOrdinal, Node > > Transpose(Xpetra::Matrix< Scalar, LocalOrdinal, GlobalOrdinal, Node > &Op, bool optimizeTranspose=false, const std::string &label=std::string(), const Teuchos::RCP< Teuchos::ParameterList > &params=Teuchos::null)
Namespace for MueLu classes and methods.
@ Statistics2
Print even more statistics.
@ Statistics1
Print more statistics.