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
802910def 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
0 commit comments