Lagrangian Motion¶
This page documents the Lagrangian mesh motion functions in the mmgpy.lagrangian module and the dataset.mmg.move(...) accessor method.
Overview¶
Lagrangian remeshing handles moving meshes by:
- Applying a displacement field to the mesh
- Remeshing to maintain quality
- Preserving boundary conditions
This is useful for:
- Moving mesh simulations
- Shape optimization
- Fluid-structure interaction
- Morphing between shapes
dataset.mmg.move() applies the displacement and remeshes for every mesh kind (TET, 2D, surface) with no external dependency. For interior propagation it ships two solvers: a pure-Python Laplacian smoother (default) and an optional fedoo-backed linear elasticity solver, selectable via propagation_method.
import numpy as np
import pyvista as pv
import mmgpy # noqa: F401 -- registers reader/writer + accessor
sphere = pv.Sphere(theta_resolution=10, phi_resolution=10)
displacement = np.zeros((sphere.n_points, 3))
displacement[:, 0] = 0.1 # Move in x direction
moved = sphere.mmg.move(displacement, hausd=0.01)
Functions¶
mmgpy.move_mesh
¶
move_mesh(
mesh: MmgMesh2D | MmgMesh3D | MmgMeshS | Mesh,
displacement: NDArray[float64],
*,
boundary_mask: NDArray[bool_] | None = None,
propagate: bool = True,
propagation_method: str = "laplacian",
n_steps: int = 1,
**remesh_options: float | bool | None,
) -> None
Move mesh vertices by displacement and remesh to maintain quality.
For large displacements, consider using multiple steps (n_steps > 1)
to avoid mesh inversion.
Parameters:
-
mesh(MmgMesh2D | MmgMesh3D | MmgMeshS | Mesh) –Mesh, MmgMesh2D, MmgMesh3D, or MmgMeshS mesh object.
-
displacement(NDArray[float64]) –Nxdim array of displacement vectors for each vertex. If boundary_mask is provided and propagate=True, only boundary values need to be correct; interior values will be computed.
-
boundary_mask(NDArray[bool_] | None, default:None) –Optional boolean array indicating which vertices have prescribed displacement. If None, all vertices are treated as having prescribed displacement (no propagation needed).
-
propagate(bool, default:True) –If True and boundary_mask is provided, propagate boundary displacement to interior using the chosen propagation_method.
-
propagation_method(str, default:'laplacian') –Method for propagating boundary displacements to the interior. Options:
"laplacian"(default): Solves the Laplace equation. Fast, no extra dependencies."elasticity": Solves a linear elasticity problem usingfedoo <https://github.com/3MAH/fedoo>_. Produces physically meaningful displacements, better for large deformations and complex geometries. Requirespip install fedoo.
-
n_steps(int, default:1) –Number of incremental steps to apply the displacement. Use more steps for large displacements to avoid mesh inversion.
-
**remesh_options(float | bool | None, default:{}) –Options passed to mesh.remesh() (hmax, hmin, etc.).
Raises:
-
ValueError–If displacement dimensions don't match mesh or propagation_method is not recognized.
-
ImportError–If propagation_method="elasticity" and fedoo is not installed.
-
RuntimeError–If remeshing fails.
options: show_root_heading: true
mmgpy.propagate_displacement
¶
propagate_displacement(
vertices: NDArray[float64],
elements: NDArray[int32],
boundary_mask: NDArray[bool_],
boundary_displacement: NDArray[float64],
) -> NDArray[np.float64]
Propagate displacement from boundary to interior using Laplacian smoothing.
Solves the Laplace equation nabla^2 u = 0 with Dirichlet boundary conditions u = boundary_displacement on the boundary. This produces a smooth displacement field that transitions from boundary values to interior.
The complexity is O(n) for building the matrix and typically O(n^1.5) for solving due to the sparse structure.
Parameters:
-
vertices(NDArray[float64]) –Nx2 or Nx3 array of vertex coordinates.
-
elements(NDArray[int32]) –Mx(nodes_per_element) array of element connectivity.
-
boundary_mask(NDArray[bool_]) –N boolean array, True for vertices with prescribed displacement.
-
boundary_displacement(NDArray[float64]) –Nxdim array of displacement vectors. Only values at boundary vertices (where boundary_mask is True) are used.
Returns:
-
NDArray[float64]–Nxdim array of displacement for all vertices.
Raises:
-
ValueError–If array dimensions don't match.
options: show_root_heading: true
mmgpy.lagrangian.propagate_displacement_elasticity
¶
propagate_displacement_elasticity(
vertices: NDArray[float64],
elements: NDArray[int32],
boundary_mask: NDArray[bool_],
boundary_displacement: NDArray[float64],
E: float = 1000000.0,
nu: float = 0.3,
) -> NDArray[np.float64]
Propagate displacement from boundary to interior using linear elasticity.
Solves a fictitious linear elasticity problem with prescribed displacements
on the vertices flagged in boundary_mask; vertices outside the mask are
free DOFs whose displacement is computed by the elasticity solve. This
produces a physically meaningful smooth field, superior to Laplacian
smoothing for large deformations and complex geometries.
Requires the fedoo package (optional dependency).
Parameters:
-
vertices(NDArray[float64]) –Nx2 or Nx3 array of vertex coordinates.
-
elements(NDArray[int32]) –Mx(nodes_per_element) array of element connectivity.
-
boundary_mask(NDArray[bool_]) –N boolean array, True for vertices with prescribed displacement.
-
boundary_displacement(NDArray[float64]) –Nxdim array of displacement vectors. Only values at boundary vertices (where boundary_mask is True) are used.
-
E(float, default:1000000.0) –Young's modulus for the fictitious elastic material. With only Dirichlet BCs (no body forces, no tractions) the displacement field is independent of
E; onlynuaffects the result. Default 1e6. -
nu(float, default:0.3) –Poisson's ratio. Default is 0.3.
Returns:
-
NDArray[float64]–Nxdim array of displacement for all vertices.
Raises:
-
ImportError–If fedoo is not installed.
-
ValueError–If array dimensions don't match.
options: show_root_heading: true
mmgpy.detect_boundary_vertices
¶
Detect boundary vertices in a mesh.
Boundary vertices are those that lie on the exterior surface of the mesh. For 3D meshes, these are vertices on surface triangles. For 2D/surface meshes, these are vertices on boundary edges.
Parameters:
-
mesh(MmgMesh2D | MmgMesh3D | MmgMeshS | Mesh) –Mesh, MmgMesh2D, MmgMesh3D, or MmgMeshS mesh object.
Returns:
-
NDArray[bool_]–Boolean array of length n_vertices, True for boundary vertices.
options: show_root_heading: true
propagate_displacement_elasticity requires the optional
fedoo backend (pip install "mmgpy[fem]").
On a cantilever bracket the Laplacian solver pivots the geometry rigidly
while the elasticity solver captures the bending kinematics:

See the Elasticity Propagation tutorial for a full walkthrough.
fedoo on conda-forge
fedoo is published on PyPI but not on conda-forge. If you installed mmgpy
from conda-forge, install fedoo separately with
pip install fedoo (or conda install -c set3MAH fedoo) inside the same
environment to enable elasticity propagation.
Deprecated: remesh_lagrangian
Mesh.remesh_lagrangian(), dataset.mmg.remesh_lagrangian() and
mmgpy.progress.remesh_mesh_lagrangian() are kept as deprecation shims
that forward to mmgpy.move_mesh / dataset.mmg.move. They emit a
DeprecationWarning and will be removed in a future release; new code
should call the move/move_mesh API directly.
Accessor Method¶
import numpy as np
import pyvista as pv
import mmgpy # noqa: F401
mesh = pv.read("input.mesh")
# Define displacement field (3D vector at each vertex)
displacement = np.zeros((mesh.n_points, 3))
displacement[:, 0] = 0.1
moved = mesh.mmg.move(displacement)
Usage Examples¶
Basic Lagrangian Remeshing¶
import numpy as np
import pyvista as pv
import mmgpy # noqa: F401
mesh = pv.read("input.mesh")
vertices = np.asarray(mesh.points)
# Create displacement: radial expansion
center = vertices.mean(axis=0)
directions = vertices - center
distances = np.linalg.norm(directions, axis=1, keepdims=True)
directions = directions / (distances + 1e-10)
displacement = directions * 0.1 * distances
remeshed = mesh.mmg.move(displacement)
print(f"Cells: {mesh.n_cells} -> {remeshed.n_cells}")
Boundary-Only Displacement¶
Move only boundary vertices:
import numpy as np
import pyvista as pv
from mmgpy import detect_boundary_vertices
import mmgpy # noqa: F401
mesh = pv.read("input.mesh")
vertices = np.asarray(mesh.points)
# detect_boundary_vertices accepts the underlying impl; fetch via _io.read
from mmgpy import read as mmgpy_read
boundary_mask = detect_boundary_vertices(mmgpy_read(mesh)._impl_unwrap)
displacement = np.zeros((len(vertices), 3))
displacement[boundary_mask, 2] = 0.05
moved = mesh.mmg.move(
displacement,
boundary_mask=boundary_mask,
propagate=True,
hmax=0.1,
)
Iterative Motion¶
For large deformations, use multiple sub-steps via the n_steps argument:
With Quality Control¶
Combine with remeshing parameters:
Complete Example¶
Deform a sphere into an ellipsoid:
import numpy as np
import pyvista as pv
import mmgpy # noqa: F401
mesh = pv.read("sphere.mesh")
vertices = np.asarray(mesh.points)
# Compute displacement: stretch in z, compress in x and y
center = vertices.mean(axis=0)
relative = vertices - center
scale = np.array([0.7, 0.7, 1.5]) # Compress x,y, stretch z
displacement = (center + relative * scale) - vertices
remeshed = mesh.mmg.move(displacement, hmax=0.1, verbose=1)
q = remeshed.mmg.element_qualities()
print(f"Vertices: {mesh.n_points} -> {remeshed.n_points}")
print(f"Mean quality: {q.mean():.3f}")
remeshed.save("ellipsoid.vtk")
Tips¶
- Small steps: For large deformations, pass
n_steps > 1todataset.mmg.move(...). - Quality monitoring: Check
dataset.mmg.element_qualities()after each step. - Boundary handling: Use
boundary_mask+propagate=Truefor interior smoothness. - Remesh parameters: Combine with
hmax,hausdfor size control. - Validation: Validate the mesh between steps with
dataset.mmg.validate().