-
Notifications
You must be signed in to change notification settings - Fork 117
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SUNStepper basics based on MRIStepInnerStepper #463
base: develop
Are you sure you want to change the base?
Conversation
@drreynolds @gardner48 I have made the changes we discussed on Tuesday. Let me know what you think. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like how clean this is now. I noticed a few items for comment (see below).
/* TryStep step result flags */ | ||
#define TRYSTEP_FAILED -1 | ||
#define TRYSTEP_SUCCESS +0 | ||
#define TRYSTEP_ADAPT +1 | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should these be in include/sundials/sundials_stepper.h
instead?
src/arkode/xbraid/arkode_xbraid.c
Outdated
/* Step was successful and passed the error test */ | ||
*ark_flag = STEP_SUCCESS; | ||
return ARK_SUCCESS; | ||
return arkStep_TryStep(arkode_mem, tstart, tstop, y, ark_flag); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a nice simplification!
if (!stepper->fused_vectors) | ||
{ | ||
stepper->fused_vectors = N_VNewVectorArray(stepper->nforcing, SUNCTX_); | ||
SUNCheckLastErr(); | ||
} | ||
|
||
if (!stepper->fused_scalars) | ||
{ | ||
stepper->fused_scalars = | ||
(sunrealtype*)calloc(count + 1, sizeof(*stepper->fused_scalars)); | ||
SUNAssert(stepper->fused_scalars, SUN_ERR_MEM_FAIL); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't both of these also check to see whether the length of the already-allocated fused_scalars
and fused_vectors
arrays have length at least count
?
#endif | ||
|
||
typedef _SUNDIALS_STRUCT_ SUNStepper_s* SUNStepper; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@balos1 what is stop_reason
needed for? Wtih OneStep
and Advance
is it to distinguish between a successful return and a stop time return?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, or a root return.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you need to differentiate between between a tstop return and a root found return or is a positive return sufficient to indicate a successful return that's different from a normal successful (zero) return?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could imagine (but dont have a concrete example at the moment) that a root return might need special handling when checkpointing and/or during backwards integration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice it this could just encoded in the return value but having a SUNErrCode
also indicate a status would be mixing purposes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, I think its best to have the error code and status separated.
|
||
typedef int (*SUNStepperTryStepFn)(SUNStepper stepper, sunrealtype t0, | ||
sunrealtype tout, N_Vector y, | ||
sunrealtype* tret, int* stop_reason); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does everyone think about removing t0
as an input and haveing y
only be for output? That is, as stepper should always advance from it's last reached solution after a successful return unless something like Reset
or ReInit
is called.
Similarly, could tout
be removed from TryStep
and OneStep
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How would someone set the t0
to begin with? Are you assuming this would be done with the package create/init routines before it is wrapped as a stepper?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would expect this to be done with the stepper Create
or Init
function or alternatively Reset
could be called in the first step if we don't require a stepper to be initialized with the starting state.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would expect this to be done with the stepper Create or Init
Do you mean the package "steppers"? The SUNStepper
create does not currently have t0
(but it could) and it has no Init
function.
alternatively Reset could be called in the first step if we don't require a stepper to be initialized with the starting state
So we would assume tR = t0 = 0
then?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean the package "steppers"? The
SUNStepper
create does not currently havet0
(but it could) and it has noInit
function.
Yes, I was thinking the constructor for an implementation of a SUNStepper
(i.e., MyStepper(t0, y0, ctx)
) could take the initial condition. The package steppers are a little different since they wrap an integrator that was already created with the initial condition set. Alternatively having SUNStepper_Init(t0, y0)
of SUNStepper_Init(t0, y0, yp0)
might not be a bad idea.
So we would assume
tR = t0 = 0
then?
Just tR = t0
since t0
is not necessarily 0
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just tR = t0 since t0 is not necessarily 0
Well, yes, but my point was that if there was no Init
that took t0
(or Create
) then we would not know t0
.
The package steppers are a little different since they wrap an integrator that was already created with the initial condition set.
CVODE/IDA do not take the initial condition and t0
until their Init
routines are called. So you could have the case where a stepper is created with a integrator that does not yet have the IC/t0 set. With that in mind, I think having SUNStepper_Init
routine is cleaner.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CVODE/IDA do not take the initial condition and
t0
until theirInit
routines are called. So you could have the case where a stepper is created with a integrator that does not yet have the IC/t0 set. With that in mind, I think havingSUNStepper_Init
routine is cleaner.
I think have an Init
function is reasonable. As far as wrapping CVODE or IDA as a stepper, creating a stepper from a CVODE / IDA instance that was not initialized would be a user-error as the RHS / RES function would also not be set.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that David's alternate option of each consuming package calling Reset
is perhaps the most appropriate. This already occurs in MRIStep (or it would take a minimal change to do so), and I anticipate that the operator-splitting stepper would also want to control the initial condition for any sub-stepper.
#endif | ||
|
||
typedef _SUNDIALS_STRUCT_ SUNStepper_s* SUNStepper; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The functions below return an int
but the corresponding SUNStepper_*
functions return a SUNErrorCode
. Should these have the same return type?
#endif | ||
|
||
typedef _SUNDIALS_STRUCT_ SUNStepper_s* SUNStepper; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should the SUNStepper
support DAEs i.e., should there be y_ret
and yp_ret
for Advance
, OneStep
and TryStep
? I'm thinking there are three ways to handle this:
- Have
SUNODEStepper
andSUNDAEStepper
with different versions of these functions - Have ODE and DAE versions of the functions i.e.,
ODETryStep
andDAETryStep
with different signatures - Add
yp_ret
to each of the functions as an optional output i.e., allow passingNULL
foryp_ret
I'm thinking 3 might be the best approach.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also like 3.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like option 3
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed.
retval = SUNStepper_SetContent(*stepper, inner_arkode_mem); | ||
if (retval != ARK_SUCCESS) { return (retval); } | ||
|
||
retval = SUNStepper_SetAdvanceFn(*stepper, arkStep_SUNStepperAdvance); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Attach OneStepFn
and TryStepFn
also?
* Attempts one internal time step using ARKStepEvolve | ||
* ---------------------------------------------------------------------------*/ | ||
|
||
int arkStep_TryStep(void* arkode_mem, sunrealtype tstart, sunrealtype tstop, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The body of this function was taken from ARKBraid_TakeStep
and some of the behavior is specific to XBraid's use (having to reset before every step and use a specific step size) and I'm not sure if it's how we want to SUNStepper
to behave. This is related to my question above about having a stepper advance from its last reached state.
- If
TryStep
is called in a loop (i.e.,SUNStepper
could provide an implementation ofAdvance
that callsTryStep
as needed) the call toReset
could lead to unnecessary RHS evaluations. - Do we want the step size to be determined by
tstart
andtstop
or internally by the stepper?
*stepper = (SUNStepper)malloc(sizeof(**stepper)); | ||
SUNAssert(stepper, SUN_ERR_MALLOC_FAIL); | ||
|
||
memset(*stepper, 0, sizeof(**stepper)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could make the malloc
above a calloc
and remove the memset
(*stepper)->ops = (SUNStepper_Ops)malloc(sizeof(*((*stepper)->ops))); | ||
SUNAssert((*stepper)->ops, SUN_ERR_MALLOC_FAIL); | ||
|
||
memset((*stepper)->ops, 0, sizeof(*((*stepper)->ops))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could make the malloc
above a calloc
and remove the memset
return SUN_SUCCESS; | ||
} | ||
|
||
SUNErrCode SUNStepper_Advance(SUNStepper stepper, sunrealtype t0, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If TryStep
advances from the last time reached and if we add a SetStopTime
function, there could be a default implementation of Advance
that calls TryStep
return SUN_ERR_NOT_IMPLEMENTED; | ||
} | ||
|
||
SUNErrCode SUNStepper_Step(SUNStepper stepper, sunrealtype t0, sunrealtype tout, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Like Advance
we could have a default implementation that calls TryStep
#endif | ||
|
||
typedef _SUNDIALS_STRUCT_ SUNStepper_s* SUNStepper; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also like 3.
if (retval != ARK_SUCCESS) { return (retval); } | ||
|
||
/* set the stop time */ | ||
retval = ARKStepSetStopTime(arkode_mem, tout); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this needed? Maybe it could be an option. For operator splitting, it would be preferable not to have this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In our team discussions we defined the Advance
function to operate in this way. That is, we said Advance
would step to precisely tout
as opposed to Evolve
which would potentially step past tout
and interpolate back. MRIStep
needs this. It sounds like you need an Evolve
function for the operator splitting stepper.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I didn't realize we would have both Advance
and Evolve
. I that case, this is not an issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the last discussion we dropped Evolve
because it seemed like we did not need it. @Steven-Roberts for the splitting methods, would Advance
still be sufficient or do you need to step past the time the method is supposed to integrate to?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Advance
alone would be sufficient, but then I don't see a way to handle symmetric operator splitting methods most efficiently. As an example of the functionality needed, consider integration a stepper from t=0.5
to t=1.5
with output needed at t=1
. Two calls to Evolve
would be ideal for this, while Advance
might add more function evaluations by forcing the first call to end exactly at t=1
. Originally, I thought adding an interpolation function to the stepper was the way to fix this, but on second thought I don't think it's reasonable to have interpolation data across the entire tspan passed to Advance
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, it's sounding like we need Evolve
+ SetStopTime
rather than Advance
or Advance
with multiple output times.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Originally we talked about having four functions:
Evolve
: step to tout, possibly passing it and returning interpolated solution at t=toutAdvance
: step to tout, stopping exactly at tout and returnStep
: take one successful step (or fail trying to) and returnTryStep
: take exactly one internal step and return
We said we did not have a use for Evolve
yet, so I removed it from the SUNStepper
API for now. However, it seems the operator splitting stepper does necessitate an Evolve
function PLUS a SUNStepper_SetTStop
function.
If we add a SUNStepper_SetTStop
function, then it should only be used with Evolve
, NOT Advance
otherwise there is no reason to have Advance
as a function since it would work just like Evolve
. Furthermore, I believe MRIStep relies on Advance
working as defined above (if it does not, then I further question why we need Advance
at all).
An alternative to adding SUNStepper_SetTStop
+Evolve
is to let Evolve
and/or Advance
return outputs at multiple time (as David suggested). This is different than any of our packages (as Steven points out), but it is also something that pretty much every other time integration package lets you do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm leaning toward Evolve
+ SetStopTime
since Advance
(or even Evolve
) with multiple outputs could just be a wrapper looping over the output times and calling the one output Evolve
with the right output vector.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However, it seems the operator splitting stepper does necessitate an Evolve function PLUS a SUNStepper_SetTStop function.
It does need the Evolve
function. SetTStop
would only be needed if Advance
was removed (as it could be achieved by combining Evolve
and SetTStop
).
So I think the question is which set of functions to use
Evolve
,Advance
,Step
,TryStep
orEvolve
,SetTStop
,Step
,TryStep
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit late to the party, but it sounds to me like Evolve
+ SetStopTime
is the way to go. I'll note that like Steven, in the MERK multirate time-stepping methods I evolve over a longer duration, periodically storing the solution for some intermediate stages. It would be perfectly handled by this approach, whereas right now I anticipate that the MRIStepInnerStepper may take a small number of extra steps as it stops to return intermediate solutions. I'll defer to the rest of the group on the need for Step
and TryStep
usage modes.
{ | ||
SUNFunctionBegin(sunctx); | ||
|
||
*stepper = NULL; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unnecessary
sunrealtype tau, taui; | ||
int i; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These seem unnecessary
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah they are leftover from copy/pasting from MRIStepInnerStepper
. I will remove them.
sunrealtype tout, N_Vector y, sunrealtype* tret, | ||
int* stop_reason) | ||
{ | ||
SUNFunctionBegin(stepper->sunctx); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do all the functions in this file need to check if stepper
is NULL
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, we do not need to check that. If the stepper
is NULL we cannot handle the error properly anyways.
I am opening this PR as a draft to gather feedback. This renames/moves
MRIStepInnerStepper
toSUNStepper
so that @Steven-Roberts and I can extend it for other purposes.