Files
mfem/linalg/sundials.cpp
T

2137 lines
60 KiB
C++

// Copyright (c) 2010-2021, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#include "sundials.hpp"
#ifdef MFEM_USE_SUNDIALS
#include "solvers.hpp"
#ifdef MFEM_USE_MPI
#include "hypre.hpp"
#endif
// SUNDIALS vectors
#include <nvector/nvector_serial.h>
#ifdef MFEM_USE_CUDA
#include <nvector/nvector_cuda.h>
#include <sunmemory/sunmemory_cuda.h>
#endif
#ifdef MFEM_USE_MPI
#include <nvector/nvector_mpiplusx.h>
#include <nvector/nvector_parallel.h>
#endif
// SUNDIALS linear solvers
#include <sunlinsol/sunlinsol_spgmr.h>
#include <sunlinsol/sunlinsol_spfgmr.h>
// Access SUNDIALS object's content pointer
#define GET_CONTENT(X) ( X->content )
using namespace std;
namespace mfem
{
// ---------------------------------------------------------------------------
// SUNMemory interface class (private)
// ---------------------------------------------------------------------------
#ifdef MFEM_USE_CUDA
class SundialsMemHelper
{
protected:
/// The actual SUNDIALS object
SUNMemoryHelper h;
friend class SundialsNVector;
public:
SundialsMemHelper()
{
/* Allocate helper */
h = SUNMemoryHelper_NewEmpty();
/* Set the ops */
h->ops->alloc = SundialsMemHelper_Alloc;
h->ops->dealloc = SundialsMemHelper_Dealloc;
#ifdef MFEM_USE_CUDA
h->ops->copy = SUNMemoryHelper_Copy_Cuda;
h->ops->copyasync = SUNMemoryHelper_CopyAsync_Cuda;
#endif
}
~SundialsMemHelper()
{
SUNMemoryHelper_Destroy(h);
}
/// Typecasting to SUNDIALS' SUNMemoryHelper type
operator SUNMemoryHelper() const { return h; }
static int SundialsMemHelper_Alloc(SUNMemoryHelper helper,
SUNMemory* memptr,
size_t memsize,
SUNMemoryType mem_type)
{
int length = memsize/sizeof(double);
SUNMemory sunmem = SUNMemoryNewEmpty();
sunmem->ptr = NULL;
sunmem->own = SUNTRUE;
if (mem_type == SUNMEMTYPE_HOST)
{
Memory<double> mem(length, Device::GetHostMemoryType());
mem.SetHostPtrOwner(false);
sunmem->ptr = mfem::HostReadWrite(mem, length);
sunmem->type = SUNMEMTYPE_HOST;
mem.Delete();
}
else if (mem_type == SUNMEMTYPE_DEVICE || mem_type == SUNMEMTYPE_UVM)
{
Memory<double> mem(length, Device::GetDeviceMemoryType());
mem.SetDevicePtrOwner(false);
sunmem->ptr = mfem::ReadWrite(mem, length);
sunmem->type = mem_type;
mem.Delete();
}
else
{
free(sunmem);
return -1;
}
*memptr = sunmem;
return 0;
}
static int SundialsMemHelper_Dealloc(SUNMemoryHelper helper, SUNMemory sunmem)
{
if (sunmem->ptr && sunmem->own && !mm.IsKnown(sunmem->ptr))
{
if (sunmem->type == SUNMEMTYPE_HOST)
{
Memory<double> mem(static_cast<double*>(sunmem->ptr), 1,
Device::GetHostMemoryType(), true);
mem.Delete();
}
else if (sunmem->type == SUNMEMTYPE_DEVICE || sunmem->type == SUNMEMTYPE_UVM)
{
Memory<double> mem(static_cast<double*>(sunmem->ptr), 1,
Device::GetDeviceMemoryType(), true);
mem.Delete();
}
else
{
MFEM_ABORT("Invalid SUNMEMTYPE");
return -1;
}
}
free(sunmem);
return 0;
}
};
SundialsMemHelper sunmemHelper;
#endif
// ---------------------------------------------------------------------------
// SUNDIALS N_Vector interface functions
// ---------------------------------------------------------------------------
void SundialsNVector::_SetNvecDataAndSize_(long glob_size)
{
#ifdef MFEM_USE_MPI
N_Vector local_x = MPIPlusX() ? N_VGetLocalVector_MPIPlusX(x) : x;
#else
N_Vector local_x = x;
#endif
N_Vector_ID id = N_VGetVectorID(local_x);
// Set the N_Vector data and length from the Vector data and size.
switch (id)
{
case SUNDIALS_NVEC_SERIAL:
{
MFEM_ASSERT(NV_OWN_DATA_S(local_x) == SUNFALSE, "invalid serial N_Vector");
NV_DATA_S(local_x) = HostReadWrite();
NV_LENGTH_S(local_x) = size;
break;
}
#ifdef MFEM_USE_CUDA
case SUNDIALS_NVEC_CUDA:
{
N_VSetHostArrayPointer_Cuda(HostReadWrite(), local_x);
N_VSetDeviceArrayPointer_Cuda(ReadWrite(), local_x);
static_cast<N_VectorContent_Cuda>(GET_CONTENT(local_x))->length = size;
break;
}
#endif
#ifdef MFEM_USE_MPI
case SUNDIALS_NVEC_PARALLEL:
{
MFEM_ASSERT(NV_OWN_DATA_P(x) == SUNFALSE, "invalid parallel N_Vector");
NV_DATA_P(x) = HostReadWrite();
NV_LOCLENGTH_P(x) = size;
if (glob_size == 0)
{
glob_size = GlobalSize();
if (glob_size == 0 && glob_size != size)
{
long local_size = size;
MPI_Allreduce(&local_size, &glob_size, 1, MPI_LONG,
MPI_SUM, GetComm());
}
}
NV_GLOBLENGTH_P(x) = glob_size;
break;
}
#endif
default:
MFEM_ABORT("N_Vector type " << id << " is not supported");
}
#ifdef MFEM_USE_MPI
if (MPIPlusX())
{
if (glob_size == 0)
{
glob_size = GlobalSize();
if (glob_size == 0 && glob_size != size)
{
long local_size = size;
MPI_Allreduce(&local_size, &glob_size, 1, MPI_LONG,
MPI_SUM, GetComm());
}
}
static_cast<N_VectorContent_MPIManyVector>(GET_CONTENT(x))->global_length =
glob_size;
}
#endif
}
void SundialsNVector::_SetDataAndSize_()
{
#ifdef MFEM_USE_MPI
N_Vector local_x = MPIPlusX() ? N_VGetLocalVector_MPIPlusX(x) : x;
#else
N_Vector local_x = x;
#endif
N_Vector_ID id = N_VGetVectorID(local_x);
// The SUNDIALS NVector owns the data if it created it.
switch (id)
{
case SUNDIALS_NVEC_SERIAL:
{
const bool known = mm.IsKnown(NV_DATA_S(local_x));
size = NV_LENGTH_S(local_x);
data.Wrap(NV_DATA_S(local_x), size, false);
if (known) { data.ClearOwnerFlags(); }
break;
}
#ifdef MFEM_USE_CUDA
case SUNDIALS_NVEC_CUDA:
{
double *h_ptr = N_VGetHostArrayPointer_Cuda(local_x);
double *d_ptr = N_VGetDeviceArrayPointer_Cuda(local_x);
const bool known = mm.IsKnown(h_ptr);
size = N_VGetLength_Cuda(local_x);
data.Wrap(h_ptr, d_ptr, size, Device::GetHostMemoryType(), false);
if (known) { data.ClearOwnerFlags(); }
UseDevice(true);
break;
}
#endif
#ifdef MFEM_USE_MPI
case SUNDIALS_NVEC_PARALLEL:
{
const bool known = mm.IsKnown(NV_DATA_P(x));
size = NV_LENGTH_S(x);
data.Wrap(NV_DATA_P(x), NV_LOCLENGTH_P(x), false);
if (known) { data.ClearOwnerFlags(); }
break;
}
#endif
default:
MFEM_ABORT("N_Vector type " << id << " is not supported");
}
}
SundialsNVector::SundialsNVector()
: Vector()
{
// MFEM creates and owns the data,
// and provides it to the SUNDIALS NVector.
UseDevice(Device::IsAvailable());
x = MakeNVector(UseDevice());
own_NVector = 1;
}
SundialsNVector::SundialsNVector(double *data_, int size_)
: Vector(data_, size_)
{
UseDevice(Device::IsAvailable());
x = MakeNVector(UseDevice());
own_NVector = 1;
_SetNvecDataAndSize_();
}
SundialsNVector::SundialsNVector(N_Vector nv)
: x(nv)
{
_SetDataAndSize_();
own_NVector = 0;
}
#ifdef MFEM_USE_MPI
SundialsNVector::SundialsNVector(MPI_Comm comm)
: Vector()
{
UseDevice(Device::IsAvailable());
x = MakeNVector(comm, UseDevice());
own_NVector = 1;
}
SundialsNVector::SundialsNVector(MPI_Comm comm, int loc_size, long glob_size)
: Vector(loc_size)
{
UseDevice(Device::IsAvailable());
x = MakeNVector(comm, UseDevice());
own_NVector = 1;
_SetNvecDataAndSize_(glob_size);
}
SundialsNVector::SundialsNVector(MPI_Comm comm, double *data_, int loc_size,
long glob_size)
: Vector(data_, loc_size)
{
UseDevice(Device::IsAvailable());
x = MakeNVector(comm, UseDevice());
own_NVector = 1;
_SetNvecDataAndSize_(glob_size);
}
SundialsNVector::SundialsNVector(HypreParVector& vec)
: SundialsNVector(vec.GetComm(), vec.GetData(), vec.Size(), vec.GlobalSize())
{}
#endif
SundialsNVector::~SundialsNVector()
{
if (own_NVector)
{
#ifdef MFEM_USE_MPI
if (MPIPlusX())
{
N_VDestroy(N_VGetLocalVector_MPIPlusX(x));
}
#endif
N_VDestroy(x);
}
}
void SundialsNVector::SetSize(int s, long glob_size)
{
Vector::SetSize(s);
_SetNvecDataAndSize_(glob_size);
}
void SundialsNVector::SetData(double *d)
{
Vector::SetData(d);
_SetNvecDataAndSize_();
}
void SundialsNVector::SetDataAndSize(double *d, int s, long glob_size)
{
Vector::SetDataAndSize(d, s);
_SetNvecDataAndSize_(glob_size);
}
N_Vector SundialsNVector::MakeNVector(bool use_device)
{
N_Vector x;
#ifdef MFEM_USE_CUDA
if (use_device)
{
x = N_VNewWithMemHelp_Cuda(0, UseManagedMemory(), sunmemHelper);
}
else
{
x = N_VNewEmpty_Serial(0);
}
#else
x = N_VNewEmpty_Serial(0);
#endif
MFEM_VERIFY(x, "Error in SundialsNVector::MakeNVector.");
return x;
}
#ifdef MFEM_USE_MPI
N_Vector SundialsNVector::MakeNVector(MPI_Comm comm, bool use_device)
{
N_Vector x;
if (comm == MPI_COMM_NULL)
{
x = MakeNVector(use_device);
}
else
{
#ifdef MFEM_USE_CUDA
if (use_device)
{
x = N_VMake_MPIPlusX(comm, N_VNewWithMemHelp_Cuda(0, UseManagedMemory(),
sunmemHelper));
}
else
{
x = N_VNewEmpty_Parallel(comm, 0, 0);
}
#else
x = N_VNewEmpty_Parallel(comm, 0, 0);
#endif // MFEM_USE_CUDA
}
MFEM_VERIFY(x, "Error in SundialsNVector::MakeNVector.");
return x;
}
#endif // MFEM_USE_MPI
// ---------------------------------------------------------------------------
// SUNMatrix interface functions
// ---------------------------------------------------------------------------
// Return the matrix ID
static SUNMatrix_ID MatGetID(SUNMatrix)
{
return (SUNMATRIX_CUSTOM);
}
//BEGIN WORKAROUND CODE
static SUNMatrix MatClone(SUNMatrix A)
{
SUNMatrix B = SUNMatNewEmpty();
MFEM_VERIFY(B, "error in MatClone()");
B->content = A->content;
int flag = SUNMatCopyOps(A, B);
MFEM_VERIFY(flag == SUNMAT_SUCCESS, "error in MatClone()");
return B;
}
static int MatCopy(SUNMatrix, SUNMatrix)
{
return 0;
}
// END WORKAROUN CODE
static void MatDestroy(SUNMatrix A)
{
if (A->content) { A->content = NULL; }
if (A->ops) { free(A->ops); A->ops = NULL; }
free(A); A = NULL;
return;
}
// ---------------------------------------------------------------------------
// SUNLinearSolver interface functions
// ---------------------------------------------------------------------------
// Return the linear solver type
static SUNLinearSolver_Type LSGetType(SUNLinearSolver)
{
return (SUNLINEARSOLVER_MATRIX_ITERATIVE);
}
static int LSFree(SUNLinearSolver LS)
{
if (LS->content) { LS->content = NULL; }
if (LS->ops) { free(LS->ops); LS->ops = NULL; }
free(LS); LS = NULL;
return (0);
}
// ---------------------------------------------------------------------------
// CVODE interface
// ---------------------------------------------------------------------------
int CVODESolver::RHS(realtype t, const N_Vector y, N_Vector ydot,
void *user_data)
{
// At this point the up-to-date data for N_Vector y and ydot is on the device.
const SundialsNVector mfem_y(y);
SundialsNVector mfem_ydot(ydot);
CVODESolver *self = static_cast<CVODESolver*>(user_data);
// Compute y' = f(t, y)
self->f->SetTime(t);
self->f->Mult(mfem_y, mfem_ydot);
// Return success
return (0);
}
int CVODESolver::root(realtype t, N_Vector y, realtype *gout, void *user_data)
{
CVODESolver *self = static_cast<CVODESolver*>(user_data);
if (!self->root_func) { return CV_RTFUNC_FAIL; }
SundialsNVector mfem_y(y);
SundialsNVector mfem_gout(gout, self->root_components);
return self->root_func(t, mfem_y, mfem_gout, self);
}
void CVODESolver::SetRootFinder(int components, RootFunction func)
{
root_func = func;
flag = CVodeRootInit(sundials_mem, components, root);
MFEM_VERIFY(flag == CV_SUCCESS, "error in SetRootFinder()");
}
int CVODESolver::LinSysSetup(realtype t, N_Vector y, N_Vector fy, SUNMatrix A,
booleantype jok, booleantype *jcur, realtype gamma,
void*, N_Vector, N_Vector, N_Vector)
{
// Get data from N_Vectors
const SundialsNVector mfem_y(y);
const SundialsNVector mfem_fy(fy);
CVODESolver *self = static_cast<CVODESolver*>(GET_CONTENT(A));
// Compute the linear system
self->f->SetTime(t);
return (self->f->SUNImplicitSetup(mfem_y, mfem_fy, jok, jcur, gamma));
}
int CVODESolver::LinSysSolve(SUNLinearSolver LS, SUNMatrix, N_Vector x,
N_Vector b, realtype tol)
{
SundialsNVector mfem_x(x);
const SundialsNVector mfem_b(b);
CVODESolver *self = static_cast<CVODESolver*>(GET_CONTENT(LS));
// Solve the linear system
return (self->f->SUNImplicitSolve(mfem_b, mfem_x, tol));
}
CVODESolver::CVODESolver(int lmm)
: lmm_type(lmm), step_mode(CV_NORMAL)
{
Y = new SundialsNVector();
}
#ifdef MFEM_USE_MPI
CVODESolver::CVODESolver(MPI_Comm comm, int lmm)
: lmm_type(lmm), step_mode(CV_NORMAL)
{
Y = new SundialsNVector(comm);
}
#endif
void CVODESolver::Init(TimeDependentOperator &f_)
{
// Initialize the base class
ODESolver::Init(f_);
// Get the vector length
long local_size = f_.Height();
#ifdef MFEM_USE_MPI
long global_size = 0;
if (Parallel())
{
MPI_Allreduce(&local_size, &global_size, 1, MPI_LONG, MPI_SUM,
Y->GetComm());
}
#endif
// Get current time
double t = f_.GetTime();
if (sundials_mem)
{
// Check if the problem size has changed since the last Init() call
int resize = 0;
if (!Parallel())
{
resize = (Y->Size() != local_size);
}
else
{
#ifdef MFEM_USE_MPI
int l_resize = (Y->Size() != local_size) ||
(saved_global_size != global_size);
MPI_Allreduce(&l_resize, &resize, 1, MPI_INT, MPI_LOR,
Y->GetComm());
#endif
}
// Free existing solver memory and re-create with new vector size
if (resize)
{
CVodeFree(&sundials_mem);
sundials_mem = NULL;
}
}
if (!sundials_mem)
{
// Temporarily set N_Vector wrapper data to create CVODE. The correct
// initial condition will be set using CVodeReInit() when Step() is
// called.
if (!Parallel())
{
Y->SetSize(local_size);
}
#ifdef MFEM_USE_MPI
else
{
Y->SetSize(local_size, global_size);
saved_global_size = global_size;
}
#endif
// Create CVODE
sundials_mem = CVodeCreate(lmm_type);
MFEM_VERIFY(sundials_mem, "error in CVodeCreate()");
// Initialize CVODE
flag = CVodeInit(sundials_mem, CVODESolver::RHS, t, *Y);
MFEM_VERIFY(flag == CV_SUCCESS, "error in CVodeInit()");
// Attach the CVODESolver as user-defined data
flag = CVodeSetUserData(sundials_mem, this);
MFEM_VERIFY(flag == CV_SUCCESS, "error in CVodeSetUserData()");
// Set default tolerances
flag = CVodeSStolerances(sundials_mem, default_rel_tol, default_abs_tol);
MFEM_VERIFY(flag == CV_SUCCESS, "error in CVodeSetSStolerances()");
// Attach MFEM linear solver by default
UseMFEMLinearSolver();
}
// Set the reinit flag to call CVodeReInit() in the next Step() call.
reinit = true;
}
void CVODESolver::Step(Vector &x, double &t, double &dt)
{
Y->MakeRef(x, 0, x.Size());
MFEM_VERIFY(Y->Size() == x.Size(), "size mismatch");
// Reinitialize CVODE memory if needed
if (reinit)
{
flag = CVodeReInit(sundials_mem, t, *Y);
MFEM_VERIFY(flag == CV_SUCCESS, "error in CVodeReInit()");
// reset flag
reinit = false;
}
// Integrate the system
double tout = t + dt;
flag = CVode(sundials_mem, tout, *Y, &t, step_mode);
MFEM_VERIFY(flag >= 0, "error in CVode()");
// Make sure host is up to date
Y->HostRead();
// Return the last incremental step size
flag = CVodeGetLastStep(sundials_mem, &dt);
MFEM_VERIFY(flag == CV_SUCCESS, "error in CVodeGetLastStep()");
}
void CVODESolver::UseMFEMLinearSolver()
{
// Free any existing matrix and linear solver
if (A != NULL) { SUNMatDestroy(A); A = NULL; }
if (LSA != NULL) { SUNLinSolFree(LSA); LSA = NULL; }
// Wrap linear solver as SUNLinearSolver and SUNMatrix
LSA = SUNLinSolNewEmpty();
MFEM_VERIFY(LSA, "error in SUNLinSolNewEmpty()");
LSA->content = this;
LSA->ops->gettype = LSGetType;
LSA->ops->solve = CVODESolver::LinSysSolve;
LSA->ops->free = LSFree;
A = SUNMatNewEmpty();
MFEM_VERIFY(A, "error in SUNMatNewEmpty()");
A->content = this;
A->ops->getid = MatGetID;
A->ops->destroy = MatDestroy;
// Attach the linear solver and matrix
flag = CVodeSetLinearSolver(sundials_mem, LSA, A);
MFEM_VERIFY(flag == CV_SUCCESS, "error in CVodeSetLinearSolver()");
// Set the linear system evaluation function
flag = CVodeSetLinSysFn(sundials_mem, CVODESolver::LinSysSetup);
MFEM_VERIFY(flag == CV_SUCCESS, "error in CVodeSetLinSysFn()");
}
void CVODESolver::UseSundialsLinearSolver()
{
// Free any existing matrix and linear solver
if (A != NULL) { SUNMatDestroy(A); A = NULL; }
if (LSA != NULL) { SUNLinSolFree(LSA); LSA = NULL; }
// Create linear solver
LSA = SUNLinSol_SPGMR(*Y, PREC_NONE, 0);
MFEM_VERIFY(LSA, "error in SUNLinSol_SPGMR()");
// Attach linear solver
flag = CVodeSetLinearSolver(sundials_mem, LSA, NULL);
MFEM_VERIFY(flag == CV_SUCCESS, "error in CVodeSetLinearSolver()");
}
void CVODESolver::SetStepMode(int itask)
{
step_mode = itask;
}
void CVODESolver::SetSStolerances(double reltol, double abstol)
{
flag = CVodeSStolerances(sundials_mem, reltol, abstol);
MFEM_VERIFY(flag == CV_SUCCESS, "error in CVodeSStolerances()");
}
void CVODESolver::SetSVtolerances(double reltol, Vector abstol)
{
MFEM_VERIFY(abstol.Size() == f->Height(),
"abs tolerance is not the same size.");
SundialsNVector mfem_abstol;
mfem_abstol.MakeRef(abstol, 0, abstol.Size());
flag = CVodeSVtolerances(sundials_mem, reltol, mfem_abstol);
MFEM_VERIFY(flag == CV_SUCCESS, "error in CVodeSVtolerances()");
}
void CVODESolver::SetMaxStep(double dt_max)
{
flag = CVodeSetMaxStep(sundials_mem, dt_max);
MFEM_VERIFY(flag == CV_SUCCESS, "error in CVodeSetMaxStep()");
}
void CVODESolver::SetMaxNSteps(int mxsteps)
{
flag = CVodeSetMaxNumSteps(sundials_mem, mxsteps);
MFEM_VERIFY(flag == CV_SUCCESS, "error in CVodeSetMaxNumSteps()");
}
long CVODESolver::GetNumSteps()
{
long nsteps;
flag = CVodeGetNumSteps(sundials_mem, &nsteps);
MFEM_VERIFY(flag == CV_SUCCESS, "error in CVodeGetNumSteps()");
return nsteps;
}
void CVODESolver::SetMaxOrder(int max_order)
{
flag = CVodeSetMaxOrd(sundials_mem, max_order);
MFEM_VERIFY(flag == CV_SUCCESS, "error in CVodeSetMaxOrd()");
}
void CVODESolver::PrintInfo() const
{
long int nsteps, nfevals, nlinsetups, netfails;
int qlast, qcur;
double hinused, hlast, hcur, tcur;
long int nniters, nncfails;
// Get integrator stats
flag = CVodeGetIntegratorStats(sundials_mem,
&nsteps,
&nfevals,
&nlinsetups,
&netfails,
&qlast,
&qcur,
&hinused,
&hlast,
&hcur,
&tcur);
MFEM_VERIFY(flag == CV_SUCCESS, "error in CVodeGetIntegratorStats()");
// Get nonlinear solver stats
flag = CVodeGetNonlinSolvStats(sundials_mem,
&nniters,
&nncfails);
MFEM_VERIFY(flag == CV_SUCCESS, "error in CVodeGetNonlinSolvStats()");
mfem::out <<
"CVODE:\n"
"num steps: " << nsteps << "\n"
"num rhs evals: " << nfevals << "\n"
"num lin setups: " << nlinsetups << "\n"
"num nonlin sol iters: " << nniters << "\n"
"num nonlin conv fail: " << nncfails << "\n"
"num error test fails: " << netfails << "\n"
"last order: " << qlast << "\n"
"current order: " << qcur << "\n"
"initial dt: " << hinused << "\n"
"last dt: " << hlast << "\n"
"current dt: " << hcur << "\n"
"current t: " << tcur << "\n" << endl;
return;
}
CVODESolver::~CVODESolver()
{
delete Y;
SUNMatDestroy(A);
SUNLinSolFree(LSA);
SUNNonlinSolFree(NLS);
CVodeFree(&sundials_mem);
}
// ---------------------------------------------------------------------------
// CVODESSolver interface
// ---------------------------------------------------------------------------
CVODESSolver::CVODESSolver(int lmm) :
CVODESolver(lmm),
ncheck(0),
indexB(0),
AB(nullptr),
LSB(nullptr)
{
q = new SundialsNVector();
qB = new SundialsNVector();
yB = new SundialsNVector();
yy = new SundialsNVector();
}
#ifdef MFEM_USE_MPI
CVODESSolver::CVODESSolver(MPI_Comm comm, int lmm) :
CVODESolver(comm, lmm),
ncheck(0),
indexB(0),
AB(nullptr),
LSB(nullptr)
{
q = new SundialsNVector(comm);
qB = new SundialsNVector(comm);
yB = new SundialsNVector(comm);
yy = new SundialsNVector(comm);
}
#endif
void CVODESSolver::EvalQuadIntegration(double t, Vector &Q)
{
MFEM_VERIFY(t <= f->GetTime(), "t > current forward solver time");
flag = CVodeGetQuad(sundials_mem, &t, *q);
MFEM_VERIFY(flag == CV_SUCCESS, "error in CVodeGetQuad()");
Q.Set(1., *q);
}
void CVODESSolver::EvalQuadIntegrationB(double t, Vector &dG_dp)
{
MFEM_VERIFY(t <= f->GetTime(), "t > current forward solver time");
flag = CVodeGetQuadB(sundials_mem, indexB, &t, *qB);
MFEM_VERIFY(flag == CV_SUCCESS, "error in CVodeGetQuadB()");
dG_dp.Set(-1., *qB);
}
void CVODESSolver::GetForwardSolution(double tB, mfem::Vector &yyy)
{
yy->MakeRef(yyy, 0, yyy.Size());
flag = CVodeGetAdjY(sundials_mem, tB, *yy);
MFEM_VERIFY(flag >= 0, "error in CVodeGetAdjY()");
}
// Implemented to enforce type checking for TimeDependentAdjointOperator
void CVODESSolver::Init(TimeDependentAdjointOperator &f_)
{
CVODESolver::Init(f_);
}
void CVODESSolver::InitB(TimeDependentAdjointOperator &f_)
{
long local_size = f_.GetAdjointHeight();
// Get current time
double tB = f_.GetTime();
yB->SetSize(local_size);
// Create the solver memory
flag = CVodeCreateB(sundials_mem, CV_BDF, &indexB);
MFEM_VERIFY(flag == CV_SUCCESS, "error in CVodeCreateB()");
// Initialize
flag = CVodeInitB(sundials_mem, indexB, RHSB, tB, *yB);
MFEM_VERIFY(flag == CV_SUCCESS, "error in CVodeInit()");
// Attach the CVODESSolver as user-defined data
flag = CVodeSetUserDataB(sundials_mem, indexB, this);
MFEM_VERIFY(flag == CV_SUCCESS, "error in CVodeSetUserDataB()");
// Set default tolerances
flag = CVodeSStolerancesB(sundials_mem, indexB, default_rel_tolB,
default_abs_tolB);
MFEM_VERIFY(flag == CV_SUCCESS, "error in CVodeSetSStolerancesB()");
// Attach MFEM linear solver by default
UseMFEMLinearSolverB();
// Set the reinit flag to call CVodeReInit() in the next Step() call.
reinit = true;
}
void CVODESSolver::InitAdjointSolve(int steps, int interpolation)
{
flag = CVodeAdjInit(sundials_mem, steps, interpolation);
MFEM_VERIFY(flag == CV_SUCCESS, "Error in CVodeAdjInit");
}
void CVODESSolver::SetMaxNStepsB(int mxstepsB)
{
flag = CVodeSetMaxNumStepsB(sundials_mem, indexB, mxstepsB);
MFEM_VERIFY(flag == CV_SUCCESS, "Error in CVodeSetMaxNumStepsB()");
}
void CVODESSolver::InitQuadIntegration(mfem::Vector &q0, double reltolQ,
double abstolQ)
{
q->MakeRef(q0, 0, q0.Size());
flag = CVodeQuadInit(sundials_mem, RHSQ, *q);
MFEM_VERIFY(flag == CV_SUCCESS, "Error in CVodeQuadInit()");
flag = CVodeSetQuadErrCon(sundials_mem, SUNTRUE);
MFEM_VERIFY(flag == CV_SUCCESS, "Error in CVodeSetQuadErrCon");
flag = CVodeQuadSStolerances(sundials_mem, reltolQ, abstolQ);
MFEM_VERIFY(flag == CV_SUCCESS, "Error in CVodeQuadSStolerances");
}
void CVODESSolver::InitQuadIntegrationB(mfem::Vector &qB0, double reltolQB,
double abstolQB)
{
qB->MakeRef(qB0, 0, qB0.Size());
flag = CVodeQuadInitB(sundials_mem, indexB, RHSQB, *qB);
MFEM_VERIFY(flag == CV_SUCCESS, "Error in CVodeQuadInitB()");
flag = CVodeSetQuadErrConB(sundials_mem, indexB, SUNTRUE);
MFEM_VERIFY(flag == CV_SUCCESS, "Error in CVodeSetQuadErrConB");
flag = CVodeQuadSStolerancesB(sundials_mem, indexB, reltolQB, abstolQB);
MFEM_VERIFY(flag == CV_SUCCESS, "Error in CVodeQuadSStolerancesB");
}
void CVODESSolver::UseMFEMLinearSolverB()
{
// Free any existing linear solver
if (AB != NULL) { SUNMatDestroy(AB); AB = NULL; }
if (LSB != NULL) { SUNLinSolFree(LSB); LSB = NULL; }
// Wrap linear solver as SUNLinearSolver and SUNMatrix
LSB = SUNLinSolNewEmpty();
MFEM_VERIFY(LSB, "error in SUNLinSolNewEmpty()");
LSB->content = this;
LSB->ops->gettype = LSGetType;
LSB->ops->solve = CVODESSolver::LinSysSolveB; // JW change
LSB->ops->free = LSFree;
AB = SUNMatNewEmpty();
MFEM_VERIFY(AB, "error in SUNMatNewEmpty()");
AB->content = this;
AB->ops->getid = MatGetID;
AB->ops->destroy = MatDestroy;
// Attach the linear solver and matrix
flag = CVodeSetLinearSolverB(sundials_mem, indexB, LSB, AB);
MFEM_VERIFY(flag == CV_SUCCESS, "error in CVodeSetLinearSolverB()");
// Set the linear system evaluation function
flag = CVodeSetLinSysFnB(sundials_mem, indexB,
CVODESSolver::LinSysSetupB); // JW change
MFEM_VERIFY(flag == CV_SUCCESS, "error in CVodeSetLinSysFn()");
}
void CVODESSolver::UseSundialsLinearSolverB()
{
// Free any existing matrix and linear solver
if (AB != NULL) { SUNMatDestroy(AB); AB = NULL; }
if (LSB != NULL) { SUNLinSolFree(LSB); LSB = NULL; }
// Set default linear solver (Newton is the default Nonlinear Solver)
LSB = SUNLinSol_SPGMR(*yB, PREC_NONE, 0);
MFEM_VERIFY(LSB, "error in SUNLinSol_SPGMR()");
/* Attach the matrix and linear solver */
flag = CVodeSetLinearSolverB(sundials_mem, indexB, LSB, NULL);
MFEM_VERIFY(flag == CV_SUCCESS, "error in CVodeSetLinearSolverB()");
}
int CVODESSolver::LinSysSetupB(realtype t, N_Vector y, N_Vector yB,
N_Vector fyB, SUNMatrix AB,
booleantype jokB, booleantype *jcurB,
realtype gammaB, void *user_data, N_Vector tmp1,
N_Vector tmp2, N_Vector tmp3)
{
// Get data from N_Vectors
const SundialsNVector mfem_y(y);
const SundialsNVector mfem_yB(yB);
SundialsNVector mfem_fyB(fyB);
CVODESSolver *self = static_cast<CVODESSolver*>(GET_CONTENT(AB));
TimeDependentAdjointOperator * f = static_cast<TimeDependentAdjointOperator *>
(self->f);
f->SetTime(t);
// Compute the linear system
return (f->SUNImplicitSetupB(t, mfem_y, mfem_yB, mfem_fyB, jokB, jcurB,
gammaB));
}
int CVODESSolver::LinSysSolveB(SUNLinearSolver LS, SUNMatrix AB, N_Vector yB,
N_Vector Rb, realtype tol)
{
SundialsNVector mfem_yB(yB);
const SundialsNVector mfem_Rb(Rb);
CVODESSolver *self = static_cast<CVODESSolver*>(GET_CONTENT(LS));
TimeDependentAdjointOperator * f = static_cast<TimeDependentAdjointOperator *>
(self->f);
// Solve the linear system
int ret = f->SUNImplicitSolveB(mfem_yB, mfem_Rb, tol);
return (ret);
}
void CVODESSolver::SetSStolerancesB(double reltol, double abstol)
{
flag = CVodeSStolerancesB(sundials_mem, indexB, reltol, abstol);
MFEM_VERIFY(flag == CV_SUCCESS, "error in CVodeSStolerancesB()");
}
void CVODESSolver::SetSVtolerancesB(double reltol, Vector abstol)
{
MFEM_VERIFY(abstol.Size() == f->Height(),
"abs tolerance is not the same size.");
SundialsNVector mfem_abstol;
mfem_abstol.MakeRef(abstol, 0, abstol.Size());
flag = CVodeSVtolerancesB(sundials_mem, indexB, reltol, mfem_abstol);
MFEM_VERIFY(flag == CV_SUCCESS, "error in CVodeSVtolerancesB()");
}
void CVODESSolver::SetWFTolerances(EWTFunction func)
{
ewt_func = func;
CVodeWFtolerances(sundials_mem, ewt);
}
// CVODESSolver static functions
int CVODESSolver::RHSQ(realtype t, const N_Vector y, N_Vector qdot,
void *user_data)
{
CVODESSolver *self = static_cast<CVODESSolver*>(user_data);
const SundialsNVector mfem_y(y);
SundialsNVector mfem_qdot(qdot);
TimeDependentAdjointOperator * f = static_cast<TimeDependentAdjointOperator *>
(self->f);
f->SetTime(t);
f->QuadratureIntegration(mfem_y, mfem_qdot);
return 0;
}
int CVODESSolver::RHSQB(realtype t, N_Vector y, N_Vector yB, N_Vector qBdot,
void *user_dataB)
{
CVODESSolver *self = static_cast<CVODESSolver*>(user_dataB);
SundialsNVector mfem_y(y);
SundialsNVector mfem_yB(yB);
SundialsNVector mfem_qBdot(qBdot);
TimeDependentAdjointOperator * f = static_cast<TimeDependentAdjointOperator *>
(self->f);
f->SetTime(t);
f->QuadratureSensitivityMult(mfem_y, mfem_yB, mfem_qBdot);
return 0;
}
int CVODESSolver::RHSB(realtype t, N_Vector y, N_Vector yB, N_Vector yBdot,
void *user_dataB)
{
CVODESSolver *self = static_cast<CVODESSolver*>(user_dataB);
SundialsNVector mfem_y(y);
SundialsNVector mfem_yB(yB);
SundialsNVector mfem_yBdot(yBdot);
mfem_yBdot = 0.;
TimeDependentAdjointOperator * f = static_cast<TimeDependentAdjointOperator *>
(self->f);
f->SetTime(t);
f->AdjointRateMult(mfem_y, mfem_yB, mfem_yBdot);
return 0;
}
int CVODESSolver::ewt(N_Vector y, N_Vector w, void *user_data)
{
CVODESSolver *self = static_cast<CVODESSolver*>(user_data);
SundialsNVector mfem_y(y);
SundialsNVector mfem_w(w);
return self->ewt_func(mfem_y, mfem_w, self);
}
// Pretty much a copy of CVODESolver::Step except we use CVodeF instead of CVode
void CVODESSolver::Step(Vector &x, double &t, double &dt)
{
Y->MakeRef(x, 0, x.Size());
MFEM_VERIFY(Y->Size() == x.Size(), "size mismatch");
// Reinitialize CVODE memory if needed, initializes the N_Vector y with x
if (reinit)
{
flag = CVodeReInit(sundials_mem, t, *Y);
MFEM_VERIFY(flag == CV_SUCCESS, "error in CVodeReInit()");
// reset flag
reinit = false;
}
// Integrate the system
double tout = t + dt;
flag = CVodeF(sundials_mem, tout, *Y, &t, step_mode, &ncheck);
MFEM_VERIFY(flag >= 0, "error in CVodeF()");
// Make sure host is up to date
Y->HostRead();
// Return the last incremental step size
flag = CVodeGetLastStep(sundials_mem, &dt);
MFEM_VERIFY(flag == CV_SUCCESS, "error in CVodeGetLastStep()");
}
void CVODESSolver::StepB(Vector &xB, double &tB, double &dtB)
{
yB->MakeRef(xB, 0, xB.Size());
MFEM_VERIFY(yB->Size() == xB.Size(), "");
// Reinitialize CVODE memory if needed
if (reinit)
{
flag = CVodeReInitB(sundials_mem, indexB, tB, *yB);
MFEM_VERIFY(flag == CV_SUCCESS, "error in CVodeReInit()");
// reset flag
reinit = false;
}
// Integrate the system
double tout = tB - dtB;
flag = CVodeB(sundials_mem, tout, step_mode);
MFEM_VERIFY(flag >= 0, "error in CVodeB()");
// Call CVodeGetB to get yB of the backward ODE problem.
flag = CVodeGetB(sundials_mem, indexB, &tB, *yB);
MFEM_VERIFY(flag >= 0, "error in CVodeGetB()");
// Make sure host is up to date
yB->HostRead();
}
CVODESSolver::~CVODESSolver()
{
delete yB;
delete yy;
delete qB;
delete q;
SUNMatDestroy(AB);
SUNLinSolFree(LSB);
}
// ---------------------------------------------------------------------------
// ARKStep interface
// ---------------------------------------------------------------------------
int ARKStepSolver::RHS1(realtype t, const N_Vector y, N_Vector ydot,
void *user_data)
{
// Get data from N_Vectors
const SundialsNVector mfem_y(y);
SundialsNVector mfem_ydot(ydot);
ARKStepSolver *self = static_cast<ARKStepSolver*>(user_data);
// Compute f(t, y) in y' = f(t, y) or fe(t, y) in y' = fe(t, y) + fi(t, y)
self->f->SetTime(t);
if (self->rk_type == IMEX)
{
self->f->SetEvalMode(TimeDependentOperator::ADDITIVE_TERM_1);
}
self->f->Mult(mfem_y, mfem_ydot);
// Return success
return (0);
}
int ARKStepSolver::RHS2(realtype t, const N_Vector y, N_Vector ydot,
void *user_data)
{
// Get data from N_Vectors
const SundialsNVector mfem_y(y);
SundialsNVector mfem_ydot(ydot);
ARKStepSolver *self = static_cast<ARKStepSolver*>(user_data);
// Compute fi(t, y) in y' = fe(t, y) + fi(t, y)
self->f->SetTime(t);
self->f->SetEvalMode(TimeDependentOperator::ADDITIVE_TERM_2);
self->f->Mult(mfem_y, mfem_ydot);
// Return success
return (0);
}
int ARKStepSolver::LinSysSetup(realtype t, N_Vector y, N_Vector fy, SUNMatrix A,
SUNMatrix, booleantype jok, booleantype *jcur,
realtype gamma,
void*, N_Vector, N_Vector, N_Vector)
{
// Get data from N_Vectors
const SundialsNVector mfem_y(y);
const SundialsNVector mfem_fy(fy);
ARKStepSolver *self = static_cast<ARKStepSolver*>(GET_CONTENT(A));
// Compute the linear system
self->f->SetTime(t);
if (self->rk_type == IMEX)
{
self->f->SetEvalMode(TimeDependentOperator::ADDITIVE_TERM_2);
}
return (self->f->SUNImplicitSetup(mfem_y, mfem_fy, jok, jcur, gamma));
}
int ARKStepSolver::LinSysSolve(SUNLinearSolver LS, SUNMatrix, N_Vector x,
N_Vector b, realtype tol)
{
SundialsNVector mfem_x(x);
const SundialsNVector mfem_b(b);
ARKStepSolver *self = static_cast<ARKStepSolver*>(GET_CONTENT(LS));
// Solve the linear system
if (self->rk_type == IMEX)
{
self->f->SetEvalMode(TimeDependentOperator::ADDITIVE_TERM_2);
}
return (self->f->SUNImplicitSolve(mfem_b, mfem_x, tol));
}
int ARKStepSolver::MassSysSetup(realtype t, SUNMatrix M,
void*, N_Vector, N_Vector, N_Vector)
{
ARKStepSolver *self = static_cast<ARKStepSolver*>(GET_CONTENT(M));
// Compute the mass matrix system
self->f->SetTime(t);
return (self->f->SUNMassSetup());
}
int ARKStepSolver::MassSysSolve(SUNLinearSolver LS, SUNMatrix, N_Vector x,
N_Vector b, realtype tol)
{
SundialsNVector mfem_x(x);
const SundialsNVector mfem_b(b);
ARKStepSolver *self = static_cast<ARKStepSolver*>(GET_CONTENT(LS));
// Solve the mass matrix system
return (self->f->SUNMassSolve(mfem_b, mfem_x, tol));
}
int ARKStepSolver::MassMult1(SUNMatrix M, N_Vector x, N_Vector v)
{
const SundialsNVector mfem_x(x);
SundialsNVector mfem_v(v);
ARKStepSolver *self = static_cast<ARKStepSolver*>(GET_CONTENT(M));
// Compute the mass matrix-vector product
return (self->f->SUNMassMult(mfem_x, mfem_v));
}
int ARKStepSolver::MassMult2(N_Vector x, N_Vector v, realtype t,
void* mtimes_data)
{
const SundialsNVector mfem_x(x);
SundialsNVector mfem_v(v);
ARKStepSolver *self = static_cast<ARKStepSolver*>(mtimes_data);
// Compute the mass matrix-vector product
self->f->SetTime(t);
return (self->f->SUNMassMult(mfem_x, mfem_v));
}
ARKStepSolver::ARKStepSolver(Type type)
: rk_type(type), step_mode(ARK_NORMAL),
use_implicit(type == IMPLICIT || type == IMEX)
{
Y = new SundialsNVector();
}
#ifdef MFEM_USE_MPI
ARKStepSolver::ARKStepSolver(MPI_Comm comm, Type type)
: rk_type(type), step_mode(ARK_NORMAL),
use_implicit(type == IMPLICIT || type == IMEX)
{
Y = new SundialsNVector(comm);
}
#endif
void ARKStepSolver::Init(TimeDependentOperator &f_)
{
// Initialize the base class
ODESolver::Init(f_);
// Get the vector length
long local_size = f_.Height();
#ifdef MFEM_USE_MPI
long global_size;
#endif
if (Parallel())
{
#ifdef MFEM_USE_MPI
MPI_Allreduce(&local_size, &global_size, 1, MPI_LONG, MPI_SUM,
Y->GetComm());
#endif
}
// Get current time
double t = f_.GetTime();
if (sundials_mem)
{
// Check if the problem size has changed since the last Init() call
int resize = 0;
if (!Parallel())
{
resize = (Y->Size() != local_size);
}
else
{
#ifdef MFEM_USE_MPI
int l_resize = (Y->Size() != local_size) ||
(saved_global_size != global_size);
MPI_Allreduce(&l_resize, &resize, 1, MPI_INT, MPI_LOR,
Y->GetComm());
#endif
}
// Free existing solver memory and re-create with new vector size
if (resize)
{
ARKStepFree(&sundials_mem);
sundials_mem = NULL;
}
}
if (!sundials_mem)
{
if (!Parallel())
{
Y->SetSize(local_size);
}
#ifdef MFEM_USE_MPI
else
{
Y->SetSize(local_size, global_size);
saved_global_size = global_size;
}
#endif
// Create ARKStep memory
if (rk_type == IMPLICIT)
{
sundials_mem = ARKStepCreate(NULL, ARKStepSolver::RHS1, t, *Y);
}
else if (rk_type == EXPLICIT)
{
sundials_mem = ARKStepCreate(ARKStepSolver::RHS1, NULL, t, *Y);
}
else
{
sundials_mem = ARKStepCreate(ARKStepSolver::RHS1, ARKStepSolver::RHS2,
t, *Y);
}
MFEM_VERIFY(sundials_mem, "error in ARKStepCreate()");
// Attach the ARKStepSolver as user-defined data
flag = ARKStepSetUserData(sundials_mem, this);
MFEM_VERIFY(flag == ARK_SUCCESS, "error in ARKStepSetUserData()");
// Set default tolerances
flag = ARKStepSStolerances(sundials_mem, default_rel_tol, default_abs_tol);
MFEM_VERIFY(flag == ARK_SUCCESS, "error in ARKStepSetSStolerances()");
// If implicit, attach MFEM linear solver by default
if (use_implicit) { UseMFEMLinearSolver(); }
}
// Set the reinit flag to call ARKStepReInit() in the next Step() call.
reinit = true;
}
void ARKStepSolver::Step(Vector &x, double &t, double &dt)
{
Y->MakeRef(x, 0, x.Size());
MFEM_VERIFY(Y->Size() == x.Size(), "size mismatch");
// Reinitialize ARKStep memory if needed
if (reinit)
{
if (rk_type == IMPLICIT)
{
flag = ARKStepReInit(sundials_mem, NULL, ARKStepSolver::RHS1, t, *Y);
}
else if (rk_type == EXPLICIT)
{
flag = ARKStepReInit(sundials_mem, ARKStepSolver::RHS1, NULL, t, *Y);
}
else
{
flag = ARKStepReInit(sundials_mem,
ARKStepSolver::RHS1, ARKStepSolver::RHS2, t, *Y);
}
MFEM_VERIFY(flag == ARK_SUCCESS, "error in ARKStepReInit()");
// reset flag
reinit = false;
}
// Integrate the system
double tout = t + dt;
flag = ARKStepEvolve(sundials_mem, tout, *Y, &t, step_mode);
MFEM_VERIFY(flag >= 0, "error in ARKStepEvolve()");
// Make sure host is up to date
Y->HostRead();
// Return the last incremental step size
flag = ARKStepGetLastStep(sundials_mem, &dt);
MFEM_VERIFY(flag == ARK_SUCCESS, "error in ARKStepGetLastStep()");
}
void ARKStepSolver::UseMFEMLinearSolver()
{
// Free any existing matrix and linear solver
if (A != NULL) { SUNMatDestroy(A); A = NULL; }
if (LSA != NULL) { SUNLinSolFree(LSA); LSA = NULL; }
// Wrap linear solver as SUNLinearSolver and SUNMatrix
LSA = SUNLinSolNewEmpty();
MFEM_VERIFY(LSA, "error in SUNLinSolNewEmpty()");
LSA->content = this;
LSA->ops->gettype = LSGetType;
LSA->ops->solve = ARKStepSolver::LinSysSolve;
LSA->ops->free = LSFree;
A = SUNMatNewEmpty();
MFEM_VERIFY(A, "error in SUNMatNewEmpty()");
A->content = this;
A->ops->getid = MatGetID;
A->ops->destroy = MatDestroy;
// Attach the linear solver and matrix
flag = ARKStepSetLinearSolver(sundials_mem, LSA, A);
MFEM_VERIFY(flag == ARK_SUCCESS, "error in ARKStepSetLinearSolver()");
// Set the linear system evaluation function
flag = ARKStepSetLinSysFn(sundials_mem, ARKStepSolver::LinSysSetup);
MFEM_VERIFY(flag == ARK_SUCCESS, "error in ARKStepSetLinSysFn()");
}
void ARKStepSolver::UseSundialsLinearSolver()
{
// Free any existing matrix and linear solver
if (A != NULL) { SUNMatDestroy(A); A = NULL; }
if (LSA != NULL) { SUNLinSolFree(LSA); LSA = NULL; }
// Create linear solver
LSA = SUNLinSol_SPGMR(*Y, PREC_NONE, 0);
MFEM_VERIFY(LSA, "error in SUNLinSol_SPGMR()");
// Attach linear solver
flag = ARKStepSetLinearSolver(sundials_mem, LSA, NULL);
MFEM_VERIFY(flag == ARK_SUCCESS, "error in ARKStepSetLinearSolver()");
}
void ARKStepSolver::UseMFEMMassLinearSolver(int tdep)
{
// Free any existing matrix and linear solver
if (M != NULL) { SUNMatDestroy(M); M = NULL; }
if (LSM != NULL) { SUNLinSolFree(LSM); LSM = NULL; }
// Wrap linear solver as SUNLinearSolver and SUNMatrix
LSM = SUNLinSolNewEmpty();
MFEM_VERIFY(LSM, "error in SUNLinSolNewEmpty()");
LSM->content = this;
LSM->ops->gettype = LSGetType;
LSM->ops->solve = ARKStepSolver::MassSysSolve;
LSA->ops->free = LSFree;
M = SUNMatNewEmpty();
MFEM_VERIFY(M, "error in SUNMatNewEmpty()");
M->content = this;
// BEGIN WORKAROUND CODE
// M->ops->getid = SUNMatGetID;
M->ops->getid = MatGetID;
M->ops->clone = MatClone;
M->ops->copy = MatCopy;
// END WORKAROUND CODE
M->ops->matvec = ARKStepSolver::MassMult1;
M->ops->destroy = MatDestroy;
// Attach the linear solver and matrix
flag = ARKStepSetMassLinearSolver(sundials_mem, LSM, M, tdep);
MFEM_VERIFY(flag == ARK_SUCCESS, "error in ARKStepSetLinearSolver()");
// Set the linear system function
flag = ARKStepSetMassFn(sundials_mem, ARKStepSolver::MassSysSetup);
MFEM_VERIFY(flag == ARK_SUCCESS, "error in ARKStepSetMassFn()");
}
void ARKStepSolver::UseSundialsMassLinearSolver(int tdep)
{
// Free any existing matrix and linear solver
if (M != NULL) { SUNMatDestroy(A); M = NULL; }
if (LSM != NULL) { SUNLinSolFree(LSM); LSM = NULL; }
// Create linear solver
LSM = SUNLinSol_SPGMR(*Y, PREC_NONE, 0);
MFEM_VERIFY(LSM, "error in SUNLinSol_SPGMR()");
// Attach linear solver
flag = ARKStepSetMassLinearSolver(sundials_mem, LSM, NULL, tdep);
MFEM_VERIFY(flag == ARK_SUCCESS, "error in ARKStepSetMassLinearSolver()");
// Attach matrix multiplication function
flag = ARKStepSetMassTimes(sundials_mem, NULL, ARKStepSolver::MassMult2,
this);
MFEM_VERIFY(flag == ARK_SUCCESS, "error in ARKStepSetMassTimes()");
}
void ARKStepSolver::SetStepMode(int itask)
{
step_mode = itask;
}
void ARKStepSolver::SetSStolerances(double reltol, double abstol)
{
flag = ARKStepSStolerances(sundials_mem, reltol, abstol);
MFEM_VERIFY(flag == ARK_SUCCESS, "error in ARKStepSStolerances()");
}
void ARKStepSolver::SetMaxIter(int iterations)
{
flag = ARKStepSetMaxNonlinIters(sundials_mem, iterations);
MFEM_VERIFY(flag == ARK_SUCCESS, "error in ARKStepSetMaxNonlinIters()");
}
void ARKStepSolver::SetMaxStep(double dt_max)
{
flag = ARKStepSetMaxStep(sundials_mem, dt_max);
MFEM_VERIFY(flag == ARK_SUCCESS, "error in ARKStepSetMaxStep()");
}
void ARKStepSolver::SetOrder(int order)
{
flag = ARKStepSetOrder(sundials_mem, order);
MFEM_VERIFY(flag == ARK_SUCCESS, "error in ARKStepSetOrder()");
}
void ARKStepSolver::SetERKTableNum(int table_num)
{
flag = ARKStepSetTableNum(sundials_mem, -1, table_num);
MFEM_VERIFY(flag == ARK_SUCCESS, "error in ARKStepSetTableNum()");
}
void ARKStepSolver::SetIRKTableNum(int table_num)
{
flag = ARKStepSetTableNum(sundials_mem, table_num, -1);
MFEM_VERIFY(flag == ARK_SUCCESS, "error in ARKStepSetTableNum()");
}
void ARKStepSolver::SetIMEXTableNum(int etable_num, int itable_num)
{
flag = ARKStepSetTableNum(sundials_mem, itable_num, etable_num);
MFEM_VERIFY(flag == ARK_SUCCESS, "error in ARKStepSetTableNum()");
}
void ARKStepSolver::SetFixedStep(double dt)
{
flag = ARKStepSetFixedStep(sundials_mem, dt);
MFEM_VERIFY(flag == ARK_SUCCESS, "error in ARKStepSetFixedStep()");
}
void ARKStepSolver::PrintInfo() const
{
long int nsteps, expsteps, accsteps, step_attempts;
long int nfe_evals, nfi_evals;
long int nlinsetups, netfails;
double hinused, hlast, hcur, tcur;
long int nniters, nncfails;
// Get integrator stats
flag = ARKStepGetTimestepperStats(sundials_mem,
&expsteps,
&accsteps,
&step_attempts,
&nfe_evals,
&nfi_evals,
&nlinsetups,
&netfails);
MFEM_VERIFY(flag == ARK_SUCCESS, "error in ARKStepGetTimestepperStats()");
flag = ARKStepGetStepStats(sundials_mem,
&nsteps,
&hinused,
&hlast,
&hcur,
&tcur);
// Get nonlinear solver stats
flag = ARKStepGetNonlinSolvStats(sundials_mem,
&nniters,
&nncfails);
MFEM_VERIFY(flag == ARK_SUCCESS, "error in ARKStepGetNonlinSolvStats()");
mfem::out <<
"ARKStep:\n"
"num steps: " << nsteps << "\n"
"num exp rhs evals: " << nfe_evals << "\n"
"num imp rhs evals: " << nfi_evals << "\n"
"num lin setups: " << nlinsetups << "\n"
"num nonlin sol iters: " << nniters << "\n"
"num nonlin conv fail: " << nncfails << "\n"
"num steps attempted: " << step_attempts << "\n"
"num acc limited steps: " << accsteps << "\n"
"num exp limited stepfails: " << expsteps << "\n"
"num error test fails: " << netfails << "\n"
"initial dt: " << hinused << "\n"
"last dt: " << hlast << "\n"
"current dt: " << hcur << "\n"
"current t: " << tcur << "\n" << endl;
return;
}
ARKStepSolver::~ARKStepSolver()
{
delete Y;
SUNMatDestroy(A);
SUNLinSolFree(LSA);
SUNNonlinSolFree(NLS);
ARKStepFree(&sundials_mem);
}
// ---------------------------------------------------------------------------
// KINSOL interface
// ---------------------------------------------------------------------------
// Wrapper for evaluating the nonlinear residual F(u) = 0
int KINSolver::Mult(const N_Vector u, N_Vector fu, void *user_data)
{
const SundialsNVector mfem_u(u);
SundialsNVector mfem_fu(fu);
KINSolver *self = static_cast<KINSolver*>(user_data);
// Compute the non-linear action F(u).
self->oper->Mult(mfem_u, mfem_fu);
// Return success
return 0;
}
// Wrapper for computing Jacobian-vector products
int KINSolver::GradientMult(N_Vector v, N_Vector Jv, N_Vector u,
booleantype *new_u, void *user_data)
{
const SundialsNVector mfem_v(v);
SundialsNVector mfem_Jv(Jv);
KINSolver *self = static_cast<KINSolver*>(user_data);
// Update Jacobian information if needed
if (*new_u)
{
const SundialsNVector mfem_u(u);
self->jacobian = &self->oper->GetGradient(mfem_u);
*new_u = SUNFALSE;
}
// Compute the Jacobian-vector product
self->jacobian->Mult(mfem_v, mfem_Jv);
// Return success
return 0;
}
// Wrapper for evaluating linear systems J u = b
int KINSolver::LinSysSetup(N_Vector u, N_Vector, SUNMatrix J,
void *, N_Vector , N_Vector )
{
const SundialsNVector mfem_u(u);
KINSolver *self = static_cast<KINSolver*>(GET_CONTENT(J));
// Update the Jacobian
self->jacobian = &self->oper->GetGradient(mfem_u);
// Set the Jacobian solve operator
self->prec->SetOperator(*self->jacobian);
// Return success
return (0);
}
// Wrapper for solving linear systems J u = b
int KINSolver::LinSysSolve(SUNLinearSolver LS, SUNMatrix, N_Vector u,
N_Vector b, realtype)
{
SundialsNVector mfem_u(u), mfem_b(b);
KINSolver *self = static_cast<KINSolver*>(GET_CONTENT(LS));
// Solve for u = [J(u)]^{-1} b, maybe approximately.
self->prec->Mult(mfem_b, mfem_u);
// Return success
return (0);
}
int KINSolver::PrecSetup(N_Vector uu,
N_Vector uscale,
N_Vector fval,
N_Vector fscale,
void *user_data)
{
SundialsNVector mfem_u(uu);
KINSolver *self = static_cast<KINSolver *>(user_data);
// Update the Jacobian
self->jacobian = &self->oper->GetGradient(mfem_u);
// Set the Jacobian solve operator
self->prec->SetOperator(*self->jacobian);
return 0;
}
int KINSolver::PrecSolve(N_Vector uu,
N_Vector uscale,
N_Vector fval,
N_Vector fscale,
N_Vector vv,
void *user_data)
{
KINSolver *self = static_cast<KINSolver *>(user_data);
SundialsNVector mfem_v(vv);
self->wrk = 0.0;
// Solve for u = P^{-1} v
self->prec->Mult(mfem_v, self->wrk);
mfem_v = self->wrk;
return 0;
}
KINSolver::KINSolver(int strategy, bool oper_grad)
: global_strategy(strategy), use_oper_grad(oper_grad), y_scale(NULL),
f_scale(NULL), jacobian(NULL), maa(0)
{
Y = new SundialsNVector();
y_scale = new SundialsNVector();
f_scale = new SundialsNVector();
// Default abs_tol and print_level
abs_tol = pow(UNIT_ROUNDOFF, 1.0/3.0);
print_level = 0;
}
#ifdef MFEM_USE_MPI
KINSolver::KINSolver(MPI_Comm comm, int strategy, bool oper_grad)
: global_strategy(strategy), use_oper_grad(oper_grad), y_scale(NULL),
f_scale(NULL), jacobian(NULL), maa(0)
{
Y = new SundialsNVector(comm);
y_scale = new SundialsNVector(comm);
f_scale = new SundialsNVector(comm);
// Default abs_tol and print_level
abs_tol = pow(UNIT_ROUNDOFF, 1.0/3.0);
print_level = 0;
}
#endif
void KINSolver::SetOperator(const Operator &op)
{
// Initialize the base class
NewtonSolver::SetOperator(op);
jacobian = NULL;
// Get the vector length
long local_size = height;
#ifdef MFEM_USE_MPI
long global_size;
#endif
if (Parallel())
{
#ifdef MFEM_USE_MPI
MPI_Allreduce(&local_size, &global_size, 1, MPI_LONG, MPI_SUM,
Y->GetComm());
#endif
}
if (sundials_mem)
{
// Check if the problem size has changed since the last SetOperator call
int resize = 0;
if (!Parallel())
{
resize = (Y->Size() != local_size);
}
else
{
#ifdef MFEM_USE_MPI
int l_resize = (Y->Size() != local_size) ||
(saved_global_size != global_size);
MPI_Allreduce(&l_resize, &resize, 1, MPI_INT, MPI_LOR,
Y->GetComm());
#endif
}
// Free existing solver memory and re-create with new vector size
if (resize)
{
KINFree(&sundials_mem);
sundials_mem = NULL;
}
}
if (!sundials_mem)
{
if (!Parallel())
{
Y->SetSize(local_size);
}
#ifdef MFEM_USE_MPI
else
{
Y->SetSize(local_size, global_size);
y_scale->SetSize(local_size, global_size);
f_scale->SetSize(local_size, global_size);
saved_global_size = global_size;
}
#endif
// Create the solver memory
sundials_mem = KINCreate();
MFEM_VERIFY(sundials_mem, "Error in KINCreate().");
// Set number of acceleration vectors
if (maa > 0)
{
flag = KINSetMAA(sundials_mem, maa);
MFEM_ASSERT(flag == KIN_SUCCESS, "error in KINSetMAA()");
}
// Initialize KINSOL
flag = KINInit(sundials_mem, KINSolver::Mult, *Y);
MFEM_VERIFY(flag == KIN_SUCCESS, "error in KINInit()");
// Attach the KINSolver as user-defined data
flag = KINSetUserData(sundials_mem, this);
MFEM_ASSERT(flag == KIN_SUCCESS, "error in KINSetUserData()");
// Set the linear solver
if (prec || jfnk)
{
KINSolver::SetSolver(*prec);
}
else
{
// Free any existing linear solver
if (A != NULL) { SUNMatDestroy(A); A = NULL; }
if (LSA != NULL) { SUNLinSolFree(LSA); LSA = NULL; }
LSA = SUNLinSol_SPGMR(*Y, PREC_NONE, 0);
MFEM_VERIFY(LSA, "error in SUNLinSol_SPGMR()");
flag = KINSetLinearSolver(sundials_mem, LSA, NULL);
MFEM_ASSERT(flag == KIN_SUCCESS, "error in KINSetLinearSolver()");
// Set Jacobian-vector product function
if (use_oper_grad)
{
flag = KINSetJacTimesVecFn(sundials_mem, KINSolver::GradientMult);
MFEM_ASSERT(flag == KIN_SUCCESS, "error in KINSetJacTimesVecFn()");
}
}
}
}
void KINSolver::SetSolver(Solver &solver)
{
if (jfnk)
{
SetJFNKSolver(solver);
}
else
{
// Store the solver
prec = &solver;
// Free any existing linear solver
if (A != NULL) { SUNMatDestroy(A); A = NULL; }
if (LSA != NULL) { SUNLinSolFree(LSA); LSA = NULL; }
// Wrap KINSolver as SUNLinearSolver and SUNMatrix
LSA = SUNLinSolNewEmpty();
MFEM_VERIFY(LSA, "error in SUNLinSolNewEmpty()");
LSA->content = this;
LSA->ops->gettype = LSGetType;
LSA->ops->solve = KINSolver::LinSysSolve;
LSA->ops->free = LSFree;
A = SUNMatNewEmpty();
MFEM_VERIFY(A, "error in SUNMatNewEmpty()");
A->content = this;
A->ops->getid = MatGetID;
A->ops->destroy = MatDestroy;
// Attach the linear solver and matrix
flag = KINSetLinearSolver(sundials_mem, LSA, A);
MFEM_VERIFY(flag == KIN_SUCCESS, "error in KINSetLinearSolver()");
// Set the Jacobian evaluation function
flag = KINSetJacFn(sundials_mem, KINSolver::LinSysSetup);
MFEM_VERIFY(flag == KIN_SUCCESS, "error in KINSetJacFn()");
}
}
void KINSolver::SetJFNKSolver(Solver &solver)
{
// Store the solver
prec = &solver;
wrk.SetSize(height);
// Free any existing linear solver
if (A != NULL) { SUNMatDestroy(A); A = NULL; }
if (LSA != NULL) { SUNLinSolFree(LSA); LSA = NULL; }
// Setup FGMRES
LSA = SUNLinSol_SPFGMR(*Y, prec ? PREC_RIGHT : PREC_NONE, maxli);
MFEM_VERIFY(LSA, "error in SUNLinSol_SPFGMR()");
flag = SUNLinSol_SPFGMRSetMaxRestarts(LSA, maxlrs);
MFEM_VERIFY(flag == SUNLS_SUCCESS, "error in SUNLinSol_SPFGMR()");
flag = KINSetLinearSolver(sundials_mem, LSA, NULL);
MFEM_VERIFY(flag == KIN_SUCCESS, "error in KINSetLinearSolver()");
if (prec)
{
flag = KINSetPreconditioner(sundials_mem,
KINSolver::PrecSetup,
KINSolver::PrecSolve);
MFEM_VERIFY(flag == KIN_SUCCESS, "error in KINSetPreconditioner()");
}
}
void KINSolver::SetScaledStepTol(double sstol)
{
flag = KINSetScaledStepTol(sundials_mem, sstol);
MFEM_ASSERT(flag == KIN_SUCCESS, "error in KINSetScaledStepTol()");
}
void KINSolver::SetMaxSetupCalls(int max_calls)
{
flag = KINSetMaxSetupCalls(sundials_mem, max_calls);
MFEM_ASSERT(flag == KIN_SUCCESS, "error in KINSetMaxSetupCalls()");
}
void KINSolver::SetMAA(int m_aa)
{
// Store internally as maa must be set before calling KINInit() to
// set the maximum acceleration space size.
maa = m_aa;
if (sundials_mem)
{
flag = KINSetMAA(sundials_mem, maa);
MFEM_ASSERT(flag == KIN_SUCCESS, "error in KINSetMAA()");
}
}
// Compute the scaling vectors and solve nonlinear system
void KINSolver::Mult(const Vector&, Vector &x) const
{
// residual norm tolerance
double tol;
// Uses c = 1, corresponding to x_scale.
c = 1.0;
if (!iterative_mode) { x = 0.0; }
// For relative tolerance, r = 1 / |residual(x)|, corresponding to fx_scale.
if (rel_tol > 0.0)
{
oper->Mult(x, r);
// Note that KINSOL uses infinity norms.
double norm = r.Normlinf();
#ifdef MFEM_USE_MPI
if (Parallel())
{
double lnorm = norm;
MPI_Allreduce(&lnorm, &norm, 1, MPI_DOUBLE, MPI_MAX,
Y->GetComm());
}
#endif
if (abs_tol > rel_tol * norm)
{
r = 1.0;
tol = abs_tol;
}
else
{
r = 1.0 / norm;
tol = rel_tol;
}
}
else
{
r = 1.0;
tol = abs_tol;
}
// Set the residual norm tolerance
flag = KINSetFuncNormTol(sundials_mem, tol);
MFEM_ASSERT(flag == KIN_SUCCESS, "error in KINSetFuncNormTol()");
// Solve the nonlinear system by calling the other Mult method
KINSolver::Mult(x, c, r);
}
// Solve the nonlinear system using the provided scaling vectors
void KINSolver::Mult(Vector &x,
const Vector &x_scale, const Vector &fx_scale) const
{
flag = KINSetNumMaxIters(sundials_mem, max_iter);
MFEM_ASSERT(flag == KIN_SUCCESS, "KINSetNumMaxIters() failed!");
Y->MakeRef(x, 0, x.Size());
y_scale->MakeRef(const_cast<Vector&>(x_scale), 0, x_scale.Size());
f_scale->MakeRef(const_cast<Vector&>(fx_scale), 0, fx_scale.Size());
int rank = -1;
if (!Parallel())
{
rank = 0;
}
else
{
#ifdef MFEM_USE_MPI
MPI_Comm_rank(Y->GetComm(), &rank);
#endif
}
if (rank == 0)
{
flag = KINSetPrintLevel(sundials_mem, print_level);
MFEM_VERIFY(flag == KIN_SUCCESS, "KINSetPrintLevel() failed!");
#ifdef SUNDIALS_BUILD_WITH_MONITORING
if (jfnk && print_level)
{
flag = SUNLinSolSetInfoFile_SPFGMR(LSA, stdout);
MFEM_VERIFY(flag == SUNLS_SUCCESS,
"error in SUNLinSolSetInfoFile_SPFGMR()");
flag = SUNLinSolSetPrintLevel_SPFGMR(LSA, 1);
MFEM_VERIFY(flag == SUNLS_SUCCESS,
"error in SUNLinSolSetPrintLevel_SPFGMR()");
}
#endif
}
if (!iterative_mode) { x = 0.0; }
// Solve the nonlinear system
flag = KINSol(sundials_mem, *Y, global_strategy, *y_scale, *f_scale);
converged = (flag >= 0);
// Make sure host is up to date
Y->HostRead();
// Get number of nonlinear iterations
long int tmp_nni;
flag = KINGetNumNonlinSolvIters(sundials_mem, &tmp_nni);
MFEM_ASSERT(flag == KIN_SUCCESS, "error in KINGetNumNonlinSolvIters()");
final_iter = (int) tmp_nni;
// Get the residual norm
flag = KINGetFuncNorm(sundials_mem, &final_norm);
MFEM_ASSERT(flag == KIN_SUCCESS, "error in KINGetFuncNorm()");
}
KINSolver::~KINSolver()
{
delete Y;
delete y_scale;
delete f_scale;
SUNMatDestroy(A);
SUNLinSolFree(LSA);
KINFree(&sundials_mem);
}
} // namespace mfem
#endif // MFEM_USE_SUNDIALS