"""This module provides various utilities for internal use."""
__author__ = "Marie E. Rognes (meg@simula.no), 2012--2013"
__all__ = ["state_space", "end_of_time", "convergence_rate",
"Projecter"]
import math
from dolfinimport import dolfin, dolfin_adjoint
if dolfin_adjoint:
from dolfin_adjoint import assemble, LUSolver, KrylovSolver
from dolfin import parameters
else:
from dolfin import assemble, LUSolver, KrylovSolver, parameters
def annotate_kwargs(ba_parameters):
if not dolfin_adjoint:
return {}
if not ba_parameters["enable_adjoint"]:
return {"annotate": False}
if parameters["adjoint"]["stop_annotating"]:
return {"annotate": False}
return {"annotate": True}
def splat(vs, dim):
if vs.function_space().ufl_element().num_sub_elements()==dim:
v = vs[0]
if dim == 2:
s = vs[1]
else:
s = dolfin.as_vector([vs[i] for i in range(1, dim)])
else:
v, s = dolfin.split(vs)
return v, s
[docs]def state_space(domain, d, family=None, k=1):
"""Return function space for the state variables.
*Arguments*
domain (:py:class:`dolfin.Mesh`)
The computational domain
d (int)
The number of states
family (string, optional)
The finite element family, defaults to "CG" if None is given.
k (int, optional)
The finite element degree, defaults to 1
*Returns*
a function space (:py:class:`dolfin.FunctionSpace`)
"""
if family is None:
family = "CG"
if d > 1:
S = dolfin.VectorFunctionSpace(domain, family, k, d)
else:
S = dolfin.FunctionSpace(domain, family, k)
return S
[docs]def end_of_time(T, t0, t1, dt):
"""
Return True if the interval (t0, t1) is the last before the end
time T, otherwise False.
"""
return (t1 + dt) > (T + dolfin.DOLFIN_EPS)
class TimeStepper:
"""
A helper object that keep track of simulated time
"""
def __init__(self, interval, dt, annotate=False):
"""
*Arguments*
interval (:py:class:`tuple`)
The time interval for the solve given by (t0, t1)
dt (:py:class:`int or :py:class:`list` of py:class:`tuples` of py:class:`float`)
The timestep for the solve. A list of tuples of floats can
also be passed. Each tuple should contain two floats where the
first includes the start time and the second the dt.
annotate (:py:class:`bool)
If enabling dolfin_adjoint timestep annotation
"""
self.annotate = annotate
if not isinstance(interval, (tuple, list)) or len(interval) != 2 or \
not all(isinstance(value, (float, int)) for value in interval):
raise TypeError("expected tuple or list of size 2 with scalars for "\
"the interval argument")
if interval[0] >= interval[1]:
raise ValueError("Start time need to be larger than stop time: "\
"interval[0] < interval[1]")
# Store time interval
(self.T0, self.T1) = interval
if not isinstance(dt, (float, int, list)):
raise TypeError("expected float or list of tuples for dt argument")
if isinstance(dt, (float, int)):
dt = [(self.T0, dt)]
# Check that all dt are tuples of size 2 with either floats or ints.
if any((not isinstance(item, tuple) or \
len(item)!=2 or not all(isinstance(value, (float, int)) \
for value in item)) for item in dt):
raise TypeError("expected list of tuples of size 2 with scalars for "\
"the dt argument")
# Check that first time value of dt is the same as the first given in interval
if dt[0][0] != self.T0:
raise ValueError("expected first time value of dt to be the same as "\
"the first value of time interval.")
# Check that all time values given in dt are increasing
if not all(dt[i][0] < dt[i+1][0] for i in range(len(dt)-1)):
raise ValueError("expected all time values in dt to be increasing")
# Check that all time step values given in dt are positive
if not all(dt[i][1] > 0 for i in range(len(dt))):
raise ValueError("expected all time step values in dt to be positive")
# Store dt
self._dt = dt
# Add a dummy dt including stop interval time
if self._dt[-1][0] < self.T1:
self._dt.append((self.T1, self._dt[-1][1]))
# Keep track of dt index
self._dt_ind = 0
self.t0 = self.T0
#self.t1 = self.T0 + self.dt
# Step through time steps until at end time.
if self.annotate and dolfin_adjoint:
dolfin_adjoint.adj_start_timestep(self.T0)
def __iter__(self):
"""
Return an iterator over time intervals
"""
eps = 1e-10
while True:
# Get next t1
t1 = self.next_t1()
# Yield time interval
yield self.t0, t1
# Break if this is the last step
if abs(t1-self.T1) < eps:
if self.annotate and dolfin_adjoint:
dolfin_adjoint.adj_inc_timestep(time=t1, finished=True)
break
# Move to next time
if self.annotate and dolfin_adjoint:
dolfin_adjoint.adj_inc_timestep(time=t1)
self.t0 = t1
def next_t1(self):
"""
Return the time of next end interval
"""
assert self._dt_ind < len(self._dt)+1
dt = self._dt[self._dt_ind][1]
time_to_switch_dt = self._dt[self._dt_ind+1][0]
if time_to_switch_dt - dolfin.DOLFIN_EPS > self.t0 + dt :
return self.t0 + dt
# Update dt index
self._dt_ind += 1
return time_to_switch_dt
[docs]def convergence_rate(hs, errors):
"""
Compute and return rates of convergence :math:`r_i` such that
.. math::
errors = C hs^r
"""
assert (len(hs) == len(errors)), "hs and errors must have same length."
# Compute converence rates
rates = [(math.log(errors[i+1]/errors[i]))/(math.log(hs[i+1]/hs[i]))
for i in range(len(hs)-1)]
# Return convergence rates
return rates
[docs]class Projecter(object):
"""Customized class for repeated projection.
*Arguments*
V (:py:class:`dolfin.FunctionSpace`)
The function space to project into
solver_type (string, optional)
"iterative" (default) or "direct"
*Example of usage*::
my_project = Projecter(V, solver_type="direct")
u = Function(V)
f = Function(W)
my_project(f, u)
"""
def __init__(self, V, params=None):
# Set parameters
self.parameters = self.default_parameters()
if params is not None:
self.parameters.update(params)
# Set-up mass matrix for L^2 projection
self.V = V
self.u = dolfin.TrialFunction(self.V)
self.v = dolfin.TestFunction(self.V)
self.m = dolfin.inner(self.u, self.v)*dolfin.dx()
self.M = assemble(self.m)
self.b = dolfin.Vector(V.mesh().mpi_comm(), V.dim())
solver_type = self.parameters["linear_solver_type"]
assert(solver_type == "lu" or solver_type == "cg"), \
"Expecting 'linear_solver_type' to be 'lu' or 'cg'"
if solver_type == "lu":
dolfin.debug("Setting up direct solver for projecter")
# Customize LU solver (reuse everything)
solver = LUSolver(self.M)
solver.parameters["same_nonzero_pattern"] = True
solver.parameters["reuse_factorization"] = True
else:
dolfin.debug("Setting up iterative solver for projecter")
# Customize Krylov solver (reuse everything)
solver = KrylovSolver("cg", "ilu")
solver.set_operator(self.M)
solver.parameters["preconditioner"]["structure"] = "same"
# solver.parameters["nonzero_initial_guess"] = True
self.solver = solver
[docs] @staticmethod
def default_parameters():
parameters = dolfin.Parameters("Projecter")
parameters.add("linear_solver_type", "cg")
return parameters
def __call__(self, f, u):
"""
Carry out projection of ufl Expression f and store result in
the function u. The user must make sure that u lives in the
right space.
*Arguments*
f (:py:class:`ufl.Expr`)
The thing to be projected into this function space
u (:py:class:`dolfin.Function`)
The result of the projection
"""
L = dolfin.inner(f, self.v)*dolfin.dx()
assemble(L, tensor=self.b)
self.solver.solve(u.vector(), self.b)