def _test_node_vertex_consistency_simplex(mesh, mgrp, tol): if mgrp.nelements == 0: return True resampling_mat = mp.resampling_matrix( mp.simplex_best_available_basis(mgrp.dim, mgrp.order), mgrp.vertex_unit_coordinates().T, mgrp.unit_nodes) # dim, nelments, nnvertices map_vertices = np.einsum( "ij,dej->dei", resampling_mat, mgrp.nodes) grp_vertices = mesh.vertices[:, mgrp.vertex_indices] per_element_vertex_errors = np.sqrt(np.sum( np.sum((map_vertices - grp_vertices)**2, axis=0), axis=-1)) if tol is None: tol = 1e3 * np.finfo(per_element_vertex_errors.dtype).eps from meshmode.mesh.processing import find_bounding_box bbox_min, bbox_max = find_bounding_box(mesh) size = la.norm(bbox_max-bbox_min) assert np.max(per_element_vertex_errors) < tol*size, \ np.max(per_element_vertex_errors) return True
def get_midpoints(group, tesselation, elements): """ Compute the midpoints of the vertices of the specified elements. :arg group: An instance of :class:`meshmode.mesh.SimplexElementGroup` :arg tesselation: With attributes `ref_vertices`, `children` :arg elements: A list of (group-relative) element numbers :return: A :class:`dict` mapping element numbers to midpoint coordinates, with each value in the map having shape ``(ambient_dim, nmidpoints)``. The ordering of the midpoints follows their ordering in the tesselation (see also :meth:`SimplexResampler.get_vertex_pair_to_midpoint_order`) """ assert len(group.vertex_indices[0]) == group.dim + 1 # Get midpoints, converted to unit coordinates. midpoints = -1 + np.array([vertex for vertex in tesselation.ref_vertices if 1 in vertex], dtype=float) resamp_mat = mp.resampling_matrix( mp.simplex_best_available_basis(group.dim, group.order), midpoints.T, group.unit_nodes) resamp_midpoints = np.einsum("mu,deu->edm", resamp_mat, group.nodes[:, elements]) return dict(zip(elements, resamp_midpoints))
def get_midpoints(self, group, tesselation, elements): """ Compute the midpoints of the vertices of the specified elements. :arg group: An instance of :class:`meshmode.mesh.SimplexElementGroup` :arg tesselation: With attributes `ref_vertices`, `children` :arg elements: A list of (group-relative) element numbers :return: A :class:`dict` mapping element numbers to midpoint coordinates, with each value in the map having shape ``(ambient_dim, nmidpoints)``. The ordering of the midpoints follows their ordering in the tesselation (see also :meth:`SimplexResampler.get_vertex_pair_to_midpoint_order`) """ assert len(group.vertex_indices[0]) == group.dim + 1 # Get midpoints, converted to unit coordinates. midpoints = -1 + np.array([vertex for vertex in tesselation.ref_vertices if 1 in vertex], dtype=float) resamp_mat = mp.resampling_matrix( mp.simplex_best_available_basis(group.dim, group.order), midpoints.T, group.unit_nodes) resamp_midpoints = np.einsum("mu,deu->edm", resamp_mat, group.nodes[:, elements]) return dict(zip(elements, resamp_midpoints))
def _test_node_vertex_consistency_simplex(mesh, mgrp): if mgrp.nelements == 0: return True resampling_mat = mp.resampling_matrix( mp.simplex_best_available_basis(mgrp.dim, mgrp.order), mgrp.vertex_unit_coordinates().T, mgrp.unit_nodes) # dim, nelments, nnvertices map_vertices = np.einsum( "ij,dej->dei", resampling_mat, mgrp.nodes) grp_vertices = mesh.vertices[:, mgrp.vertex_indices] per_element_vertex_errors = np.sqrt(np.sum( np.sum((map_vertices - grp_vertices)**2, axis=0), axis=-1)) tol = 1e3 * np.finfo(per_element_vertex_errors.dtype).eps from meshmode.mesh.processing import find_bounding_box bbox_min, bbox_max = find_bounding_box(mesh) size = la.norm(bbox_max-bbox_min) assert np.max(per_element_vertex_errors) < tol*size, \ np.max(per_element_vertex_errors) return True
def is_affine_simplex_group(group, abs_tol=None): if abs_tol is None: abs_tol = 1.0e-13 if not isinstance(group, SimplexElementGroup): raise TypeError("expected a 'SimplexElementGroup' not '%s'" % type(group).__name__) # get matrices basis = mp.simplex_best_available_basis(group.dim, group.order) grad_basis = mp.grad_simplex_best_available_basis(group.dim, group.order) vinv = la.inv(mp.vandermonde(basis, group.unit_nodes)) diff = mp.differentiation_matrices(basis, grad_basis, group.unit_nodes) if not isinstance(diff, tuple): diff = (diff,) # construct all second derivative matrices (including cross terms) from itertools import product mats = [] for n in product(range(group.dim), repeat=2): if n[0] > n[1]: continue mats.append(vinv.dot(diff[n[0]].dot(diff[n[1]]))) # check just the first element for a non-affine local-to-global mapping ddx_coeffs = np.einsum("aij,bj->abi", mats, group.nodes[:, 0, :]) norm_inf = np.max(np.abs(ddx_coeffs)) if norm_inf > abs_tol: return False # check all elements for a non-affine local-to-global mapping ddx_coeffs = np.einsum("aij,bcj->abci", mats, group.nodes) norm_inf = np.max(np.abs(ddx_coeffs)) return norm_inf < abs_tol
def get_tesselated_nodes(group, tesselation, elements): """ Compute the nodes of the child elements according to the tesselation. :arg group: An instance of :class:`meshmode.mesh.SimplexElementGroup` :arg tesselation: With attributes `ref_vertices`, `children` :arg elements: A list of (group-relative) element numbers :return: A :class:`dict` mapping element numbers to node coordinates, with each value in the map having shape ``(ambient_dim, nchildren, nunit_nodes)``. The ordering of the child nodes follows the ordering of ``tesselation.children.`` """ assert len(group.vertex_indices[0]) == group.dim + 1 from meshmode.mesh.refinement.utils import map_unit_nodes_to_children # Get child unit node coordinates. child_unit_nodes = np.hstack( list(map_unit_nodes_to_children(group.unit_nodes, tesselation))) resamp_mat = mp.resampling_matrix( mp.simplex_best_available_basis(group.dim, group.order), child_unit_nodes, group.unit_nodes) resamp_unit_nodes = np.einsum("cu,deu->edc", resamp_mat, group.nodes[:, elements]) ambient_dim = len(group.nodes) nunit_nodes = len(group.unit_nodes[0]) return dict((elem, resamp_unit_nodes[ielem].reshape((ambient_dim, -1, nunit_nodes))) for ielem, elem in enumerate(elements))
def get_simplex_element_flip_matrix(order, unit_nodes, permutation=None): """ Generate a resampling matrix that corresponds to a permutation of the barycentric coordinates being applied. The default permutation is to swap the first two barycentric coordinates. :param order: The order of the function space on the simplex, (see second argument in :fun:`modepy.simplex_best_available_basis`) :param unit_nodes: A np array of unit nodes with shape *(dim, nunit_nodes)* :param permutation: Either *None*, or a tuple of shape storing a permutation: the *i*th barycentric coordinate gets mapped to the *permutation[i]*th coordinate. :return: A numpy array of shape *(nunit_nodes, nunit_nodes)* which, when its transpose is right-applied to the matrix of nodes (shaped *(dim, nunit_nodes)*), corresponds to the permutation being applied """ from modepy.tools import barycentric_to_unit, unit_to_barycentric bary_unit_nodes = unit_to_barycentric(unit_nodes) flipped_bary_unit_nodes = bary_unit_nodes.copy() if permutation is None: flipped_bary_unit_nodes[0, :] = bary_unit_nodes[1, :] flipped_bary_unit_nodes[1, :] = bary_unit_nodes[0, :] else: flipped_bary_unit_nodes[permutation, :] = bary_unit_nodes flipped_unit_nodes = barycentric_to_unit(flipped_bary_unit_nodes) dim = unit_nodes.shape[0] flip_matrix = mp.resampling_matrix( mp.simplex_best_available_basis(dim, order), flipped_unit_nodes, unit_nodes) flip_matrix[np.abs(flip_matrix) < 1e-15] = 0 # Flipping twice should be the identity if permutation is None: assert la.norm( np.dot(flip_matrix, flip_matrix) - np.eye(len(flip_matrix))) < 1e-13 return flip_matrix
def flip_simplex_element_group(vertices, grp, grp_flip_flags): from modepy.tools import barycentric_to_unit, unit_to_barycentric from meshmode.mesh import SimplexElementGroup if not isinstance(grp, SimplexElementGroup): raise NotImplementedError( "flips only supported on " "exclusively SimplexElementGroup-based meshes") # Swap the first two vertices on elements to be flipped. new_vertex_indices = grp.vertex_indices.copy() new_vertex_indices[grp_flip_flags, 0] \ = grp.vertex_indices[grp_flip_flags, 1] new_vertex_indices[grp_flip_flags, 1] \ = grp.vertex_indices[grp_flip_flags, 0] # Generate a resampling matrix that corresponds to the # first two barycentric coordinates being swapped. bary_unit_nodes = unit_to_barycentric(grp.unit_nodes) flipped_bary_unit_nodes = bary_unit_nodes.copy() flipped_bary_unit_nodes[0, :] = bary_unit_nodes[1, :] flipped_bary_unit_nodes[1, :] = bary_unit_nodes[0, :] flipped_unit_nodes = barycentric_to_unit(flipped_bary_unit_nodes) flip_matrix = mp.resampling_matrix( mp.simplex_best_available_basis(grp.dim, grp.order), flipped_unit_nodes, grp.unit_nodes) flip_matrix[np.abs(flip_matrix) < 1e-15] = 0 # Flipping twice should be the identity assert la.norm( np.dot(flip_matrix, flip_matrix) - np.eye(len(flip_matrix))) < 1e-13 # Apply the flip matrix to the nodes. new_nodes = grp.nodes.copy() new_nodes[:, grp_flip_flags] = np.einsum("ij,dej->dei", flip_matrix, grp.nodes[:, grp_flip_flags]) return SimplexElementGroup(grp.order, new_vertex_indices, new_nodes, unit_nodes=grp.unit_nodes)
def flip_simplex_element_group(vertices, grp, grp_flip_flags): from modepy.tools import barycentric_to_unit, unit_to_barycentric from meshmode.mesh import SimplexElementGroup if not isinstance(grp, SimplexElementGroup): raise NotImplementedError("flips only supported on " "exclusively SimplexElementGroup-based meshes") # Swap the first two vertices on elements to be flipped. new_vertex_indices = grp.vertex_indices.copy() new_vertex_indices[grp_flip_flags, 0] \ = grp.vertex_indices[grp_flip_flags, 1] new_vertex_indices[grp_flip_flags, 1] \ = grp.vertex_indices[grp_flip_flags, 0] # Generate a resampling matrix that corresponds to the # first two barycentric coordinates being swapped. bary_unit_nodes = unit_to_barycentric(grp.unit_nodes) flipped_bary_unit_nodes = bary_unit_nodes.copy() flipped_bary_unit_nodes[0, :] = bary_unit_nodes[1, :] flipped_bary_unit_nodes[1, :] = bary_unit_nodes[0, :] flipped_unit_nodes = barycentric_to_unit(flipped_bary_unit_nodes) flip_matrix = mp.resampling_matrix( mp.simplex_best_available_basis(grp.dim, grp.order), flipped_unit_nodes, grp.unit_nodes) flip_matrix[np.abs(flip_matrix) < 1e-15] = 0 # Flipping twice should be the identity assert la.norm( np.dot(flip_matrix, flip_matrix) - np.eye(len(flip_matrix))) < 1e-13 # Apply the flip matrix to the nodes. new_nodes = grp.nodes.copy() new_nodes[:, grp_flip_flags] = np.einsum( "ij,dej->dei", flip_matrix, grp.nodes[:, grp_flip_flags]) return SimplexElementGroup( grp.order, new_vertex_indices, new_nodes, unit_nodes=grp.unit_nodes)
def flip_matrix(self): """ :return: The matrix which should be applied to the *(dim, nunitnodes)*-shaped array of nodes corresponding to an element in order to change orientation - <-> +. The matrix will be *(dim, dim)* and orthogonal with *np.float64* type entries. """ if self._flip_matrix is None: # This is very similar to :mod:`meshmode` in processing.py # the function :function:`from_simplex_element_group`, but # we needed to use firedrake nodes from modepy.tools import barycentric_to_unit, unit_to_barycentric # Generate a resampling matrix that corresponds to the # first two barycentric coordinates being swapped. bary_unit_nodes = unit_to_barycentric(self.unit_nodes()) flipped_bary_unit_nodes = bary_unit_nodes.copy() flipped_bary_unit_nodes[0, :] = bary_unit_nodes[1, :] flipped_bary_unit_nodes[1, :] = bary_unit_nodes[0, :] flipped_unit_nodes = barycentric_to_unit(flipped_bary_unit_nodes) from modepy import resampling_matrix, simplex_best_available_basis flip_matrix = resampling_matrix( simplex_best_available_basis(self.dim(), self.analog().degree), flipped_unit_nodes, self.unit_nodes()) flip_matrix[np.abs(flip_matrix) < 1e-15] = 0 # Flipping twice should be the identity assert la.norm( np.dot(flip_matrix, flip_matrix) - np.eye(len(flip_matrix))) < 1e-13 self._flip_matrix = flip_matrix return self._flip_matrix
def get_tesselated_nodes(group, tesselation, elements): """ Compute the nodes of the child elements according to the tesselation. :arg group: An instance of :class:`meshmode.mesh.SimplexElementGroup` :arg tesselation: With attributes `ref_vertices`, `children` :arg elements: A list of (group-relative) element numbers :return: A :class:`dict` mapping element numbers to node coordinates, with each value in the map having shape ``(ambient_dim, nchildren, nunit_nodes)``. The ordering of the child nodes follows the ordering of ``tesselation.children.`` """ assert len(group.vertex_indices[0]) == group.dim + 1 from meshmode.mesh.refinement.utils import map_unit_nodes_to_children # Get child unit node coordinates. child_unit_nodes = np.hstack(list( map_unit_nodes_to_children(group.unit_nodes, tesselation))) resamp_mat = mp.resampling_matrix( mp.simplex_best_available_basis(group.dim, group.order), child_unit_nodes, group.unit_nodes) resamp_unit_nodes = np.einsum("cu,deu->edc", resamp_mat, group.nodes[:, elements]) ambient_dim = len(group.nodes) nunit_nodes = len(group.unit_nodes[0]) return dict((elem, resamp_unit_nodes[ielem].reshape( (ambient_dim, -1, nunit_nodes))) for ielem, elem in enumerate(elements))
def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): r""" Create a firedrake mesh corresponding to one :class:`~meshmode.mesh.Mesh`'s :class:`~meshmode.mesh.SimplexElementGroup`. :param mesh: A :class:`~meshmode.mesh.Mesh` to convert with at least one :class:`~meshmode.mesh.SimplexElementGroup`. 'mesh.is_conforming' must evaluate to *True*. 'mesh' must have vertices supplied, i.e. 'mesh.vertices' must not be *None*. :param group_nr: The group number to be converted into a firedrake mesh. The corresponding group must be of type :class:`~meshmode.mesh.SimplexElementGroup`. If *None* and *mesh* has only one group, that group is used. Otherwise, a *ValueError* is raised. :param comm: The communicator to build the dmplex mesh on :return: A tuple *(fdrake_mesh, fdrake_cell_ordering, perm2cell)* where * *fdrake_mesh* is a :mod:`firedrake` `firedrake.mesh.MeshGeometry` corresponding to *mesh* * *fdrake_cell_ordering* is a numpy array whose *i*\ th element in *mesh* (i.e. the *i*\ th element in *mesh.groups[group_nr].vertex_indices*) corresponds to the *fdrake_cell_ordering[i]*\ th :mod:`firedrake` cell * *perm2cell* is a dictionary, mapping tuples to 1-D numpy arrays of meshmode element indices. Each meshmode element index appears in exactly one of these arrays. The corresponding tuple describes how firedrake reordered the local vertex indices on that cell. In particular, if *c* is in the list *perm2cell[p]* for a tuple *p*, then the *p[i]*\ th local vertex of the *fdrake_cell_ordering[c]*\ th firedrake cell corresponds to the *i*\ th local vertex of the *c*\ th meshmode element. .. warning:: Currently, no custom boundary tags are exported along with the mesh. :mod:`firedrake` seems to only allow one marker on each facet, whereas :mod:`meshmode` allows many. """ if not isinstance(mesh, Mesh): raise TypeError("'mesh' must of type meshmode.mesh.Mesh," " not '%s'." % type(mesh)) if group_nr is None: if len(mesh.groups) != 1: raise ValueError("'group_nr' is *None* but 'mesh' has " "more than one group.") group_nr = 0 if not isinstance(group_nr, int): raise TypeError("Expecting 'group_nr' to be of type int, not " f"'{type(group_nr)}'") if group_nr < 0 or group_nr >= len(mesh.groups): raise ValueError("'group_nr' is an invalid group index:" f" '{group_nr}' fails to satisfy " f"0 <= {group_nr} < {len(mesh.groups)}") if not isinstance(mesh.groups[group_nr], SimplexElementGroup): raise TypeError("Expecting 'mesh.groups[group_nr]' to be of type " "meshmode.mesh.SimplexElementGroup, not " f"'{type(mesh.groups[group_nr])}'") if mesh.vertices is None: raise ValueError("'mesh' has no vertices " "('mesh.vertices' is *None*)") if not mesh.is_conforming: raise ValueError(f"'mesh.is_conforming' is {mesh.is_conforming} " "instead of *True*. Converting non-conforming " " meshes to Firedrake is not supported") # Get the vertices and vertex indices of the requested group with ProcessLogger(logger, "Obtaining vertices from selected group"): group = mesh.groups[group_nr] fd2mm_indices = np.unique(group.vertex_indices.flatten()) coords = mesh.vertices[:, fd2mm_indices].T mm2fd_indices = dict(zip(fd2mm_indices, np.arange(np.size(fd2mm_indices)))) cells = np.vectorize(mm2fd_indices.__getitem__)(group.vertex_indices) # Get a dmplex object and then a mesh topology with ProcessLogger(logger, "Building dmplex object and MeshTopology"): if comm is None: from pyop2.mpi import COMM_WORLD comm = COMM_WORLD # FIXME : not sure how to get around the private accesses import firedrake.mesh as fd_mesh plex = fd_mesh._from_cell_list(group.dim, cells, coords, comm) # Nb : One might be tempted to pass reorder=False and thereby save some # hassle in exchange for forcing firedrake to have slightly # less efficient caching. Unfortunately, that only prevents # the cells from being reordered, and does not prevent the # vertices from being (locally) reordered on each cell... # the tl;dr is we don't actually save any hassle top = fd_mesh.Mesh(plex, dim=mesh.ambient_dim) # mesh topology top.init() # Get new element ordering: with ProcessLogger(logger, "Determining permutations applied" " to local vertex numbers"): c_start, c_end = top._topology_dm.getHeightStratum(0) cell_index_mm2fd = np.vectorize(top._cell_numbering.getOffset)( np.arange(c_start, c_end)) v_start, v_end = top._topology_dm.getDepthStratum(0) # Firedrake goes crazy reordering local vertex numbers, # we've got to work to figure out what changes they made. # # *perm2cells* will map permutations of local vertex numbers to # the list of all the meshmode cells # which firedrake reordered according to that permutation # # Permutations on *n* vertices are stored as a tuple # containing all of the integers *0*, *1*, *2*, ..., *n-1* # exactly once. A permutation *p* # represents relabeling the *i*\ th local vertex # of a meshmode element as the *p[i]*\ th local vertex # in the corresponding firedrake cell. # # *perm2cells[p]* is a list of all the meshmode element indices # for which *p* represents the reordering applied by firedrake perm2cells = {} for mm_cell_id, dmp_ids in enumerate(top.cell_closure[cell_index_mm2fd]): # look at order of vertices in firedrake cell vert_dmp_ids = \ dmp_ids[np.logical_and(v_start <= dmp_ids, dmp_ids < v_end)] fdrake_order = vert_dmp_ids - v_start # get original order mm_order = mesh.groups[group_nr].vertex_indices[mm_cell_id] # want permutation p so that mm_order[p] = fdrake_order # To do so, look at permutations acting by composition. # # mm_order \circ argsort(mm_order) = # fdrake_order \circ argsort(fdrake_order) # so # mm_order \circ argsort(mm_order) \circ inv(argsort(fdrake_order)) # = fdrake_order # # argsort acts as an inverse, so the desired permutation is: perm = tuple(np.argsort(mm_order)[np.argsort(np.argsort(fdrake_order))]) perm2cells.setdefault(perm, []) perm2cells[perm].append(mm_cell_id) # Make perm2cells map to numpy arrays instead of lists perm2cells = {perm: np.array(cells) for perm, cells in perm2cells.items()} # Now make a coordinates function with ProcessLogger(logger, "Building firedrake function " "space for mesh coordinates"): from firedrake import VectorFunctionSpace, Function coords_fspace = VectorFunctionSpace(top, "CG", group.order, dim=mesh.ambient_dim) coords = Function(coords_fspace) # get firedrake unit nodes and map onto meshmode reference element fd_ref_cell_to_mm = get_affine_reference_simplex_mapping(group.dim, True) fd_unit_nodes = get_finat_element_unit_nodes(coords_fspace.finat_element) fd_unit_nodes = fd_ref_cell_to_mm(fd_unit_nodes) basis = simplex_best_available_basis(group.dim, group.order) resampling_mat = resampling_matrix(basis, new_nodes=fd_unit_nodes, old_nodes=group.unit_nodes) # Store the meshmode data resampled to firedrake unit nodes # (but still in meshmode order) resampled_group_nodes = np.matmul(group.nodes, resampling_mat.T) # Now put the nodes in the right local order # nodes is shaped *(ambient dim, nelements, nunit nodes)* with ProcessLogger(logger, "Storing meshmode mesh coordinates" " in firedrake nodal order"): from meshmode.mesh.processing import get_simplex_element_flip_matrix for perm, cells in perm2cells.items(): flip_mat = get_simplex_element_flip_matrix(group.order, fd_unit_nodes, perm) flip_mat = np.rint(flip_mat).astype(np.int32) resampled_group_nodes[:, cells, :] = \ np.matmul(resampled_group_nodes[:, cells, :], flip_mat.T) # store resampled data in right cell ordering with ProcessLogger(logger, "resampling mesh coordinates to " "firedrake unit nodes"): reordered_cell_node_list = coords_fspace.cell_node_list[cell_index_mm2fd] coords.dat.data[reordered_cell_node_list, :] = \ resampled_group_nodes.transpose((1, 2, 0)) return fd_mesh.Mesh(coords), cell_index_mm2fd, perm2cells
def from_mesh_interp_matrix(self): meg = self.mesh_el_group return mp.resampling_matrix( mp.simplex_best_available_basis(meg.dim, meg.order), self.unit_nodes, meg.unit_nodes)
def basis(self): return mp.simplex_best_available_basis(self.dim, self.order)