Skip to content

Commit

Permalink
Add Monge-Ampere zoom plot and tangling exercise (#65)
Browse files Browse the repository at this point in the history
Closes #50. Partly addresses #19.

This PR adds a zoom plot to the first Monge-Ampere demo to show that the
elements aren't tangled, as well as an exercise to check this with
`MeshTanglingChecker`.

The PR also applies some minor refactoring to `MeshTanglingChecker`, as
well as writing unit tests for it.
  • Loading branch information
jwallwork23 authored Apr 4, 2024
1 parent 25cf058 commit c6b51c1
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 31 deletions.
21 changes: 21 additions & 0 deletions demos/monge_ampere1.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,27 @@ def ring_monitor(mesh):
# :figwidth: 60%
# :align: center
#
# Whilst it looks like the mesh might have tangled elements, closer inspection shows
# that this is not the case.

fig, axes = plt.subplots()
triplot(mover.mesh, axes=axes)
axes.set_xlim([0.15, 0.3])
axes.set_ylim([0.15, 0.3])
axes.set_aspect(1)
plt.savefig("monge_ampere1-adapted_mesh_zoom.jpg")

# .. figure:: monge_ampere1-adapted_mesh_zoom.jpg
# :figwidth: 60%
# :align: center
#
# .. rubric:: Exercise
#
# To further convince yourself that there are no tangled elements, go back to the start
# of the demo and set up a :class:`movement.tangling.MeshTanglingChecker` object using
# the initial mesh. Use it to check for tangling after the mesh movement has been
# applied.
#
# This tutorial can be dowloaded as a `Python script <monge_ampere1.py>`__.
#
#
Expand Down
41 changes: 19 additions & 22 deletions movement/tangling.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,29 @@
import firedrake
import ufl
import warnings


__all__ = ["MeshTanglingChecker"]


class MeshTanglingChecker(object):
class MeshTanglingChecker:
"""
A class for tracking whether a mesh has
tangled, i.e. whether any of its elements
A class for tracking whether a mesh has tangled, i.e. whether any of its elements
have become inverted.
"""

def __init__(self, mesh, mode="warn"):
def __init__(self, mesh, raise_error=True):
"""
:arg mesh: the mesh to track if tangled
:kwarg mode: should a warning or an error
be raised when tangling is encountered?
:type mesh: :class:`firedrake.mesh.MeshGeometry`
:kwarg raise_error: if ``True``, an error is raised if any element is tangled,
otherwise a warning is raised
:type raise_error: :class:`bool`
"""
dim = mesh.topological_dimension()
if dim != 2:
raise ValueError(f"Cannot check for tangling of {dim}D meshes (only 2D)")
if mesh.topological_dimension() != 2:
raise NotImplementedError("Tangling check only currently supported in 2D.")
self.mesh = mesh
if mode not in ["warn", "error"]:
raise ValueError("Choose mode from 'warn' and 'error'")
self.mode = mode
self.raise_error = raise_error

# Store initial signs of Jacobian determinant
P0 = firedrake.FunctionSpace(mesh, "DG", 0)
Expand Down Expand Up @@ -55,17 +54,15 @@ def scaled_jacobian(self):

def check(self):
"""
Check for tangling.
Check whether any element orientations have changed since the tangling checker
was created.
"""
sj = self.scaled_jacobian.dat.data
sj = self.scaled_jacobian.dat.data_with_halos
num_tangled = len(sj[sj < 0])
if num_tangled == 0:
return 0
msg = f"Mesh has {num_tangled} tangled elements"
if self.mode == "warn":
import warnings

if num_tangled > 0:
plural = "s" if num_tangled > 1 else ""
msg = f"Mesh has {num_tangled} tangled element{plural}."
if self.raise_error:
raise ValueError(msg)
warnings.warn(msg)
else:
raise ValueError(msg)
return num_tangled
37 changes: 28 additions & 9 deletions test/test_tangling.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,34 @@
from firedrake import *
from movement import *
import unittest


def test_tangling_checker():
class TestTangling(unittest.TestCase):
"""
Test that :class:`MeshTanglingChecker`
can correctly identify tangled elements
in a 2D triangular mesh.
Unit tests for mesh tangling checking.
"""
mesh = UnitSquareMesh(3, 3)
checker = MeshTanglingChecker(mesh)
assert checker.check() == 0
mesh.coordinates.dat.data[3] += 0.2
assert checker.check() == 1

def test_tangling_checker_error1(self):
mesh = UnitSquareMesh(3, 3)
checker = MeshTanglingChecker(mesh, raise_error=True)
mesh.coordinates.dat.data[3] += 0.2
with self.assertRaises(ValueError) as cm:
checker.check()
msg = "Mesh has 1 tangled element."
self.assertEqual(str(cm.exception), msg)

def test_tangling_checker_error2(self):
mesh = UnitSquareMesh(3, 3)
checker = MeshTanglingChecker(mesh, raise_error=True)
mesh.coordinates.dat.data[3] += 0.5
with self.assertRaises(ValueError) as cm:
checker.check()
msg = "Mesh has 3 tangled elements."
self.assertEqual(str(cm.exception), msg)

def test_tangling_checker_warning1(self):
mesh = UnitSquareMesh(3, 3)
checker = MeshTanglingChecker(mesh, raise_error=False)
self.assertEqual(checker.check(), 0)
mesh.coordinates.dat.data[3] += 0.2
self.assertEqual(checker.check(), 1)

0 comments on commit c6b51c1

Please sign in to comment.