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 sp
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)) def _create_functions(self): super()._create_functions() self.rhs = firedrake.Function(self.coord_space, name="Zero RHS") @PETSc.Log.EventDecorator() def _solve(self, boundary_conditions, solver_parameters=None): """ Solve the Laplace system. :kwarg boundary_conditions: Dirichlet boundary conditions to be enforced :type boundary_conditions: :class:`~.DirichletBC` or :class:`list` thereof :kwarg solver_parameters: solver parameters for solving the Laplace equation :type solver_parameters: :class:`dict` """ if not hasattr(self, "_solver"): test = firedrake.TestFunction(self.coord_space) trial = firedrake.TrialFunction(self.coord_space) a = ufl.inner(ufl.grad(trial), ufl.grad(test)) * self.dx L = ufl.inner(self.rhs, test) * self.dx problem = firedrake.LinearVariationalProblem( a, L, self.v, bcs=boundary_conditions ) self._solver = firedrake.LinearVariationalSolver( problem, solver_parameters=solver_parameters or sp.cg_ilu, ) self._solver.solve()
[docs] @PETSc.Log.EventDecorator() def move( self, time, update_boundary_velocity=None, boundary_conditions=None, solver_parameters=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 :kwarg solver_parameters: solver parameters for solving the Laplace equation :type solver_parameters: :class:`dict` """ 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" ) # Solve on computational mesh self.mesh.coordinates.assign(self.xi) try: self._solve(boundary_conditions, solver_parameters=solver_parameters) 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) self.volume.interpolate(ufl.CellVolume(self.mesh)) PETSc.Sys.Print( f"{time:.2f} s" f" Volume ratio {self.volume_ratio:5.2f}" f" Variation (σ/μ) {self.coefficient_of_variation:8.2e}" f" Displacement {np.linalg.norm(self.displacement):.2f} m" ) if hasattr(self, "tangling_checker"): self.tangling_checker.check()