Chaste Commit::29c3c4ab2bee00ddd311a035a1e522c03cbe928a
CvodeAdaptor.cpp
1/*
2
3Copyright (c) 2005-2026, University of Oxford.
4All rights reserved.
5
6University of Oxford means the Chancellor, Masters and Scholars of the
7University of Oxford, having an administrative office at Wellington
8Square, Oxford OX1 2JD, UK.
9
10This file is part of Chaste.
11
12Redistribution and use in source and binary forms, with or without
13modification, are permitted provided that the following conditions are met:
14 * Redistributions of source code must retain the above copyright notice,
15 this list of conditions and the following disclaimer.
16 * Redistributions in binary form must reproduce the above copyright notice,
17 this list of conditions and the following disclaimer in the documentation
18 and/or other materials provided with the distribution.
19 * Neither the name of the University of Oxford nor the names of its
20 contributors may be used to endorse or promote products derived from this
21 software without specific prior written permission.
22
23THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
27LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
28CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
29GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
32OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33
34*/
35
36#include "CvodeAdaptor.hpp"
37
38#include "Exception.hpp"
39#include "MathsCustomFunctions.hpp" // For tolerance check.
40#include "TimeStepper.hpp"
42
43#include <iostream>
44#include <sstream>
45
46// CVODE headers
47#include <sundials/sundials_nvector.h>
48
49#if CHASTE_SUNDIALS_VERSION < 70000
50#include <cvode/cvode_direct.h> /* access to CVDls interface */
51#endif
52#include <sundials/sundials_types.h> /* defs. of realtype, sunindextype */
53#include <sunlinsol/sunlinsol_dense.h> /* access to dense SUNLinearSolver */
54#include <sunmatrix/sunmatrix_dense.h> /* access to dense SUNMatrix */
55
56#if CHASTE_SUNDIALS_VERSION >= 60000
57#include "CvodeContextManager.hpp" // access to shared SUNContext object required by Sundials 6.0+
58#endif
59
76int CvodeRhsAdaptor(realtype t, N_Vector y, N_Vector ydot, void* pData)
77{
78 assert(pData != nullptr);
79 CvodeData* p_data = (CvodeData*)pData;
80 // Get y, ydot into std::vector<>s
81 static std::vector<realtype> ydot_vec;
82 CopyToStdVector(y, *p_data->pY);
83 CopyToStdVector(ydot, ydot_vec);
84 // Call our function
85 try
86 {
87 p_data->pSystem->EvaluateYDerivatives(t, *(p_data->pY), ydot_vec);
88 }
89 catch (const Exception& e)
90 {
91 std::cerr << "CVODE RHS Exception: " << e.GetMessage() << std::endl
92 << std::flush;
93 return -1;
94 }
95 // Copy derivative back
96 CopyFromStdVector(ydot_vec, ydot);
97 return 0;
98}
99
120int CvodeRootAdaptor(realtype t, N_Vector y, realtype* pGOut, void* pData)
121{
122 assert(pData != nullptr);
123 CvodeData* p_data = (CvodeData*)pData;
124 // Get y into a std::vector
125 CopyToStdVector(y, *p_data->pY);
126 // Call our function
127 try
128 {
129 *pGOut = p_data->pSystem->CalculateRootFunction(t, *p_data->pY);
130 }
131 catch (const Exception& e)
132 {
133 std::cerr << "CVODE Root Exception: " << e.GetMessage() << std::endl
134 << std::flush;
135 return -1;
136 }
137 return 0;
138}
139
140// /**
141// * Jacobian computation adaptor function.
142// *
143// * If solving an AbstractOdeSystemWithAnalyticJacobian, this function
144// * can be used to allow CVODE to compute the Jacobian analytically.
145// *
146// * Note to self: can test using pSystem->GetUseAnalyticJacobian().
147// */
148// int CvodeDenseJacobianAdaptor(long int numberOfStateVariables, DenseMat J,
149// realtype t, N_Vector y, N_Vector fy,
150// void* pData,
151// N_Vector tmp1, N_Vector tmp2, N_Vector tmp3)
152// {
153// AbstractOdeSystemWithAnalyticJacobian* pSystem
154// = (AbstractOdeSystemWithAnalyticJacobian*) pData;
155// // Get the current time step
156// double dt;
157// CVodeGetCurrentStep(CvodeMem, &dt);
158// // Get std::vector<> for y and double** for J
159// std::vector<realtype>& y_vec = *NV_DATA_STL(y);
160// double** ppJ = J->data; // organised column-wise: J_{i,j} = ppJ[j][i]
161// // Call our function
162// try
163// {
164// pSystem->AnalyticJacobian(y_vec, ppJ, t, dt);
165// }
166// catch (const Exception &e)
167// {
168// std::cerr << "CVODE J Exception: " << e.GetMessage() << std::endl << std::flush;
169// return -1;
170// }
171// // Update J (if needed)
172// return 0;
173// }
174
175#if CHASTE_SUNDIALS_VERSION >= 70000
176void CvodeErrorHandler(int errorCode, const char* module, const char* function,
177 const char* message, SUNErrCode errCode, void* pData, SUNContext sunContext)
178#else
179void CvodeErrorHandler(int errorCode, const char* module, const char* function,
180 char* message, void* pData)
181#endif
182{
183 std::stringstream err;
184 err << "CVODE Error " << errorCode << " in module " << module
185 << " function " << function << ": " << message;
186 std::cerr << "*" << err.str() << std::endl
187 << std::flush;
188 // Throwing the exception here causes termination on Maverick (g++ 4.4)
189 //EXCEPTION(err.str());
190}
191
193 std::vector<double>& rInitialY,
194 double startTime,
195 double maxStep)
196{
197 assert(rInitialY.size() == pOdeSystem->GetNumberOfStateVariables());
198 assert(maxStep > 0.0);
199
201#if CHASTE_SUNDIALS_VERSION >= 60000
202 N_Vector initial_values = N_VMake_Serial(rInitialY.size(), &(rInitialY[0]), CvodeContextManager::Instance()->GetSundialsContext());
203#else
204 N_Vector initial_values = N_VMake_Serial(rInitialY.size(), &(rInitialY[0]));
205#endif
206 assert(NV_DATA_S(initial_values) == &(rInitialY[0]));
207 assert(!NV_OWN_DATA_S(initial_values));
208 // std::cout << " Initial values: "; N_VPrint_Serial(initial_values);
209 // std::cout << " Rtol: " << mRelTol << ", Atol: " << mAbsTol << std::endl;
210 // std::cout << " Start: " << startTime << " max dt=" << maxStep << std::endl << std::flush;
211
212 // Find out if we need to (re-)initialise
214 if (!reinit && !mForceMinimalReset)
215 {
216 const unsigned size = GetVectorSize(rInitialY);
217 for (unsigned i = 0; i < size; i++)
218 {
220 {
221 reinit = true;
222 break;
223 }
224 }
225 }
226
227 if (!mpCvodeMem) // First run of this solver, set up CVODE memory
228 {
229 // Set up CVODE's memory.
230#if CHASTE_SUNDIALS_VERSION >= 60000
231 mpCvodeMem = CVodeCreate(CV_BDF, CvodeContextManager::Instance()->GetSundialsContext());
232#else
233 // v4.0.0 release notes: instead of specifying the nonlinear iteration type when creating the CVODE(S) memory structure,
234 // CVODE(S) uses the SUNNONLINSOL_NEWTON module implementation of a Newton iteration by default.
235 mpCvodeMem = CVodeCreate(CV_BDF);
236#endif
237
238 if (mpCvodeMem == nullptr)
239 EXCEPTION("Failed to SetupCvode CVODE"); // LCOV_EXCL_LINE
240 // Set error handler
241#if CHASTE_SUNDIALS_VERSION >= 70000
242 SUNContext_PushErrHandler(CvodeContextManager::Instance()->GetSundialsContext(), CvodeErrorHandler, nullptr);
243#else
244 CVodeSetErrHandlerFn(mpCvodeMem, CvodeErrorHandler, nullptr);
245#endif
246 // Set the user data
247 mData.pSystem = pOdeSystem;
248 mData.pY = &rInitialY;
249 CVodeSetUserData(mpCvodeMem, (void*)(&mData));
250
251// Setup CVODE
252 CVodeInit(mpCvodeMem, CvodeRhsAdaptor, startTime, initial_values);
253 CVodeSStolerances(mpCvodeMem, mRelTol, mAbsTol);
254
255 // Set the rootfinder function if wanted
256 if (mCheckForRoots)
257 {
258 CVodeRootInit(mpCvodeMem, 1, CvodeRootAdaptor);
259 }
260
261 /* Create dense SUNMatrix for use in linear solves */
262#if CHASTE_SUNDIALS_VERSION >= 60000
263 mpSundialsDenseMatrix = SUNDenseMatrix(rInitialY.size(), rInitialY.size(), CvodeContextManager::Instance()->GetSundialsContext());
264#else
265 mpSundialsDenseMatrix = SUNDenseMatrix(rInitialY.size(), rInitialY.size());
266#endif
267
268#if CHASTE_SUNDIALS_VERSION >= 60000
269 /* Create dense SUNLinearSolver object for use by CVode */
270 mpSundialsLinearSolver = SUNLinSol_Dense(initial_values, mpSundialsDenseMatrix, CvodeContextManager::Instance()->GetSundialsContext());
271
272 /* Call CVodeSetLinearSolver to attach the matrix and linear solver to CVode */
274#else
275 /* Create dense SUNLinearSolver object for use by CVode */
276 mpSundialsLinearSolver = SUNLinSol_Dense(initial_values, mpSundialsDenseMatrix);
277
278 /* Call CVodeSetLinearSolver to attach the matrix and linear solver to CVode */
280#endif
281 }
282 else if (reinit) // Could be new ODE system, or new Y values
283 {
284 // Set the user data
285 mData.pSystem = pOdeSystem; // stays the same on a re-initialize
286 mData.pY = &rInitialY; // changes on a re-initialize
287 CVodeSetUserData(mpCvodeMem, (void*)(&mData));
288
289 CVodeReInit(mpCvodeMem, startTime, initial_values);
290 CVodeSStolerances(mpCvodeMem, mRelTol, mAbsTol);
291
293 {
294 /* Free the linear solver memory */
295 SUNLinSolFree(mpSundialsLinearSolver);
296 }
298 {
299 /* Free the matrix memory */
300 SUNMatDestroy(mpSundialsDenseMatrix);
301 }
302
303 /* Create dense matrix of type SUNDenseMatrix for use in linear solves */
304#if CHASTE_SUNDIALS_VERSION >= 60000
305 mpSundialsDenseMatrix = SUNDenseMatrix(rInitialY.size(), rInitialY.size(), CvodeContextManager::Instance()->GetSundialsContext());
306#else
307 mpSundialsDenseMatrix = SUNDenseMatrix(rInitialY.size(), rInitialY.size());
308#endif
309
310#if CHASTE_SUNDIALS_VERSION >= 60000
311 /* Create dense SUNLinSol_Dense object for use by CVode */
312 mpSundialsLinearSolver = SUNLinSol_Dense(initial_values, mpSundialsDenseMatrix, CvodeContextManager::Instance()->GetSundialsContext());
313
314 /* Call CVodeSetLinearSolver to attach the matrix and linear solver to CVode */
316#else
317 /* Create dense SUNLinSol_Dense object for use by CVode */
318 mpSundialsLinearSolver = SUNLinSol_Dense(initial_values, mpSundialsDenseMatrix);
319
320 /* Call CVodeSetLinearSolver to attach the matrix and linear solver to CVode */
322#endif
323 }
324
325 CVodeSetMaxStep(mpCvodeMem, maxStep);
326 // Change max steps if wanted
327 if (mMaxSteps > 0)
328 {
329 CVodeSetMaxNumSteps(mpCvodeMem, mMaxSteps);
330 CVodeSetMaxErrTestFails(mpCvodeMem, 15);
331 }
332 DeleteVector(initial_values);
333}
334
336{
337 if (mpCvodeMem)
338 {
339 CVodeFree(&mpCvodeMem);
340 }
341 mpCvodeMem = nullptr;
342
344 {
345 /* Free the linear solver memory */
346 SUNLinSolFree(mpSundialsLinearSolver);
347 }
348 mpSundialsLinearSolver = nullptr;
349
351 {
352 /* Free the matrix memory */
353 SUNMatDestroy(mpSundialsDenseMatrix);
354 }
355 mpSundialsDenseMatrix = nullptr;
356}
357
358void CvodeAdaptor::SetForceReset(bool autoReset)
359{
360 mForceReset = autoReset;
361 if (mForceReset)
362 {
363 SetMinimalReset(false);
364 ResetSolver();
365 }
366}
367
368void CvodeAdaptor::SetMinimalReset(bool minimalReset)
369{
370 mForceMinimalReset = minimalReset;
372 {
373 SetForceReset(false);
374 }
375}
376
378{
379 // Doing this makes the SetupCvode think it needs to reset things.
381}
382
383void CvodeAdaptor::CvodeError(int flag, const char* msg)
384{
385 std::stringstream err;
386 char* p_flag_name = CVodeGetReturnFlagName(flag);
387 err << msg << ": " << p_flag_name;
388 free(p_flag_name);
389 std::cerr << err.str() << std::endl
390 << std::flush;
391 EXCEPTION(err.str());
392}
393
395 std::vector<double>& rYValues,
396 double startTime,
397 double endTime,
398 double maxStep,
399 double timeSampling)
400{
401 assert(endTime > startTime);
402 assert(timeSampling > 0.0);
403
405 if (mCheckForRoots && pOdeSystem->CalculateStoppingEvent(startTime, rYValues) == true)
406 {
407 EXCEPTION("(Solve with sampling) Stopping event is true for initial condition");
408 }
409
410 SetupCvode(pOdeSystem, rYValues, startTime, maxStep);
411
412 TimeStepper stepper(startTime, endTime, timeSampling);
413#if CHASTE_SUNDIALS_VERSION >= 60000
414 N_Vector yout = N_VMake_Serial(rYValues.size(), &(rYValues[0]), CvodeContextManager::Instance()->GetSundialsContext());
415#else
416 N_Vector yout = N_VMake_Serial(rYValues.size(), &(rYValues[0]));
417#endif
418
419 // Set up ODE solution
420 OdeSolution solutions;
421 solutions.SetNumberOfTimeSteps(stepper.EstimateTimeSteps());
422 solutions.rGetSolutions().push_back(rYValues);
423 solutions.rGetTimes().push_back(startTime);
424 solutions.SetOdeSystemInformation(pOdeSystem->GetSystemInformation());
425
426 // Main time sampling loop
427 while (!stepper.IsTimeAtEnd() && !mStoppingEventOccurred)
428 {
429 // This should stop CVODE going past the end of where we wanted and interpolating back.
430 int ierr = CVodeSetStopTime(mpCvodeMem, stepper.GetNextTime());
431 assert(ierr == CV_SUCCESS);
432 UNUSED_OPT(ierr); // avoid unused var warning
433
434 double tend;
435 ierr = CVode(mpCvodeMem, stepper.GetNextTime(), yout, &tend, CV_NORMAL);
436 if (ierr < 0)
437 {
439 DeleteVector(yout);
440 CvodeError(ierr, "CVODE failed to solve system");
441 }
442 // Store solution
443 solutions.rGetSolutions().push_back(rYValues);
444 solutions.rGetTimes().push_back(tend);
445 if (ierr == CV_ROOT_RETURN)
446 {
447 // Stopping event occurred
449 mStoppingTime = tend;
450 }
451 mLastSolutionTime = tend;
452 stepper.AdvanceOneTimeStep();
453 }
454
455 // stepper.EstimateTimeSteps may have been an overestimate...
456 solutions.SetNumberOfTimeSteps(stepper.GetTotalTimeStepsTaken());
457
458 int ierr = CVodeGetLastStep(mpCvodeMem, &mLastInternalStepSize);
459 assert(ierr == CV_SUCCESS);
460 UNUSED_OPT(ierr); // avoid unused var warning
462 DeleteVector(yout);
463
464 return solutions;
465}
466
468 std::vector<double>& rYValues,
469 double startTime,
470 double endTime,
471 double maxStep)
472{
473 assert(endTime > startTime);
474
476 if (mCheckForRoots && pOdeSystem->CalculateStoppingEvent(startTime, rYValues) == true)
477 {
478 EXCEPTION("(Solve) Stopping event is true for initial condition");
479 }
480
481 SetupCvode(pOdeSystem, rYValues, startTime, maxStep);
482
483#if CHASTE_SUNDIALS_VERSION >= 60000
484 N_Vector yout = N_VMake_Serial(rYValues.size(), &(rYValues[0]), CvodeContextManager::Instance()->GetSundialsContext());
485#else
486 N_Vector yout = N_VMake_Serial(rYValues.size(), &(rYValues[0]));
487#endif
488
489 // This should stop CVODE going past the end of where we wanted and interpolating back.
490 int ierr = CVodeSetStopTime(mpCvodeMem, endTime);
491 assert(ierr == CV_SUCCESS);
492 UNUSED_OPT(ierr); // avoid unused var warning
493
494 double tend;
495 ierr = CVode(mpCvodeMem, endTime, yout, &tend, CV_NORMAL);
496 if (ierr < 0)
497 {
499 DeleteVector(yout);
500 CvodeError(ierr, "CVODE failed to solve system");
501 }
502 if (ierr == CV_ROOT_RETURN)
503 {
504 // Stopping event occurred
506 mStoppingTime = tend;
507 }
508 assert(NV_DATA_S(yout) == &(rYValues[0]));
509 assert(!NV_OWN_DATA_S(yout));
510
511 // long int steps;
512 // CVodeGetNumSteps(mpCvodeMem, &steps);
513 // std::cout << " Solved to " << endTime << " in " << steps << " steps.\n";
514
515 ierr = CVodeGetLastStep(mpCvodeMem, &mLastInternalStepSize);
516 assert(ierr == CV_SUCCESS);
517 UNUSED_OPT(ierr); // avoid unused var warning
518 RecordStoppingPoint(tend, yout);
519 DeleteVector(yout);
520}
521
522CvodeAdaptor::CvodeAdaptor(double relTol, double absTol)
524 mpCvodeMem(nullptr),
525#if CHASTE_SUNDIALS_VERSION >= 60000
526 mpSundialsContextManager(CvodeContextManager::Instance()),
527#endif
528 mRelTol(relTol),
529 mAbsTol(absTol),
530 mLastInternalStepSize(-0.0),
531 mMaxSteps(0),
532 mCheckForRoots(false),
533 mLastSolutionState(nullptr),
534 mLastSolutionTime(0.0),
535 mForceReset(false),
536 mForceMinimalReset(false)
537 ,
538 mpSundialsDenseMatrix(nullptr),
539 mpSundialsLinearSolver(nullptr)
540{
541}
542
548
550{
551 if (!mForceReset)
552 {
553 const unsigned size = GetVectorSize(yEnd);
555 for (unsigned i = 0; i < size; i++)
556 {
558 }
559 mLastSolutionTime = stopTime;
560 }
561}
562
563void CvodeAdaptor::SetTolerances(double relTol, double absTol)
564{
565 mRelTol = relTol;
566 mAbsTol = absTol;
567}
568
570{
571 return mRelTol;
572}
573
575{
576 return mAbsTol;
577}
578
583
588
589void CvodeAdaptor::SetMaxSteps(long int numSteps)
590{
591 mMaxSteps = numSteps;
592}
593
595{
596 return mMaxSteps;
597}
598
599// Serialization for Boost >= 1.36
#define EXCEPTION(message)
#define UNUSED_OPT(var)
#define CHASTE_CLASS_EXPORT(T)
void DeleteVector(VECTOR &rVec)
void CreateVectorIfEmpty(VECTOR &rVec, unsigned size)
void SetVectorComponent(VECTOR &rVec, unsigned index, double value)
double GetVectorComponent(const VECTOR &rVec, unsigned index)
unsigned GetVectorSize(const VECTOR &rVec)
void CopyFromStdVector(const std::vector< double > &rSrc, VECTOR &rDest)
void CopyToStdVector(const VECTOR &rSrc, std::vector< double > &rDest)
virtual double CalculateRootFunction(double time, const std::vector< double > &rY)
virtual void EvaluateYDerivatives(double time, const std::vector< double > &rY, std::vector< double > &rDY)=0
virtual bool CalculateStoppingEvent(double time, const std::vector< double > &rY)
boost::shared_ptr< const AbstractOdeSystemInformation > GetSystemInformation() const
static bool WithinAnyTolerance(double number1, double number2, double relTol=DBL_EPSILON, double absTol=DBL_EPSILON, bool printError=false)
double GetAbsoluteTolerance()
long int GetMaxSteps()
void CvodeError(int flag, const char *msg)
long int mMaxSteps
void SetMinimalReset(bool minimalReset)
N_Vector mLastSolutionState
SUNLinearSolver mpSundialsLinearSolver
void SetMaxSteps(long int numSteps)
void FreeCvodeMemory()
void SetForceReset(bool autoReset)
void CheckForStoppingEvents()
CvodeData mData
OdeSolution Solve(AbstractOdeSystem *pOdeSystem, std::vector< double > &rYValues, double startTime, double endTime, double maxStep, double timeSampling)
double GetLastStepSize()
double mLastInternalStepSize
void RecordStoppingPoint(double stopTime, N_Vector yEnd)
void SetupCvode(AbstractOdeSystem *pOdeSystem, std::vector< double > &rInitialY, double startTime, double maxStep)
double mLastSolutionTime
double GetRelativeTolerance()
SUNMatrix mpSundialsDenseMatrix
void SetTolerances(double relTol=1e-4, double absTol=1e-6)
CvodeAdaptor(double relTol=1e-4, double absTol=1e-6)
void SetNumberOfTimeSteps(unsigned numTimeSteps)
std::vector< std::vector< double > > & rGetSolutions()
std::vector< double > & rGetTimes()
void SetOdeSystemInformation(boost::shared_ptr< const AbstractOdeSystemInformation > pOdeSystemInfo)
bool IsTimeAtEnd() const
unsigned GetTotalTimeStepsTaken() const
void AdvanceOneTimeStep()
double GetNextTime() const
unsigned EstimateTimeSteps() const
AbstractOdeSystem * pSystem
std::vector< realtype > * pY