Source code for WaveBlocksND.TimeManager

"""The WaveBlocks Project

Provides several computation routines for
handling time and timesteps.

@author: R. Bourquin
@copyright: Copyright (C) 2010, 2011, 2012, 2013, 2015, 2016 R. Bourquin
@license: Modified BSD License
"""

from scipy import floor

__all__ = ["TimeManager"]


[docs]class TimeManager(object): r"""This class performs several computations with time, timesteps and so forth. The important quantities here are: ============ ============== ====================================================== Quantity Parameter Name Description ============ ============== ====================================================== :math:`T` T the fixed simulation end time :math:`\tau` dt the size of the timestep :math:`N` nsteps the overall number of timesteps. :math:`t` an unspecified time in the interval :math:`[0, T]` :math:`n` an unspecified timestep in the interval :math:`[0, N]` ============ ============== ====================================================== The important relations that hold are :math:`T = N \tau` and in analogy :math:`t = n \tau`. There are also conversion routines for :math:`t` and :math:`n`. The simulation parameters handed over to the constructor must contain at least two out of the three values :math:`T`, :math:`\tau` and :math:`N`. If all three are given, the user is responsible for compatible values. Additionally the class contains some routines for determining if and when some events (for example saving data) should occur. """ def __init__(self, parameters): if parameters is None: parameters = {} # We need two out of three: T, dt and nsteps have_enough = 0 if "T" in parameters: self._T = float(parameters["T"]) have_enough += 1 else: self._T = None if "dt" in parameters: self._dt = float(parameters["dt"]) have_enough += 1 else: self._dt = None if "nsteps" in parameters: self._nsteps = int(parameters["nsteps"]) have_enough += 1 else: self._nsteps = None if have_enough < 2: raise KeyError("Parameters provide to little data to construct a 'TimeManager'.") if self._T is None: self._T = self.compute_endtime() if self._dt is None: self._dt = self.compute_timestep_size() if self._nsteps is None: self._nsteps = self.compute_number_timesteps() # Interval for regular events self._interval = 1 if "write_nth" in parameters: self.set_interval(int(parameters["write_nth"])) # List of timesteps of irregular events self._eventtimes = [] if "save_at" in parameters: self.add_to_eventlist(parameters["save_at"]) def __str__(self): s = "TimeManager configured with:\n" s += " Final time T: "+str(self._T)+"\n" s += " Timestep size dt: "+str(self._dt)+"\n" s += " Number of steps : "+str(self._nsteps)+"\n" return s
[docs] def set_T(self, T): r"""Set the simulation endtime :math:`T`. :param T: The simulation end time. """ self._T = float(T)
[docs] def set_dt(self, dt): r"""Set the simulation timestep size :math:`\tau`. :param dt: The simulation timestep size. """ self._dt = float(dt)
[docs] def set_nsteps(self, nsteps): r"""Set the number of timesteps the simulation runs. :param nsteps: The number :math:`n` timesteps we do. """ self._nsteps = int(nsteps)
[docs] def get_T(self): r"""Set the simulation endtime :math:`T`. :returns: The endtime :math:`T`. """ return self._T
[docs] def get_dt(self): r"""Get the simulation timestep size :math:`\tau`. :returns: The timestep :math:`\tau`. """ return self._dt
[docs] def get_nsteps(self): r"""Get the number :math:`n` of timesteps the simulation runs. :returns: the number :math:`n` of timesteps. """ return self._nsteps
[docs] def compute_endtime(self): r"""Computes the simulation endtime :math:`T`. :returns: The endtime :math:`T`. """ if self._T is not None: return self._T else: return float(self._nsteps * self._dt)
[docs] def compute_timestep_size(self): r"""Computes the simulation timestep size :math:`\tau`. :returns: The timestep :math:`\tau`. """ if self._dt is not None: return self._dt else: return self._T / float(self._nsteps)
[docs] def compute_number_timesteps(self): r"""Computes the number :math:`n` of time steps we will perform. :returns: the number :math:`n` of timesteps. """ if self._nsteps is not None: return self._nsteps else: return int(floor(self._T / float(self._dt)))
[docs] def compute_timestep(self, t): r"""Compute the timestep :math:`n` from a time :math:`t` such that :math:`t = n \tau` holds. :param t: The time t of which we want to find the timestep number. :returns: The corresponding timestep :math:`n`. Note that the user has to ensure that time :math:`t` is an integral multiple of :math:`\tau`. """ stepo = t / self._dt step = round(stepo) if abs(stepo - step) > 1e-10: print("Warning: Questionable rounding for timestep computation!") return int(step)
[docs] def compute_time(self, n): r"""Compute the time :math:`t` from a timestep :math:`n` such that :math:`t = n \tau` holds. :param n: The timestep n of which we want to find the corresponding time. :returns: The corresponding time :math:`t`. """ return float(n * self._dt)
[docs] def set_interval(self, interval): r"""Set the interval for regular events. :param interval: The interval at which regular events get triggered. Note that a value of ``0`` means there are no regular events. """ self._interval = int(interval)
[docs] def add_to_eventlist(self, alist): r"""Add a list of times and/or timesteps to the list of times when irregular events get triggered. :param alist: A list with integers (interpreted as timesteps) and/or floats (interpreted as times) Note that the times and timesteps can be mixed and need not to be given in monotone order. """ timesteps = [] # If the list is empty (global default), shortcut if len(alist) == 0: return # Integers are interpreted as timesteps, floats are interpreted as times (and converted to timesteps) for item in alist: if type(item) == int: timesteps.append(item) elif type(item) == float: timesteps.append(self.compute_timestep(item)) # Validate timesteps and check if n in [0,...,N] tmp = len(timesteps) timesteps = [i for i in timesteps if 0 <= i <= self._nsteps] if tmp != len(timesteps): print("Warning: Dropped %d timestep(s) due to invalidity!" % (tmp - len(timesteps))) # Assure unique elements, just silently remove duplicates oldlist = set(self._eventtimes) newlist = set(timesteps) times = list(oldlist.union(newlist)) # Sort in ascending order times.sort() # Write back self._eventtimes = times
[docs] def compute_number_events(self): r"""Compute the number of events we will perform during the simulation. This can for example be used to determine how much space to allocate in the output files if the events are times at which simulation data is saved. :returns: The number of events. """ # We do not save at regular intervals if self._interval == 0: # The number of saves resulting from saving at a regular interval is zero n_si = 0 # Determine the number of saves resulting from the savelist n_sl = len(self._eventtimes) # We do save at regular intervals else: # Determine the number of saves resulting from saving at a regular interval n_si = 1 + self._nsteps // self._interval # Determine the number of saves resulting from the savelist and # exclude the timesteps which coincide with the regular intervals n_sl = len([i for i in self._eventtimes if i % self._interval != 0]) # Total number of saves we will perform is given by the sum number_events = n_si + n_sl return number_events
[docs] def is_event(self, n): r"""Determine if an event occurs right now. :param n: The current timestep in question. :returns: ``True`` or ``False``. """ if self._interval == 1: # Save every timestep return True elif self._interval != 0 and n % self._interval == 0: # Save every k-th timestep specified by the interval return True elif n in self._eventtimes: # Save if the n is in the list of timesteps return True return False