I'm using FiPy to simulate a moderately large system of coupled PDEs (11 equations) that calculate the time-dependent evolution of a biochemical advection-diffusion-reaction network. Currently I'm using a fixed time step, and it works fine. However, the nature of the model is such that during the initial time steps the variables change a lot, this requires a small value of dt
. After a while the model 'burns in' and the variable changes are much more gradual with time. To save on computational resources, the dt
could then be increased. Looking at the documentation, the fipy.steppers.pidStepper
seems ideally suited to this. The documentation on the stepper is, however, very scant and I could not find any code examples that have implemented this (either in the FiPy examples themselves, or from a general internet search).
The skeleton of the evaluation loop currently looks as follows (I'd be happy to provide more details if needed); v
is an array of 11 cell variables, and p[j]
is the PDE for v[j]
:
# steps, err and u defined elsewhere
for step in range(steps):
r = [1e3] * len(v)
while max(r) > err:
for j in range(len(v)):
r[j] = min(r[j], p[j].sweep(v[j], dt=u))
for j in range(len(v)):
v[j].updateOld()
Any pointers on how to adapt this for pidStepper
would be appreciated. One requirement would be that I need to be able to specify the saving of model outputs at a set of given time points. These are however much further apart than the intial value of dt
used. But if the step size is adaptive, it might not 'land' exactly on one of these time points, so how to implement this?
PIDStepper
is pretty archaic (last substantive change was about fourteen years ago) and, as you've found, is neither documented nor used anywhere publicly. Since we don't test it, either, we should probably cornfield it.That said, after rummaging around on my hard drive, I've found a few cases where I experimented with it and, amazingly, it still works.
Using a trivial diffusion problem as an example:
To solve this with
PIDStepper
, you need to define asweepFn()
to actually solve your equation(s). Thevardata
argument is a tuple of tuples, each of which holds aCellVariable
to solve for, the equation to apply, and the old-style boundary conditions to use (all of this radically predates coupled equations or constraints). Thedt
argument is the adapted time step to attempt.*args
and**kwargs
are any additional agruments you wish to pass at each step. This function must return an error normalized to one; here we return the L1 norm ofvar - var.old
normalized to the L1 norm ofvar.old
.You can optionally define a
successFn()
to perform after a successful adaptive solution step. Thevardata
argument is as above. Thedt
argument is the time step that was requested. ThedtPrev
argument is the time step that was actually taken. Theelapsed
argument is how much time is elapsed for this time step.*args
and**kwargs
are any additional agruments you wish to pass at each step and must be the same as forsweepFn()
. Here, we'll want access to theviewer
, the list of time steps that were takendts
, the list of elapsed times each was taken atelapseds
, and the global clocktotal_elapsed
.You could also optionally define a
failFn()
to perform wheneversweepFn()
returns an error greater than one. This would take the same arguments assuccessFn()
.Finally, instantiate a
stepper
with appropriatevardata
and callstep()
for each desired time step. Pass the desired time stepdt
, the time step to try firstdtTry
, the smallest time step to allowdtMin
, the last time step attempteddtPrev
, thesweepFn()
we just defined, thesuccessFn()
we just defined, and the optional arguments that oursuccessFn()
makes use of.You could also do a similar thing by defining a
PseudoRKQSStepper
:and use the same
sweepFn()
andsuccessFn()
.My current practice
While this works, I found the whole process of defining and passing around helper functions to be kind of opaque and more trouble than it's worth. Until now, I couldn't be bothered to document it or show anybody else how I'd used it.
These days, I do something more like this:
This involves about the same amount of code in your script (a little less, actually) and none of the additional 100+ lines of code in
PIDStepper
andStepper
.None of these have been tuned, and this diffusion problem isn't much of a test as it's unconditionally stable, but once they get going, all exhibit about the same acceleration.