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.size() - 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 }
#define ASSERTL0(condition, msg)
Definition: ErrorUtil.hpp:216
A global linear system.
Definition: GlobalLinSys.h:73
const std::weak_ptr< ExpList > m_expList
Local Matrix System.
Definition: GlobalLinSys.h:127
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.
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.
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.
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:52
The above copyright notice and this permission notice shall be included.
Definition: CoupledSolver.h:1
double NekDouble
T Vsum(int n, const T *x, const int incx)
Subtract return sum(x)
Definition: Vmath.cpp:846
void Vcopy(int n, const T *x, const int incx, T *y, const int incy)
Definition: Vmath.cpp:1199
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.