Source code for movement.laplacian_smoothing
import firedrake
import firedrake.exceptions as fexc
import numpy as np
import ufl
from firedrake.petsc import PETSc
import movement.solver_parameters as solver_parameters
from movement.mover import PrimeMover
__all__ = ["LaplacianSmoother"]
[docs]
class LaplacianSmoother(PrimeMover):
r"""
Movement of a ``mesh`` is driven by a mesh velocity :math:`\mathbf{v}`, which is
determined by solving a vector Laplace problem
.. math::
\nabla^2_{\boldsymbol{\xi}}\mathbf{v} = \boldsymbol{0}, \quad \boldsymbol{\xi}\in\Omega,
under non-zero Dirichlet boundary conditions on a forced boundary section
:math:`\partial\Omega_f` and zero Dirichlet boundary conditions elsewhere:
.. math::
\mathbf{v} = \left\{\begin{array}{rl}
\mathbf{v}_D, & \boldsymbol{\xi}\in\partial\Omega_f\\
\boldsymbol{0}, & \boldsymbol{\xi}\in\partial\Omega\backslash\partial\Omega_f
\end{array}\right.
where the computational coordinates :math:`\boldsymbol{\xi} := \mathbf{x}(t_0)` are
the physical coordinates at the beginning of the simulation.
"""
@PETSc.Log.EventDecorator()
def __init__(self, mesh, timestep, **kwargs):
"""
:arg mesh: the physical mesh to be moved
:type mesh: :class:`firedrake.mesh.MeshGeometry`
:arg timestep: the timestep length used
:type timestep: :class:`float`
"""
super().__init__(mesh, **kwargs)
assert timestep > 0.0
self.dt = timestep
dim = self.mesh.topological_dimension()
self.displacement = np.zeros((self.mesh.num_vertices(), dim))
@PETSc.Log.EventDecorator()
def _setup_solver(self, boundary_conditions):
if not hasattr(self, "_solver"):
test = firedrake.TestFunction(self.coord_space)
trial = firedrake.TrialFunction(self.coord_space)
f = firedrake.Function(self.coord_space, name="Zero RHS")
a = ufl.inner(ufl.grad(trial), ufl.grad(test)) * self.dx
L = ufl.inner(f, test) * self.dx
problem = firedrake.LinearVariationalProblem(
a, L, self.v, bcs=boundary_conditions
)
self._solver = firedrake.LinearVariationalSolver(
problem,
solver_parameters=solver_parameters.cg_ilu,
)
self._solver.solve()
[docs]
@PETSc.Log.EventDecorator()
def move(self, time, update_boundary_velocity=None, boundary_conditions=None):
"""
Assemble and solve the Laplacian system and update the coordinates.
:arg time: the current time
:type time: :class:`float`
:kwarg update_boundary_velocity: function that updates the boundary conditions at
the current time
:type update_boundary_velocity: :class:`~.Callable` with a single argument of
:class:`float` type
:kwarg boundary_conditions: Dirichlet boundary conditions to be enforced
:type boundary_conditions: :class:`~.DirichletBC` or :class:`list` thereof
"""
if update_boundary_velocity is not None:
update_boundary_velocity(time)
if not boundary_conditions:
boundary_conditions = firedrake.DirichletBC(
self.coord_space, 0, "on_boundary"
)
self._setup_solver(boundary_conditions)
# Solve on computational mesh
self.mesh.coordinates.assign(self.xi)
try:
self._solver.solve()
except fexc.ConvergenceError as conv_err:
self._convergence_error(exception=conv_err)
# Update mesh coordinates
self.displacement[:] = self.v.dat.data_with_halos * self.dt
self.x.dat.data_with_halos[:] += self.displacement
self.mesh.coordinates.assign(self.x)