Nektar++
GlobalLinSysPETSc.cpp
Go to the documentation of this file.
1 ///////////////////////////////////////////////////////////////////////////////
2 //
3 // File: GlobalLinSysPETSc.cpp
4 //
5 // For more information, please see: http://www.nektar.info
6 //
7 // The MIT License
8 //
9 // Copyright (c) 2006 Division of Applied Mathematics, Brown University (USA),
10 // Department of Aeronautics, Imperial College London (UK), and Scientific
11 // Computing and Imaging Institute, University of Utah (USA).
12 //
13 // Permission is hereby granted, free of charge, to any person obtaining a
14 // copy of this software and associated documentation files (the "Software"),
15 // to deal in the Software without restriction, including without limitation
16 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
17 // and/or sell copies of the Software, and to permit persons to whom the
18 // Software is furnished to do so, subject to the following conditions:
19 //
20 // The above copyright notice and this permission notice shall be included
21 // in all copies or substantial portions of the Software.
22 //
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
24 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
26 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
28 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
29 // DEALINGS IN THE SOFTWARE.
30 //
31 // Description: GlobalLinSysPETSc definition
32 //
33 ///////////////////////////////////////////////////////////////////////////////
34 
37 
38 #include "petscis.h"
39 #include "petscversion.h"
40 
41 using namespace std;
42 
43 namespace Nektar
44 {
45 namespace MultiRegions
46 {
47 std::string GlobalLinSysPETSc::matMult =
48  LibUtilities::SessionReader::RegisterDefaultSolverInfo("PETScMatMult",
49  "Sparse");
50 std::string GlobalLinSysPETSc::matMultIds[] = {
51  LibUtilities::SessionReader::RegisterEnumValue(
52  "PETScMatMult", "Sparse", MultiRegions::ePETScMatMultSparse),
53  LibUtilities::SessionReader::RegisterEnumValue(
54  "PETScMatMult", "Shell", MultiRegions::ePETScMatMultShell)};
55 
56 /**
57  * @class GlobalLinSysPETSc
58  *
59  * Solves a linear system using PETSc.
60  */
61 GlobalLinSysPETSc::GlobalLinSysPETSc(
62  const GlobalLinSysKey &pKey, const std::weak_ptr<ExpList> &pExp,
63  const std::shared_ptr<AssemblyMap> &pLocToGloMap)
64  : GlobalLinSys(pKey, pExp, pLocToGloMap)
65 {
66  // Determine whether to use standard sparse matrix approach or
67  // shell.
68  m_matMult = pExp.lock()->GetSession()->GetSolverInfoAsEnum<PETScMatMult>(
69  "PETScMatMult");
70 
71  // Check PETSc is initialized. For some reason, this is needed on
72  // OS X as logging is not initialized properly in the call within
73  // CommMpi.
74  PetscBool isInitialized;
75  PetscInitialized(&isInitialized);
76  if (!isInitialized)
77  {
78 #ifdef NEKTAR_USE_MPI
79  std::string commType =
80  m_expList.lock()->GetSession()->GetComm()->GetType();
81  if (commType.find("MPI") != std::string::npos)
82  {
84  std::static_pointer_cast<LibUtilities::CommMpi>(
85  m_expList.lock()->GetSession()->GetComm());
86  PETSC_COMM_WORLD = comm->GetComm();
87  }
88 #endif
89  PetscInitializeNoArguments();
90  }
91 
92  // Create matrix
93  MatCreate(PETSC_COMM_WORLD, &m_matrix);
94 }
95 
96 /**
97  * @brief Clean up PETSc objects.
98  *
99  * Note that if SessionReader::Finalize is called before the end of the
100  * program, PETSc may have been finalized already, at which point we
101  * cannot deallocate our objects. If that's the case we do nothing and
102  * let the kernel clear up after us.
103  */
105 {
106  PetscBool isFinalized;
107  PetscFinalized(&isFinalized);
108 
109  // Sometimes, PetscFinalized returns false when (in fact) CommMpi's
110  // Finalise routine has been called. We therefore also need to check
111  // whether MPI has been finalised. This might arise from the
112  // additional call to PetscInitializeNoArguments in the constructor
113  // above.
114 #ifdef NEKTAR_USE_MPI
115  int mpiFinal = 0;
116  MPI_Finalized(&mpiFinal);
117  isFinalized = isFinalized || mpiFinal ? PETSC_TRUE : PETSC_FALSE;
118 #endif
119 
120  if (!isFinalized)
121  {
122  KSPDestroy(&m_ksp);
123  PCDestroy(&m_pc);
124  MatDestroy(&m_matrix);
125  VecDestroy(&m_x);
126  VecDestroy(&m_b);
127  VecDestroy(&m_locVec);
128  }
129 }
130 
131 /**
132  * @brief Solve linear system using PETSc.
133  *
134  * The general strategy being a PETSc solve is to:
135  *
136  * - Copy values into the PETSc vector #m_b
137  * - Solve the system #m_ksp and place result into #m_x.
138  * - Scatter results back into #m_locVec using #m_ctx scatter object.
139  * - Copy from #m_locVec to output array #pOutput.
140  */
142  const int pNumRows, const Array<OneD, const NekDouble> &pInput,
143  Array<OneD, NekDouble> &pOutput, const AssemblyMapSharedPtr &locToGloMap,
144  const int pNumDir)
145 {
146  const int nHomDofs = pNumRows - pNumDir;
147 
149  {
150  m_precon = CreatePrecon(locToGloMap);
151  m_precon->BuildPreconditioner();
152  }
153 
154  // Populate RHS vector from input
155  VecSetValues(m_b, nHomDofs, &m_reorderedMap[0], &pInput[pNumDir],
156  INSERT_VALUES);
157 
158  // Assemble RHS vector
159  VecAssemblyBegin(m_b);
160  VecAssemblyEnd(m_b);
161 
162  // Do system solve
163  KSPSolve(m_ksp, m_b, m_x);
164 
165  KSPConvergedReason reason;
166  KSPGetConvergedReason(m_ksp, &reason);
167  ASSERTL0(reason > 0, "PETSc solver diverged, reason is: " +
168  std::string(KSPConvergedReasons[reason]));
169 
170  // Scatter results to local vector
171  VecScatterBegin(m_ctx, m_x, m_locVec, INSERT_VALUES, SCATTER_FORWARD);
172  VecScatterEnd(m_ctx, m_x, m_locVec, INSERT_VALUES, SCATTER_FORWARD);
173 
174  // Copy results into output vector
175  PetscScalar *tmp;
176  VecGetArray(m_locVec, &tmp);
177  Vmath::Vcopy(nHomDofs, tmp, 1, &pOutput[pNumDir], 1);
178  VecRestoreArray(m_locVec, &tmp);
179 }
180 
181 /**
182  * @brief Set up PETSc local (equivalent to Nektar++ global) and global
183  * (equivalent to universal) scatter maps.
184  *
185  * These maps are used in GlobalLinSysPETSc::v_SolveLinearSystem to
186  * scatter the solution vector back to each process.
187  */
189 {
190  const int nHomDofs = m_reorderedMap.size();
191 
192  // Create local and global numbering systems for vector
193  IS isGlobal, isLocal;
194  ISCreateGeneral(PETSC_COMM_SELF, nHomDofs, &m_reorderedMap[0],
195  PETSC_COPY_VALUES, &isGlobal);
196  ISCreateStride(PETSC_COMM_SELF, nHomDofs, 0, 1, &isLocal);
197 
198  // Create local vector for output
199  VecCreate(PETSC_COMM_SELF, &m_locVec);
200  VecSetSizes(m_locVec, nHomDofs, PETSC_DECIDE);
201  VecSetFromOptions(m_locVec);
202 
203  // Create scatter context
204  VecScatterCreate(m_x, isGlobal, m_locVec, isLocal, &m_ctx);
205 
206  // Clean up
207  ISDestroy(&isGlobal);
208  ISDestroy(&isLocal);
209 }
210 
211 /**
212  * @brief Calculate a reordering of universal IDs for PETSc.
213  *
214  * PETSc requires a unique, contiguous index of all global and universal
215  * degrees of freedom which represents its position inside the
216  * matrix. Presently Gs does not guarantee this, so this routine
217  * constructs a new universal mapping.
218  *
219  * @param glo2uniMap Global to universal map
220  * @param glo2unique Global to unique map
221  * @param pLocToGloMap Assembly map for this system
222  */
224  const Array<OneD, const int> &glo2uniMap,
225  const Array<OneD, const int> &glo2unique,
226  const AssemblyMapSharedPtr &pLocToGloMap)
227 {
229  m_expList.lock()->GetSession()->GetComm();
230 
231  const int nDirDofs = pLocToGloMap->GetNumGlobalDirBndCoeffs();
232  const int nHomDofs = glo2uniMap.size() - nDirDofs;
233  const int nProc = vComm->GetSize();
234  const int rank = vComm->GetRank();
235 
236  int n, cnt;
237 
238  // Count number of unique degrees of freedom on each process.
239  m_nLocal = Vmath::Vsum(nHomDofs, glo2unique + nDirDofs, 1);
240  m_reorderedMap.resize(nHomDofs);
241 
242  // Reduce coefficient counts across all processors.
243  Array<OneD, int> localCounts(nProc, 0), localOffset(nProc, 0);
244  localCounts[rank] = nHomDofs;
245  vComm->AllReduce(localCounts, LibUtilities::ReduceSum);
246 
247  for (n = 1; n < nProc; ++n)
248  {
249  localOffset[n] = localOffset[n - 1] + localCounts[n - 1];
250  }
251 
252  int totHomDofs = Vmath::Vsum(nProc, localCounts, 1);
253  vector<unsigned int> allUniIds(totHomDofs, 0);
254 
255  // Assemble list of universal IDs
256  for (n = 0; n < nHomDofs; ++n)
257  {
258  int gid = n + nDirDofs;
259  allUniIds[n + localOffset[rank]] = glo2uniMap[gid];
260  }
261 
262  // Reduce this across processors so that each process has a list of
263  // all universal IDs.
264  vComm->AllReduce(allUniIds, LibUtilities::ReduceSum);
265  std::sort(allUniIds.begin(), allUniIds.end());
266  map<int, int> uniIdReorder;
267 
268  // Renumber starting from 0.
269  for (cnt = n = 0; n < allUniIds.size(); ++n)
270  {
271  if (uniIdReorder.count(allUniIds[n]) > 0)
272  {
273  continue;
274  }
275 
276  uniIdReorder[allUniIds[n]] = cnt++;
277  }
278 
279  // Populate reordering map.
280  for (n = 0; n < nHomDofs; ++n)
281  {
282  int gid = n + nDirDofs;
283  int uniId = glo2uniMap[gid];
284  ASSERTL0(uniIdReorder.count(uniId) > 0, "Error in ordering");
285  m_reorderedMap[n] = uniIdReorder[uniId];
286  }
287 }
288 
289 /**
290  * @brief Construct PETSc matrix and vector handles.
291  *
292  * @todo Preallocation should be done at this point, since presently
293  * matrix allocation takes a significant amount of time.
294  *
295  * @param nGlobal Number of global degrees of freedom in the system (on
296  * this processor)
297  * @param nDir Number of Dirichlet degrees of freedom (on this
298  * processor).
299  */
300 void GlobalLinSysPETSc::SetUpMatVec(int nGlobal, int nDir)
301 {
302  // CREATE VECTORS
303  VecCreate(PETSC_COMM_WORLD, &m_x);
304  VecSetSizes(m_x, m_nLocal, PETSC_DECIDE);
305  VecSetFromOptions(m_x);
306  VecDuplicate(m_x, &m_b);
307 
308  // CREATE MATRICES
310  {
311  // Create ShellCtx context object which will store the matrix
312  // size and a pointer to the linear system. We do this so that
313  // we can call a member function to the matrix-vector and
314  // preconditioning multiplication in a subclass.
315  ShellCtx *ctx1 = new ShellCtx(), *ctx2 = new ShellCtx();
316  ctx1->nGlobal = ctx2->nGlobal = nGlobal;
317  ctx1->nDir = ctx2->nDir = nDir;
318  ctx1->linSys = ctx2->linSys = this;
319 
320  // Set up MatShell object.
321  MatCreateShell(PETSC_COMM_WORLD, m_nLocal, m_nLocal, PETSC_DETERMINE,
322  PETSC_DETERMINE, (void *)ctx1, &m_matrix);
323  MatShellSetOperation(m_matrix, MATOP_MULT,
324  (void (*)(void))DoMatrixMultiply);
325  MatShellSetOperation(m_matrix, MATOP_DESTROY,
326  (void (*)(void))DoDestroyMatCtx);
327 
328  // Create a PCShell to go alongside the MatShell.
329  PCCreate(PETSC_COMM_WORLD, &m_pc);
330 #if PETSC_VERSION_GE(3, 5, 0)
331  PCSetOperators(m_pc, m_matrix, m_matrix);
332 #else
333  PCSetOperators(m_pc, m_matrix, m_matrix, SAME_NONZERO_PATTERN);
334 #endif
335  PCSetType(m_pc, PCSHELL);
336  PCShellSetApply(m_pc, DoPreconditioner);
337  PCShellSetDestroy(m_pc, DoDestroyPCCtx);
338  PCShellSetContext(m_pc, ctx2);
339  }
340  else
341  {
342  // Otherwise we create a PETSc matrix and use MatSetFromOptions
343  // so that we can set various options on the command line.
344  MatCreate(PETSC_COMM_WORLD, &m_matrix);
345  MatSetType(m_matrix, MATAIJ);
346  MatSetSizes(m_matrix, m_nLocal, m_nLocal, PETSC_DETERMINE,
347  PETSC_DETERMINE);
348  MatSetFromOptions(m_matrix);
349  MatSetUp(m_matrix);
350  }
351 }
352 
353 /**
354  * @brief Perform either matrix multiplication or preconditioning using
355  * Nektar++ routines.
356  *
357  * This static function uses Nektar++ routines to calculate the
358  * matrix-vector product of @p M with @p in, storing the output in @p
359  * out.
360  *
361  * @todo There's a lot of scatters and copies that might possibly be
362  * eliminated to make this more efficient.
363  *
364  * @param in Input vector.
365  * @param out Output vector.
366  * @param ctx ShellCtx object that points to our instance of
367  * GlobalLinSysPETSc.
368  * @param precon If true, we apply a preconditioner, if false, we
369  * perform a matrix multiplication.
370  */
371 void GlobalLinSysPETSc::DoNekppOperation(Vec &in, Vec &out, ShellCtx *ctx,
372  bool precon)
373 {
374  const int nGlobal = ctx->nGlobal;
375  const int nDir = ctx->nDir;
376  const int nHomDofs = nGlobal - nDir;
377  GlobalLinSysPETSc *linSys = ctx->linSys;
378 
379  // Scatter from PETSc ordering to our local ordering. It's actually
380  // unclear whether this step might also do some communication in
381  // parallel, which is probably not ideal.
382  VecScatterBegin(linSys->m_ctx, in, linSys->m_locVec, INSERT_VALUES,
383  SCATTER_FORWARD);
384  VecScatterEnd(linSys->m_ctx, in, linSys->m_locVec, INSERT_VALUES,
385  SCATTER_FORWARD);
386 
387  // Temporary storage to pass to Nektar++
388  Array<OneD, NekDouble> tmpIn(nHomDofs), tmpOut(nHomDofs);
389 
390  // Get values from input vector and copy to tmpIn.
391  PetscScalar *tmpLocIn;
392  VecGetArray(linSys->m_locVec, &tmpLocIn);
393  Vmath::Vcopy(nHomDofs, tmpLocIn, 1, &tmpIn[0], 1);
394  VecRestoreArray(linSys->m_locVec, &tmpLocIn);
395 
396  // Do matrix multiply in Nektar++, store in tmpOut.
397  if (precon)
398  {
399  linSys->m_precon->DoPreconditioner(tmpIn, tmpOut);
400  }
401  else
402  {
403  linSys->v_DoMatrixMultiply(tmpIn, tmpOut);
404  }
405 
406  // Scatter back to PETSc ordering and put in out.
407  VecSetValues(out, nHomDofs, &linSys->m_reorderedMap[0], &tmpOut[0],
408  INSERT_VALUES);
409  VecAssemblyBegin(out);
410  VecAssemblyEnd(out);
411 }
412 
413 /**
414  * @brief Perform matrix multiplication using Nektar++ routines.
415  *
416  * This static function uses Nektar++ routines to calculate the
417  * matrix-vector product of @p M with @p in, storing the output in @p
418  * out.
419  *
420  * @param M Original MatShell matrix, which stores the ShellCtx
421  * object.
422  * @param in Input vector.
423  * @param out Output vector.
424  */
425 PetscErrorCode GlobalLinSysPETSc::DoMatrixMultiply(Mat M, Vec in, Vec out)
426 {
427  // Grab our shell context from M.
428  void *ptr;
429  MatShellGetContext(M, &ptr);
430  ShellCtx *ctx = (ShellCtx *)ptr;
431 
432  DoNekppOperation(in, out, ctx, false);
433 
434  // Must return 0, otherwise PETSc complains.
435  return 0;
436 }
437 
438 /**
439  * @brief Apply preconditioning using Nektar++ routines.
440  *
441  * This static function uses Nektar++ routines to apply the
442  * preconditioner stored in GlobalLinSysPETSc::m_precon from the context
443  * of @p pc to the vector @p in, storing the output in @p out.
444  *
445  * @param pc Preconditioner object that stores the ShellCtx.
446  * @param in Input vector.
447  * @param out Output vector.
448  */
449 PetscErrorCode GlobalLinSysPETSc::DoPreconditioner(PC pc, Vec in, Vec out)
450 {
451  // Grab our PCShell context from pc.
452  void *ptr;
453  PCShellGetContext(pc, &ptr);
454  ShellCtx *ctx = (ShellCtx *)ptr;
455 
456  DoNekppOperation(in, out, ctx, true);
457 
458  // Must return 0, otherwise PETSc complains.
459  return 0;
460 }
461 
462 /**
463  * @brief Destroy matrix shell context object.
464  *
465  * Note the matrix shell and preconditioner share a common context so
466  * this might have already been deallocated below, in which case we do
467  * nothing.
468  *
469  * @param M Matrix shell object
470  */
472 {
473  void *ptr;
474  MatShellGetContext(M, &ptr);
475  ShellCtx *ctx = (ShellCtx *)ptr;
476  delete ctx;
477  return 0;
478 }
479 
480 /**
481  * @brief Destroy preconditioner context object.
482  *
483  * Note the matrix shell and preconditioner share a common context so
484  * this might have already been deallocated above, in which case we do
485  * nothing.
486  *
487  * @param pc Preconditioner object
488  */
490 {
491  void *ptr;
492  PCShellGetContext(pc, &ptr);
493  ShellCtx *ctx = (ShellCtx *)ptr;
494  delete ctx;
495  return 0;
496 }
497 
498 /**
499  * @brief Set up KSP solver object.
500  *
501  * This is reasonably generic setup -- most solver types can be changed
502  * using the .petscrc file.
503  *
504  * @param tolerance Residual tolerance to converge to.
505  */
507 {
508  KSPCreate(PETSC_COMM_WORLD, &m_ksp);
509  KSPSetTolerances(m_ksp, tolerance, PETSC_DEFAULT, PETSC_DEFAULT,
510  PETSC_DEFAULT);
511  KSPSetFromOptions(m_ksp);
512 #if PETSC_VERSION_GE(3, 5, 0)
513  KSPSetOperators(m_ksp, m_matrix, m_matrix);
514 #else
515  KSPSetOperators(m_ksp, m_matrix, m_matrix, SAME_NONZERO_PATTERN);
516 #endif
517 
519  {
520  KSPSetPC(m_ksp, m_pc);
521  }
522 }
523 } // namespace MultiRegions
524 } // namespace Nektar
#define ASSERTL0(condition, msg)
Definition: ErrorUtil.hpp:215
A global linear system.
Definition: GlobalLinSys.h:72
const std::weak_ptr< ExpList > m_expList
Local Matrix System.
Definition: GlobalLinSys.h:124
PreconditionerSharedPtr CreatePrecon(AssemblyMapSharedPtr asmMap)
Create a preconditioner object from the parameters defined in the supplied assembly map.
A PETSc global linear system.
Vec m_x
PETSc vector objects used for local storage.
PETScMatMult m_matMult
Enumerator to select matrix multiplication type.
static void DoNekppOperation(Vec &in, Vec &out, ShellCtx *ctx, bool precon)
Perform either matrix multiplication or preconditioning using Nektar++ routines.
std::vector< int > m_reorderedMap
Reordering that takes universal IDs to a unique row in the PETSc matrix.
void SetUpScatter()
Set up PETSc local (equivalent to Nektar++ global) and global (equivalent to universal) scatter maps.
virtual void v_DoMatrixMultiply(const Array< OneD, const NekDouble > &pInput, Array< OneD, NekDouble > &pOutput)=0
static PetscErrorCode DoPreconditioner(PC pc, Vec in, Vec out)
Apply preconditioning using Nektar++ routines.
void SetUpSolver(NekDouble tolerance)
Set up KSP solver object.
static PetscErrorCode DoMatrixMultiply(Mat M, Vec in, Vec out)
Perform matrix multiplication using Nektar++ routines.
void SetUpMatVec(int nGlobal, int nDir)
Construct PETSc matrix and vector handles.
static PetscErrorCode DoDestroyMatCtx(Mat M)
Destroy matrix shell context object.
VecScatter m_ctx
PETSc scatter context that takes us between Nektar++ global ordering and PETSc vector ordering.
virtual ~GlobalLinSysPETSc()
Clean up PETSc objects.
PC m_pc
PCShell for preconditioner.
void CalculateReordering(const Array< OneD, const int > &glo2uniMap, const Array< OneD, const int > &glo2unique, const AssemblyMapSharedPtr &pLocToGloMap)
Calculate a reordering of universal IDs for PETSc.
virtual void v_SolveLinearSystem(const int pNumRows, const Array< OneD, const NekDouble > &pInput, Array< OneD, NekDouble > &pOutput, const AssemblyMapSharedPtr &locToGloMap, const int pNumDir) override
Solve linear system using PETSc.
KSP m_ksp
KSP object that represents solver system.
static PetscErrorCode DoDestroyPCCtx(PC pc)
Destroy preconditioner context object.
int m_nLocal
Number of unique degrees of freedom on this process.
std::shared_ptr< CommMpi > CommMpiSharedPtr
Pointer to a Communicator object.
Definition: CommMpi.h:55
std::shared_ptr< Comm > CommSharedPtr
Pointer to a Communicator object.
Definition: Comm.h:54
std::shared_ptr< AssemblyMap > AssemblyMapSharedPtr
Definition: AssemblyMap.h:51
The above copyright notice and this permission notice shall be included.
Definition: CoupledSolver.h:2
double NekDouble
T Vsum(int n, const T *x, const int incx)
Subtract return sum(x)
Definition: Vmath.cpp:895
void Vcopy(int n, const T *x, const int incx, T *y, const int incy)
Definition: Vmath.cpp:1255
Internal struct for MatShell and PCShell calls to store current context for callback.
int nDir
Number of Dirichlet degrees of freedom.
int nGlobal
Number of global degrees of freedom.
GlobalLinSysPETSc * linSys
Pointer to the original calling object.