Skip to content

Commit af68e2d

Browse files
majosminducer
andauthored
Use elementwise size in node vertex consistency check (#316)
* use elementwise size in node vertex consistency check * add comment explaining maximum(..., 1) * use test based on maximum absolute vertex coordinate instead of element size, and add tests * assume absolute and relative tolerances are the same for now * add test for false negatives * zero-size -> zero-D * improve vertex consistency check error message * use a specific exception type for consistency check * improve tests * just use relative tolerance and skip dim==0 case * only do consistency check in debug run * Reenable 0D tests, remove 1e-5 crit tol scaling, take into account abs tol in node-vert-consistency test tests Co-authored-by: Andreas Kloeckner <[email protected]>
1 parent e7613ae commit af68e2d

File tree

3 files changed

+182
-11
lines changed

3 files changed

+182
-11
lines changed

meshmode/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
.. exception:: Error
2626
.. exception:: DataUnavailable
2727
.. exception:: FileExistsError
28+
.. exception:: InconsistentVerticesError
2829
"""
2930

3031
from builtins import FileExistsError # noqa: F401
@@ -39,6 +40,14 @@ class DataUnavailable(Error):
3940
pass
4041

4142

43+
class InconsistentVerticesError(Error):
44+
"""
45+
Raised when an element's local-to-global mapping does not map the unit vertices
46+
to the corresponding values in the mesh's *vertices* array.
47+
"""
48+
pass
49+
50+
4251
def _acf():
4352
"""A tiny undocumented function to pass to tests that take an ``actx_factory``
4453
argument when running them from the command line.

meshmode/mesh/__init__.py

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1218,28 +1218,37 @@ def _mesh_group_node_vertex_error(mesh, mgrp):
12181218
return map_vertices - grp_vertices
12191219

12201220

1221-
def _test_node_vertex_consistency_resampling(mesh, mgrp, tol):
1221+
def _test_node_vertex_consistency_resampling(mesh, igrp, tol):
12221222
if mesh.vertices is None:
12231223
return True
12241224

1225+
mgrp = mesh.groups[igrp]
1226+
12251227
if mgrp.nelements == 0:
12261228
return True
12271229

12281230
per_vertex_errors = _mesh_group_node_vertex_error(mesh, mgrp)
1229-
per_element_vertex_errors = np.sqrt(
1230-
np.sum(np.sum((per_vertex_errors)**2, axis=0), axis=-1)
1231-
)
1231+
per_element_vertex_errors = np.max(
1232+
np.max(np.abs(per_vertex_errors), axis=-1), axis=0)
12321233

12331234
if tol is None:
12341235
tol = 1e3 * np.finfo(per_element_vertex_errors.dtype).eps
12351236

1236-
from meshmode.mesh.processing import find_bounding_box
1237+
grp_vertices = mesh.vertices[:, mgrp.vertex_indices]
1238+
1239+
coord_scales = np.max(np.max(np.abs(grp_vertices), axis=-1), axis=0)
12371240

1238-
bbox_min, bbox_max = find_bounding_box(mesh)
1239-
size = la.norm(bbox_max-bbox_min)
1241+
per_element_tols = tol + tol * coord_scales
12401242

1241-
max_el_vertex_error = np.max(per_element_vertex_errors)
1242-
assert max_el_vertex_error < tol*size, max_el_vertex_error
1243+
elements_above_tol, = np.where(per_element_vertex_errors >= per_element_tols)
1244+
if len(elements_above_tol) > 0:
1245+
i_grp_elem = elements_above_tol[0]
1246+
ielem = i_grp_elem + mesh.base_element_nrs[igrp]
1247+
from meshmode import InconsistentVerticesError
1248+
raise InconsistentVerticesError(
1249+
f"vertex consistency check failed for element {ielem}; "
1250+
f"{per_element_vertex_errors[i_grp_elem]} >= "
1251+
f"{per_element_tols[i_grp_elem]}")
12431252

12441253
return True
12451254

@@ -1248,10 +1257,12 @@ def _test_node_vertex_consistency(mesh, tol):
12481257
"""Ensure that order of by-index vertices matches that of mapped
12491258
unit vertices.
12501259
"""
1260+
if not __debug__:
1261+
return True
12511262

1252-
for mgrp in mesh.groups:
1263+
for igrp, mgrp in enumerate(mesh.groups):
12531264
if isinstance(mgrp, _ModepyElementGroup):
1254-
assert _test_node_vertex_consistency_resampling(mesh, mgrp, tol)
1265+
assert _test_node_vertex_consistency_resampling(mesh, igrp, tol)
12551266
else:
12561267
from warnings import warn
12571268
warn("not implemented: node-vertex consistency check for '%s'"

test/test_mesh.py

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"""
2525

2626
from functools import partial
27+
from dataclasses import replace
2728
import numpy as np
2829
import numpy.linalg as la
2930
import pytest
@@ -1130,6 +1131,156 @@ def test_mesh_element_group_constructor(shape_cls, group_cls):
11301131
# }}}
11311132

11321133

1134+
# {{{ test_node_vertex_consistency_check
1135+
1136+
def test_node_vertex_consistency_check(actx_factory):
1137+
actx = actx_factory()
1138+
1139+
from meshmode import InconsistentVerticesError
1140+
1141+
dtype = np.float64
1142+
tol = 1e3 * np.finfo(dtype).eps
1143+
1144+
# Mesh bounds invariance
1145+
1146+
def gen_rect_mesh_with_perturbed_vertices(
1147+
a, b, nelements_per_axis, perturb_amount):
1148+
mesh_unperturbed = mgen.generate_regular_rect_mesh(
1149+
a=a, b=b, nelements_per_axis=nelements_per_axis)
1150+
return mesh_unperturbed.copy( # noqa: F841
1151+
vertices=(
1152+
mesh_unperturbed.vertices
1153+
+ perturb_amount*np.ones(mesh_unperturbed.vertices.shape)),
1154+
node_vertex_consistency_tolerance=tol)
1155+
1156+
# Find critical perturbation amount "w" such that vertices shifted by w works
1157+
# but 10*w doesn't
1158+
h = 1
1159+
nelems = 8
1160+
size = h*nelems
1161+
crit_perturb = None
1162+
for p in range(32):
1163+
w = h*10**(p-16)
1164+
try:
1165+
gen_rect_mesh_with_perturbed_vertices(
1166+
a=(-size/2,), b=(size/2,), nelements_per_axis=(nelems,),
1167+
perturb_amount=w)
1168+
except InconsistentVerticesError:
1169+
if p > 0:
1170+
crit_perturb = w/10
1171+
break
1172+
if crit_perturb is None:
1173+
raise RuntimeError("failed to find critical vertex perturbation amount")
1174+
1175+
# Scale invariance
1176+
nelems = 8
1177+
for p in range(32):
1178+
h = 10**(p-16)
1179+
size = h*nelems
1180+
perturb = crit_perturb*h
1181+
gen_rect_mesh_with_perturbed_vertices(
1182+
a=(-size/2,)*3, b=(size/2,)*3, nelements_per_axis=(nelems,)*3,
1183+
perturb_amount=perturb)
1184+
if 10*perturb > tol:
1185+
with pytest.raises(InconsistentVerticesError):
1186+
gen_rect_mesh_with_perturbed_vertices(
1187+
a=(-size/2,)*3, b=(size/2,)*3, nelements_per_axis=(nelems,)*3,
1188+
perturb_amount=10*perturb)
1189+
1190+
# Translation invariance
1191+
h = 1
1192+
nelems = 8
1193+
size = h*nelems
1194+
for p in range(10):
1195+
shift = 10**p-1
1196+
perturb = crit_perturb*(size/2 + shift)/(size/2)
1197+
gen_rect_mesh_with_perturbed_vertices(
1198+
a=(shift-size/2,)*3, b=(shift+size/2,)*3,
1199+
nelements_per_axis=(nelems,)*3,
1200+
perturb_amount=perturb)
1201+
with pytest.raises(InconsistentVerticesError):
1202+
gen_rect_mesh_with_perturbed_vertices(
1203+
a=(shift-size/2,)*3, b=(shift+size/2,)*3,
1204+
nelements_per_axis=(nelems,)*3,
1205+
perturb_amount=10*perturb)
1206+
1207+
# Aspect ratio invariance
1208+
h = 1
1209+
nelems = 8
1210+
size = h*nelems
1211+
for p in range(10):
1212+
h_x = 10**p * h
1213+
size_x = h_x * nelems
1214+
perturb = crit_perturb*h_x
1215+
gen_rect_mesh_with_perturbed_vertices(
1216+
a=(-size_x/2,) + (-size/2,)*2,
1217+
b=(size_x/2,) + (size/2,)*2,
1218+
nelements_per_axis=(nelems,)*3,
1219+
perturb_amount=perturb)
1220+
with pytest.raises(InconsistentVerticesError):
1221+
gen_rect_mesh_with_perturbed_vertices(
1222+
a=(-size_x/2,) + (-size/2,)*2,
1223+
b=(size_x/2,) + (size/2,)*2,
1224+
nelements_per_axis=(nelems,)*3,
1225+
perturb_amount=10*perturb)
1226+
1227+
# Mesh size relative to element size invariance
1228+
h = 1
1229+
for p in range(5):
1230+
nelems = 2**(5-p)
1231+
size = h*nelems
1232+
perturb = crit_perturb
1233+
gen_rect_mesh_with_perturbed_vertices(
1234+
a=(-size/2,)*3, b=(size/2,)*3, nelements_per_axis=(nelems,)*3,
1235+
perturb_amount=perturb)
1236+
with pytest.raises(InconsistentVerticesError):
1237+
gen_rect_mesh_with_perturbed_vertices(
1238+
a=(-size/2,)*3, b=(size/2,)*3, nelements_per_axis=(nelems,)*3,
1239+
perturb_amount=10*perturb)
1240+
1241+
# Zero-D elements
1242+
h = 1
1243+
nelems = 7
1244+
size = h*nelems
1245+
vol_mesh = mgen.generate_regular_rect_mesh(
1246+
a=(-size/2,), b=(size/2,),
1247+
nelements_per_axis=(nelems,))
1248+
from meshmode.discretization import Discretization
1249+
group_factory = default_simplex_group_factory(1, 1)
1250+
vol_discr = Discretization(actx, vol_mesh, group_factory)
1251+
from meshmode.discretization.connection import (
1252+
FACE_RESTR_ALL,
1253+
make_face_restriction)
1254+
make_face_restriction(
1255+
actx, vol_discr, group_factory, FACE_RESTR_ALL, per_face_groups=False)
1256+
1257+
# Zero-D elements at the origin
1258+
h = 1
1259+
nelems = 8
1260+
size = h*nelems
1261+
vol_mesh = mgen.generate_regular_rect_mesh(
1262+
a=(-size/2,), b=(size/2,),
1263+
nelements_per_axis=(nelems,))
1264+
group_factory = default_simplex_group_factory(1, 1)
1265+
vol_discr = Discretization(actx, vol_mesh, group_factory)
1266+
make_face_restriction(
1267+
actx, vol_discr, group_factory, FACE_RESTR_ALL, per_face_groups=False)
1268+
1269+
# Element vertex indices rotated
1270+
with pytest.raises(InconsistentVerticesError):
1271+
vol_mesh_unrotated = mgen.generate_regular_rect_mesh(
1272+
a=(-1,)*2, b=(1,)*2,
1273+
nelements_per_axis=(8,)*2)
1274+
vol_mesh = vol_mesh_unrotated.copy( # noqa: F841
1275+
groups=[
1276+
replace(
1277+
grp,
1278+
vertex_indices=np.roll(grp.vertex_indices, 1, axis=1))
1279+
for grp in vol_mesh_unrotated.groups])
1280+
1281+
# }}}
1282+
1283+
11331284
# {{{ mesh boundary gluing
11341285

11351286
@pytest.mark.parametrize("use_tree", [False, True])

0 commit comments

Comments
 (0)