def test_resampling_matrix(dims, shape_cls, ncoarse=5, nfine=10): shape = shape_cls(dims) coarse_space = mp.space_for_shape(shape, ncoarse) fine_space = mp.space_for_shape(shape, nfine) coarse_nodes = mp.edge_clustered_nodes_for_space(coarse_space, shape) coarse_basis = mp.basis_for_space(coarse_space, shape) fine_nodes = mp.edge_clustered_nodes_for_space(fine_space, shape) fine_basis = mp.basis_for_space(fine_space, shape) my_eye = np.dot( mp.resampling_matrix(fine_basis.functions, coarse_nodes, fine_nodes), mp.resampling_matrix(coarse_basis.functions, fine_nodes, coarse_nodes)) assert la.norm(my_eye - np.eye(len(my_eye))) < 3e-13 my_eye_least_squares = np.dot( mp.resampling_matrix(coarse_basis.functions, coarse_nodes, fine_nodes, least_squares_ok=True), mp.resampling_matrix(coarse_basis.functions, fine_nodes, coarse_nodes), ) assert la.norm(my_eye_least_squares - np.eye(len(my_eye_least_squares))) < 4e-13
def test_resampling_matrix(dims): ncoarse = 5 nfine = 10 coarse_nodes = mp.warp_and_blend_nodes(dims, ncoarse) fine_nodes = mp.warp_and_blend_nodes(dims, nfine) coarse_basis = mp.simplex_onb(dims, ncoarse) fine_basis = mp.simplex_onb(dims, nfine) my_eye = np.dot( mp.resampling_matrix(fine_basis, coarse_nodes, fine_nodes), mp.resampling_matrix(coarse_basis, fine_nodes, coarse_nodes)) assert la.norm(my_eye - np.eye(len(my_eye))) < 1e-13 my_eye_least_squares = np.dot( mp.resampling_matrix(coarse_basis, coarse_nodes, fine_nodes, least_squares_ok=True), mp.resampling_matrix(coarse_basis, fine_nodes, coarse_nodes), ) assert la.norm(my_eye_least_squares - np.eye(len(my_eye_least_squares))) < 4e-13
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 _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 _resample_matrix(self, actx: ArrayContext, to_group_index, ibatch_index): import modepy as mp ibatch = self.groups[to_group_index].batches[ibatch_index] from_grp = self.from_discr.groups[ibatch.from_group_index] nfrom_unit_nodes = from_grp.unit_nodes.shape[1] if np.array_equal(from_grp.unit_nodes, ibatch.result_unit_nodes): # Nodes are exactly identical? We can 'interpolate' even when there # isn't a basis. result = np.eye(nfrom_unit_nodes) else: if len(from_grp.basis()) != nfrom_unit_nodes: from meshmode.discretization import NoninterpolatoryElementGroupError raise NoninterpolatoryElementGroupError( "%s does not support interpolation because it is not " "unisolvent (its unit node count does not match its " "number of basis functions). Using connections requires " "the ability to interpolate." % type(from_grp).__name__) result = mp.resampling_matrix(from_grp.basis(), ibatch.result_unit_nodes, from_grp.unit_nodes) return actx.freeze(actx.from_numpy(result))
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 _resample_matrix(self, to_group_index, ibatch_index): import modepy as mp ibatch = self.groups[to_group_index].batches[ibatch_index] from_grp = self.from_discr.groups[ibatch.from_group_index] nfrom_unit_nodes = from_grp.unit_nodes.shape[1] if np.array_equal(from_grp.unit_nodes, ibatch.result_unit_nodes): # Nodes are exactly identical? We can 'interpolate' even when there # isn't a basis. result = np.eye(nfrom_unit_nodes) else: if len(from_grp.basis()) != nfrom_unit_nodes: from meshmode.discretization import NoninterpolatoryElementGroupError raise NoninterpolatoryElementGroupError( "%s does not support interpolation because it is not " "unisolvent (its unit node count does not match its " "number of basis functions). Using connections requires " "the ability to interpolate." % type(from_grp).__name__) result = mp.resampling_matrix( from_grp.basis(), ibatch.result_unit_nodes, from_grp.unit_nodes) with cl.CommandQueue(self.cl_context) as queue: return cl.array.to_device(queue, result).with_queue(None)
def plot_element_values(n, nodes, values, resample_n=None, node_tuples=None, show_nodes=False): dims = len(nodes) orig_nodes = nodes orig_values = values if resample_n is not None: import modepy as mp basis = mp.simplex_onb(dims, n) fine_nodes = mp.equidistant_nodes(dims, resample_n) values = np.dot(mp.resampling_matrix(basis, fine_nodes, nodes), values) nodes = fine_nodes n = resample_n from pytools import generate_nonnegative_integer_tuples_summing_to_at_most \ as gnitstam if dims == 1: import matplotlib.pyplot as pt pt.plot(nodes[0], values) if show_nodes: pt.plot(orig_nodes[0], orig_values, "x") pt.show() elif dims == 2: import mayavi.mlab as mlab mlab.triangular_mesh( nodes[0], nodes[1], values, submesh(list(gnitstam(n, 2)))) if show_nodes: mlab.points3d(orig_nodes[0], orig_nodes[1], orig_values, scale_factor=0.05) mlab.show() else: raise RuntimeError("unsupported dimensionality %d" % dims)
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 from_mesh_interp_matrix(grp: NodalElementGroupBase) -> np.ndarray: meg = grp.mesh_el_group meg_space = type(grp.space)(meg.dim, meg.order) return mp.resampling_matrix( mp.basis_for_space(meg_space, grp.shape).functions, grp.unit_nodes, meg.unit_nodes)
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 _build_new_group_table(from_conn, to_conn): def find_batch(nodes, gtb): for igrp, batches in enumerate(gtb): for ibatch, batch in enumerate(batches): if np.allclose(nodes, batch.result_unit_nodes): return (igrp, ibatch) return (-1, -1) nfrom_groups = len(from_conn.groups) nto_groups = len(to_conn.groups) # construct a table from (old groups) -> (new groups) # NOTE: we try to reduce the number of new groups and batches by matching # the `result_unit_nodes` and only adding a new batch if necessary grp_to_grp = {} batch_info = [[] for i in range(nfrom_groups * nto_groups)] for (igrp, ibatch), (fgrp, fbatch) in _iterbatches(from_conn.groups): for (jgrp, jbatch), (tgrp, tbatch) in _iterbatches(to_conn.groups): # compute result_unit_nodes ffgrp = from_conn.from_discr.groups[fbatch.from_group_index] from_matrix = mp.resampling_matrix( ffgrp.basis(), fbatch.result_unit_nodes, ffgrp.unit_nodes) result_unit_nodes = from_matrix.dot(ffgrp.unit_nodes.T) tfgrp = to_conn.from_discr.groups[tbatch.from_group_index] to_matrix = mp.resampling_matrix( tfgrp.basis(), tbatch.result_unit_nodes, tfgrp.unit_nodes) result_unit_nodes = to_matrix.dot(result_unit_nodes).T # find new (group, batch) (igrp_new, ibatch_new) = find_batch(result_unit_nodes, batch_info) if igrp_new < 0: igrp_new = nto_groups * igrp + jgrp ibatch_new = len(batch_info[igrp_new]) batch_info[igrp_new].append(_ConnectionBatchData( from_group_index=fbatch.from_group_index, result_unit_nodes=result_unit_nodes, to_element_face=tbatch.to_element_face)) grp_to_grp[igrp, ibatch, jgrp, jbatch] = (igrp_new, ibatch_new) return grp_to_grp, batch_info
def to_mesh_interp_matrix(grp) -> np.ndarray: import modepy as mp mat = mp.resampling_matrix( grp.basis_obj().functions, grp.mesh_el_group.unit_nodes, grp.unit_nodes) return actx.freeze(actx.from_numpy(mat))
def _resample_matrix(self, elgroup_index, ibatch_index): import modepy as mp ibatch = self.groups[elgroup_index].batches[ibatch_index] from_grp = self.from_discr.groups[elgroup_index] return mp.resampling_matrix( mp.simplex_onb(self.from_discr.dim, from_grp.order), ibatch.result_unit_nodes, from_grp.unit_nodes)
def from_mesh_interp_matrix(self): from modepy.modes import tensor_product_basis, jacobi from functools import partial meg = self.mesh_el_group basis = tensor_product_basis( self.dim, tuple(partial(jacobi, 0, 0, i) for i in range(meg.order + 1))) return mp.resampling_matrix(basis, self.unit_nodes, meg.unit_nodes)
def from_mesh_interp_matrix(self): from modepy.modes import tensor_product_basis, jacobi from functools import partial meg = self.mesh_el_group basis = tensor_product_basis( self.dim, tuple( partial(jacobi, 0, 0, i) for i in range(meg.order+1))) return mp.resampling_matrix(basis, self.unit_nodes, meg.unit_nodes)
def _resample_matrix(self, to_group_index, ibatch_index): import modepy as mp ibatch = self.groups[to_group_index].batches[ibatch_index] from_grp = self.from_discr.groups[ibatch.from_group_index] result = mp.resampling_matrix( from_grp.basis(), ibatch.result_unit_nodes, from_grp.unit_nodes) with cl.CommandQueue(self.cl_context) as queue: return cl.array.to_device(queue, result).with_queue(None)
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] shape = mp.Simplex(dim) space = mp.PN(dim, order) basis = mp.basis_for_space(space, shape) flip_matrix = mp.resampling_matrix(basis.functions, 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 test_resampling_matrix(dims, eltype): ncoarse = 5 nfine = 10 if eltype == "simplex": coarse_nodes = mp.warp_and_blend_nodes(dims, ncoarse) fine_nodes = mp.warp_and_blend_nodes(dims, nfine) coarse_basis = mp.simplex_onb(dims, ncoarse) fine_basis = mp.simplex_onb(dims, nfine) elif eltype == "tensor": coarse_nodes = mp.legendre_gauss_lobatto_tensor_product_nodes( dims, ncoarse) fine_nodes = mp.legendre_gauss_lobatto_tensor_product_nodes( dims, nfine) coarse_basis = mp.legendre_tensor_product_basis(dims, ncoarse) fine_basis = mp.legendre_tensor_product_basis(dims, nfine) else: raise ValueError(f"unknown element type: {eltype}") my_eye = np.dot( mp.resampling_matrix(fine_basis, coarse_nodes, fine_nodes), mp.resampling_matrix(coarse_basis, fine_nodes, coarse_nodes)) assert la.norm(my_eye - np.eye(len(my_eye))) < 3e-13 my_eye_least_squares = np.dot( mp.resampling_matrix(coarse_basis, coarse_nodes, fine_nodes, least_squares_ok=True), mp.resampling_matrix(coarse_basis, fine_nodes, coarse_nodes), ) assert la.norm(my_eye_least_squares - np.eye(len(my_eye_least_squares))) < 4e-13
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 _(meg: _ModepyElementGroup, el_tess_info, elements): shape = meg._modepy_shape space = meg._modepy_space # get midpoints in reference coordinates midpoints = -1 + np.array( _get_ref_midpoints(shape, el_tess_info.ref_vertices)) # resample midpoints to ambient coordinates resampling_mat = mp.resampling_matrix( mp.basis_for_space(space, shape).functions, midpoints.T, meg.unit_nodes) resampled_midpoints = np.einsum("mu,deu->edm", resampling_mat, meg.nodes[:, elements]) return dict(zip(elements, resampled_midpoints))
def make_resampling_matrix(self, element_grp): """ :arg element_grp: A :class:`meshmode.discretization.InterpolatoryElementGroupBase` whose basis functions span the same space as the FInAT element. :return: A matrix which resamples a function sampled at the firedrake unit nodes to a function sampled at *element_grp.unit_nodes()* (by matrix multiplication) """ from meshmode.discretization import InterpolatoryElementGroupBase assert isinstance(element_grp, InterpolatoryElementGroupBase), \ "element group must be an interpolatory element group so that" \ " can redistribute onto its nodes" from modepy import resampling_matrix return resampling_matrix(element_grp.basis(), new_nodes=element_grp.unit_nodes, old_nodes=self.unit_nodes())
def _resample_matrix(self, to_group_index, ibatch_index): import modepy as mp ibatch = self.groups[to_group_index].batches[ibatch_index] from_grp = self.from_discr.groups[ibatch.from_group_index] if len(from_grp.basis()) != from_grp.unit_nodes.shape[1]: from meshmode.discretization import NoninterpolatoryElementGroupError raise NoninterpolatoryElementGroupError( "%s does not support interpolation because it is not " "unisolvent (its unit node count does not match its " "number of basis functions). Using connections requires " "the ability to interpolate." % type(from_grp).__name__) result = mp.resampling_matrix(from_grp.basis(), ibatch.result_unit_nodes, from_grp.unit_nodes) with cl.CommandQueue(self.cl_context) as queue: return cl.array.to_device(queue, result).with_queue(None)
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 _test_node_vertex_consistency_resampling(mesh, mgrp, tol): if mesh.vertices is None: return True if mgrp.nelements == 0: return True if isinstance(mgrp, _ModepyElementGroup): basis = mp.basis_for_space(mgrp._modepy_space, mgrp._modepy_shape).functions else: raise TypeError(f"unsupported group type: {type(mgrp).__name__}") resampling_mat = mp.resampling_matrix( basis, 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) max_el_vertex_error = np.max(per_element_vertex_errors) assert max_el_vertex_error < tol*size, max_el_vertex_error return True
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 _(meg: _ModepyElementGroup, el_tess_info, elements): shape = meg._modepy_shape space = meg._modepy_space # get child unit node coordinates. from meshmode.mesh.refinement.utils import map_unit_nodes_to_children child_unit_nodes = np.hstack( list(map_unit_nodes_to_children(meg, meg.unit_nodes, el_tess_info))) # resample child nodes to ambient coordinates resampling_mat = mp.resampling_matrix( mp.basis_for_space(space, shape).functions, child_unit_nodes, meg.unit_nodes) resampled_unit_nodes = np.einsum("cu,deu->edc", resampling_mat, meg.nodes[:, elements]) ambient_dim = len(meg.nodes) nunit_nodes = len(meg.unit_nodes[0]) return { el: resampled_unit_nodes[iel].reshape((ambient_dim, -1, nunit_nodes)) for iel, el in enumerate(elements) }
def make_face_restriction(discr, group_factory, boundary_tag, per_face_groups=False): """Create a mesh, a discretization and a connection to restrict a function on *discr* to its values on the edges of element faces denoted by *boundary_tag*. :arg boundary_tag: The boundary tag for which to create a face restriction. May be :class:`meshmode.discretization.connection.FRESTR_INTERIOR_FACES` to indicate interior faces, or :class:`meshmode.discretization.connection.FRESTR_ALL_FACES` to make a discretization consisting of all (interior and boundary) faces. :arg per_face_groups: If *True*, the resulting discretization is guaranteed to have groups organized as:: (grp0, face0), (grp0, face1), ... (grp0, faceN), (grp1, face0), (grp1, face1), ... (grp1, faceN), ... each with the elements in the same order as the originating group. If *False*, volume and boundary groups correspond with each other one-to-one, and an interpolation batch is created per face. :return: a :class:`meshmode.discretization.connection.DirectDiscretizationConnection` representing the new connection. The new boundary discretization can be obtained from the :attr:`meshmode.discretization.connection.DirectDiscretizationConnection.to_discr` attribute of the return value, and the corresponding new boundary mesh from that. """ if boundary_tag is None: boundary_tag = FACE_RESTR_INTERIOR from warnings import warn warn("passing *None* for boundary_tag is deprecated--pass " "FRESTR_INTERIOR_FACES instead", DeprecationWarning, stacklevel=2) logger.info("building face restriction: start") # {{{ gather boundary vertices bdry_vertex_vol_nrs = _get_face_vertices(discr.mesh, boundary_tag) vol_to_bdry_vertices = np.empty( discr.mesh.vertices.shape[-1], discr.mesh.vertices.dtype) vol_to_bdry_vertices.fill(-1) vol_to_bdry_vertices[bdry_vertex_vol_nrs] = np.arange( len(bdry_vertex_vol_nrs), dtype=np.intp) bdry_vertices = discr.mesh.vertices[:, bdry_vertex_vol_nrs] # }}} from meshmode.mesh import Mesh, SimplexElementGroup bdry_mesh_groups = [] connection_data = {} btag_bit = discr.mesh.boundary_tag_bit(boundary_tag) for igrp, (grp, fagrp_map) in enumerate( zip(discr.groups, discr.mesh.facial_adjacency_groups)): mgrp = grp.mesh_el_group if not isinstance(mgrp, SimplexElementGroup): raise NotImplementedError("can only take boundary of " "SimplexElementGroup-based meshes") # {{{ pull together per-group face lists group_boundary_faces = [] if boundary_tag is FACE_RESTR_INTERIOR: for fagrp in six.itervalues(fagrp_map): if fagrp.ineighbor_group is None: # boundary faces -> not looking for those continue group_boundary_faces.extend( zip(fagrp.elements, fagrp.element_faces)) elif boundary_tag is FACE_RESTR_ALL: group_boundary_faces.extend( (iel, iface) for iface in range(grp.mesh_el_group.nfaces) for iel in range(grp.nelements) ) else: bdry_grp = fagrp_map.get(None) if bdry_grp is not None: nb_el_bits = -bdry_grp.neighbors face_relevant_flags = (nb_el_bits & btag_bit) != 0 group_boundary_faces.extend( zip( bdry_grp.elements[face_relevant_flags], bdry_grp.element_faces[face_relevant_flags])) # }}} grp_face_vertex_indices = mgrp.face_vertex_indices() grp_vertex_unit_coordinates = mgrp.vertex_unit_coordinates() batch_base = 0 # group by face_id for face_id in range(mgrp.nfaces): batch_boundary_el_numbers_in_grp = np.array( [ ibface_el for ibface_el, ibface_face in group_boundary_faces if ibface_face == face_id], dtype=np.intp) # {{{ preallocate arrays for mesh group nbatch_elements = len(batch_boundary_el_numbers_in_grp) if per_face_groups or face_id == 0: if per_face_groups: ngroup_bdry_elements = nbatch_elements else: ngroup_bdry_elements = len(group_boundary_faces) vertex_indices = np.empty( (ngroup_bdry_elements, mgrp.dim+1-1), mgrp.vertex_indices.dtype) bdry_unit_nodes = mp.warp_and_blend_nodes(mgrp.dim-1, mgrp.order) bdry_unit_nodes_01 = (bdry_unit_nodes + 1)*0.5 vol_basis = mp.simplex_onb(mgrp.dim, mgrp.order) nbdry_unit_nodes = bdry_unit_nodes_01.shape[-1] nodes = np.empty( (discr.ambient_dim, ngroup_bdry_elements, nbdry_unit_nodes), dtype=np.float64) # }}} new_el_numbers = batch_base + np.arange(nbatch_elements) if not per_face_groups: batch_base += nbatch_elements # {{{ no per-element axes in these computations # Find boundary vertex indices loc_face_vertices = list(grp_face_vertex_indices[face_id]) # Find unit nodes for boundary element face_vertex_unit_coordinates = \ grp_vertex_unit_coordinates[loc_face_vertices] # Find A, b such that A [e_1 e_2] + b = [r_1 r_2] # (Notation assumes that the volume is 3D and the face is 2D. # Code does not.) b = face_vertex_unit_coordinates[0] A = ( # noqa face_vertex_unit_coordinates[1:] - face_vertex_unit_coordinates[0]).T face_unit_nodes = (np.dot(A, bdry_unit_nodes_01).T + b).T resampling_mat = mp.resampling_matrix( vol_basis, face_unit_nodes, mgrp.unit_nodes) # }}} # {{{ build information for mesh element group # Find vertex_indices glob_face_vertices = mgrp.vertex_indices[ batch_boundary_el_numbers_in_grp][:, loc_face_vertices] vertex_indices[new_el_numbers] = \ vol_to_bdry_vertices[glob_face_vertices] # Find nodes nodes[:, new_el_numbers, :] = np.einsum( "ij,dej->dei", resampling_mat, mgrp.nodes[:, batch_boundary_el_numbers_in_grp, :]) # }}} connection_data[igrp, face_id] = _ConnectionBatchData( group_source_element_indices=batch_boundary_el_numbers_in_grp, group_target_element_indices=new_el_numbers, A=A, b=b, ) is_last_face = face_id + 1 == mgrp.nfaces if per_face_groups or is_last_face: bdry_mesh_group = SimplexElementGroup( mgrp.order, vertex_indices, nodes, unit_nodes=bdry_unit_nodes) bdry_mesh_groups.append(bdry_mesh_group) bdry_mesh = Mesh(bdry_vertices, bdry_mesh_groups) from meshmode.discretization import Discretization bdry_discr = Discretization( discr.cl_context, bdry_mesh, group_factory) with cl.CommandQueue(discr.cl_context) as queue: connection = _build_boundary_connection( queue, discr, bdry_discr, connection_data, per_face_groups) logger.info("building face restriction: done") return connection
def make_face_restriction(discr, group_factory, boundary_tag, per_face_groups=False): """Create a mesh, a discretization and a connection to restrict a function on *discr* to its values on the edges of element faces denoted by *boundary_tag*. :arg boundary_tag: The boundary tag for which to create a face restriction. May be :class:`meshmode.discretization.connection.FRESTR_INTERIOR_FACES` to indicate interior faces, or :class:`meshmode.discretization.connection.FRESTR_ALL_FACES` to make a discretization consisting of all (interior and boundary) faces. :arg per_face_groups: If *True*, the resulting discretization is guaranteed to have groups organized as:: (grp0, face0), (grp0, face1), ... (grp0, faceN), (grp1, face0), (grp1, face1), ... (grp1, faceN), ... each with the elements in the same order as the originating group. If *False*, volume and boundary groups correspond with each other one-to-one, and an interpolation batch is created per face. :return: a :class:`meshmode.discretization.connection.DirectDiscretizationConnection` representing the new connection. The new boundary discretization can be obtained from the :attr:`meshmode.discretization.connection.DirectDiscretizationConnection.to_discr` attribute of the return value, and the corresponding new boundary mesh from that. """ if boundary_tag is None: boundary_tag = FACE_RESTR_INTERIOR from warnings import warn warn( "passing *None* for boundary_tag is deprecated--pass " "FRESTR_INTERIOR_FACES instead", DeprecationWarning, stacklevel=2) logger.info("building face restriction: start") # {{{ gather boundary vertices bdry_vertex_vol_nrs = _get_face_vertices(discr.mesh, boundary_tag) vol_to_bdry_vertices = np.empty(discr.mesh.vertices.shape[-1], discr.mesh.vertices.dtype) vol_to_bdry_vertices.fill(-1) vol_to_bdry_vertices[bdry_vertex_vol_nrs] = np.arange( len(bdry_vertex_vol_nrs), dtype=np.intp) bdry_vertices = discr.mesh.vertices[:, bdry_vertex_vol_nrs] # }}} from meshmode.mesh import Mesh, SimplexElementGroup bdry_mesh_groups = [] connection_data = {} btag_bit = discr.mesh.boundary_tag_bit(boundary_tag) for igrp, (grp, fagrp_map) in enumerate( zip(discr.groups, discr.mesh.facial_adjacency_groups)): mgrp = grp.mesh_el_group if not isinstance(mgrp, SimplexElementGroup): raise NotImplementedError("can only take boundary of " "SimplexElementGroup-based meshes") # {{{ pull together per-group face lists group_boundary_faces = [] if boundary_tag is FACE_RESTR_INTERIOR: for fagrp in six.itervalues(fagrp_map): if fagrp.ineighbor_group is None: # boundary faces -> not looking for those continue group_boundary_faces.extend( zip(fagrp.elements, fagrp.element_faces)) elif boundary_tag is FACE_RESTR_ALL: group_boundary_faces.extend( (iel, iface) for iface in range(grp.mesh_el_group.nfaces) for iel in range(grp.nelements)) else: bdry_grp = fagrp_map.get(None) if bdry_grp is not None: nb_el_bits = -bdry_grp.neighbors face_relevant_flags = (nb_el_bits & btag_bit) != 0 group_boundary_faces.extend( zip(bdry_grp.elements[face_relevant_flags], bdry_grp.element_faces[face_relevant_flags])) # }}} grp_face_vertex_indices = mgrp.face_vertex_indices() grp_vertex_unit_coordinates = mgrp.vertex_unit_coordinates() batch_base = 0 # group by face_id for face_id in range(mgrp.nfaces): batch_boundary_el_numbers_in_grp = np.array([ ibface_el for ibface_el, ibface_face in group_boundary_faces if ibface_face == face_id ], dtype=np.intp) # {{{ preallocate arrays for mesh group nbatch_elements = len(batch_boundary_el_numbers_in_grp) if per_face_groups or face_id == 0: if per_face_groups: ngroup_bdry_elements = nbatch_elements else: ngroup_bdry_elements = len(group_boundary_faces) vertex_indices = np.empty( (ngroup_bdry_elements, mgrp.dim + 1 - 1), mgrp.vertex_indices.dtype) bdry_unit_nodes = mp.warp_and_blend_nodes( mgrp.dim - 1, mgrp.order) bdry_unit_nodes_01 = (bdry_unit_nodes + 1) * 0.5 vol_basis = mp.simplex_onb(mgrp.dim, mgrp.order) nbdry_unit_nodes = bdry_unit_nodes_01.shape[-1] nodes = np.empty((discr.ambient_dim, ngroup_bdry_elements, nbdry_unit_nodes), dtype=np.float64) # }}} new_el_numbers = batch_base + np.arange(nbatch_elements) if not per_face_groups: batch_base += nbatch_elements # {{{ no per-element axes in these computations # Find boundary vertex indices loc_face_vertices = list(grp_face_vertex_indices[face_id]) # Find unit nodes for boundary element face_vertex_unit_coordinates = \ grp_vertex_unit_coordinates[loc_face_vertices] # Find A, b such that A [e_1 e_2] + b = [r_1 r_2] # (Notation assumes that the volume is 3D and the face is 2D. # Code does not.) b = face_vertex_unit_coordinates[0] A = ( # noqa face_vertex_unit_coordinates[1:] - face_vertex_unit_coordinates[0]).T face_unit_nodes = (np.dot(A, bdry_unit_nodes_01).T + b).T resampling_mat = mp.resampling_matrix(vol_basis, face_unit_nodes, mgrp.unit_nodes) # }}} # {{{ build information for mesh element group # Find vertex_indices glob_face_vertices = mgrp.vertex_indices[ batch_boundary_el_numbers_in_grp][:, loc_face_vertices] vertex_indices[new_el_numbers] = \ vol_to_bdry_vertices[glob_face_vertices] # Find nodes nodes[:, new_el_numbers, :] = np.einsum( "ij,dej->dei", resampling_mat, mgrp.nodes[:, batch_boundary_el_numbers_in_grp, :]) # }}} connection_data[igrp, face_id] = _ConnectionBatchData( group_source_element_indices=batch_boundary_el_numbers_in_grp, group_target_element_indices=new_el_numbers, A=A, b=b, ) is_last_face = face_id + 1 == mgrp.nfaces if per_face_groups or is_last_face: bdry_mesh_group = SimplexElementGroup( mgrp.order, vertex_indices, nodes, unit_nodes=bdry_unit_nodes) bdry_mesh_groups.append(bdry_mesh_group) bdry_mesh = Mesh(bdry_vertices, bdry_mesh_groups) from meshmode.discretization import Discretization bdry_discr = Discretization(discr.cl_context, bdry_mesh, group_factory) with cl.CommandQueue(discr.cl_context) as queue: connection = _build_boundary_connection(queue, discr, bdry_discr, connection_data, per_face_groups) logger.info("building face restriction: done") return connection
def from_mesh_interp_matrix(self): meg = self.mesh_el_group meg_space = mp.QN(meg.dim, meg.order) return mp.resampling_matrix( mp.basis_for_space(meg_space, self._shape).functions, self.unit_nodes, meg.unit_nodes)
def interpolate_from_meshmode(queue, dof_vec, elements_to_sources_lookup, order="tree"): """Interpolate a DoF vector from :mod:`meshmode`. :arg dof_vec: a DoF vector representing a field in :mod:`meshmode` of shape ``(..., nnodes)``. :arg elements_to_sources_lookup: a :class:`ElementsToSourcesLookup`. :arg order: order of the output potential, either "tree" or "user". .. note:: This function currently supports meshes with just one element group. Also, the element group must be simplex-based. .. note:: This function does some heavy-lifting computation in Python, which we intend to optimize in the future. In particular, we plan to shift the batched linear solves and basis evaluations to :mod:`loopy`. TODO: make linear solvers available as :mod:`loopy` callables. TODO: make :mod:`modepy` emit :mod:`loopy` callables for basis evaluation. """ if not isinstance(dof_vec, cl.array.Array): raise TypeError("non-array passed to interpolator") assert len(elements_to_sources_lookup.discr.groups) == 1 assert len(elements_to_sources_lookup.discr.mesh.groups) == 1 degroup = elements_to_sources_lookup.discr.groups[0] megroup = elements_to_sources_lookup.discr.mesh.groups[0] if not degroup.is_affine: raise ValueError( "interpolation requires global-to-local map, " "which is only available for affinely mapped elements") mesh = elements_to_sources_lookup.discr.mesh dim = elements_to_sources_lookup.discr.dim template_simplex = mesh.groups[0].vertex_unit_coordinates().T # ------------------------------------------------------- # Inversely map source points with a global-to-local map. # # 1. For each element, solve for the affine map. # # 2. Apply the map to corresponding source points. # # This step computes `unit_sources`, the list of inversely # mapped source points. sources_in_element_starts = \ elements_to_sources_lookup.sources_in_element_starts.get(queue) sources_in_element_lists = \ elements_to_sources_lookup.sources_in_element_lists.get(queue) tree = elements_to_sources_lookup.tree.get(queue) unit_sources_host = make_obj_array( [np.zeros_like(srccrd) for srccrd in tree.sources]) for iel in range(degroup.nelements): vertex_ids = megroup.vertex_indices[iel] vertices = mesh.vertices[:, vertex_ids] afa, afb = compute_affine_transform(vertices, template_simplex) beg = sources_in_element_starts[iel] end = sources_in_element_starts[iel + 1] source_ids_in_el = sources_in_element_lists[beg:end] sources_in_el = np.vstack( [tree.sources[iaxis][source_ids_in_el] for iaxis in range(dim)]) ivmapped_el_sources = afa @ sources_in_el + afb.reshape([dim, 1]) for iaxis in range(dim): unit_sources_host[iaxis][source_ids_in_el] = \ ivmapped_el_sources[iaxis, :] unit_sources = make_obj_array( [cl.array.to_device(queue, usc) for usc in unit_sources_host]) # ----------------------------------------------------- # Carry out evaluations in the local (template) frames. # # 1. Assemble a resampling matrix for each element, with # the basis functions and the local source points. # # 2. For each element, perform matvec on the resampling # matrix and the local DoF coefficients. # # This step assumes `unit_sources` computed on device, so # that the previous step can be swapped with a kernel without # interrupting the followed computation. mapped_sources = np.vstack([usc.get(queue) for usc in unit_sources]) basis_funcs = degroup.basis() arr_ctx = PyOpenCLArrayContext(queue) dof_vec_view = unflatten(arr_ctx, elements_to_sources_lookup.discr, dof_vec)[0] dof_vec_view = dof_vec_view.get() sym_shape = dof_vec.shape[:-1] source_vec = np.zeros(sym_shape + (tree.nsources, )) for iel in range(degroup.nelements): beg = sources_in_element_starts[iel] end = sources_in_element_starts[iel + 1] source_ids_in_el = sources_in_element_lists[beg:end] mapped_sources_in_el = mapped_sources[:, source_ids_in_el] local_dof_vec = dof_vec_view[..., iel, :] # resampling matrix built from Vandermonde matrices import modepy as mp rsplm = mp.resampling_matrix(basis=basis_funcs, new_nodes=mapped_sources_in_el, old_nodes=degroup.unit_nodes) if len(sym_shape) == 0: local_coeffs = local_dof_vec source_vec[source_ids_in_el] = rsplm @ local_coeffs else: from pytools import indices_in_shape for sym_id in indices_in_shape(sym_shape): source_vec[sym_id + (source_ids_in_el, )] = \ rsplm @ local_dof_vec[sym_id] source_vec = cl.array.to_device(queue, source_vec) if order == "tree": pass # no need to do anything elif order == "user": source_vec = source_vec[tree.sorted_target_ids] # into user order else: raise ValueError(f"order must be 'tree' or 'user' (got {order}).") return source_vec
def make_boundary_restriction(queue, discr, group_factory): """ :return: a tuple ``(bdry_mesh, bdry_discr, connection)`` """ logger.info("building boundary connection: start") # {{{ build face_map # maps (igrp, el_grp, face_id) to a frozenset of vertex IDs face_map = {} for igrp, mgrp in enumerate(discr.mesh.groups): grp_face_vertex_indices = mgrp.face_vertex_indices() for iel_grp in range(mgrp.nelements): for fid, loc_face_vertices in enumerate(grp_face_vertex_indices): face_vertices = frozenset( mgrp.vertex_indices[iel_grp, fvi] for fvi in loc_face_vertices ) face_map.setdefault(face_vertices, []).append( (igrp, iel_grp, fid)) del face_vertices # }}} boundary_faces = [ face_ids[0] for face_vertices, face_ids in six.iteritems(face_map) if len(face_ids) == 1] from pytools import flatten bdry_vertex_vol_nrs = sorted(set(flatten(six.iterkeys(face_map)))) vol_to_bdry_vertices = np.empty( discr.mesh.vertices.shape[-1], discr.mesh.vertices.dtype) vol_to_bdry_vertices.fill(-1) vol_to_bdry_vertices[bdry_vertex_vol_nrs] = np.arange( len(bdry_vertex_vol_nrs)) bdry_vertices = discr.mesh.vertices[:, bdry_vertex_vol_nrs] from meshmode.mesh import Mesh, SimplexElementGroup bdry_mesh_groups = [] connection_data = {} for igrp, grp in enumerate(discr.groups): mgrp = grp.mesh_el_group group_boundary_faces = [ (ibface_el, ibface_face) for ibface_group, ibface_el, ibface_face in boundary_faces if ibface_group == igrp] if not isinstance(mgrp, SimplexElementGroup): raise NotImplementedError("can only take boundary of " "SimplexElementGroup-based meshes") # {{{ Preallocate arrays for mesh group ngroup_bdry_elements = len(group_boundary_faces) vertex_indices = np.empty( (ngroup_bdry_elements, mgrp.dim+1-1), mgrp.vertex_indices.dtype) bdry_unit_nodes = mp.warp_and_blend_nodes(mgrp.dim-1, mgrp.order) bdry_unit_nodes_01 = (bdry_unit_nodes + 1)*0.5 vol_basis = mp.simplex_onb(mgrp.dim, mgrp.order) nbdry_unit_nodes = bdry_unit_nodes_01.shape[-1] nodes = np.empty( (discr.ambient_dim, ngroup_bdry_elements, nbdry_unit_nodes), dtype=np.float64) # }}} grp_face_vertex_indices = mgrp.face_vertex_indices() grp_vertex_unit_coordinates = mgrp.vertex_unit_coordinates() # batch by face_id batch_base = 0 for face_id in range(len(grp_face_vertex_indices)): batch_boundary_el_numbers_in_grp = np.array( [ ibface_el for ibface_el, ibface_face in group_boundary_faces if ibface_face == face_id], dtype=np.intp) new_el_numbers = np.arange( batch_base, batch_base + len(batch_boundary_el_numbers_in_grp)) # {{{ no per-element axes in these computations # Find boundary vertex indices loc_face_vertices = list(grp_face_vertex_indices[face_id]) # Find unit nodes for boundary element face_vertex_unit_coordinates = \ grp_vertex_unit_coordinates[loc_face_vertices] # Find A, b such that A [e_1 e_2] + b = [r_1 r_2] # (Notation assumes that the volume is 3D and the face is 2D. # Code does not.) b = face_vertex_unit_coordinates[0] A = ( face_vertex_unit_coordinates[1:] - face_vertex_unit_coordinates[0]).T face_unit_nodes = (np.dot(A, bdry_unit_nodes_01).T + b).T resampling_mat = mp.resampling_matrix( vol_basis, face_unit_nodes, mgrp.unit_nodes) # }}} # {{{ build information for mesh element group # Find vertex_indices glob_face_vertices = mgrp.vertex_indices[ batch_boundary_el_numbers_in_grp][:, loc_face_vertices] vertex_indices[new_el_numbers] = \ vol_to_bdry_vertices[glob_face_vertices] # Find nodes nodes[:, new_el_numbers, :] = np.einsum( "ij,dej->dei", resampling_mat, mgrp.nodes[:, batch_boundary_el_numbers_in_grp, :]) # }}} connection_data[igrp, face_id] = _ConnectionBatchData( group_source_element_indices=batch_boundary_el_numbers_in_grp, group_target_element_indices=new_el_numbers, A=A, b=b, ) batch_base += len(batch_boundary_el_numbers_in_grp) bdry_mesh_group = SimplexElementGroup( mgrp.order, vertex_indices, nodes, unit_nodes=bdry_unit_nodes) bdry_mesh_groups.append(bdry_mesh_group) bdry_mesh = Mesh(bdry_vertices, bdry_mesh_groups) from meshmode.discretization import Discretization bdry_discr = Discretization( discr.cl_context, bdry_mesh, group_factory) connection = _build_boundary_connection( queue, discr, bdry_discr, connection_data) logger.info("building boundary connection: done") return bdry_mesh, bdry_discr, connection
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 __init__(self, discr, fdrake_fspace, mm2fd_node_mapping, group_nr=None): """ :param discr: A :class:`meshmode.discretization.Discretization` :param fdrake_fspace: A :class:`firedrake.functionspaceimpl.WithGeometry`. Must use ufl family ``"Discontinuous Lagrange"``. :param mm2fd_node_mapping: Used as attribute :attr:`mm2fd_node_mapping`. A 2-D numpy integer array with the same dtype as ``fdrake_fspace.cell_node_list.dtype`` :param group_nr: The index of the group in *discr* which is being connected to *fdrake_fspace*. The group must be a :class:`~meshmode.discretization.InterpolatoryElementGroupBase` of the same topological dimension as *fdrake_fspace*. If *discr* has only one group, *group_nr=None* may be supplied. :raises TypeError: If any input arguments are of the wrong type, if the designated group is of the wrong type, or if *fdrake_fspace* is of the wrong family. :raises ValueError: If *mm2fd_node_mapping* is of the wrong shape or dtype, if *group_nr* is an invalid index, or if *group_nr* is *None* when *discr* has more than one group. """ # {{{ Validate input if not isinstance(discr, Discretization): raise TypeError("'discr' must be of type " "meshmode.discretization.Discretization, " "not '%s'`." % type(discr)) from firedrake.functionspaceimpl import WithGeometry if not isinstance(fdrake_fspace, WithGeometry): raise TypeError("'fdrake_fspace' must be of type " "firedrake.functionspaceimpl.WithGeometry, " "not '%s'." % type(fdrake_fspace)) if not isinstance(mm2fd_node_mapping, np.ndarray): raise TypeError("'mm2fd_node_mapping' must be of type " "numpy.ndarray, " "not '%s'." % type(mm2fd_node_mapping)) if not isinstance(group_nr, int) and group_nr is not None: raise TypeError("'group_nr' must be of type int or be " "*None*, not of type '%s'." % type(group_nr)) # Convert group_nr to an integer if *None* if group_nr is None: if len(discr.groups) != 1: raise ValueError("'group_nr' is *None* but 'discr' " "has '%s' != 1 groups." % len(discr.groups)) group_nr = 0 # store element_grp as variable for convenience element_grp = discr.groups[group_nr] if group_nr < 0 or group_nr >= len(discr.groups): raise ValueError("'group_nr' has value '%s', which an invalid " "index into list 'discr.groups' of length '%s'." % (group_nr, len(discr.groups))) if not isinstance(element_grp, InterpolatoryElementGroupBase): raise TypeError("'discr.groups[group_nr]' must be of type " "InterpolatoryElementGroupBase" ", not '%s'." % type(element_grp)) if fdrake_fspace.ufl_element().family() != "Discontinuous Lagrange": raise TypeError("'fdrake_fspace.ufl_element().family()' must be" "'Discontinuous Lagrange', not " f"'{fdrake_fspace.ufl_element().family()}'") if mm2fd_node_mapping.shape != (element_grp.nelements, element_grp.nunit_dofs): raise ValueError("'mm2fd_node_mapping' must be of shape ", "(%s,), not '%s'" % ((discr.groups[group_nr].ndofs,), mm2fd_node_mapping.shape)) if mm2fd_node_mapping.dtype != fdrake_fspace.cell_node_list.dtype: raise ValueError("'mm2fd_node_mapping' must have dtype " "%s, not '%s'" % (fdrake_fspace.cell_node_list.dtype, mm2fd_node_mapping.dtype)) if np.size(np.unique(mm2fd_node_mapping)) != np.size(mm2fd_node_mapping): raise ValueError("'mm2fd_node_mapping' must have unique entries; " "no two meshmode nodes may be associated to the " "same Firedrake node") # }}} # Get meshmode unit nodes mm_unit_nodes = element_grp.unit_nodes # get firedrake unit nodes and map onto meshmode reference element tdim = fdrake_fspace.mesh().topological_dimension() fd_ref_cell_to_mm = get_affine_reference_simplex_mapping(tdim, True) fd_unit_nodes = get_finat_element_unit_nodes(fdrake_fspace.finat_element) fd_unit_nodes = fd_ref_cell_to_mm(fd_unit_nodes) # compute and store resampling matrices self._resampling_mat_fd2mm = resampling_matrix(element_grp.basis(), new_nodes=mm_unit_nodes, old_nodes=fd_unit_nodes) self._resampling_mat_mm2fd = resampling_matrix(element_grp.basis(), new_nodes=fd_unit_nodes, old_nodes=mm_unit_nodes) # Store input self.discr = discr self.group_nr = group_nr self.mm2fd_node_mapping = mm2fd_node_mapping self._mesh_geometry = fdrake_fspace.mesh() self._ufl_element = fdrake_fspace.ufl_element()
def resampling_matrix(self): meg = self.mesh_el_group return mp.resampling_matrix( mp.simplex_onb(self.dim, meg.order), self.unit_nodes, meg.unit_nodes)
def make_face_restriction(actx, discr, group_factory, boundary_tag, per_face_groups=False): """Create a mesh, a discretization and a connection to restrict a function on *discr* to its values on the edges of element faces denoted by *boundary_tag*. :arg boundary_tag: The boundary tag for which to create a face restriction. May be :class:`FACE_RESTR_INTERIOR` to indicate interior faces, or :class:`FACE_RESTR_ALL` to make a discretization consisting of all (interior and boundary) faces. :arg per_face_groups: If *True*, the resulting discretization is guaranteed to have groups organized as:: (grp0, face0), (grp0, face1), ... (grp0, faceN), (grp1, face0), (grp1, face1), ... (grp1, faceN), ... each with the elements in the same order as the originating group. If *False*, volume and boundary groups correspond with each other one-to-one, and an interpolation batch is created per face. :return: a :class:`meshmode.discretization.connection.DirectDiscretizationConnection` representing the new connection. The new boundary discretization can be obtained from the :attr:`meshmode.discretization.connection.DirectDiscretizationConnection.to_discr` attribute of the return value, and the corresponding new boundary mesh from that. """ if boundary_tag is None: boundary_tag = FACE_RESTR_INTERIOR from warnings import warn warn("passing *None* for boundary_tag is deprecated--pass " "FACE_RESTR_INTERIOR instead", DeprecationWarning, stacklevel=2) logger.info("building face restriction: start") # {{{ gather boundary vertices bdry_vertex_vol_nrs = _get_face_vertices(discr.mesh, boundary_tag) vol_to_bdry_vertices = np.empty( discr.mesh.vertices.shape[-1], discr.mesh.vertices.dtype) vol_to_bdry_vertices.fill(-1) vol_to_bdry_vertices[bdry_vertex_vol_nrs] = np.arange( len(bdry_vertex_vol_nrs), dtype=np.intp) bdry_vertices = discr.mesh.vertices[:, bdry_vertex_vol_nrs] # }}} from meshmode.mesh import Mesh, _ModepyElementGroup bdry_mesh_groups = [] connection_data = {} if boundary_tag not in [FACE_RESTR_ALL, FACE_RESTR_INTERIOR]: btag_bit = discr.mesh.boundary_tag_bit(boundary_tag) for igrp, (grp, fagrp_map) in enumerate( zip(discr.groups, discr.mesh.facial_adjacency_groups)): mgrp = grp.mesh_el_group if not isinstance(mgrp, _ModepyElementGroup): raise NotImplementedError("can only take boundary of " "meshes based on SimplexElementGroup and " "TensorProductElementGroup") # {{{ pull together per-group face lists group_boundary_faces = [] if boundary_tag is FACE_RESTR_INTERIOR: for fagrp in fagrp_map.values(): if fagrp.ineighbor_group is None: # boundary faces -> not looking for those continue group_boundary_faces.extend( zip(fagrp.elements, fagrp.element_faces)) elif boundary_tag is FACE_RESTR_ALL: group_boundary_faces.extend( (iel, iface) for iface in range(grp.mesh_el_group.nfaces) for iel in range(grp.nelements) ) else: bdry_grp = fagrp_map.get(None) if bdry_grp is not None: nb_el_bits = -bdry_grp.neighbors face_relevant_flags = (nb_el_bits & btag_bit) != 0 group_boundary_faces.extend( zip( bdry_grp.elements[face_relevant_flags], bdry_grp.element_faces[face_relevant_flags])) # }}} batch_base = 0 # group by face_index for face in mgrp._modepy_faces: batch_boundary_el_numbers_in_grp = np.array([ ibface_el for ibface_el, ibface_face in group_boundary_faces if ibface_face == face.face_index ], dtype=np.intp) # {{{ preallocate arrays for mesh group nbatch_elements = len(batch_boundary_el_numbers_in_grp) if per_face_groups or face.face_index == 0: if per_face_groups: ngroup_bdry_elements = nbatch_elements else: ngroup_bdry_elements = len(group_boundary_faces) # make up some not-terrible nodes for the boundary Mesh space = mp.space_for_shape(face, mgrp.order) bdry_unit_nodes = mp.edge_clustered_nodes_for_space(space, face) vol_basis = mp.basis_for_space( mgrp._modepy_space, mgrp._modepy_shape).functions vertex_indices = np.empty( (ngroup_bdry_elements, face.nvertices), mgrp.vertex_indices.dtype) nbdry_unit_nodes = bdry_unit_nodes.shape[-1] nodes = np.empty( (discr.ambient_dim, ngroup_bdry_elements, nbdry_unit_nodes), dtype=np.float64) # }}} new_el_numbers = batch_base + np.arange(nbatch_elements) if not per_face_groups: batch_base += nbatch_elements # {{{ no per-element axes in these computations face_unit_nodes = face.map_to_volume(bdry_unit_nodes) resampling_mat = mp.resampling_matrix( vol_basis, face_unit_nodes, mgrp.unit_nodes) # }}} # {{{ build information for mesh element group # Find vertex_indices glob_face_vertices = mgrp.vertex_indices[ batch_boundary_el_numbers_in_grp][:, face.volume_vertex_indices] vertex_indices[new_el_numbers] = \ vol_to_bdry_vertices[glob_face_vertices] # Find nodes nodes[:, new_el_numbers, :] = np.einsum( "ij,dej->dei", resampling_mat, mgrp.nodes[:, batch_boundary_el_numbers_in_grp, :]) # }}} connection_data[igrp, face.face_index] = _ConnectionBatchData( group_source_element_indices=batch_boundary_el_numbers_in_grp, group_target_element_indices=new_el_numbers, face=face, ) is_last_face = face.face_index + 1 == mgrp.nfaces if per_face_groups or is_last_face: bdry_mesh_group = type(mgrp)( mgrp.order, vertex_indices, nodes, unit_nodes=bdry_unit_nodes) bdry_mesh_groups.append(bdry_mesh_group) bdry_mesh = Mesh(bdry_vertices, bdry_mesh_groups) from meshmode.discretization import Discretization bdry_discr = Discretization( actx, bdry_mesh, group_factory) connection = _build_boundary_connection( actx, discr, bdry_discr, connection_data, per_face_groups) logger.info("building face restriction: done") return connection
def to_mesh_interp_matrix(grp: NodalElementGroupBase) -> np.ndarray: return mp.resampling_matrix(grp.basis_obj().functions, grp.mesh_el_group.unit_nodes, grp.unit_nodes)
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 from_mesh_interp_matrix(self): meg = self.mesh_el_group return mp.resampling_matrix(self.basis(), self.unit_nodes, meg.unit_nodes)