diff --git a/CHANGELOG.md b/CHANGELOG.md index 74abb6520e..6d48f922d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,12 @@ specific linker flags e.g., MKL. ### Bug Fixes +Removed error floors from the SUNAdaptController implementations which could +unnecessarily limit the time size growth, particularly after the first step. + +On the first two time steps, the Soderlind controller uses an I controller +instead of omitting unavailable terms. + Fixed `ARKodeResize` not using the default `hscale` when an argument of `0` was provided. diff --git a/doc/shared/RecentChanges.rst b/doc/shared/RecentChanges.rst index fc2568901e..634e767fda 100644 --- a/doc/shared/RecentChanges.rst +++ b/doc/shared/RecentChanges.rst @@ -21,6 +21,13 @@ specific linker flags e.g., MKL. **Bug Fixes** +Removed error floors from the SUNAdaptController implementations which could +unnecessarily limit the time size growth, particularly after the first step. + +On the first two time steps, the +:ref:`Soderlind controller ` uses an I controller +instead of omitting unavailable terms. + Fixed c:func:`ARKodeResize` not using the default ``hscale`` when an argument of ``0`` was provided. diff --git a/doc/shared/sunadaptcontroller/SUNAdaptController_ImExGus.rst b/doc/shared/sunadaptcontroller/SUNAdaptController_ImExGus.rst index 0afdf7d786..11284031c3 100644 --- a/doc/shared/sunadaptcontroller/SUNAdaptController_ImExGus.rst +++ b/doc/shared/sunadaptcontroller/SUNAdaptController_ImExGus.rst @@ -45,9 +45,7 @@ with the form In the above formulas, the default values of :math:`k_1^E`, :math:`k_2^E`, :math:`k_1^I`, and :math:`k_2^I` are 0.367, 0.268, 0.98, and 0.95, respectively, -and :math:`p` is the global order of the time integration method. In these -estimates, a floor of :math:`\varepsilon_* > 10^{-10}` is enforced to avoid -division-by-zero errors. +and :math:`p` is the global order of the time integration method. The SUNAdaptController_ImExGus controller implements both formulas :eq:`expGusController` and :eq:`impGusController`, and sets its recommended step diff --git a/doc/shared/sunadaptcontroller/SUNAdaptController_Soderlind.rst b/doc/shared/sunadaptcontroller/SUNAdaptController_Soderlind.rst index 0c1b0cc620..830a7f2d17 100644 --- a/doc/shared/sunadaptcontroller/SUNAdaptController_Soderlind.rst +++ b/doc/shared/sunadaptcontroller/SUNAdaptController_Soderlind.rst @@ -27,11 +27,10 @@ and :cite:p:`Sod:06`. This controller has the form with default parameter values :math:`k_1 = 1.25`, :math:`k_2 = 0.5`, :math:`k_3 = -0.75`, :math:`k_4 = 0.25`, and :math:`k_5 = 0.75`, where -:math:`p` is the global order of the time integration method. In this estimate, -a floor of :math:`\varepsilon_* > 10^{-10}` is enforced to avoid division-by-zero -errors. During the first two steps (when :math:`\varepsilon_{n-2}`, -:math:`\varepsilon_{n-1}`, :math:`h_{n-2}`, and :math:`h_{n-2}` may be unavailable), -the corresponding terms are merely omitted during estimation of :math:`h'`. +:math:`p` is the global order of the time integration method. During the first +two steps (when :math:`\varepsilon_{n-2}`, :math:`\varepsilon_{n-1}`, +:math:`h_{n-2}`, and :math:`h_{n-2}` may be unavailable), the I controller +:math:`h' = h_n \varepsilon_1^{-1/(p+1)}` is used. The SUNAdaptController_Soderlind controller is implemented as a derived SUNAdaptController class, and defines its *content* field as: diff --git a/include/sundials/sundials_math.h b/include/sundials/sundials_math.h index 4585c7c7f4..c89be0af1e 100644 --- a/include/sundials/sundials_math.h +++ b/include/sundials/sundials_math.h @@ -160,18 +160,27 @@ extern "C" { /* * ----------------------------------------------------------------- - * Function : SUNRpowerI + * Function : SUNRcopysign * ----------------------------------------------------------------- - * Usage : int exponent; - * sunrealtype base, ans; - * ans = SUNRpowerI(base,exponent); + * Usage : sunrealtype z; + * z = SUNRcopysign(x, y); * ----------------------------------------------------------------- - * SUNRpowerI returns the value of base^exponent, where base is of type - * sunrealtype and exponent is of type int. + * SUNRcopysign(x, y) returns x with the sign of y. * ----------------------------------------------------------------- */ -SUNDIALS_EXPORT sunrealtype SUNRpowerI(sunrealtype base, int exponent); +#ifndef SUNRcopysign +#if defined(SUNDIALS_DOUBLE_PRECISION) +#define SUNRcopysign(x, y) (copysign((x), (y))) +#elif defined(SUNDIALS_SINGLE_PRECISION) +#define SUNRcopysign(x, y) (copysignf((x), (y))) +#elif defined(SUNDIALS_EXTENDED_PRECISION) +#define SUNRcopysign(x, y) (copysignl((x), (y))) +#else +#error \ + "SUNDIALS precision not defined, report to github.com/LLNL/sundials/issues" +#endif +#endif /* * ----------------------------------------------------------------- @@ -181,12 +190,36 @@ SUNDIALS_EXPORT sunrealtype SUNRpowerI(sunrealtype base, int exponent); * ans = SUNRpowerR(base,exponent); * ----------------------------------------------------------------- * SUNRpowerR returns the value of base^exponent, where both base and - * exponent are of type sunrealtype. If base < ZERO, then SUNRpowerR - * returns ZERO. + * exponent are of type sunrealtype. + * ----------------------------------------------------------------- + */ +#ifndef SUNRpowerR +#if defined(SUNDIALS_DOUBLE_PRECISION) +#define SUNRpowerR(base, exponent) (pow(base, exponent)) +#elif defined(SUNDIALS_SINGLE_PRECISION) +#define SUNRpowerR(base, exponent) (powf(base, exponent)) +#elif defined(SUNDIALS_EXTENDED_PRECISION) +#define SUNRpowerR(base, exponent) (powl(base, exponent)) +#else +#error \ + "SUNDIALS precision not defined, report to github.com/LLNL/sundials/issues" +#endif +#endif + +/* + * ----------------------------------------------------------------- + * Function : SUNRpowerI + * ----------------------------------------------------------------- + * Usage : int exponent; + * sunrealtype base, ans; + * ans = SUNRpowerI(base,exponent); + * ----------------------------------------------------------------- + * SUNRpowerI returns the value of base^exponent, where base is of type + * sunrealtype and exponent is of type int. * ----------------------------------------------------------------- */ -SUNDIALS_EXPORT sunrealtype SUNRpowerR(sunrealtype base, sunrealtype exponent); +SUNDIALS_EXPORT sunrealtype SUNRpowerI(sunrealtype base, int exponent); /* * ----------------------------------------------------------------- diff --git a/src/arkode/arkode_adapt.c b/src/arkode/arkode_adapt.c index ada5cda343..564414ce84 100644 --- a/src/arkode/arkode_adapt.c +++ b/src/arkode/arkode_adapt.c @@ -98,7 +98,7 @@ int arkAdapt(ARKodeMem ark_mem, ARKodeHAdaptMem hadapt_mem, N_Vector ycur, sunrealtype tcur, sunrealtype hcur, sunrealtype dsm) { int retval; - sunrealtype h_acc, h_cfl, int_dir; + sunrealtype h_acc, h_cfl; int controller_order; /* Return with no stepsize adjustment if the controller is NULL */ @@ -130,9 +130,6 @@ int arkAdapt(ARKodeMem ark_mem, ARKodeHAdaptMem hadapt_mem, N_Vector ycur, return (ARK_CONTROLLER_ERR); } - /* determine direction of integration */ - int_dir = hcur / SUNRabs(hcur); - /* Call explicit stability function */ retval = hadapt_mem->expstab(ycur, tcur, &h_cfl, hadapt_mem->estab_data); if (retval != ARK_SUCCESS) @@ -141,7 +138,7 @@ int arkAdapt(ARKodeMem ark_mem, ARKodeHAdaptMem hadapt_mem, N_Vector ycur, "Error in explicit stability function."); return (ARK_ILL_INPUT); } - if (h_cfl <= ZERO) { h_cfl = SUN_RCONST(1.0e30) * SUNRabs(hcur); } + if (h_cfl <= ZERO) { h_cfl = INFINITY; } #if SUNDIALS_LOGGING_LEVEL >= SUNDIALS_LOGGING_INFO SUNLogger_QueueMsg(ARK_LOGGER, SUN_LOGLEVEL_INFO, "ARKODE::arkAdapt", @@ -151,34 +148,39 @@ int arkAdapt(ARKodeMem ark_mem, ARKodeHAdaptMem hadapt_mem, N_Vector ycur, /* enforce safety factors */ h_acc *= hadapt_mem->safety; - h_cfl *= hadapt_mem->cfl * int_dir; + h_cfl *= hadapt_mem->cfl; /* enforce maximum bound on time step growth */ - h_acc = int_dir * SUNMIN(SUNRabs(h_acc), SUNRabs(hadapt_mem->etamax * hcur)); + h_acc = SUNMIN(SUNRabs(h_acc), SUNRabs(hadapt_mem->etamax * hcur)); /* enforce minimum bound time step reduction */ - h_acc = int_dir * SUNMAX(SUNRabs(h_acc), SUNRabs(hadapt_mem->etamin * hcur)); + h_acc = SUNMAX(h_acc, SUNRabs(hadapt_mem->etamin * hcur)); #if SUNDIALS_LOGGING_LEVEL >= SUNDIALS_LOGGING_INFO SUNLogger_QueueMsg(ARK_LOGGER, SUN_LOGLEVEL_INFO, "ARKODE::arkAdapt", "new-step-after-max-min-bounds", - "h_acc = %" RSYM ", h_cfl = %" RSYM, h_acc, h_cfl); + "h_acc = %" RSYM ", h_cfl = %" RSYM, + SUNRcopysign(h_acc, hcur), h_cfl); #endif /* increment the relevant step counter, set desired step */ - if (SUNRabs(h_acc) < SUNRabs(h_cfl)) { hadapt_mem->nst_acc++; } - else { hadapt_mem->nst_exp++; } - h_acc = int_dir * SUNMIN(SUNRabs(h_acc), SUNRabs(h_cfl)); + if (h_acc <= h_cfl) { hadapt_mem->nst_acc++; } + else + { + hadapt_mem->nst_exp++; + h_acc = h_cfl; + } /* enforce adaptivity bounds to retain Jacobian/preconditioner accuracy */ if (dsm <= ONE) { - if ((SUNRabs(h_acc) > SUNRabs(hcur * hadapt_mem->lbound * ONEMSM)) && - (SUNRabs(h_acc) < SUNRabs(hcur * hadapt_mem->ubound * ONEPSM))) + if ((h_acc > SUNRabs(hcur * hadapt_mem->lbound * ONEMSM)) && + (h_acc < SUNRabs(hcur * hadapt_mem->ubound * ONEPSM))) { h_acc = hcur; } } + h_acc = SUNRcopysign(h_acc, hcur); /* set basic value of ark_eta */ ark_mem->eta = h_acc / hcur; diff --git a/src/sunadaptcontroller/imexgus/sunadaptcontroller_imexgus.c b/src/sunadaptcontroller/imexgus/sunadaptcontroller_imexgus.c index e4bb23e6dd..0bbcda4b0a 100644 --- a/src/sunadaptcontroller/imexgus/sunadaptcontroller_imexgus.c +++ b/src/sunadaptcontroller/imexgus/sunadaptcontroller_imexgus.c @@ -48,7 +48,6 @@ #define DEFAULT_K1I SUN_RCONST(0.95) #define DEFAULT_K2I SUN_RCONST(0.95) #define DEFAULT_BIAS SUN_RCONST(1.5) -#define TINY SUN_RCONST(1.0e-10) /* ----------------------------------------------------------------- * exported functions @@ -128,32 +127,38 @@ SUNErrCode SUNAdaptController_EstimateStep_ImExGus(SUNAdaptController C, { SUNFunctionBegin(C->sunctx); - SUNAssert(hnew, SUN_ERR_ARG_CORRUPT); - /* order parameter to use */ const int ord = p + 1; - /* set usable time-step adaptivity parameters -- first step */ - const sunrealtype k = -SUN_RCONST(1.0) / ord; - const sunrealtype e = SUNMAX(SACIMEXGUS_BIAS(C) * dsm, TINY); - - /* set usable time-step adaptivity parameters -- subsequent steps */ - const sunrealtype k1e = -SACIMEXGUS_K1E(C) / ord; - const sunrealtype k2e = -SACIMEXGUS_K2E(C) / ord; - const sunrealtype k1i = -SACIMEXGUS_K1I(C) / ord; - const sunrealtype k2i = -SACIMEXGUS_K2I(C) / ord; - const sunrealtype e1 = SUNMAX(SACIMEXGUS_BIAS(C) * dsm, TINY); - const sunrealtype e2 = e1 / SUNMAX(SACIMEXGUS_EP(C), TINY); - const sunrealtype hrat = h / SACIMEXGUS_HP(C); - /* compute estimated time step size, modifying the first step formula */ - if (SACIMEXGUS_FIRSTSTEP(C)) { *hnew = h * SUNRpowerR(e, k); } + if (SACIMEXGUS_FIRSTSTEP(C)) + { + /* set usable time-step adaptivity parameters -- first step */ + const sunrealtype k = -SUN_RCONST(1.0) / ord; + const sunrealtype e = SACIMEXGUS_BIAS(C) * dsm; + *hnew = h * SUNRpowerR(e, k); + } else { + /* set usable time-step adaptivity parameters -- subsequent steps */ + const sunrealtype k1e = -SACIMEXGUS_K1E(C) / ord; + const sunrealtype k2e = -SACIMEXGUS_K2E(C) / ord; + const sunrealtype k1i = -SACIMEXGUS_K1I(C) / ord; + const sunrealtype k2i = -SACIMEXGUS_K2I(C) / ord; + const sunrealtype e1 = SACIMEXGUS_BIAS(C) * dsm; + const sunrealtype e2 = e1 / SACIMEXGUS_EP(C); + const sunrealtype hrat = h / SACIMEXGUS_HP(C); *hnew = h * SUNMIN(hrat * SUNRpowerR(e1, k1i) * SUNRpowerR(e2, k2i), SUNRpowerR(e1, k1e) * SUNRpowerR(e2, k2e)); } + if (!isfinite(*hnew)) + { + /* hnew can be INFINITY or NAN if multiple e's are 0 or there are overflows. + * In that case, make hnew INFINITY with the same sign as h */ + *hnew = SUNRcopysign(INFINITY, h); + } + /* return with success */ return SUN_SUCCESS; } diff --git a/src/sunadaptcontroller/soderlind/sunadaptcontroller_soderlind.c b/src/sunadaptcontroller/soderlind/sunadaptcontroller_soderlind.c index bca88252cb..ebee48b849 100644 --- a/src/sunadaptcontroller/soderlind/sunadaptcontroller_soderlind.c +++ b/src/sunadaptcontroller/soderlind/sunadaptcontroller_soderlind.c @@ -62,7 +62,6 @@ #define DEFAULT_IMPGUS_K1 SUN_RCONST(0.98) /* Implicit Gustafsson parameters */ #define DEFAULT_IMPGUS_K2 SUN_RCONST(0.95) #define DEFAULT_BIAS SUN_RCONST(1.5) -#define TINY SUN_RCONST(1.0e-10) /* ----------------------------------------------------------------- * exported functions @@ -76,12 +75,8 @@ SUNAdaptController SUNAdaptController_Soderlind(SUNContext sunctx) { SUNFunctionBegin(sunctx); - SUNAdaptController C; - SUNAdaptControllerContent_Soderlind content; - /* Create an empty controller object */ - C = NULL; - C = SUNAdaptController_NewEmpty(sunctx); + SUNAdaptController C = SUNAdaptController_NewEmpty(sunctx); SUNCheckLastErrNull(); /* Attach operations */ @@ -95,12 +90,9 @@ SUNAdaptController SUNAdaptController_Soderlind(SUNContext sunctx) C->ops->space = SUNAdaptController_Space_Soderlind; /* Create content */ - content = NULL; - content = (SUNAdaptControllerContent_Soderlind)malloc(sizeof *content); - SUNAssertNull(content, SUN_ERR_MALLOC_FAIL); - - /* Attach content */ - C->content = content; + C->content = (SUNAdaptControllerContent_Soderlind)malloc( + sizeof(struct _SUNAdaptControllerContent_Soderlind)); + SUNAssertNull(C->content, SUN_ERR_MALLOC_FAIL); /* Fill content with default/reset values */ SUNCheckCallNull(SUNAdaptController_SetDefaults_Soderlind(C)); @@ -169,8 +161,7 @@ SUNAdaptController SUNAdaptController_PI(SUNContext sunctx) { SUNFunctionBegin(sunctx); - SUNAdaptController C; - C = SUNAdaptController_Soderlind(sunctx); + SUNAdaptController C = SUNAdaptController_Soderlind(sunctx); SUNCheckLastErrNull(); SUNCheckCallNull( @@ -203,8 +194,7 @@ SUNAdaptController SUNAdaptController_I(SUNContext sunctx) { SUNFunctionBegin(sunctx); - SUNAdaptController C; - C = SUNAdaptController_Soderlind(sunctx); + SUNAdaptController C = SUNAdaptController_Soderlind(sunctx); SUNCheckLastErrNull(); SUNCheckCallNull(SUNAdaptController_SetParams_I(C, DEFAULT_I_K1)); @@ -235,8 +225,7 @@ SUNAdaptController SUNAdaptController_ExpGus(SUNContext sunctx) { SUNFunctionBegin(sunctx); - SUNAdaptController C; - C = SUNAdaptController_Soderlind(sunctx); + SUNAdaptController C = SUNAdaptController_Soderlind(sunctx); SUNCheckLastErrNull(); SUNCheckCallNull(SUNAdaptController_SetParams_ExpGus(C, DEFAULT_EXPGUS_K1, @@ -269,8 +258,7 @@ SUNAdaptController SUNAdaptController_ImpGus(SUNContext sunctx) { SUNFunctionBegin(sunctx); - SUNAdaptController C; - C = SUNAdaptController_Soderlind(sunctx); + SUNAdaptController C = SUNAdaptController_Soderlind(sunctx); SUNCheckLastErrNull(); SUNCheckCallNull(SUNAdaptController_SetParams_ImpGus(C, DEFAULT_IMPGUS_K1, @@ -311,33 +299,40 @@ SUNErrCode SUNAdaptController_EstimateStep_Soderlind(SUNAdaptController C, sunrealtype* hnew) { SUNFunctionBegin(C->sunctx); - SUNAssert(hnew, SUN_ERR_ARG_CORRUPT); /* order parameter to use */ const int ord = p + 1; - /* set usable time-step adaptivity parameters */ - const sunrealtype k1 = -SODERLIND_K1(C) / ord; - const sunrealtype k2 = -SODERLIND_K2(C) / ord; - const sunrealtype k3 = -SODERLIND_K3(C) / ord; - const sunrealtype k4 = SODERLIND_K4(C); - const sunrealtype k5 = SODERLIND_K5(C); - const sunrealtype e1 = SUNMAX(SODERLIND_BIAS(C) * dsm, TINY); - const sunrealtype e2 = SUNMAX(SODERLIND_EP(C), TINY); - const sunrealtype e3 = SUNMAX(SODERLIND_EPP(C), TINY); - const sunrealtype hrat = h / SODERLIND_HP(C); - const sunrealtype hrat2 = SODERLIND_HP(C) / SODERLIND_HPP(C); - - /* compute estimated optimal time step size */ - if (SODERLIND_FIRSTSTEPS(C) < 1) { *hnew = h * SUNRpowerR(e1, k1); } - else if (SODERLIND_FIRSTSTEPS(C) < 2) + if (SODERLIND_FIRSTSTEPS(C) > 1) { - *hnew = h * SUNRpowerR(e1, k1) * SUNRpowerR(e2, k2) * SUNRpowerR(hrat, k4); + /* After the first 2 steps, there is sufficient history */ + const sunrealtype k1 = -SODERLIND_K1(C) / ord; + const sunrealtype k2 = -SODERLIND_K2(C) / ord; + const sunrealtype k3 = -SODERLIND_K3(C) / ord; + const sunrealtype k4 = SODERLIND_K4(C); + const sunrealtype k5 = SODERLIND_K5(C); + const sunrealtype e1 = SODERLIND_BIAS(C) * dsm; + const sunrealtype e2 = SODERLIND_EP(C); + const sunrealtype e3 = SODERLIND_EPP(C); + const sunrealtype hrat = h / SODERLIND_HP(C); + const sunrealtype hrat2 = SODERLIND_HP(C) / SODERLIND_HPP(C); + + *hnew = h * SUNRpowerR(e1, k1) * SUNRpowerR(e2, k2) * SUNRpowerR(e3, k3) * + SUNRpowerR(hrat, k4) * SUNRpowerR(hrat2, k5); } else { - *hnew = h * SUNRpowerR(e1, k1) * SUNRpowerR(e2, k2) * SUNRpowerR(e3, k3) * - SUNRpowerR(hrat, k4) * SUNRpowerR(hrat2, k5); + /* Use an I controller on the first two steps */ + const sunrealtype k = -SUN_RCONST(1.0) / ord; + const sunrealtype e = SODERLIND_BIAS(C) * dsm; + *hnew = h * SUNRpowerR(e, k); + } + + if (!isfinite(*hnew)) + { + /* hnew can be INFINITY or NAN if multiple e's are 0 or there are overflows. + * In that case, make hnew INFINITY with the same sign as h */ + *hnew = SUNRcopysign(INFINITY, h); } /* return with success */ diff --git a/src/sundials/sundials_adaptcontroller.c b/src/sundials/sundials_adaptcontroller.c index 39a56480d0..124ad3f15b 100644 --- a/src/sundials/sundials_adaptcontroller.c +++ b/src/sundials/sundials_adaptcontroller.c @@ -16,6 +16,7 @@ * operations listed in sundials_adaptcontroller.h * -----------------------------------------------------------------*/ +#include #include #include @@ -131,6 +132,9 @@ SUNErrCode SUNAdaptController_EstimateStep(SUNAdaptController C, sunrealtype h, SUNErrCode ier = SUN_SUCCESS; if (C == NULL) { return SUN_ERR_ARG_CORRUPT; } SUNFunctionBegin(C->sunctx); + SUNAssert(isfinite(h), SUN_ERR_ARG_OUTOFRANGE); + SUNAssert(p >= 0, SUN_ERR_ARG_OUTOFRANGE); + SUNAssert(dsm >= SUN_RCONST(0.0), SUN_ERR_ARG_OUTOFRANGE); SUNAssert(hnew, SUN_ERR_ARG_CORRUPT); *hnew = h; /* initialize output with identity */ if (C->ops->estimatestep) { ier = C->ops->estimatestep(C, h, p, dsm, hnew); } @@ -170,6 +174,7 @@ SUNErrCode SUNAdaptController_SetErrorBias(SUNAdaptController C, sunrealtype bia SUNErrCode ier = SUN_SUCCESS; if (C == NULL) { return SUN_ERR_ARG_CORRUPT; } SUNFunctionBegin(C->sunctx); + SUNAssert(bias >= SUN_RCONST(1.0), SUN_ERR_ARG_OUTOFRANGE); if (C->ops->seterrorbias) { ier = C->ops->seterrorbias(C, bias); } return (ier); } @@ -180,6 +185,8 @@ SUNErrCode SUNAdaptController_UpdateH(SUNAdaptController C, sunrealtype h, SUNErrCode ier = SUN_SUCCESS; if (C == NULL) { return SUN_ERR_ARG_CORRUPT; } SUNFunctionBegin(C->sunctx); + SUNAssert(isfinite(h), SUN_ERR_ARG_OUTOFRANGE); + SUNAssert(dsm >= SUN_RCONST(0.0), SUN_ERR_ARG_OUTOFRANGE); if (C->ops->updateh) { ier = C->ops->updateh(C, h, dsm); } return (ier); } diff --git a/src/sundials/sundials_math.c b/src/sundials/sundials_math.c index b60e9c2540..4e4122c7be 100644 --- a/src/sundials/sundials_math.c +++ b/src/sundials/sundials_math.c @@ -34,22 +34,6 @@ sunrealtype SUNRpowerI(sunrealtype base, int exponent) return (prod); } -sunrealtype SUNRpowerR(sunrealtype base, sunrealtype exponent) -{ - if (base <= SUN_RCONST(0.0)) { return (SUN_RCONST(0.0)); } - -#if defined(SUNDIALS_DOUBLE_PRECISION) - return (pow(base, exponent)); -#elif defined(SUNDIALS_SINGLE_PRECISION) - return (powf(base, exponent)); -#elif defined(SUNDIALS_EXTENDED_PRECISION) - return (powl(base, exponent)); -#else -#error \ - "SUNDIALS precision not defined, report to github.com/LLNL/sundials/issues" -#endif -} - sunbooleantype SUNRCompare(sunrealtype a, sunrealtype b) { return (SUNRCompareTol(a, b, 10 * SUN_UNIT_ROUNDOFF)); diff --git a/test/unit_tests/arkode/C_serial/CMakeLists.txt b/test/unit_tests/arkode/C_serial/CMakeLists.txt index 6794a213b4..3670b76da4 100644 --- a/test/unit_tests/arkode/C_serial/CMakeLists.txt +++ b/test/unit_tests/arkode/C_serial/CMakeLists.txt @@ -16,6 +16,7 @@ # List of test tuples of the form "name\;args" set(ARKODE_unit_tests + "ark_test_adapt\;" "ark_test_arkstepsetforcing\;1 0" "ark_test_arkstepsetforcing\;1 1" "ark_test_arkstepsetforcing\;1 2" diff --git a/test/unit_tests/arkode/C_serial/ark_test_adapt.c b/test/unit_tests/arkode/C_serial/ark_test_adapt.c new file mode 100644 index 0000000000..8885668e2d --- /dev/null +++ b/test/unit_tests/arkode/C_serial/ark_test_adapt.c @@ -0,0 +1,172 @@ +/* ----------------------------------------------------------------------------- + * Programmer(s): Steven B. Roberts @ LLNL + * ----------------------------------------------------------------------------- + * SUNDIALS Copyright Start + * Copyright (c) 2002-2024, Lawrence Livermore National Security + * and Southern Methodist University. + * All rights reserved. + * + * See the top-level LICENSE and NOTICE files for details. + * + * SPDX-License-Identifier: BSD-3-Clause + * SUNDIALS Copyright End + * ----------------------------------------------------------------------------- + * Unit test for step size growth and handling of inf/nan within a controller + * due to 0 local error. + * ---------------------------------------------------------------------------*/ + +#include +#include + +#include "arkode/arkode_erkstep.h" +#include "nvector/nvector_serial.h" + +/* If an error occurs, print to stderr and exit */ +static void err_fn(const int line, const char* const func, const char* const file, + const char* const msg, const SUNErrCode err_code, + void* const err_user_data, const SUNContext sunctx) +{ + fprintf(stderr, "Error at line %i of %s in %s: %s\n", line, func, file, msg); + exit(err_code); +} + +/* RHS for the simple ODE y'=0 */ +static int f(const sunrealtype t, const N_Vector y, const N_Vector ydot, + void* const user_data) +{ + N_VConst(SUN_RCONST(0.0), ydot); + return 0; +} + +/* Take a single time step solving y'=0 and check the max growth was attained */ +static int check_step(void* const arkode_mem, const N_Vector y, + const sunrealtype h_expected, const int step) +{ + /* Integration much farther than expected step */ + const sunrealtype tout = SUN_RCONST(100.0) * h_expected; + sunrealtype tret; + /* The ERK method should be able to take the maximum possible timestep without + any rejected steps or local error */ + ARKodeEvolve(arkode_mem, tout, y, &tret, ARK_ONE_STEP); + + long int local_err_fails; + ARKodeGetNumErrTestFails(arkode_mem, &local_err_fails); + if (local_err_fails != 0) + { + fprintf(stderr, "Expected 0 local error failures at step %i but is %li\n", + step, local_err_fails); + return 1; + } + + const N_Vector err = N_VClone(y); + ARKodeGetEstLocalErrors(arkode_mem, err); + const sunrealtype err_norm = N_VMaxNorm(err); + N_VDestroy(err); + + if (err_norm != SUN_RCONST(0.0)) + { + fprintf(stderr, "Expected local error at step %i to be 0 but is %g\n", step, + err_norm); + return 1; + } + + sunrealtype h_actual; + ARKodeGetCurrentStep(arkode_mem, &h_actual); + if (SUNRCompare(h_expected, h_actual)) + { + fprintf(stderr, "Expected h at step %i to be %g but is %g\n", step, + h_expected, h_actual); + return 1; + } + + return 0; +} + +/* Take several steps solving y'=0 and check the max growth was attained */ +static int check_steps(void* const arkode_mem, const N_Vector y, + const sunrealtype h0, const sunrealtype first_growth, + const sunrealtype growth) +{ + ARKodeSetInitStep(arkode_mem, h0); + ARKodeSetMaxFirstGrowth(arkode_mem, first_growth); + ARKodeSetMaxGrowth(arkode_mem, growth); + + sunrealtype h_expect = first_growth * h0; + /* Take enough steps to allow the controller history to fill up */ + const int num_steps = 5; + for (int step = 1; step <= num_steps; step++) + { + const int retval = check_step(arkode_mem, y, h_expect, step); + if (retval != 0) { return retval; } + h_expect *= growth; + } + + return 0; +} + +int main() +{ + SUNContext sunctx; + int retval = SUNContext_Create(SUN_COMM_NULL, &sunctx); + if (retval != 0) + { + fprintf(stderr, "SUNContext_Create returned %i\n", retval); + return 1; + } + + retval = SUNContext_PushErrHandler(sunctx, err_fn, NULL); + if (retval != 0) + { + fprintf(stderr, "SUNContext_PushErrHandler returned %i\n", retval); + return 1; + } + + const N_Vector y = N_VNew_Serial(1, sunctx); + N_VConst(SUN_RCONST(1.0), y); + + /* Forward integration from 0 */ + void* arkode_mem = ERKStepCreate(f, SUN_RCONST(0.0), y, sunctx); + retval = check_steps(arkode_mem, y, SUN_RCONST(1.0e-4), SUN_RCONST(1234.0), + SUN_RCONST(3.0)); + if (retval != 0) { return retval; } + + /* Backward integration from positive time */ + ERKStepReInit(arkode_mem, f, SUN_RCONST(999.0), y); + retval = check_steps(arkode_mem, y, SUN_RCONST(-1.0e-2), SUN_RCONST(1.6), + SUN_RCONST(2.3)); + if (retval != 0) { return retval; } + + /* Forward integration from a negative time */ + ERKStepReInit(arkode_mem, f, SUN_RCONST(-999.0), y); + retval = check_steps(arkode_mem, y, SUN_RCONST(20.0), SUN_RCONST(1.0e5), + SUN_RCONST(1.1e3)); + if (retval != 0) { return retval; } + + /* Try a non-default controller */ + ERKStepReInit(arkode_mem, f, SUN_RCONST(0.0), y); + SUNAdaptController controller1 = SUNAdaptController_ExpGus(sunctx); + ARKodeSetAdaptController(arkode_mem, controller1); + retval = check_steps(arkode_mem, y, SUN_RCONST(0.1), SUN_RCONST(4.0), + SUN_RCONST(10.0)); + if (retval != 0) { return retval; } + + /* Try another non-default controller */ + ERKStepReInit(arkode_mem, f, SUN_RCONST(0.0), y); + SUNAdaptController controller2 = SUNAdaptController_Soderlind(sunctx); + SUNAdaptController_SetParams_PI(controller2, SUN_RCONST(0.123), + SUN_RCONST(-0.456)); + ARKodeSetAdaptController(arkode_mem, controller2); + retval = check_steps(arkode_mem, y, SUN_RCONST(-0.1), SUN_RCONST(4.0), + SUN_RCONST(10.0)); + if (retval != 0) { return retval; } + + SUNAdaptController_Destroy(controller1); + SUNAdaptController_Destroy(controller2); + ARKodeFree(&arkode_mem); + N_VDestroy(y); + SUNContext_Free(&sunctx); + + printf("SUCCESS\n"); + + return 0; +}