Source code for festim.stepsize

import fenics as f
import numpy as np
import warnings


[docs] class Stepsize: """ Description of Stepsize Args: initial_value (float, optional): initial stepsize. Defaults to 0.0. stepsize_change_ratio (float, optional): stepsize change ratio. Defaults to None. t_stop (float, optional): time at which the adaptive stepsize stops. Defaults to None. stepsize_stop_max (float, optional): Maximum stepsize after t_stop. Defaults to None. max_stepsize (float or callable, optional): Maximum stepsize. Can be a function of festim.t. Defaults to None. dt_min (float, optional): Minimum stepsize below which an error is raised. Defaults to None. milestones (list, optional): list of times by which the simulation must pass. Defaults to None. Attributes: adaptive_stepsize (dict): contains the parameters for adaptive stepsize value (fenics.Constant): value of dt milestones (list): list of times by which the simulation must pass. Example:: my_stepsize = Stepsize( initial_value=0.5, stepsize_change_ratio=1.1, max_stepsize=lambda t: None if t < 1 else 2, dt_min=1e-05 ) """ def __init__( self, initial_value=0.0, stepsize_change_ratio=None, t_stop=None, stepsize_stop_max=None, max_stepsize=None, dt_min=None, milestones=None, ) -> None: self.adaptive_stepsize = None if stepsize_change_ratio is not None: if t_stop or stepsize_stop_max: warnings.warn( "stepsize_stop_max and t_stop attributes will be deprecated in a future release, please use max_stepsize instead", DeprecationWarning, ) max_stepsize = lambda t: stepsize_stop_max if t >= t_stop else None self.adaptive_stepsize = { "stepsize_change_ratio": stepsize_change_ratio, "max_stepsize": max_stepsize, "dt_min": dt_min, } self.initial_value = initial_value self.value = None self.milestones = milestones self.initialise_value() @property def milestones(self): return self._milestones @milestones.setter def milestones(self, value): if value: self._milestones = sorted(value) else: self._milestones = value
[docs] def initialise_value(self): """Creates a fenics.Constant object initialised with self.initial_value and stores it in self.value""" self.value = f.Constant(self.initial_value, name="dt")
[docs] def adapt(self, t, nb_it, converged): """Changes the stepsize based on convergence. Args: t (float): current time. nb_it (int): number of iterations the solver required to converge. converged (bool): True if the solver converged, else False. """ if self.adaptive_stepsize: change_ratio = self.adaptive_stepsize["stepsize_change_ratio"] dt_min = self.adaptive_stepsize["dt_min"] max_stepsize = self.adaptive_stepsize["max_stepsize"] if not converged: if dt_min is None: raise ValueError("Solver diverged but dt_min is not set.") self.value.assign(float(self.value) / change_ratio) if float(self.value) < dt_min: raise ValueError("stepsize reached minimal value") if nb_it < 5: self.value.assign(float(self.value) * change_ratio) else: self.value.assign(float(self.value) / change_ratio) if callable(max_stepsize): max_stepsize = max_stepsize(t) if max_stepsize is not None: if float(self.value) > max_stepsize: self.value.assign(max_stepsize) # adapt for next milestone next_milestone = self.next_milestone(t) if next_milestone is not None: if t + float(self.value) > next_milestone and not np.isclose( t, next_milestone, atol=0 ): self.value.assign((next_milestone - t))
[docs] def next_milestone(self, current_time: float): """Returns the next milestone that the simulation must pass. Returns None if there are no more milestones. Args: current_time (float): current time. Returns: float: next milestone. """ if self.milestones is None: return None for milestone in self.milestones: if current_time < milestone: return milestone return None