Source code for coffee.ibvp
#!/usr/bin/env python
# encoding: utf-8
"""
This is the class that handles the iterative step and actions of the
simulation.
The name ibvp comes from the original design of this class as the
iterative step in an initial boundary value problem solver. However, this class
is general enough to handle numerical techniques that have, at their highest
level, some iterative method.
"""
[docs]
class IBVP:
"""Handles computation of the solution to initial boundary value problems.
This class manages the interative process of simulations and interleaves
interative steps with calls to a list of actions. Information about events
during the simulation are writen to logging.getLogger("IBVP").
"""
theActions = None
theGrid = None
theSolver = None
iteration = 0
maxIteration = None
def __init__(self, sol, eqn, grid, action=[], maxIteration=10000, minTimestep=1e-8):
"""IBVP constructor.
Parameters
----------
sol : solver.Solver
An ODE solver.
eqn : system.System
The system of equations whose solution will be computed.
grid : grid.Grid
The spatial grid over which the values of the solution to the system
will be computed.
action : list, actions.Prototype
Elements of the list must be instances of actions.Prototype. Each
action will be run after initialisation of the timeslice and after
each new timeslice is calculated.
maxIteraction : int, Optional
Specifies the maximum number of timesteps.
minTimestep : float, Optinoal
Specifies the minimum time step.
"""
self.theSolver = sol
self.theSystem = eqn
self.maxIteration = maxIteration
self.theGrid = grid
self.theActions = action
self.minTimestep = minTimestep
[docs]
def run(self, tstart, tstop=float("inf"), thits=None):
"""Go for it! Starts the simulation.
Parameters
----------
tstart : float
A number giving the time at the initial interation.
tstop : float
A number giving the time to finish the simulation at.
thits : list, float
A list of times that the simulation is forced to hit exactly.
"""
# Set up thits
if thits is None:
thits = []
if tstop not in thits:
thits += [tstop]
# Order the list of times, and ensure that they are popped from smallest
# to largest.
thits = sorted(thits)
thits.reverse()
tstop = thits.pop()
# Set start time.
t = tstart
# Get initial data and configure timeslices for multiple processors.
u = self.theSystem.initial_data(t, self.theGrid)
advance = self.theSolver.advance
computation_valid = True
while computation_valid and t < tstop:
# Check against maxIteration
if self.iteration > self.maxIteration:
break
dt = self.theSystem.timestep(u)
# Check dt for size
if dt < self.minTimestep:
break
# Check if dt needs to change in order to hit the next thits value.
timeleft = tstop - t
if timeleft < dt:
dt = timeleft
if not thits:
computation_valid = False
else:
tstop = thits.pop()
# Run the actions.
self._run_actions(t, u)
try:
t, u = advance(t, u, dt)
except OverflowError as e:
# OverflowErrors arn't always appropirately handled. So we
# do it ourselves here.
print("Overflow error({0}): {1}".format(e.errno, e.strerror))
computation_valid = False
# Dirichlet boundary conditions
try:
u = self.theSystem.dirichlet_boundary(u)
except AttributeError:
pass
# If we're using an mpi enable grid, this ensures that all
# processes have gotten to the same point before continuing the
# simulation.
u.barrier()
# On to the next iteration.
self.iteration += 1
# end (while)
# Run the actions once more before exiting.
self._run_actions(t, u)
# This statement might be unnecessary. In principle it ensures that all
# processes are about to complete the current simulation before exit
# occurs.
u.barrier()
return u
def _run_actions(self, t, u):
"""A utility method that ensures that actions are only run after all data is collected across MPI nodes and only if an action is due to run.
Parameters
----------
t : float
The current time.
u: tslices.TimeSlice
The current timeslice.
"""
# Ideally u.collect_data() should only be executed if there
# actions that will run. Because of single process access to
# actions this causes an issue.
# Some thought is required to fix this problem.
tslice = u.collect_data()
if tslice is not None:
actions_do_actions = [
action.will_run(self.iteration, tslice) for action in self.theActions
]
if any(actions_do_actions):
for i, action in enumerate(self.theActions):
if actions_do_actions[i]:
action(self.iteration, tslice)