Skip to content

Commit 4ecbeca

Browse files
alexfiklinducer
authored andcommitted
mesh: make check_mesh_consistency raise instead of assert
1 parent ca888f4 commit 4ecbeca

File tree

2 files changed

+157
-55
lines changed

2 files changed

+157
-55
lines changed

meshmode/mesh/__init__.py

Lines changed: 155 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
.. autoclass:: Mesh
4545
.. autofunction:: make_mesh
4646
.. autofunction:: check_mesh_consistency
47+
.. autofunction:: is_mesh_consistent
4748
4849
.. autoclass:: NodalAdjacency
4950
.. autoclass:: FacialAdjacencyGroup
@@ -761,42 +762,149 @@ def check_mesh_consistency(
761762
* The facial adjacency shapes and dtypes.
762763
* The mesh orientation using
763764
:func:`~meshmode.mesh.processing.find_volume_mesh_element_orientations`.
765+
766+
:arg node_vertex_consistency_tolerance: If *False*, do not check for
767+
consistency between vertex and nodal data. If *None*, a default tolerance
768+
based on the :class:`~numpy.dtype` of the *vertices* array will be used.
769+
Otherwise, the given value is used as the tolerance.
770+
:arg skip_element_orientation_test: If *False*, check that element
771+
orientation is positive in volume meshes (i.e. ones where ambient and
772+
topological dimension match).
773+
774+
:raises InconsistentMeshError: when the mesh is found to be inconsistent in
775+
some fashion.
764776
"""
777+
from meshmode import (
778+
InconsistentAdjacencyError, InconsistentArrayDTypeError,
779+
InconsistentMeshError)
780+
765781
if node_vertex_consistency_tolerance is not False:
766-
assert _test_node_vertex_consistency(
767-
mesh, node_vertex_consistency_tolerance)
782+
_test_node_vertex_consistency(mesh, tol=node_vertex_consistency_tolerance)
783+
784+
for i, g in enumerate(mesh.groups):
785+
if g.vertex_indices is None:
786+
continue
768787

769-
for g in mesh.groups:
770-
if g.vertex_indices is not None:
771-
assert g.vertex_indices.dtype == mesh.vertex_id_dtype
788+
if g.vertex_indices.dtype != mesh.vertex_id_dtype:
789+
raise InconsistentArrayDTypeError(
790+
f"Group '{i}' attribute 'vertex_indices' has incorrect dtype: "
791+
f"{g.vertex_indices.dtype!r} (expected mesh 'vertex_id_dtype' = "
792+
f"{mesh.vertex_id_dtype!r})")
772793

773794
nodal_adjacency = mesh._nodal_adjacency
774795
if nodal_adjacency:
775-
assert nodal_adjacency.neighbors_starts.shape == (mesh.nelements + 1,)
776-
assert len(nodal_adjacency.neighbors.shape) == 1
777-
assert nodal_adjacency.neighbors_starts.dtype == mesh.element_id_dtype
778-
assert nodal_adjacency.neighbors.dtype == mesh.element_id_dtype
796+
if nodal_adjacency.neighbors_starts.shape != (mesh.nelements + 1,):
797+
raise InconsistentAdjacencyError(
798+
"Nodal adjacency 'neighbors_starts' has incorrect shape: "
799+
f"'{nodal_adjacency.neighbors_starts.shape}' (expected "
800+
f"nelements + 1 = {mesh.nelements + 1})")
801+
802+
if len(nodal_adjacency.neighbors.shape) != 1:
803+
raise InconsistentAdjacencyError(
804+
"Nodal adjacency 'neighbors' have incorrect dim: "
805+
f"{nodal_adjacency.neighbors.shape} (expected ndim = 1)")
806+
807+
if nodal_adjacency.neighbors_starts.dtype != mesh.element_id_dtype:
808+
raise InconsistentArrayDTypeError(
809+
"Nodal adjacency 'neighbors_starts' has incorrect dtype: "
810+
f"{nodal_adjacency.neighbors_starts.dtype!r} (expected mesh "
811+
f"'element_id_dtype' = {mesh.element_id_dtype!r})")
812+
813+
if nodal_adjacency.neighbors.dtype != mesh.element_id_dtype:
814+
raise InconsistentArrayDTypeError(
815+
"Nodal adjacency 'neighbors' has incorrect dtype: "
816+
f"{nodal_adjacency.neighbors.dtype!r} (expected mesh "
817+
f"'element_id_dtype' = {mesh.element_id_dtype!r})")
779818

780819
facial_adjacency_groups = mesh._facial_adjacency_groups
781820
if facial_adjacency_groups:
782-
assert len(facial_adjacency_groups) == len(mesh.groups)
821+
if len(facial_adjacency_groups) != len(mesh.groups):
822+
raise InconsistentAdjacencyError(
823+
"Facial adjacency groups do not match mesh groups: "
824+
f"{len(facial_adjacency_groups)} (expected {len(mesh.groups)})")
825+
826+
for igrp, fagrp_list in enumerate(facial_adjacency_groups):
827+
for ifagrp, fagrp in enumerate(fagrp_list):
828+
if len(fagrp.elements.shape) != 1:
829+
raise InconsistentAdjacencyError(
830+
f"Facial adjacency {ifagrp} for group {igrp} has incorrect "
831+
f"'elements' shape: {fagrp.elements.shape} "
832+
"(expected ndim = 1)")
783833

784-
for fagrp_list in facial_adjacency_groups:
785-
for fagrp in fagrp_list:
786834
nfagrp_elements, = fagrp.elements.shape
787-
assert fagrp.element_faces.dtype == mesh.face_id_dtype
788-
assert fagrp.element_faces.shape == (nfagrp_elements,)
835+
if fagrp.element_faces.shape != (nfagrp_elements,):
836+
raise InconsistentAdjacencyError(
837+
f"Facial adjacency {ifagrp} for group {igrp} has incorrect "
838+
f"'element_faces' shape: {fagrp.element_faces.shape} "
839+
f"(expected 'elements.shape' = {fagrp.elements.shape})")
840+
841+
if fagrp.element_faces.dtype != mesh.face_id_dtype:
842+
raise InconsistentArrayDTypeError(
843+
f"Facial adjacency {ifagrp} for group {igrp} has "
844+
"incorrect 'element_faces' dtype: "
845+
f"{fagrp.element_faces.dtype!r} (expected mesh "
846+
f"'face_id_dtype' = {mesh.face_id_dtype!r})")
789847

790848
if isinstance(fagrp, InteriorAdjacencyGroup):
791-
assert fagrp.neighbors.dtype == mesh.element_id_dtype
792-
assert fagrp.neighbors.shape == (nfagrp_elements,)
793-
assert fagrp.neighbor_faces.dtype == mesh.face_id_dtype
794-
assert fagrp.neighbor_faces.shape == (nfagrp_elements,)
849+
if fagrp.neighbors.dtype != mesh.element_id_dtype:
850+
raise InconsistentArrayDTypeError(
851+
f"Facial adjacency {ifagrp} for group {igrp} has "
852+
"incorrect 'neighbors' dtype: "
853+
f"{fagrp.neighbors.dtype!r} (expected mesh "
854+
f"'element_id_dtype' = {mesh.element_id_dtype!r})")
855+
856+
if fagrp.neighbor_faces.dtype != mesh.face_id_dtype:
857+
raise InconsistentArrayDTypeError(
858+
f"Facial adjacency {ifagrp} for group {igrp} has "
859+
"incorrect 'neighbor_faces' dtype: "
860+
f"{fagrp.neighbor_faces.dtype!r} (expected mesh "
861+
f"'face_id_dtype' = {mesh.face_id_dtype!r})")
862+
863+
if fagrp.neighbors.shape != (nfagrp_elements,):
864+
raise InconsistentAdjacencyError(
865+
f"Facial adjacency {ifagrp} for group {igrp} has "
866+
"incorrect 'neighbors' shape: "
867+
f"{fagrp.neighbors.shape} (expected "
868+
f"'elements.shape' = {fagrp.elements.shape})")
869+
870+
if fagrp.neighbor_faces.shape != (nfagrp_elements,):
871+
raise InconsistentAdjacencyError(
872+
f"Facial adjacency {ifagrp} for group {igrp} has "
873+
"incorrect 'neighbor_faces' shape: "
874+
f"{fagrp.neighbor_faces.shape} (expected "
875+
f"'elements.shape' = {fagrp.elements.shape})")
795876

796877
from meshmode.mesh.processing import test_volume_mesh_element_orientations
797878

798-
if mesh.dim == mesh.ambient_dim and not skip_element_orientation_test:
799-
assert test_volume_mesh_element_orientations(mesh)
879+
if not skip_element_orientation_test:
880+
if mesh.dim == mesh.ambient_dim:
881+
if not test_volume_mesh_element_orientations(mesh):
882+
raise InconsistentMeshError("Mesh has inconsistent orientations")
883+
else:
884+
warn("Cannot check element orientation for a mesh with "
885+
"mesh.dim != mesh.ambient_dim", stacklevel=2)
886+
887+
888+
def is_mesh_consistent(
889+
mesh: "Mesh",
890+
*,
891+
node_vertex_consistency_tolerance: Optional[
892+
Union[Literal[False], float]] = None,
893+
skip_element_orientation_test: bool = False,
894+
) -> bool:
895+
"""A boolean version of :func:`check_mesh_consistency`."""
896+
897+
from meshmode import InconsistentMeshError
898+
899+
try:
900+
check_mesh_consistency(
901+
mesh,
902+
node_vertex_consistency_tolerance=node_vertex_consistency_tolerance,
903+
skip_element_orientation_test=skip_element_orientation_test)
904+
except InconsistentMeshError:
905+
return False
906+
else:
907+
return True
800908

801909

802910
def make_mesh(
@@ -857,12 +965,8 @@ def make_mesh(
857965
:arg skip_tests: a flag used to skip any mesh consistency checks. This can
858966
be set to *True* in special situation, e.g. when loading a broken mesh
859967
that will be fixed later.
860-
:arg node_vertex_consistency_tolerance: If *False*, do not check for
861-
consistency between vertex and nodal data. If *None*, a default tolerance
862-
based on the :class:`~numpy.dtype` of the *vertices* array will be used.
863-
:arg skip_element_orientation_test: If *False*, check that element
864-
orientation is positive in volume meshes (i.e. ones where ambient and
865-
topological dimension match).
968+
:arg node_vertex_consistency_tolerance: see :func:`check_mesh_consistency`.
969+
:arg skip_element_orientation_test: see :func:`check_mesh_consistency`.
866970
"""
867971
vertex_id_dtype = np.dtype(vertex_id_dtype)
868972
if vertex_id_dtype.kind not in {"i", "u"}:
@@ -1145,8 +1249,8 @@ def dim(self) -> int:
11451249
def nvertices(self) -> int:
11461250
"""Number of vertices in the mesh, if available."""
11471251
if self.vertices is None:
1148-
from meshmode import DataUnavailable
1149-
raise DataUnavailable("vertices")
1252+
from meshmode import DataUnavailableError
1253+
raise DataUnavailableError("vertices")
11501254

11511255
return self.vertices.shape[-1]
11521256

@@ -1175,8 +1279,8 @@ def base_node_nrs(self):
11751279
def vertex_dtype(self):
11761280
"""The :class:`~numpy.dtype` of the :attr:`~Mesh.vertices` array, if any."""
11771281
if self.vertices is None:
1178-
from meshmode import DataUnavailable
1179-
raise DataUnavailable("vertices")
1282+
from meshmode import DataUnavailableError
1283+
raise DataUnavailableError("vertices")
11801284

11811285
return self.vertices.dtype
11821286

@@ -1187,17 +1291,17 @@ def nodal_adjacency(self) -> NodalAdjacency:
11871291
This property gets the :attr:`Mesh._nodal_adjacency` of the mesh. If the
11881292
attribute value is *None*, the adjacency is computed and cached.
11891293
1190-
:raises DataUnavailable: if the nodal adjacency cannot be obtained.
1294+
:raises DataUnavailableError: if the nodal adjacency cannot be obtained.
11911295
"""
1192-
from meshmode import DataUnavailable
1296+
from meshmode import DataUnavailableError
11931297

11941298
nodal_adjacency = self._nodal_adjacency
11951299
if nodal_adjacency is False:
1196-
raise DataUnavailable("Nodal adjacency is not available")
1300+
raise DataUnavailableError("Nodal adjacency is not available")
11971301

11981302
if nodal_adjacency is None:
11991303
if not self.is_conforming:
1200-
raise DataUnavailable(
1304+
raise DataUnavailableError(
12011305
"Nodal adjacency can only be computed for conforming meshes"
12021306
)
12031307

@@ -1254,17 +1358,17 @@ def facial_adjacency_groups(
12541358
Note that element groups are not necessarily geometrically contiguous
12551359
like the figure may suggest.
12561360
1257-
:raises DataUnavailable: if the facial adjacency cannot be obtained.
1361+
:raises DataUnavailableError: if the facial adjacency cannot be obtained.
12581362
"""
1259-
from meshmode import DataUnavailable
1363+
from meshmode import DataUnavailableError
12601364

12611365
facial_adjacency_groups = self._facial_adjacency_groups
12621366
if facial_adjacency_groups is False:
1263-
raise DataUnavailable("Facial adjacency is not available")
1367+
raise DataUnavailableError("Facial adjacency is not available")
12641368

12651369
if facial_adjacency_groups is None:
12661370
if not self.is_conforming:
1267-
raise DataUnavailable(
1371+
raise DataUnavailableError(
12681372
"Facial adjacency can only be computed for conforming meshes"
12691373
)
12701374

@@ -1333,14 +1437,17 @@ def _mesh_group_node_vertex_error(mesh, mgrp):
13331437
return map_vertices - grp_vertices
13341438

13351439

1336-
def _test_node_vertex_consistency_resampling(mesh, igrp, tol):
1440+
def _test_group_node_vertex_consistency_resampling(
1441+
mesh: Mesh, igrp: int, *, tol: Optional[float] = None) -> None:
13371442
if mesh.vertices is None:
1338-
return True
1443+
return
13391444

13401445
mgrp = mesh.groups[igrp]
13411446

13421447
if mgrp.nelements == 0:
1343-
return True
1448+
return
1449+
1450+
from meshmode import InconsistentVerticesError
13441451

13451452
per_vertex_errors = _mesh_group_node_vertex_error(mesh, mgrp)
13461453
per_element_vertex_errors = np.max(
@@ -1359,32 +1466,27 @@ def _test_node_vertex_consistency_resampling(mesh, igrp, tol):
13591466
if len(elements_above_tol) > 0:
13601467
i_grp_elem = elements_above_tol[0]
13611468
ielem = i_grp_elem + mesh.base_element_nrs[igrp]
1362-
from meshmode import InconsistentVerticesError
1469+
13631470
raise InconsistentVerticesError(
1364-
f"vertex consistency check failed for element {ielem}; "
1471+
f"Vertex consistency check failed for element {ielem}; "
13651472
f"{per_element_vertex_errors[i_grp_elem]} >= "
13661473
f"{per_element_tols[i_grp_elem]}")
13671474

1368-
return True
13691475

1476+
def _test_node_vertex_consistency(
1477+
mesh: Mesh, *, tol: Optional[float] = None) -> None:
1478+
"""Ensure that order of by-index vertices matches that of mapped unit vertices.
13701479
1371-
def _test_node_vertex_consistency(mesh, tol):
1372-
"""Ensure that order of by-index vertices matches that of mapped
1373-
unit vertices.
1480+
:raises InconsistentVerticesError: if the vertices are not consistent.
13741481
"""
1375-
if not __debug__:
1376-
return True
1377-
13781482
for igrp, mgrp in enumerate(mesh.groups):
13791483
if isinstance(mgrp, _ModepyElementGroup):
1380-
assert _test_node_vertex_consistency_resampling(mesh, igrp, tol)
1484+
_test_group_node_vertex_consistency_resampling(mesh, igrp, tol=tol)
13811485
else:
13821486
warn("Not implemented: node-vertex consistency check for "
13831487
f"groups of type '{type(mgrp).__name__}'.",
13841488
stacklevel=3)
13851489

1386-
return True
1387-
13881490
# }}}
13891491

13901492

meshmode/mesh/io.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -441,12 +441,12 @@ def group_to_json(group):
441441
"dim": group.dim,
442442
}
443443

444-
from meshmode import DataUnavailable
444+
from meshmode import DataUnavailableError
445445

446446
def nodal_adjacency_to_json(mesh):
447447
try:
448448
na = mesh.nodal_adjacency
449-
except DataUnavailable:
449+
except DataUnavailableError:
450450
return None
451451

452452
return {

0 commit comments

Comments
 (0)