def test_deprecated_nodal_face_mass_matrix(dims, order=3): # FIXME DEPRECATED remove along with nodal_face_mass_matrix (>=2022) vol_shape = mp.Simplex(dims) vol_space = mp.space_for_shape(vol_shape, order) vertices = mp.unit_vertices_for_shape(vol_shape) volume_nodes = mp.edge_clustered_nodes_for_space(vol_space, vol_shape) volume_basis = mp.basis_for_space(vol_space, vol_shape) from modepy.matrices import nodal_face_mass_matrix for face in mp.faces_for_shape(vol_shape): face_space = mp.space_for_shape(face, order) face_nodes = mp.edge_clustered_nodes_for_space(face_space, face) face_vertices = vertices[:, face.volume_vertex_indices] fmm = nodal_face_mass_matrix( volume_basis.functions, volume_nodes, face_nodes, order, face_vertices) fmm2 = nodal_face_mass_matrix( volume_basis.functions, volume_nodes, face_nodes, order+1, face_vertices) error = la.norm(fmm - fmm2, np.inf) / la.norm(fmm2, np.inf) logger.info("fmm error: %.5e", error) assert error < 5e-11, f"error {error:.5e} on face {face.face_index}" fmm[np.abs(fmm) < 1e-13] = 0 nnz = np.sum(fmm > 0) logger.info("fmm: nnz %d\n%s", nnz, fmm) logger.info("mass matrix:\n%s", mp.mass_matrix( mp.basis_for_space(face_space, face).functions, mp.edge_clustered_nodes_for_space(face_space, face)))
def test_modal_mass_matrix_for_face(dims, shape_cls, order=3): vol_shape = shape_cls(dims) vol_space = mp.space_for_shape(vol_shape, order) vol_basis = mp.basis_for_space(vol_space, vol_shape) from modepy.matrices import modal_mass_matrix_for_face for face in mp.faces_for_shape(vol_shape): face_space = mp.space_for_shape(face, order) face_basis = mp.basis_for_space(face_space, face) face_quad = mp.quadrature_for_space(mp.space_for_shape(face, 2*order), face) face_quad2 = mp.quadrature_for_space( mp.space_for_shape(face, 2*order+2), face) fmm = modal_mass_matrix_for_face( face, face_quad, face_basis.functions, vol_basis.functions) fmm2 = modal_mass_matrix_for_face( face, face_quad2, face_basis.functions, vol_basis.functions) error = la.norm(fmm - fmm2, np.inf) / la.norm(fmm2, np.inf) logger.info("fmm error: %.5e", error) assert error < 1e-11, f"error {error:.5e} on face {face.face_index}" fmm[np.abs(fmm) < 1e-13] = 0 nnz = np.sum(fmm > 0) logger.info("fmm: nnz %d\n%s", nnz, fmm)
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 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 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.basis_for_space(group._modepy_space, group._modepy_shape) vinv = la.inv(mp.vandermonde(basis.functions, group.unit_nodes)) diff = mp.differentiation_matrices( basis.functions, basis.gradients, 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 test_deprecated_modal_face_mass_matrix(dims, order=3): # FIXME DEPRECATED remove along with modal_face_mass_matrix (>=2022) shape = mp.Simplex(dims) space = mp.space_for_shape(shape, order) vertices = mp.unit_vertices_for_shape(shape) basis = mp.basis_for_space(space, shape) from modepy.matrices import modal_face_mass_matrix for face in mp.faces_for_shape(shape): face_vertices = vertices[:, face.volume_vertex_indices] fmm = modal_face_mass_matrix( basis.functions, order, face_vertices) fmm2 = modal_face_mass_matrix( basis.functions, order+1, face_vertices) error = la.norm(fmm - fmm2, np.inf) / la.norm(fmm2, np.inf) logger.info("fmm error: %.5e", error) assert error < 1e-11, f"error {error:.5e} on face {face.face_index}" fmm[np.abs(fmm) < 1e-13] = 0 nnz = np.sum(fmm > 0) logger.info("fmm: nnz %d\n%s", nnz, fmm)
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_nodal_mass_matrix_for_face(dims, shape_cls, order=3): vol_shape = shape_cls(dims) vol_space = mp.space_for_shape(vol_shape, order) volume_nodes = mp.edge_clustered_nodes_for_space(vol_space, vol_shape) volume_basis = mp.basis_for_space(vol_space, vol_shape) from modepy.matrices import (nodal_mass_matrix_for_face, nodal_quad_mass_matrix_for_face) for face in mp.faces_for_shape(vol_shape): face_space = mp.space_for_shape(face, order) face_basis = mp.basis_for_space(face_space, face) face_nodes = mp.edge_clustered_nodes_for_space(face_space, face) face_quad = mp.quadrature_for_space(mp.space_for_shape(face, 2*order), face) face_quad2 = mp.quadrature_for_space( mp.space_for_shape(face, 2*order+2), face) fmm = nodal_mass_matrix_for_face( face, face_quad, face_basis.functions, volume_basis.functions, volume_nodes, face_nodes) fmm2 = nodal_quad_mass_matrix_for_face( face, face_quad2, volume_basis.functions, volume_nodes) for f_face in face_basis.functions: fval_nodal = f_face(face_nodes) fval_quad = f_face(face_quad2.nodes) assert ( la.norm(fmm@fval_nodal - fmm2@fval_quad, np.inf) / la.norm(fval_quad)) < 3e-15 fmm[np.abs(fmm) < 1e-13] = 0 nnz = np.sum(fmm > 0) logger.info("fmm: nnz %d\n%s", nnz, fmm) logger.info("mass matrix:\n%s", mp.mass_matrix(face_basis.functions, face_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 test_diff_matrix(dims, shape_cls, order=5): shape = shape_cls(dims) space = mp.space_for_shape(shape, order) nodes = mp.edge_clustered_nodes_for_space(space, shape) basis = mp.basis_for_space(space, shape) diff_mat = mp.differentiation_matrices(basis.functions, basis.gradients, nodes) if isinstance(diff_mat, tuple): diff_mat = diff_mat[0] f = np.sin(nodes[0]) df_dx = np.cos(nodes[0]) df_dx_num = np.dot(diff_mat, f) error = la.norm(df_dx - df_dx_num) / la.norm(df_dx) logger.info("error: %.5e", error) assert error < 2.0e-4, error
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 _(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 basis_obj(self): return mp.basis_for_space(self.space, self.shape)
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 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 _basis(self): return mp.basis_for_space(self._space, self._shape)