ultimatepp/bazaar/STEM4U/sundials.cpp
koldo 66957ff519 STEM4U: Added numerical integration and DAE solving
git-svn-id: svn://ultimatepp.org/upp/trunk@14562 f0d560ea-af0d-0410-9eb7-867de7ffcac7
2020-06-07 10:18:34 +00:00

416 lines
14 KiB
C++

#include <Core/Core.h>
#include <Functions4U/Functions4U.h>
#include <plugin/Eigen/Eigen.h>
#include "sundials.h"
namespace Upp {
using namespace Eigen;
#include <kinsol/kinsol.h> /* access to KINSOL func., consts. */
#include <ida/ida.h> /* prototypes for IDA fcts., consts. */
#include <nvector/nvector_serial.h> /* access to serial N_Vector */
#include <sunmatrix/sunmatrix_dense.h> /* access to dense SUNMatrix */
#include <sunlinsol/sunlinsol_dense.h> /* access to dense SUNLinearSolver */
#include <sunnonlinsol/sunnonlinsol_newton.h> /* access to Newton SUNNonlinearSolver */
#include <sundials/sundials_types.h> /* defs. of realtype, sunindextype */
#include <sundials/sundials_math.h> /* defs. of SUNRabs, SUNRexp, etc. */
static void CheckMem(void *returnvalue, const char *funcname) {
if (returnvalue == NULL)
throw Exc(Format(t_("SUNDIALS_ERROR: %s() failed - returned NULL pointer"), funcname));
}
static void CheckRet(int returnvalue, const char *funcname) {
if (returnvalue < 0)
throw Exc(Format(t_("SUNDIALS_ERROR: %s() failed with retval = %d"), funcname, returnvalue));
}
static void PrintFinalStats(void *mem) {
int retval;
long int nst, nni, nje, nre, nreLS, netf, ncfn, nge;
retval = IDAGetNumSteps(mem, &nst);
CheckRet(retval, "IDAGetNumSteps");
retval = IDAGetNumResEvals(mem, &nre);
CheckRet(retval, "IDAGetNumResEvals");
retval = IDAGetNumJacEvals(mem, &nje);
CheckRet(retval, "IDAGetNumJacEvals");
retval = IDAGetNumNonlinSolvIters(mem, &nni);
CheckRet(retval, "IDAGetNumNonlinSolvIters");
retval = IDAGetNumErrTestFails(mem, &netf);
CheckRet(retval, "IDAGetNumErrTestFails");
retval = IDAGetNumNonlinSolvConvFails(mem, &ncfn);
CheckRet(retval, "IDAGetNumNonlinSolvConvFails");
retval = IDAGetNumLinResEvals(mem, &nreLS);
CheckRet(retval, "IDAGetNumLinResEvals");
retval = IDAGetNumGEvals(mem, &nge);
CheckRet(retval, "IDAGetNumGEvals");
printf("\nFinal Run Statistics: \n\n");
printf("Number of steps = %ld\n", nst);
printf("Number of residual evaluations = %ld\n", nre+nreLS);
printf("Number of Jacobian evaluations = %ld\n", nje);
printf("Number of nonlinear iterations = %ld\n", nni);
printf("Number of error test failures = %ld\n", netf);
printf("Number of nonlinear conv. failures = %ld\n", ncfn);
printf("Number of root fn. evaluations = %ld\n", nge);
}
static void PrintIteration(realtype t, realtype *y, int neq) {
Cout() << "\n" << Format("%7.3f ", t);
for (int i = 0; i < neq; ++i)
Cout() << Format("%12.4e ", y[i]);
}
static void PrintRootInfo(int *roots, int nroots) {
Cout() << "\nRoots found[] = ";
for (int i = 0; i < nroots; ++i)
Cout() << roots[i] << " ";
}
static String GetIdaErrorMsg(int ret) {
switch (ret) {
case IDA_TOO_MUCH_WORK: return "IDASolve: IDA_TOO_MUCH_WORK";
case IDA_TOO_MUCH_ACC: return "IDASolve: IDA_TOO_MUCH_ACC";
case IDA_ERR_FAIL: return "IDASolve: IDA_ERR_FAIL";
case IDA_MEM_NULL: return "ida_mem is NULL";
case IDA_NO_MALLOC: return "ida_mem was not allocated";
case IDA_ILL_INPUT: return "bad value for icopt, tout1, or id";
case IDA_LINIT_FAIL: return "the linear solver linit routine failed";
case IDA_BAD_EWT: return "zero value of some component of ewt";
case IDA_RES_FAIL: return "res had a non-recoverable error";
case IDA_FIRST_RES_FAIL:return "res failed recoverably on the first call";
case IDA_LSETUP_FAIL: return "lsetup had a non-recoverable error";
case IDA_LSOLVE_FAIL: return "lsolve had a non-recoverable error";
case IDA_NO_RECOVERY: return "res, lsetup, or lsolve had a recoverable error, but IDACalcIC could not recover";
case IDA_CONSTR_FAIL: return "the inequality constraints could not be met";
case IDA_LINESEARCH_FAIL:return"the linesearch failed (either on steptol test or on the maxbacks test)";
case IDA_CONV_FAIL: return "the Newton iterations failed to converge";
default: return Format("Unknown SUndoasl eerror %d", ret);
}
}
void SolveDAE(const VectorXd &y, const VectorXd &dy, double dt, double maxt, Upp::Array<VectorXd> &res,
Function <bool(double t, const double y[], const double dy[], double residual[])>Residual, int numZero,
Function <bool(double t, const double y[], const double dy[], double residual[])>ResidualZero) {
ASSERT(y.size() == dy.size());
Eigen::Index numEq = y.size();
res.SetCount(int(numEq));
for (int i = 0; i < numEq; ++i) {
res[i].resize(int(maxt/dt)+1);
res[i](0) = y[i];
}
SolveDAE(y.data(), dy.data(), int(numEq), dt, maxt, Residual, numZero, ResidualZero, [&](double t, const double y[], const double dy[], bool isZero, int *whichZero)->int {
int iter = int(round(t/dt));
for (int i = 0; i < int(numEq); ++i)
res[i](iter) = y[i];
return true;
});
}
void SolveDAE(const VectorXd &y, const VectorXd &dy, double dt, double maxt,
Upp::Array<VectorXd> &res, Upp::Array<VectorXd> &dres,
Function <bool(double t, const double y[], const double dy[], double residual[])>Residual, int numZero,
Function <bool(double t, const double y[], const double dy[], double residual[])>ResidualZero) {
ASSERT(y.size() == dy.size());
Eigen::Index numEq = y.size();
res.SetCount(int(numEq));
dres.SetCount(int(numEq));
for (int i = 0; i < numEq; ++i) {
res[i].resize(int(maxt/dt)+1);
res[i](0) = y[i];
dres[i].resize(int(maxt/dt)+1);
dres[i](0) = dy[i];
}
SolveDAE(y.data(), dy.data(), int(numEq), dt, maxt, Residual, numZero, ResidualZero, [&](double t, const double y[], const double dy[], bool isZero, int *whichZero)->int {
int iter = int(round(t/dt));
for (int i = 0; i < int(numEq); ++i) {
res[i](iter) = y[i];
dres[i](iter) = dy[i];
}
return true;
});
}
void SolveDAE(const double y[], const double dy[], int numEq, double dt, double maxt,
Function <bool(double t, const double y[], const double dy[], double residual[])> Residual, int numZero,
Function <bool(double t, const double y[], const double dy[], double residual[])> ResidualZero,
Function <bool(double t, const double y[], const double dy[], bool isZero, int *whichZero)>OnIteration) {
int retval, retvalr;
Buffer<int> rootsfound(numEq);
SUNNonlinearSolver NLS = NULL;
SUNLinearSolver LS = NULL;
SUNMatrix A = NULL;
void *mem = NULL;
N_Vector avtol = NULL, yy = NULL, yp = NULL;
Exc error;
try {
yy = N_VNew_Serial(numEq);
CheckMem((void *)yy, "N_VNew_Serial");
yp = N_VNew_Serial(numEq);
CheckMem((void *)yp, "N_VNew_Serial");
avtol = N_VNew_Serial(numEq);
CheckMem((void *)avtol, "N_VNew_Serial");
/* Create and initialize y, y', and absolute tolerance vectors. */
realtype *yval = N_VGetArrayPointer(yy);
memcpy(yval, y, numEq*sizeof(double));
realtype *ypval = N_VGetArrayPointer(yp);
memcpy(ypval, dy, numEq*sizeof(double));
realtype rtol = 1.0e-4;
realtype *atval = N_VGetArrayPointer(avtol);
for (int i = 0; i < numEq; ++i)
atval[i] = 1.0e-8;
/* Integration limits */
double t0 = 0;
struct UserData {
Function <bool(double t, const double y[], const double dy[], double residual[])> Residual;
Function <bool(double t, const double y[], const double dy[], double residual[])> ResidualZero;
} userData;
userData.Residual = Residual;
userData.ResidualZero = ResidualZero;
auto ResFun = [](realtype t, N_Vector _y, N_Vector _dy, N_Vector _res, void *user_data) {
realtype *y = N_VGetArrayPointer(_y);
realtype *dy = N_VGetArrayPointer(_dy);
realtype *res = N_VGetArrayPointer(_res);
UserData &userData = *(UserData *)user_data;
return userData.Residual(t, y, dy, res) ? 0 : -1;
};
auto ResZeroFun = [](realtype t, N_Vector _y, N_Vector _dy, realtype *res, void *user_data) {
realtype *y = N_VGetArrayPointer(_y);
realtype *dy = N_VGetArrayPointer(_dy);
UserData &userData = *(UserData *)user_data;
return userData.ResidualZero(t, y, dy, res) ? 0 : -1;
};
/* Call IDACreate and IDAInit to initialize IDA memory */
mem = IDACreate();
CheckMem((void *)mem, "IDACreate");
retval = IDASetUserData(mem, (void *)&userData);
CheckRet(retval, "IDASetUserData");
retval = IDAInit(mem, ResFun, t0, yy, yp);
CheckRet(retval, "IDAInit");
/* Call IDASVtolerances to set tolerances */
retval = IDASVtolerances(mem, rtol, avtol);
CheckRet(retval, "IDASVtolerances");
/* Call IDARootInit to specify the root function grob with 2 components */
if (ResidualZero && numZero > 0) {
retval = IDARootInit(mem, numZero, ResZeroFun);
CheckRet(retval, "IDARootInit");
}
/* Create dense SUNMatrix for use in linear solver */
A = SUNDenseMatrix(numEq, numEq);
CheckMem((void *)A, "SUNDenseMatrix");
/* Create dense SUNLinearSolver object */
LS = SUNLinSol_Dense(yy, A);
CheckMem((void *)LS, "SUNLinSol_Dense");
/* Attach the matrix and linear solver */
retval = IDASetLinearSolver(mem, LS, A);
CheckRet(retval, "IDASetLinearSolver");
retval = IDASetMaxNumSteps(mem, 20000);
CheckRet(retval, "IDASetLinearSolver");
/* Create Newton SUNNonlinearSolver object. IDA uses a
* Newton SUNNonlinearSolver by default, so it is unecessary
* to create it and attach it. It is done in this example code
* solely for demonstration purposes. */
NLS = SUNNonlinSol_Newton(yy);
CheckMem((void *)NLS, "SUNNonlinSol_Newton");
/* Attach the nonlinear solver */
retval = IDASetNonlinearSolver(mem, NLS);
CheckRet(retval, "IDASetNonlinearSolver");
/* In loop, call IDASolve, print results, and test for error.
Break out of loop when NOUT preset output times have been reached. */
int iiter = 1;
double tnext = dt;
double titer;
while(true) {
retval = IDASolve(mem, tnext, &titer, yy, yp, IDA_NORMAL);
CheckRet(retval, "IDASolve");
realtype *y = N_VGetArrayPointer(yy);
realtype *dy = N_VGetArrayPointer(yp);
//PrintIteration(titer, y, numEq);
if (retval == IDA_ROOT_RETURN) {
retvalr = IDAGetRootInfo(mem, rootsfound);
CheckRet(retvalr, "IDAGetRootInfo");
//PrintRootInfo(rootsfound, numEq);
} else if (retval == IDA_SUCCESS) {
iiter++;
tnext = iiter*dt;
} else
throw Exc(Format(t_("Sundials IDA error: %s"), GetIdaErrorMsg(retval)));
if(OnIteration && !OnIteration(titer, y, dy, retval == IDA_ROOT_RETURN, rootsfound))
break;
else if (tnext > maxt)
break;
}
//PrintFinalStats(mem);
} catch(Exc err) {
error = err;
}
IDAFree(&mem);
SUNNonlinSolFree(NLS);
SUNLinSolFree(LS);
SUNMatDestroy(A);
N_VDestroy(avtol);
N_VDestroy(yy);
N_VDestroy(yp);
if (!error.IsEmpty())
throw error;
}
/*
0 then no constraint is imposed on ui.
1 then ui will be constrained to be ui 0:0.
-1 then ui will be constrained to be ui 0:0.
2 then ui will be constrained to be ui > 0:0.
-2 then ui will be constrained to be ui < 0:0.
*/
void SolveNonLinearEquationsSun(double y[], int numEq,
Function <bool(const double b[], double residuals[])> Residual, int constraints[]) {
N_Vector s = NULL;
void *kmem = NULL;
Exc error;
try {
N_Vector u = N_VNew_Serial(numEq);
CheckMem((void *)u, "N_VNew_Serial");
realtype *udata = NV_DATA_S(u);
memcpy(udata, y, numEq*sizeof(double));
s = N_VNew_Serial(numEq);
CheckMem((void *)s, "N_VNew_Serial");
N_VConst_Serial(1. ,s); /* no scaling */
double fnormtol = 1.e-6;
double scsteptol = 1.e-6;
int numIteraciones = 500;
bool ret = true;
kmem = KINCreate();
CheckMem((void *)kmem, "KINCreate");
auto ErrorFun = [](int error_code, const char *module, const char *function, char *msg, void *data) {
String serror = Format("%d, module %s, function %s", error_code, module, function);
if (error_code == KIN_WARNING)
Cout() << Format(t_("Kinsol Warning (%s): %s"), serror, msg);
else {
char *str = KINGetReturnFlagName(error_code);
String cerror(str);
free(str);
throw Exc(Format(t_("Kinsol Error %s (%s): %s"), cerror, serror, msg));
}
};
int flag;
flag = KINSetErrHandlerFn(kmem, ErrorFun, NULL);
CheckRet(flag, "KINSetErrHandlerFn");
struct UserData {
Function <bool(const double y[], double residual[])> Residual;
} userData;
userData.Residual = Residual;
flag = KINSetUserData(kmem, &userData);
CheckRet(flag, "KINSetUserData");
flag = KINSetFuncNormTol(kmem, fnormtol);
CheckRet(flag, "KINSetFuncNormTol");
flag = KINSetScaledStepTol(kmem, scsteptol);
CheckRet(flag, "KINSetScaledStepTol");
flag = KINSetNumMaxIters(kmem, numIteraciones);
CheckRet(flag, "KINSetNumMaxIters");
auto ResFun = [](N_Vector u, N_Vector f, void *user_data) {
realtype *y = NV_DATA_S(u);
realtype *res = NV_DATA_S(f);
UserData &userData = *(UserData *)user_data;
return userData.Residual(y, res) ? 0 : 1;
};
flag = KINInit(kmem, ResFun, u);
CheckRet(flag, "KINInit");
if (constraints != nullptr) {
N_Vector constr = N_VNew_Serial(numEq);
CheckMem((void *)constr, "N_VNew_Serial");
realtype *constrdata = NV_DATA_S(constr);
for (int i = 0; i < numEq; ++i)
constrdata[i] = constraints[i];
flag = KINSetConstraints(kmem, constr);
CheckRet(flag, "KINSetConstraints");
N_VDestroy_Serial(constr);
}
/* Create dense SUNMatrix */
SUNMatrix J = SUNDenseMatrix(numEq, numEq);
CheckMem((void *)J, "SUNDenseMatrix");
/* Create dense SUNLinearSolver object */
SUNLinearSolver LS = SUNLinSol_Dense(u, J);
CheckMem((void *)LS, "SUNLinSol_Dense");
/* Attach the matrix and linear solver to KINSOL */
flag = KINSetLinearSolver(kmem, LS, J);
CheckRet(flag, "KINSetLinearSolver");
int glstr = KIN_NONE; // KIN_NONE KIN_LINESEARCH Using line search
int mset = 1; // 1 Exact Newton 0 Modified Newton
flag = KINSetMaxSetupCalls(kmem, mset);
CheckRet(flag, "KINSetMaxSetupCalls");
flag = KINSol(kmem, u, glstr, s, s);
CheckRet(flag, "KINSol");
for (int i = 0; i < numEq; ++i)
if (udata[i] != udata[i])
throw Exc(Format(t_("Obtained NaN in value %d"), i));
memcpy(y, udata, numEq*sizeof(double));
} catch(Exc err) {
error = err;
}
N_VDestroy_Serial(s);
KINFree(&kmem);
if (!error.IsEmpty())
throw error;
}
}