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
41using namespace std;
42
43namespace Nektar
44{
45namespace MultiRegions
46{
49 "Sparse");
50std::string GlobalLinSysPETSc::matMultIds[] = {
52 "PETScMatMult", "Sparse", MultiRegions::ePETScMatMultSparse),
54 "PETScMatMult", "Shell", MultiRegions::ePETScMatMultShell)};
55
56/**
57 * @class GlobalLinSysPETSc
58 *
59 * Solves a linear system using PETSc.
60 */
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 Array<OneD, NekDouble> Glo(pNumRows);
155 locToGloMap->Assemble(pInput, Glo);
156
157 // Populate RHS vector from input
158 VecSetValues(m_b, nHomDofs, &m_reorderedMap[0], &Glo[pNumDir],
159 INSERT_VALUES);
160
161 // Assemble RHS vector
162 VecAssemblyBegin(m_b);
163 VecAssemblyEnd(m_b);
164
165 // Do system solve
166 KSPSolve(m_ksp, m_b, m_x);
167
168 KSPConvergedReason reason;
169 KSPGetConvergedReason(m_ksp, &reason);
170 ASSERTL0(reason > 0, "PETSc solver diverged, reason is: " +
171 std::string(KSPConvergedReasons[reason]));
172
173 // Scatter results to local vector
174 VecScatterBegin(m_ctx, m_x, m_locVec, INSERT_VALUES, SCATTER_FORWARD);
175 VecScatterEnd(m_ctx, m_x, m_locVec, INSERT_VALUES, SCATTER_FORWARD);
176
177 // Copy results into output vector
178 PetscScalar *tmp;
179 VecGetArray(m_locVec, &tmp);
180 Vmath::Vcopy(nHomDofs, tmp, 1, &Glo[pNumDir], 1);
181 Vmath::Zero(pNumDir, Glo, 1);
182 locToGloMap->GlobalToLocal(Glo, pOutput);
183 VecRestoreArray(m_locVec, &tmp);
184}
185
186/**
187 * @brief Set up PETSc local (equivalent to Nektar++ global) and global
188 * (equivalent to universal) scatter maps.
189 *
190 * These maps are used in GlobalLinSysPETSc::v_SolveLinearSystem to
191 * scatter the solution vector back to each process.
192 */
194{
195 const int nHomDofs = m_reorderedMap.size();
196
197 // Create local and global numbering systems for vector
198 IS isGlobal, isLocal;
199 ISCreateGeneral(PETSC_COMM_SELF, nHomDofs, &m_reorderedMap[0],
200 PETSC_COPY_VALUES, &isGlobal);
201 ISCreateStride(PETSC_COMM_SELF, nHomDofs, 0, 1, &isLocal);
202
203 // Create local vector for output
204 VecCreate(PETSC_COMM_SELF, &m_locVec);
205 VecSetSizes(m_locVec, nHomDofs, PETSC_DECIDE);
206 VecSetFromOptions(m_locVec);
207
208 // Create scatter context
209 VecScatterCreate(m_x, isGlobal, m_locVec, isLocal, &m_ctx);
210
211 // Clean up
212 ISDestroy(&isGlobal);
213 ISDestroy(&isLocal);
214}
215
216/**
217 * @brief Calculate a reordering of universal IDs for PETSc.
218 *
219 * PETSc requires a unique, contiguous index of all global and universal
220 * degrees of freedom which represents its position inside the
221 * matrix. Presently Gs does not guarantee this, so this routine
222 * constructs a new universal mapping.
223 *
224 * @param glo2uniMap Global to universal map
225 * @param glo2unique Global to unique map
226 * @param pLocToGloMap Assembly map for this system
227 */
229 const Array<OneD, const int> &glo2uniMap,
230 const Array<OneD, const int> &glo2unique,
231 const AssemblyMapSharedPtr &pLocToGloMap)
232{
234 m_expList.lock()->GetSession()->GetComm();
235
236 const int nDirDofs = pLocToGloMap->GetNumGlobalDirBndCoeffs();
237 const int nHomDofs = glo2uniMap.size() - nDirDofs;
238 const int nProc = vComm->GetSize();
239 const int rank = vComm->GetRank();
240
241 int n, cnt;
242
243 // Count number of unique degrees of freedom on each process.
244 m_nLocal = Vmath::Vsum(nHomDofs, glo2unique + nDirDofs, 1);
245 m_reorderedMap.resize(nHomDofs);
246
247 // Reduce coefficient counts across all processors.
248 Array<OneD, int> localCounts(nProc, 0), localOffset(nProc, 0);
249 localCounts[rank] = nHomDofs;
250 vComm->AllReduce(localCounts, LibUtilities::ReduceSum);
251
252 for (n = 1; n < nProc; ++n)
253 {
254 localOffset[n] = localOffset[n - 1] + localCounts[n - 1];
255 }
256
257 int totHomDofs = Vmath::Vsum(nProc, localCounts, 1);
258 vector<unsigned int> allUniIds(totHomDofs, 0);
259
260 // Assemble list of universal IDs
261 for (n = 0; n < nHomDofs; ++n)
262 {
263 int gid = n + nDirDofs;
264 allUniIds[n + localOffset[rank]] = glo2uniMap[gid];
265 }
266
267 // Reduce this across processors so that each process has a list of
268 // all universal IDs.
269 vComm->AllReduce(allUniIds, LibUtilities::ReduceSum);
270 std::sort(allUniIds.begin(), allUniIds.end());
271 map<int, int> uniIdReorder;
272
273 // Renumber starting from 0.
274 for (cnt = n = 0; n < allUniIds.size(); ++n)
275 {
276 if (uniIdReorder.count(allUniIds[n]) > 0)
277 {
278 continue;
279 }
280
281 uniIdReorder[allUniIds[n]] = cnt++;
282 }
283
284 // Populate reordering map.
285 for (n = 0; n < nHomDofs; ++n)
286 {
287 int gid = n + nDirDofs;
288 int uniId = glo2uniMap[gid];
289 ASSERTL0(uniIdReorder.count(uniId) > 0, "Error in ordering");
290 m_reorderedMap[n] = uniIdReorder[uniId];
291 }
292}
293
294/**
295 * @brief Construct PETSc matrix and vector handles.
296 *
297 * @todo Preallocation should be done at this point, since presently
298 * matrix allocation takes a significant amount of time.
299 *
300 * @param nGlobal Number of global degrees of freedom in the system (on
301 * this processor)
302 * @param nDir Number of Dirichlet degrees of freedom (on this
303 * processor).
304 */
305void GlobalLinSysPETSc::SetUpMatVec(int nGlobal, int nDir)
306{
307 // CREATE VECTORS
308 VecCreate(PETSC_COMM_WORLD, &m_x);
309 VecSetSizes(m_x, m_nLocal, PETSC_DECIDE);
310 VecSetFromOptions(m_x);
311 VecDuplicate(m_x, &m_b);
312
313 // CREATE MATRICES
315 {
316 // Create ShellCtx context object which will store the matrix
317 // size and a pointer to the linear system. We do this so that
318 // we can call a member function to the matrix-vector and
319 // preconditioning multiplication in a subclass.
320 ShellCtx *ctx1 = new ShellCtx(), *ctx2 = new ShellCtx();
321 ctx1->nGlobal = ctx2->nGlobal = nGlobal;
322 ctx1->nDir = ctx2->nDir = nDir;
323 ctx1->linSys = ctx2->linSys = this;
324
325 // Set up MatShell object.
326 MatCreateShell(PETSC_COMM_WORLD, m_nLocal, m_nLocal, PETSC_DETERMINE,
327 PETSC_DETERMINE, (void *)ctx1, &m_matrix);
328 MatShellSetOperation(m_matrix, MATOP_MULT,
329 (void (*)(void))DoMatrixMultiply);
330 MatShellSetOperation(m_matrix, MATOP_DESTROY,
331 (void (*)(void))DoDestroyMatCtx);
332
333 // Create a PCShell to go alongside the MatShell.
334 PCCreate(PETSC_COMM_WORLD, &m_pc);
335#if PETSC_VERSION_GE(3, 5, 0)
336 PCSetOperators(m_pc, m_matrix, m_matrix);
337#else
338 PCSetOperators(m_pc, m_matrix, m_matrix, SAME_NONZERO_PATTERN);
339#endif
340 PCSetType(m_pc, PCSHELL);
341 PCShellSetApply(m_pc, DoPreconditioner);
342 PCShellSetDestroy(m_pc, DoDestroyPCCtx);
343 PCShellSetContext(m_pc, ctx2);
344 }
345 else
346 {
347 // Otherwise we create a PETSc matrix and use MatSetFromOptions
348 // so that we can set various options on the command line.
349 MatCreate(PETSC_COMM_WORLD, &m_matrix);
350 MatSetType(m_matrix, MATAIJ);
351 MatSetSizes(m_matrix, m_nLocal, m_nLocal, PETSC_DETERMINE,
352 PETSC_DETERMINE);
353 MatSetFromOptions(m_matrix);
354 MatSetUp(m_matrix);
355 }
356}
357
358/**
359 * @brief Perform either matrix multiplication or preconditioning using
360 * Nektar++ routines.
361 *
362 * This static function uses Nektar++ routines to calculate the
363 * matrix-vector product of @p M with @p in, storing the output in @p
364 * out.
365 *
366 * @todo There's a lot of scatters and copies that might possibly be
367 * eliminated to make this more efficient.
368 *
369 * @param in Input vector.
370 * @param out Output vector.
371 * @param ctx ShellCtx object that points to our instance of
372 * GlobalLinSysPETSc.
373 * @param precon If true, we apply a preconditioner, if false, we
374 * perform a matrix multiplication.
375 */
377 bool precon)
378{
379 const int nGlobal = ctx->nGlobal;
380 const int nDir = ctx->nDir;
381 const int nHomDofs = nGlobal - nDir;
382 GlobalLinSysPETSc *linSys = ctx->linSys;
383
384 // Scatter from PETSc ordering to our local ordering. It's actually
385 // unclear whether this step might also do some communication in
386 // parallel, which is probably not ideal.
387 VecScatterBegin(linSys->m_ctx, in, linSys->m_locVec, INSERT_VALUES,
388 SCATTER_FORWARD);
389 VecScatterEnd(linSys->m_ctx, in, linSys->m_locVec, INSERT_VALUES,
390 SCATTER_FORWARD);
391
392 // Temporary storage to pass to Nektar++
393 Array<OneD, NekDouble> tmpIn(nHomDofs), tmpOut(nHomDofs);
394
395 // Get values from input vector and copy to tmpIn.
396 PetscScalar *tmpLocIn;
397 VecGetArray(linSys->m_locVec, &tmpLocIn);
398 Vmath::Vcopy(nHomDofs, tmpLocIn, 1, &tmpIn[0], 1);
399 VecRestoreArray(linSys->m_locVec, &tmpLocIn);
400
401 // Do matrix multiply in Nektar++, store in tmpOut.
402 if (precon)
403 {
404 linSys->m_precon->DoPreconditioner(tmpIn, tmpOut);
405 }
406 else
407 {
408 linSys->v_DoMatrixMultiply(tmpIn, tmpOut);
409 }
410
411 // Scatter back to PETSc ordering and put in out.
412 VecSetValues(out, nHomDofs, &linSys->m_reorderedMap[0], &tmpOut[0],
413 INSERT_VALUES);
414 VecAssemblyBegin(out);
415 VecAssemblyEnd(out);
416}
417
418/**
419 * @brief Perform matrix multiplication using Nektar++ routines.
420 *
421 * This static function uses Nektar++ routines to calculate the
422 * matrix-vector product of @p M with @p in, storing the output in @p
423 * out.
424 *
425 * @param M Original MatShell matrix, which stores the ShellCtx
426 * object.
427 * @param in Input vector.
428 * @param out Output vector.
429 */
430PetscErrorCode GlobalLinSysPETSc::DoMatrixMultiply(Mat M, Vec in, Vec out)
431{
432 // Grab our shell context from M.
433 void *ptr;
434 MatShellGetContext(M, &ptr);
435 ShellCtx *ctx = (ShellCtx *)ptr;
436
437 DoNekppOperation(in, out, ctx, false);
438
439 // Must return 0, otherwise PETSc complains.
440 return 0;
441}
442
443/**
444 * @brief Apply preconditioning using Nektar++ routines.
445 *
446 * This static function uses Nektar++ routines to apply the
447 * preconditioner stored in GlobalLinSysPETSc::m_precon from the context
448 * of @p pc to the vector @p in, storing the output in @p out.
449 *
450 * @param pc Preconditioner object that stores the ShellCtx.
451 * @param in Input vector.
452 * @param out Output vector.
453 */
454PetscErrorCode GlobalLinSysPETSc::DoPreconditioner(PC pc, Vec in, Vec out)
455{
456 // Grab our PCShell context from pc.
457 void *ptr;
458 PCShellGetContext(pc, &ptr);
459 ShellCtx *ctx = (ShellCtx *)ptr;
460
461 DoNekppOperation(in, out, ctx, true);
462
463 // Must return 0, otherwise PETSc complains.
464 return 0;
465}
466
467/**
468 * @brief Destroy matrix shell context object.
469 *
470 * Note the matrix shell and preconditioner share a common context so
471 * this might have already been deallocated below, in which case we do
472 * nothing.
473 *
474 * @param M Matrix shell object
475 */
477{
478 void *ptr;
479 MatShellGetContext(M, &ptr);
480 ShellCtx *ctx = (ShellCtx *)ptr;
481 delete ctx;
482 return 0;
483}
484
485/**
486 * @brief Destroy preconditioner context object.
487 *
488 * Note the matrix shell and preconditioner share a common context so
489 * this might have already been deallocated above, in which case we do
490 * nothing.
491 *
492 * @param pc Preconditioner object
493 */
495{
496 void *ptr;
497 PCShellGetContext(pc, &ptr);
498 ShellCtx *ctx = (ShellCtx *)ptr;
499 delete ctx;
500 return 0;
501}
502
503/**
504 * @brief Set up KSP solver object.
505 *
506 * This is reasonably generic setup -- most solver types can be changed
507 * using the .petscrc file.
508 *
509 * @param tolerance Residual tolerance to converge to.
510 */
512{
513 KSPCreate(PETSC_COMM_WORLD, &m_ksp);
514 KSPSetTolerances(m_ksp, tolerance, PETSC_DEFAULT, PETSC_DEFAULT,
515 PETSC_DEFAULT);
516 KSPSetFromOptions(m_ksp);
517#if PETSC_VERSION_GE(3, 5, 0)
518 KSPSetOperators(m_ksp, m_matrix, m_matrix);
519#else
520 KSPSetOperators(m_ksp, m_matrix, m_matrix, SAME_NONZERO_PATTERN);
521#endif
522
524 {
525 KSPSetPC(m_ksp, m_pc);
526 }
527}
528} // namespace MultiRegions
529} // namespace Nektar
#define ASSERTL0(condition, msg)
Definition: ErrorUtil.hpp:215
static std::string RegisterEnumValue(std::string pEnum, std::string pString, int pEnumValue)
Registers an enumeration value.
static std::string RegisterDefaultSolverInfo(const std::string &pName, const std::string &pValue)
Registers the default string value of a solver info property.
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.
GlobalLinSysPETSc(const GlobalLinSysKey &pKey, const std::weak_ptr< ExpList > &pExp, const std::shared_ptr< AssemblyMap > &pLocToGloMap)
Constructor for full direct matrix solve.
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:58
std::shared_ptr< Comm > CommSharedPtr
Pointer to a Communicator object.
Definition: Comm.h:57
std::shared_ptr< AssemblyMap > AssemblyMapSharedPtr
Definition: AssemblyMap.h:52
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:890
void Zero(int n, T *x, const int incx)
Zero vector.
Definition: Vmath.cpp:487
void Vcopy(int n, const T *x, const int incx, T *y, const int incy)
Definition: Vmath.cpp:1191
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.