opm-simulators
ParallelOverlappingILU0_impl.hpp
1 /*
2  Copyright 2015, 2022 Dr. Blatt - HPC-Simulation-Software & Services
3  Copyright 2015 Statoil AS
4 
5  This file is part of the Open Porous Media project (OPM).
6 
7  OPM is free software: you can redistribute it and/or modify
8  it under the terms of the GNU General Public License as published by
9  the Free Software Foundation, either version 3 of the License, or
10  (at your option) any later version.
11 
12  OPM is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  GNU General Public License for more details.
16 
17  You should have received a copy of the GNU General Public License
18  along with OPM. If not, see <http://www.gnu.org/licenses/>.
19 */
20 
21 #include <opm/simulators/linalg/ParallelOverlappingILU0.hpp>
22 
23 #include <dune/common/version.hh>
24 
25 #include <dune/istl/ilu.hh>
26 #include <dune/istl/owneroverlapcopy.hh>
27 
28 #include <opm/common/ErrorMacros.hpp>
29 #include <opm/common/TimingMacros.hpp>
30 
31 #include <opm/simulators/linalg/GraphColoring.hpp>
32 #include <opm/simulators/linalg/matrixblock.hh>
33 
34 #include <cassert>
35 
36 namespace Opm
37 {
38 namespace detail
39 {
40 
42 template<class M>
43 void ghost_last_bilu0_decomposition (M& A, std::size_t interiorSize)
44 {
45  OPM_TIMEBLOCK(GhostLastBlockILU0Decomp);
46  // iterator types
47  assert(interiorSize <= A.N());
48  using rowiterator = typename M::RowIterator;
49  using coliterator = typename M::ColIterator;
50  using block = typename M::block_type;
51 
52  // implement left looking variant with stored inverse
53  for (rowiterator i = A.begin(); i.index() < interiorSize; ++i)
54  {
55  // coliterator is diagonal after the following loop
56  coliterator endij=(*i).end(); // end of row i
57  coliterator ij;
58 
59  // eliminate entries left of diagonal; store L factor
60  for (ij=(*i).begin(); ij.index()<i.index(); ++ij)
61  {
62  // find A_jj which eliminates A_ij
63  coliterator jj = A[ij.index()].find(ij.index());
64 
65  // compute L_ij = A_jj^-1 * A_ij
66  (*ij).rightmultiply(*jj);
67 
68  // modify row
69  coliterator endjk=A[ij.index()].end(); // end of row j
70  coliterator jk=jj; ++jk;
71  coliterator ik=ij; ++ik;
72  while (ik!=endij && jk!=endjk)
73  if (ik.index()==jk.index())
74  {
75  block B(*jk);
76  B.leftmultiply(*ij);
77  *ik -= B;
78  ++ik; ++jk;
79  }
80  else
81  {
82  if (ik.index()<jk.index())
83  ++ik;
84  else
85  ++jk;
86  }
87  }
88 
89  // invert pivot and store it in A
90  if (ij.index()!=i.index())
91  DUNE_THROW(Dune::ISTLError,"diagonal entry missing");
92  try {
93  (*ij).invert(); // compute inverse of diagonal block
94  }
95  catch (Dune::FMatrixError & e) {
96  DUNE_THROW(Dune::ISTLError,"ILU failed to invert matrix block");
97  }
98  }
99 }
100 
102 template<class M, class CRS, class InvVector>
103 void convertToCRS(const M& A, CRS& lower, CRS& upper, InvVector& inv)
104 {
105  OPM_TIMEBLOCK(convertToCRS);
106  // No need to do anything for 0 rows. Return to prevent indexing a
107  // a zero sized array.
108  if ( A.N() == 0 )
109  {
110  return;
111  }
112 
113  using size_type = typename M :: size_type;
114 
115  lower.clear();
116  upper.clear();
117  inv.clear();
118  lower.resize( A.N() );
119  upper.resize( A.N() );
120  inv.resize( A.N() );
121 
122  // Count the lower and upper matrix entries.
123  size_type numLower = 0;
124  size_type numUpper = 0;
125  const auto endi = A.end();
126  for (auto i = A.begin(); i != endi; ++i) {
127  const size_type iIndex = i.index();
128  size_type numLowerRow = 0;
129  for (auto j = (*i).begin(); j.index() < iIndex; ++j) {
130  ++numLowerRow;
131  }
132  numLower += numLowerRow;
133  numUpper += (*i).size() - numLowerRow - 1;
134  }
135  assert(numLower + numUpper + A.N() == A.nonzeroes());
136 
137  lower.reserveAdditional( numLower );
138 
139  // implement left looking variant with stored inverse
140  size_type row = 0;
141  size_type colcount = 0;
142  lower.rows_[ 0 ] = colcount;
143  for (auto i=A.begin(); i!=endi; ++i, ++row)
144  {
145  const size_type iIndex = i.index();
146 
147  // eliminate entries left of diagonal; store L factor
148  for (auto j=(*i).begin(); j.index() < iIndex; ++j )
149  {
150  lower.push_back( (*j), j.index() );
151  ++colcount;
152  }
153  lower.rows_[ iIndex+1 ] = colcount;
154  }
155 
156  assert(colcount == numLower);
157 
158  const auto rbegini = std::make_reverse_iterator(A.begin());
159  row = 0;
160  colcount = 0;
161  upper.rows_[ 0 ] = colcount ;
162 
163  upper.reserveAdditional( numUpper );
164 
165  // NOTE: upper and inv store entries in reverse order, reverse here
166  // relative to ILU
167  auto rindex = [](auto it) { return std::prev(it.base()).index(); };
168  for (auto i=std::make_reverse_iterator(A.end()); i!=rbegini; ++i, ++ row )
169  {
170  const size_type iIndex = rindex(i);
171 
172  // store in reverse row order
173  // eliminate entries left of diagonal; store L factor
174  for (auto j=std::make_reverse_iterator(i->end()); rindex(j)>=iIndex; ++j )
175  {
176  const size_type jIndex = rindex(j);
177  if( rindex(j) == iIndex )
178  {
179  inv[ row ] = (*j);
180  break;
181  }
182  else if ( rindex(j) >= rindex(i) )
183  {
184  upper.push_back( (*j), jIndex );
185  ++colcount ;
186  }
187  }
188  upper.rows_[ row+1 ] = colcount;
189  }
190  assert(colcount == numUpper);
191 }
192 
193 template <class PI>
194 size_t set_interiorSize( [[maybe_unused]] size_t N, size_t interiorSize, [[maybe_unused]] const PI& comm)
195 {
196  return interiorSize;
197 }
198 
199 #if HAVE_MPI
200 template<>
201 size_t set_interiorSize(size_t N, size_t interiorSize, const Dune::OwnerOverlapCopyCommunication<int,int>& comm)
202 {
203  if (interiorSize<=N)
204  return interiorSize;
205  auto indexSet = comm.indexSet();
206  if (indexSet.size() == 0)
207  return 0;
208 
209  size_t new_is = 0;
210  for (auto idx = indexSet.begin(); idx!=indexSet.end(); ++idx)
211  {
212  if (idx->local().attribute()==1)
213  {
214  auto loc = idx->local().local();
215  if (loc > new_is) {
216  new_is = loc;
217  }
218  }
219  }
220  return new_is + 1;
221 }
222 #endif
223 
224 } // end namespace detail
225 
226 
227 template<class Matrix, class Domain, class Range, class ParallelInfoT>
228 Dune::SolverCategory::Category
229 ParallelOverlappingILU0<Matrix,Domain,Range,ParallelInfoT>::category() const
230 {
231  return std::is_same_v<ParallelInfoT, Dune::Amg::SequentialInformation> ?
232  Dune::SolverCategory::sequential : Dune::SolverCategory::overlapping;
233 }
234 
235 template<class Matrix, class Domain, class Range, class ParallelInfoT>
237 ParallelOverlappingILU0(const Matrix& A,
238  const int n, const field_type w,
239  MILU_VARIANT milu, bool redblack,
240  bool reorder_sphere)
241  : lower_(),
242  upper_(),
243  inv_(),
244  comm_(nullptr), w_(w),
245  relaxation_( std::abs( w - 1.0 ) > 1e-15 ),
246  A_(&reinterpret_cast<const Matrix&>(A)), iluIteration_(n),
247  milu_(milu), redBlack_(redblack), reorderSphere_(reorder_sphere)
248 {
249  interiorSize_ = A.N();
250  // BlockMatrix is a Subclass of FieldMatrix that just adds
251  // methods. Therefore this cast should be safe.
252  update();
253 }
254 
255 template<class Matrix, class Domain, class Range, class ParallelInfoT>
257 ParallelOverlappingILU0(const Matrix& A,
258  const ParallelInfo& comm, const int n, const field_type w,
259  MILU_VARIANT milu, bool redblack,
260  bool reorder_sphere)
261  : lower_(),
262  upper_(),
263  inv_(),
264  comm_(&comm), w_(w),
265  relaxation_( std::abs( w - 1.0 ) > 1e-15 ),
266  A_(&reinterpret_cast<const Matrix&>(A)), iluIteration_(n),
267  milu_(milu), redBlack_(redblack), reorderSphere_(reorder_sphere)
268 {
269  interiorSize_ = A.N();
270  // BlockMatrix is a Subclass of FieldMatrix that just adds
271  // methods. Therefore this cast should be safe.
272  update();
273 }
274 
275 template<class Matrix, class Domain, class Range, class ParallelInfoT>
277 ParallelOverlappingILU0(const Matrix& A,
278  const field_type w, MILU_VARIANT milu, bool redblack,
279  bool reorder_sphere)
280  : ParallelOverlappingILU0( A, 0, w, milu, redblack, reorder_sphere )
281 {}
282 
283 template<class Matrix, class Domain, class Range, class ParallelInfoT>
285 ParallelOverlappingILU0(const Matrix& A,
286  const ParallelInfo& comm, const field_type w,
287  MILU_VARIANT milu, bool redblack,
288  bool reorder_sphere)
289  : lower_(),
290  upper_(),
291  inv_(),
292  comm_(&comm), w_(w),
293  relaxation_( std::abs( w - 1.0 ) > 1e-15 ),
294  A_(&reinterpret_cast<const Matrix&>(A)), iluIteration_(0),
295  milu_(milu), redBlack_(redblack), reorderSphere_(reorder_sphere)
296 {
297  interiorSize_ = A.N();
298  // BlockMatrix is a Subclass of FieldMatrix that just adds
299  // methods. Therefore this cast should be safe.
300  update();
301 }
302 
303 template<class Matrix, class Domain, class Range, class ParallelInfoT>
305 ParallelOverlappingILU0(const Matrix& A,
306  const ParallelInfo& comm,
307  const field_type w, MILU_VARIANT milu,
308  size_type interiorSize, bool redblack,
309  bool reorder_sphere)
310  : lower_(),
311  upper_(),
312  inv_(),
313  comm_(&comm), w_(w),
314  relaxation_( std::abs( w - 1.0 ) > 1e-15 ),
315  interiorSize_(interiorSize),
316  A_(&reinterpret_cast<const Matrix&>(A)), iluIteration_(0),
317  milu_(milu), redBlack_(redblack), reorderSphere_(reorder_sphere)
318 {
319  // BlockMatrix is a Subclass of FieldMatrix that just adds
320  // methods. Therefore this cast should be safe.
321  assert(interiorSize <= A_->N());
322  update( );
323 }
324 
325 template<class Matrix, class Domain, class Range, class ParallelInfoT>
327 apply (Domain& v, const Range& d)
328 {
329  OPM_TIMEBLOCK(apply);
330  Range& md = reorderD(d);
331  Domain& mv = reorderV(v);
332 
333  // iterator types
334  using dblock = typename Range ::block_type;
335  using vblock = typename Domain::block_type;
336 
337  const size_type iEnd = lower_.rows();
338  const size_type lastRow = iEnd - 1;
339  size_type upperLoopStart = iEnd - interiorSize_;
340  size_type lowerLoopEnd = interiorSize_;
341  if (iEnd != upper_.rows())
342  {
343  OPM_THROW(std::logic_error,"ILU: number of lower and upper rows must be the same");
344  }
345 
346  // lower triangular solve
347  for (size_type i = 0; i < lowerLoopEnd; ++i)
348  {
349  dblock rhs( md[ i ] );
350  const size_type rowI = lower_.rows_[ i ];
351  const size_type rowINext = lower_.rows_[ i+1 ];
352 
353  for (size_type col = rowI; col < rowINext; ++col)
354  {
355  lower_.values_[ col ].mmv( mv[ lower_.cols_[ col ] ], rhs );
356  }
357 
358  mv[ i ] = rhs; // Lii = I
359  }
360 
361  for (size_type i = upperLoopStart; i < iEnd; ++i)
362  {
363  vblock& vBlock = mv[ lastRow - i ];
364  vblock rhs ( vBlock );
365  const size_type rowI = upper_.rows_[ i ];
366  const size_type rowINext = upper_.rows_[ i+1 ];
367 
368  for (size_type col = rowI; col < rowINext; ++col)
369  {
370  upper_.values_[ col ].mmv( mv[ upper_.cols_[ col ] ], rhs );
371  }
372 
373  // apply inverse and store result
374  inv_[ i ].mv( rhs, vBlock);
375  }
376 
377  copyOwnerToAll( mv );
378 
379  if( relaxation_ ) {
380  mv *= w_;
381  }
382  reorderBack(mv, v);
383 }
384 
385 template<class Matrix, class Domain, class Range, class ParallelInfoT>
386 template<class V>
388 copyOwnerToAll(V& v) const
389 {
390  if( comm_ ) {
391  comm_->copyOwnerToAll(v, v);
392  }
393 }
394 
395 template<class Matrix, class Domain, class Range, class ParallelInfoT>
396 void ParallelOverlappingILU0<Matrix,Domain,Range,ParallelInfoT>::
397 update()
398 {
399  OPM_TIMEBLOCK(update);
400  // (For older DUNE versions the communicator might be
401  // invalid if redistribution in AMG happened on the coarset level.
402  // Therefore we check for nonzero size
403  if (comm_ && comm_->communicator().size() <= 0)
404  {
405  if (A_->N() > 0)
406  {
407  OPM_THROW(std::logic_error, "Expected a matrix with zero rows for an invalid communicator.");
408  }
409  else
410  {
411  // simply set the communicator to null
412  comm_ = nullptr;
413  }
414  }
415 
416  int ilu_setup_successful = 1;
417  std::string message;
418  const int rank = comm_ ? comm_->communicator().rank() : 0;
419 
420  if (redBlack_)
421  {
422  using Graph = Dune::Amg::MatrixGraph<const Matrix>;
423  Graph graph(*A_);
424  auto colorsTuple = colorVerticesWelshPowell(graph);
425  const auto& colors = std::get<0>(colorsTuple);
426  const auto& verticesPerColor = std::get<2>(colorsTuple);
427  auto noColors = std::get<1>(colorsTuple);
428  if ( reorderSphere_ )
429  {
430  ordering_ = reorderVerticesSpheres(colors, noColors, verticesPerColor,
431  graph, 0);
432  }
433  else
434  {
435  ordering_ = reorderVerticesPreserving(colors, noColors, verticesPerColor,
436  graph);
437  }
438  }
439 
440  std::vector<std::size_t> inverseOrdering(ordering_.size());
441  {
442  OPM_TIMEBLOCK(createInverseOrdering);
443  std::size_t index = 0;
444  for (const auto newIndex : ordering_)
445  {
446  inverseOrdering[newIndex] = index++;
447  }
448  }
449 
450  try
451  {
452  OPM_TIMEBLOCK(iluDecomposition);
453  if (iluIteration_ == 0) {
454 
455  if (comm_) {
456  interiorSize_ = detail::set_interiorSize(A_->N(), interiorSize_, *comm_);
457  assert(interiorSize_ <= A_->N());
458  }
459 
460  // create ILU-0 decomposition
461  if (ordering_.empty())
462  {
463  OPM_TIMEBLOCK(iluDecompositionUpdateMatrix);
464  if (ILU_) {
465  OPM_TIMEBLOCK(iluDecompositionCopyEntries);
466  // The ILU_ matrix is already a copy with the same
467  // sparse structure as A_, but the values of A_ may
468  // have changed, so we must copy all elements.
469  for (std::size_t row = 0; row < A_->N(); ++row) {
470  const auto& Arow = (*A_)[row];
471  auto Ait = Arow.begin();
472  auto Iit = (*ILU_)[row].begin();
473  for (; Ait != Arow.end(); ++Ait, ++Iit) {
474  *Iit = *Ait;
475  }
476  }
477  } else {
478  OPM_TIMEBLOCK(iluDecompositionDuplicateMatrix);
479  // First call, must duplicate matrix.
480  ILU_ = std::make_unique<Matrix>(*A_);
481  }
482  }
483  else
484  {
485  ILU_ = std::make_unique<Matrix>(A_->N(), A_->M(),
486  A_->nonzeroes(), Matrix::row_wise);
487  auto& newA = *ILU_;
488  // Create sparsity pattern
489  auto endcreateA = newA.createend();
490  for (auto iter = newA.createbegin(); iter != endcreateA; ++iter)
491  {
492  const auto& row = (*A_)[inverseOrdering[iter.index()]];
493  for (auto col = row.begin(), cend = row.end(); col != cend; ++col)
494  {
495  iter.insert(ordering_[col.index()]);
496  }
497  }
498  // Copy values
499  for (auto iter = A_->begin(); iter != A_->end(); ++iter)
500  {
501  auto newRow = newA.begin() + ordering_[iter.index()];
502  for (auto&& [A_ij, j] : sparseRange(*newRow))
503  {
504  (*newRow)[ordering_[j]] = A_ij;
505  }
506  }
507  }
508 
509  switch (milu_)
510  {
512  detail::milu0_decomposition ( *ILU_);
513  break;
515  detail::milu0_decomposition ( *ILU_, detail::identityFunctor<typename Matrix::field_type>,
516  detail::signFunctor<typename Matrix::field_type> );
517  break;
519  detail::milu0_decomposition ( *ILU_, detail::absFunctor<typename Matrix::field_type>,
520  detail::signFunctor<typename Matrix::field_type> );
521  break;
523  detail::milu0_decomposition ( *ILU_, detail::identityFunctor<typename Matrix::field_type>,
524  detail::isPositiveFunctor<typename Matrix::field_type> );
525  break;
526  default:
527  if (interiorSize_ == A_->N())
528  Dune::ILU::blockILU0Decomposition( *ILU_ );
529  else
530  detail::ghost_last_bilu0_decomposition(*ILU_, interiorSize_);
531  break;
532  }
533  }
534  else {
535  // create ILU-n decomposition
536  ILU_ = std::make_unique<Matrix>(A_->N(), A_->M(), Matrix::row_wise);
537  std::unique_ptr<detail::Reorderer> reorderer, inverseReorderer;
538  if (ordering_.empty())
539  {
540  reorderer.reset(new detail::NoReorderer());
541  inverseReorderer.reset(new detail::NoReorderer());
542  }
543  else
544  {
545  reorderer.reset(new detail::RealReorderer(ordering_));
546  inverseReorderer.reset(new detail::RealReorderer(inverseOrdering));
547  }
548 
549  milun_decomposition( *A_, iluIteration_, milu_, *ILU_, *reorderer, *inverseReorderer );
550  }
551  }
552  catch (const Dune::MatrixBlockError& error)
553  {
554  message = error.what();
555  std::cerr << "Exception occurred on process " << rank << " during " <<
556  "setup of ILU0 preconditioner with message: "
557  << message<<std::endl;
558  ilu_setup_successful = 0;
559  }
560 
561  // Check whether there was a problem on some process
562  {
563  OPM_TIMEBLOCK(checkParallelFailure);
564  const bool parallel_failure = comm_ && comm_->communicator().min(ilu_setup_successful) == 0;
565  const bool local_failure = ilu_setup_successful == 0;
566  if (local_failure || parallel_failure)
567  {
568  throw Dune::MatrixBlockError();
569  }
570  }
571 
572  // store ILU in simple CRS format
573  detail::convertToCRS(*ILU_, lower_, upper_, inv_);
574 }
575 
576 template<class Matrix, class Domain, class Range, class ParallelInfoT>
578 reorderD(const Range& d)
579 {
580  if (ordering_.empty())
581  {
582  // As d is non-const in the apply method of the
583  // solver casting away constness in this particular
584  // setting is not undefined. It is ugly though but due
585  // to the preconditioner interface of dune-istl.
586  return const_cast<Range&>(d);
587  }
588  else
589  {
590  reorderedD_.resize(d.size());
591  std::size_t i = 0;
592  for (const auto index : ordering_)
593  {
594  reorderedD_[index] = d[i++];
595  }
596  return reorderedD_;
597  }
598 }
599 
600 template<class Matrix, class Domain, class Range, class ParallelInfoT>
602 reorderV(Domain& v)
603 {
604  if (ordering_.empty())
605  {
606  return v;
607  }
608  else
609  {
610  reorderedV_.resize(v.size());
611  std::size_t i = 0;
612  for (const auto index : ordering_)
613  {
614  reorderedV_[index] = v[i++];
615  }
616  return reorderedV_;
617  }
618 }
619 
620 template<class Matrix, class Domain, class Range, class ParallelInfoT>
622 reorderBack(const Range& reorderedV, Range& v)
623 {
624  if (!ordering_.empty())
625  {
626  std::size_t i = 0;
627  for (const auto index : ordering_)
628  {
629  v[i++] = reorderedV[index];
630  }
631  }
632 }
633 
634 } // end namespace Opm
A two-step version of an overlapping Schwarz preconditioner using one step ILU0 as.
Definition: ParallelOverlappingILU0.hpp:37
MILU_VARIANT
Definition: MILU.hpp:34
std::vector< std::size_t > reorderVerticesPreserving(const std::vector< int > &colors, int noColors, const std::vector< std::size_t > &verticesPerColor, const Graph &graph)
! Reorder colored graph preserving order of vertices with the same color.
Definition: GraphColoring.hpp:169
std::tuple< std::vector< int >, int, std::vector< std::size_t > > colorVerticesWelshPowell(const Graph &graph)
Color the vertices of graph.
Definition: GraphColoring.hpp:113
Range & reorderD(const Range &d)
Reorder D if needed and return a reference to it.
Definition: ParallelOverlappingILU0_impl.hpp:578
void apply(Domain &v, const Range &d) override
Apply the preconditoner.
Definition: ParallelOverlappingILU0_impl.hpp:327
Structs needed for tpfalinearizer and its gpuparams struct extracted to be defined in one place that ...
Definition: blackoilbioeffectsmodules.hh:45
sum(dropped entries)
typename Domain::field_type field_type
The field type of the preconditioner.
Definition: ParallelOverlappingILU0.hpp:142
sum(|dropped entries|)
sum(dropped entries)
std::vector< std::size_t > reorderVerticesSpheres(const std::vector< int > &colors, int noColors, const std::vector< std::size_t > &verticesPerColor, const Graph &graph, typename Graph::VertexDescriptor root)
! Reorder Vetrices in spheres
Definition: GraphColoring.hpp:189
sum(dropped entries)
Domain & reorderV(Domain &v)
Reorder V if needed and return a reference to it.
Definition: ParallelOverlappingILU0_impl.hpp:602
ParallelOverlappingILU0(const Matrix &A, const int n, const field_type w, MILU_VARIANT milu, bool redblack=false, bool reorder_sphere=true)
Constructor.
Definition: ParallelOverlappingILU0_impl.hpp:237