import numpy as np
from pySDC.core.errors import ParameterError, ProblemError
from pySDC.core.problem import Problem, WorkCounter
from pySDC.implementations.datatype_classes.mesh import mesh
[docs]
class DiscontinuousTestODE(Problem):
r"""
This class implements a very simple test case of a ordinary differential equation consisting of one discrete event. The dynamics of
the solution changes when the state function :math:`h(u) := u - 5` changes the sign. The problem is defined by:
if :math:`u - 5 < 0:`
.. math::
\frac{d u}{dt} = u
else:
.. math::
\frac{d u}{dt} = \frac{4}{t^*},
where :math:`t^* = \log(5) \approx 1.6094379`. For :math:`h(u) < 0`, i.e. :math:`t \leq t^*`, the exact solution is
:math:`u(t) = \exp(t)`; for :math:`h(u) \geq 0`, i.e. :math:`t \geq t^*`, the exact solution is :math:`u(t) = \frac{4 t}{t^*} + 1`.
Attributes
----------
t_switch_exact : float
Exact event time with :math:`t^* = \log(5)`.
t_switch: float
Time point of the discrete event found by switch estimation.
nswitches: int
Number of switches found by switch estimation.
Attributes
----------
work_counters : WorkCounter
Counts different things, here: Number of Newton iterations is counted.
"""
dtype_u = mesh
dtype_f = mesh
def __init__(self, newton_maxiter=100, newton_tol=1e-8, stop_at_nan=True):
"""Initialization routine"""
nvars = 1
super().__init__(init=(nvars, None, np.dtype('float64')))
self._makeAttributeAndRegister('nvars', localVars=locals(), readOnly=True)
self._makeAttributeAndRegister('newton_maxiter', 'newton_tol', 'stop_at_nan', localVars=locals())
if self.nvars != 1:
raise ParameterError('nvars has to be equal to 1!')
self.t_switch_exact = np.log(5)
self.t_switch = None
self.nswitches = 0
self.work_counters['newton'] = WorkCounter()
[docs]
def eval_f(self, u, t):
"""
Routine to evaluate the right-hand side of the problem.
Parameters
----------
u : dtype_u
Current values of the numerical solution.
t : float
Current time of the numerical solution is computed.
Returns
-------
f : dtype_f
The right-hand side of the problem.
"""
t_switch = np.inf if self.t_switch is None else self.t_switch
f = self.dtype_f(self.init, val=0.0)
h = u[0] - 5
if h >= 0 or t >= t_switch:
f[:] = 4 / self.t_switch_exact
else:
f[:] = u
return f
[docs]
def solve_system(self, rhs, dt, u0, t):
r"""
Simple Newton solver for :math:`(I-factor\cdot A)\vec{u}=\vec{rhs}`.
Parameters
----------
rhs : dtype_f
Right-hand side for the linear system.
dt : float
Abbrev. for the local stepsize (or any other factor required).
u0 : dtype_u
Initial guess for the iterative solver.
t : float
Current time (e.g. for time-dependent BCs).
Returns
-------
me : dtype_u
The solution as mesh.
"""
t_switch = np.inf if self.t_switch is None else self.t_switch
h = rhs[0] - 5
u = self.dtype_u(u0)
n = 0
res = 99
while n < self.newton_maxiter:
# form function g with g(u) = 0
if h >= 0 or t >= t_switch:
g = u - dt * (4 / self.t_switch_exact) - rhs
else:
g = u - dt * u - rhs
# if g is close to 0, then we are done
res = np.linalg.norm(g, np.inf)
if res < self.newton_tol:
break
if h >= 0 or t >= t_switch:
dg = 1
else:
dg = 1 - dt
# newton update
u -= 1.0 / dg * g
n += 1
self.work_counters['newton']()
if np.isnan(res) and self.stop_at_nan:
raise ProblemError('Newton got nan after %i iterations, aborting...' % n)
elif np.isnan(res):
self.logger.warning('Newton got nan after %i iterations...' % n)
if n == self.newton_maxiter:
self.logger.warning('Newton did not converge after %i iterations, error is %s' % (n, res))
me = self.dtype_u(self.init)
me[:] = u[:]
return me
[docs]
def u_exact(self, t, u_init=None, t_init=None):
r"""
Routine to compute the exact solution at time :math:`t`.
Parameters
----------
t : float
Time of the exact solution.
u_init : dtype_u
Initial conditions for getting the exact solution.
t_init : float
The starting time.
Returns
-------
me : dtype_u
The exact solution.
"""
if t_init is not None and u_init is not None:
self.logger.warning(
f'{type(self).__name__} uses an analytic exact solution from t=0. If you try to compute the local error, you will get the global error instead!'
)
me = self.dtype_u(self.init)
if t <= self.t_switch_exact:
me[:] = np.exp(t)
else:
me[:] = (4 * t) / self.t_switch_exact + 1
return me
[docs]
def get_switching_info(self, u, t):
r"""
Provides information about the state function of the problem. When the state function changes its sign,
typically an event occurs. So the check for an event should be done in the way that the state function
is checked for a sign change. If this is the case, the intermediate value theorem states a root in this
step.
Parameters
----------
u : dtype_u
Current values of the numerical solution at time :math:`t`.
t : float
Current time of the numerical solution.
Returns
-------
switch_detected : bool
Indicates whether a discrete event is found or not.
m_guess : int
The index before the sign changes.
state_function : list
Defines the values of the state function at collocation nodes where it changes the sign.
"""
switch_detected = False
m_guess = -100
for m in range(1, len(u)):
h_prev_node = u[m - 1][0] - 5
h_curr_node = u[m][0] - 5
if h_prev_node < 0 and h_curr_node >= 0:
switch_detected = True
m_guess = m - 1
break
state_function = [u[m][0] - 5 for m in range(len(u))]
return switch_detected, m_guess, state_function
[docs]
def count_switches(self):
"""
Setter to update the number of switches if one is found.
"""
self.nswitches += 1
[docs]
class ExactDiscontinuousTestODE(DiscontinuousTestODE):
r"""
Dummy ODE problem for testing the ``SwitchEstimator`` class. The problem contains the exact dynamics
of the problem class ``DiscontinuousTestODE``.
"""
def __init__(self, newton_maxiter=100, newton_tol=1e-8):
"""Initialization routine"""
super().__init__(newton_maxiter, newton_tol)
[docs]
def eval_f(self, u, t):
"""
Derivative.
Parameters
----------
u : dtype_u
Exact value of u.
t : float
Time :math:`t`.
Returns
-------
f : dtype_f
Derivative.
"""
f = self.dtype_f(self.init)
t_switch = np.inf if self.t_switch is None else self.t_switch
h = u[0] - 5
if h >= 0 or t >= t_switch:
f[:] = 1
else:
f[:] = np.exp(t)
return f
[docs]
def solve_system(self, rhs, factor, u0, t):
"""
Just return the exact solution...
Parameters
----------
rhs : dtype_f
Right-hand side for the linear system.
factor : float
Abbrev. for the local stepsize (or any other factor required).
u0 : dtype_u
Initial guess for the iterative solver.
t : float
Current time (e.g. for time-dependent BCs).
Returns
-------
me : dtype_u
The solution as mesh.
"""
return self.u_exact(t)