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