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_nodal_face_mass_matrix(dim, order=3): from modepy.tools import unit_vertices all_verts = unit_vertices(dim).T basis = mp.simplex_onb(dim, order) np.set_printoptions(linewidth=200) from modepy.matrices import nodal_face_mass_matrix volume_nodes = mp.warp_and_blend_nodes(dim, order) face_nodes = mp.warp_and_blend_nodes(dim - 1, order) for iface in range(dim + 1): verts = np.hstack([all_verts[:, :iface], all_verts[:, iface + 1:]]) fmm = nodal_face_mass_matrix(basis, volume_nodes, face_nodes, order, verts) fmm2 = nodal_face_mass_matrix(basis, volume_nodes, face_nodes, order + 1, verts) assert la.norm(fmm - fmm2, np.inf) < 1e-11 fmm[np.abs(fmm) < 1e-13] = 0 print(fmm) nnz = np.sum(np.abs(fmm) > 0) print(nnz) print( mp.mass_matrix( mp.simplex_onb(dim - 1, order), mp.warp_and_blend_nodes(dim - 1, order), ))
def test_nodal_face_mass_matrix(dim, order=3): from modepy.tools import unit_vertices all_verts = unit_vertices(dim).T basis = mp.simplex_onb(dim, order) np.set_printoptions(linewidth=200) from modepy.matrices import nodal_face_mass_matrix volume_nodes = mp.warp_and_blend_nodes(dim, order) face_nodes = mp.warp_and_blend_nodes(dim-1, order) for iface in range(dim+1): verts = np.hstack([all_verts[:, :iface], all_verts[:, iface+1:]]) fmm = nodal_face_mass_matrix(basis, volume_nodes, face_nodes, order, verts) fmm2 = nodal_face_mass_matrix(basis, volume_nodes, face_nodes, order+1, verts) assert la.norm(fmm-fmm2, np.inf) < 1e-11 fmm[np.abs(fmm) < 1e-13] = 0 print(fmm) nnz = np.sum(np.abs(fmm) > 0) print(nnz) print(mp.mass_matrix( mp.simplex_onb(dim-1, order), mp.warp_and_blend_nodes(dim-1, order), ))
def unit_nodes(self): dim = self.mesh_el_group.dim if self.order == 0: result = mp.warp_and_blend_nodes(dim, 1) result = np.mean(result, axis=1).reshape(-1, 1) else: result = mp.warp_and_blend_nodes(dim, self.order) dim2, nunit_nodes = result.shape assert dim2 == dim return result
def make_group_from_vertices(vertices, vertex_indices, order): el_vertices = vertices[:, vertex_indices] el_origins = el_vertices[:, :, 0][:, :, np.newaxis] # ambient_dim, nelements, nspan_vectors spanning_vectors = ( el_vertices[:, :, 1:] - el_origins) nspan_vectors = spanning_vectors.shape[-1] dim = nspan_vectors # dim, nunit_nodes if dim <= 3: unit_nodes = mp.warp_and_blend_nodes(dim, order) else: unit_nodes = mp.equidistant_nodes(dim, order) unit_nodes_01 = 0.5 + 0.5*unit_nodes nodes = np.einsum( "si,des->dei", unit_nodes_01, spanning_vectors) + el_origins # make contiguous nodes = nodes.copy() from meshmode.mesh import SimplexElementGroup return SimplexElementGroup( order, vertex_indices, nodes, unit_nodes=unit_nodes)
def unit_nodes(self): dim = self.mesh_el_group.dim result = mp.warp_and_blend_nodes(dim, self.order) dim2, nunit_nodes = result.shape assert dim2 == dim return result
def test_diff_matrix(dims, eltype): n = 5 if eltype == "simplex": nodes = mp.warp_and_blend_nodes(dims, n) basis = mp.simplex_onb(dims, n) grad_basis = mp.grad_simplex_onb(dims, n) elif eltype == "tensor": nodes = mp.legendre_gauss_lobatto_tensor_product_nodes(dims, n) basis = mp.legendre_tensor_product_basis(dims, n) grad_basis = mp.grad_legendre_tensor_product_basis(dims, n) else: raise ValueError(f"unknown element type: {eltype}") diff_mat = mp.differentiation_matrices(basis, grad_basis, 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 make_curve_mesh(curve_f, element_boundaries, order, unit_nodes=None, node_vertex_consistency_tolerance=None, return_parametrization_points=False): """ :arg curve_f: A callable representing a parametrization for a curve, accepting a vector of point locations and returning an array of shape *(2, npoints)*. :arg element_boundaries: a vector of element boundary locations in :math:`[0,1]`, in order. 0 must be the first entry, 1 the last one. :arg unit_nodes: if given, the unit nodes to use. Must have shape ``(dim, nnoodes)``. :returns: a :class:`meshmode.mesh.Mesh`, or if *return_parametrization_points* is True, a tuple ``(mesh, par_points)``, where *par_points* is an array of parametrization points. """ assert element_boundaries[0] == 0 assert element_boundaries[-1] == 1 nelements = len(element_boundaries) - 1 if unit_nodes is None: unit_nodes = mp.warp_and_blend_nodes(1, order) nodes_01 = 0.5*(unit_nodes+1) vertices = curve_f(element_boundaries) el_lengths = np.diff(element_boundaries) el_starts = element_boundaries[:-1] # (el_nr, node_nr) t = el_starts[:, np.newaxis] + el_lengths[:, np.newaxis]*nodes_01 t = t.ravel() nodes = curve_f(t).reshape(vertices.shape[0], nelements, -1) from meshmode.mesh import Mesh, SimplexElementGroup egroup = SimplexElementGroup( order, vertex_indices=np.vstack([ np.arange(nelements, dtype=np.int32), np.arange(1, nelements+1, dtype=np.int32) % nelements, ]).T, nodes=nodes, unit_nodes=unit_nodes) mesh = Mesh( vertices=vertices, groups=[egroup], nodal_adjacency=None, facial_adjacency_groups=None, node_vertex_consistency_tolerance=node_vertex_consistency_tolerance) if return_parametrization_points: return mesh, t else: return mesh
def make_curve_mesh(curve_f, element_boundaries, order, unit_nodes=None, node_vertex_consistency_tolerance=None, return_parametrization_points=False): """ :arg curve_f: A callable representing a parametrization for a curve, accepting a vector of point locations and returning an array of shape *(2, npoints)*. :arg element_boundaries: a vector of element boundary locations in :math:`[0,1]`, in order. 0 must be the first entry, 1 the last one. :arg unit_nodes: if given, the unit nodes to use. Must have shape ``(dim, nnoodes)``. :returns: a :class:`meshmode.mesh.Mesh`, or if *return_parametrization_points* is True, a tuple ``(mesh, par_points)``, where *par_points* is an array of parametrization points. """ assert element_boundaries[0] == 0 assert element_boundaries[-1] == 1 nelements = len(element_boundaries) - 1 if unit_nodes is None: unit_nodes = mp.warp_and_blend_nodes(1, order) nodes_01 = 0.5*(unit_nodes+1) vertices = curve_f(element_boundaries) el_lengths = np.diff(element_boundaries) el_starts = element_boundaries[:-1] # (el_nr, node_nr) t = el_starts[:, np.newaxis] + el_lengths[:, np.newaxis]*nodes_01 t = t.ravel() nodes = curve_f(t).reshape(vertices.shape[0], nelements, -1) from meshmode.mesh import Mesh, SimplexElementGroup egroup = SimplexElementGroup( order, vertex_indices=np.vstack([ np.arange(nelements, dtype=np.int32), np.arange(1, nelements+1, dtype=np.int32) % nelements, ]).T, nodes=nodes, unit_nodes=unit_nodes) mesh = Mesh( vertices=vertices, groups=[egroup], node_vertex_consistency_tolerance=node_vertex_consistency_tolerance, is_conforming=True) if return_parametrization_points: return mesh, t else: return mesh
def estimate_resid(inner_n): nodes = mp.warp_and_blend_nodes(dims, inner_n) basis = mp.simplex_onb(dims, inner_n) vdm = mp.vandermonde(basis, nodes) f = test_func(nodes[0]) coeffs = la.solve(vdm, f) from modepy.modal_decay import estimate_relative_expansion_residual return estimate_relative_expansion_residual( coeffs.reshape(1, -1), dims, inner_n)
def estimate_resid(inner_n): nodes = mp.warp_and_blend_nodes(dims, inner_n) basis = mp.simplex_onb(dims, inner_n) vdm = mp.vandermonde(basis, nodes) f = test_func(nodes[0]) coeffs = la.solve(vdm, f) from modepy.modal_decay import estimate_relative_expansion_residual return estimate_relative_expansion_residual(coeffs.reshape(1, -1), dims, inner_n)
def estimate_resid(inner_n): nodes = mp.warp_and_blend_nodes(dims, inner_n) basis = mp.orthonormal_basis_for_space( mp.PN(dims, inner_n), mp.Simplex(dims)) vdm = mp.vandermonde(basis.functions, nodes) f = test_func(nodes[0]) coeffs = la.solve(vdm, f) from modepy.modal_decay import estimate_relative_expansion_residual return estimate_relative_expansion_residual( coeffs.reshape(1, -1), dims, inner_n)
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 test_diff_matrix(dims): n = 5 nodes = mp.warp_and_blend_nodes(dims, n) f = np.sin(nodes[0]) df_dx = np.cos(nodes[0]) diff_mat = mp.differentiation_matrices(mp.simplex_onb(dims, n), mp.grad_simplex_onb(dims, n), nodes) if isinstance(diff_mat, tuple): diff_mat = diff_mat[0] df_dx_num = np.dot(diff_mat, f) print(la.norm(df_dx - df_dx_num)) assert la.norm(df_dx - df_dx_num) < 1e-3
def test_diff_matrix(dims): n = 5 nodes = mp.warp_and_blend_nodes(dims, n) f = np.sin(nodes[0]) df_dx = np.cos(nodes[0]) diff_mat = mp.differentiation_matrices( mp.simplex_onb(dims, n), mp.grad_simplex_onb(dims, n), nodes) if isinstance(diff_mat, tuple): diff_mat = diff_mat[0] df_dx_num = np.dot(diff_mat, f) print((la.norm(df_dx-df_dx_num))) assert la.norm(df_dx-df_dx_num) < 1e-3
def test_modal_decay(case_name, test_func, dims, n, expected_expn): nodes = mp.warp_and_blend_nodes(dims, n) basis = mp.simplex_onb(dims, n) vdm = mp.vandermonde(basis, nodes) f = test_func(nodes[0]) coeffs = la.solve(vdm, f) if 0: from modepy.tools import plot_element_values plot_element_values(n, nodes, f, resample_n=70, show_nodes=True) from modepy.modal_decay import fit_modal_decay expn, _ = fit_modal_decay(coeffs.reshape(1, -1), dims, n) expn = expn[0] print(f"{case_name}: computed: {expn:g}, expected: {expected_expn:g}") assert abs(expn - expected_expn) < 0.1
def test_diff_matrix_permutation(dims): order = 5 space = mp.PN(dims, order) from pytools import \ generate_nonnegative_integer_tuples_summing_to_at_most as gnitstam node_tuples = list(gnitstam(order, dims)) simplex_onb = mp.orthonormal_basis_for_space(space, mp.Simplex(dims)) nodes = np.array(mp.warp_and_blend_nodes(dims, order, node_tuples=node_tuples)) diff_matrices = mp.differentiation_matrices( simplex_onb.functions, simplex_onb.gradients, nodes) for iref_axis in range(dims): perm = mp.diff_matrix_permutation(node_tuples, iref_axis) assert la.norm( diff_matrices[iref_axis] - diff_matrices[0][perm][:, perm]) < 1e-10
def test_modal_decay(case_name, test_func, dims, n, expected_expn): nodes = mp.warp_and_blend_nodes(dims, n) basis = mp.simplex_onb(dims, n) vdm = mp.vandermonde(basis, nodes) f = test_func(nodes[0]) coeffs = la.solve(vdm, f) if 0: from modepy.tools import plot_element_values plot_element_values(n, nodes, f, resample_n=70, show_nodes=True) from modepy.modal_decay import fit_modal_decay expn, _ = fit_modal_decay(coeffs.reshape(1, -1), dims, n) expn = expn[0] print(("%s: computed: %g, expected: %g" % (case_name, expn, expected_expn))) assert abs(expn-expected_expn) < 0.1
def test_diff_matrix_permutation(dims): order = 5 from pytools import \ generate_nonnegative_integer_tuples_summing_to_at_most as gnitstam node_tuples = list(gnitstam(order, dims)) simplex_onb = mp.simplex_onb(dims, order) grad_simplex_onb = mp.grad_simplex_onb(dims, order) nodes = np.array( mp.warp_and_blend_nodes(dims, order, node_tuples=node_tuples)) diff_matrices = mp.differentiation_matrices(simplex_onb, grad_simplex_onb, nodes) for iref_axis in range(dims): perm = mp.diff_matrix_permutation(node_tuples, iref_axis) assert la.norm(diff_matrices[iref_axis] - diff_matrices[0][perm][:, perm]) < 1e-10
def __init__(self, order, vertex_indices, nodes, element_nr_base=None, node_nr_base=None, unit_nodes=None, dim=None): """ :arg order: the maximum total degree used for interpolation. :arg nodes: ``[ambient_dim, nelements, nunit_nodes]`` The nodes are assumed to be mapped versions of *unit_nodes*. :arg unit_nodes: ``[dim, nunit_nodes]`` The unit nodes of which *nodes* is a mapped version. If unspecified, the nodes from :func:`modepy.warp_and_blend_nodes` for *dim* are assumed. These must be in unit coordinates as defined in :mod:`modepy`. :arg dim: only used if *unit_nodes* is None, to get the default unit nodes. Do not supply *element_nr_base* and *node_nr_base*, they will be automatically assigned. """ if unit_nodes is None: if dim is None: raise TypeError("'dim' must be passed " "if 'unit_nodes' is not passed") if dim <= 3: unit_nodes = mp.warp_and_blend_nodes(dim, order) else: unit_nodes = mp.equidistant_nodes(dim, order) dims = unit_nodes.shape[0] if vertex_indices is not None: if not issubclass(vertex_indices.dtype.type, np.integer): raise TypeError("vertex_indices must be integral") if vertex_indices.shape[-1] != dims+1: raise ValueError("vertex_indices has wrong number of vertices per " "element. expected: %d, got: %d" % (dims+1, vertex_indices.shape[-1])) super().__init__(order, vertex_indices, nodes, element_nr_base, node_nr_base, unit_nodes, dim)
def test_estimate_lebesgue_constant(dims, order, domain, visualize=False): logging.basicConfig(level=logging.INFO) if domain == "simplex": nodes = mp.warp_and_blend_nodes(dims, order) elif domain == "hypercube": from modepy.nodes import legendre_gauss_lobatto_tensor_product_nodes nodes = legendre_gauss_lobatto_tensor_product_nodes(dims, order) else: raise ValueError(f"unknown domain: '{domain}'") from modepy.tools import estimate_lebesgue_constant lebesgue_constant = estimate_lebesgue_constant(order, nodes, domain=domain) logger.info("%s-%d/%s: %.5e", domain, dims, order, lebesgue_constant) if not visualize: return from modepy.tools import _evaluate_lebesgue_function lebesgue, equi_node_tuples, equi_nodes = \ _evaluate_lebesgue_function(order, nodes, domain) import matplotlib.pyplot as plt fig = plt.figure() ax = fig.gca() ax.grid() if dims == 1: ax.plot(equi_nodes[0], lebesgue) ax.set_xlabel("$x$") ax.set_ylabel(fr"$\lambda_{order}$") elif dims == 2: ax.plot(nodes[0], nodes[1], "ko") p = ax.tricontourf(equi_nodes[0], equi_nodes[1], lebesgue, levels=16) fig.colorbar(p) ax.set_xlim([-1.1, 1.1]) ax.set_ylim([-1.1, 1.1]) ax.set_aspect("equal") else: raise ValueError(f"unsupported dimension: {dims}") fig.savefig(f"estimate_lebesgue_constant_{domain}_{dims}_order_{order}")
def __init__(self, order, vertex_indices, nodes, element_nr_base=None, node_nr_base=None, unit_nodes=None, dim=None): """ :arg order: the mamximum total degree used for interpolation. :arg nodes: ``[ambient_dim, nelements, nunit_nodes]`` The nodes are assumed to be mapped versions of *unit_nodes*. :arg unit_nodes: ``[dim, nunit_nodes]`` The unit nodes of which *nodes* is a mapped version. If unspecified, the nodes from :func:`modepy.warp_and_blend_nodes` for *dim* are assumed. These must be in unit coordinates as defined in :mod:`modepy.nodes`. :arg dim: only used if *unit_nodes* is None, to get the default unit nodes. Do not supply *element_nr_base* and *node_nr_base*, they will be automatically assigned. """ if not issubclass(vertex_indices.dtype.type, np.integer): raise TypeError("vertex_indices must be integral") if unit_nodes is None: if dim is None: raise TypeError("'dim' must be passed " "if 'unit_nodes' is not passed") if dim <= 3: unit_nodes = mp.warp_and_blend_nodes(dim, order) else: unit_nodes = mp.equidistant_nodes(dim, order) dims = unit_nodes.shape[0] if vertex_indices.shape[-1] != dims+1: raise ValueError("vertex_indices has wrong number of vertices per " "element. expected: %d, got: %d" % (dims+1, vertex_indices.shape[-1])) super(SimplexElementGroup, self).__init__(order, vertex_indices, nodes, element_nr_base, node_nr_base, unit_nodes, dim)
def make_curve_mesh(curve_f, element_boundaries, order): """ :arg curve_f: A callable representing a parametrization for a curve, accepting a vector of point locations and returning an array of shape *(2, npoints)*. :arg element_boundaries: a vector of element boundary locations in :math:`[0,1]`, in order. 0 must be the first entry, 1 the last one. :returns: a :class:`meshmode.mesh.Mesh` """ assert element_boundaries[0] == 0 assert element_boundaries[-1] == 1 nelements = len(element_boundaries) - 1 unodes = mp.warp_and_blend_nodes(1, order) nodes_01 = 0.5*(unodes+1) vertices = curve_f(element_boundaries) el_lengths = np.diff(element_boundaries) el_starts = element_boundaries[:-1] # (el_nr, node_nr) t = el_starts[:, np.newaxis] + el_lengths[:, np.newaxis]*nodes_01 nodes = curve_f(t.ravel()).reshape(vertices.shape[0], nelements, -1) from meshmode.mesh import Mesh, SimplexElementGroup egroup = SimplexElementGroup( order, vertex_indices=np.vstack([ np.arange(nelements, dtype=np.int32), np.arange(1, nelements+1, dtype=np.int32) % nelements, ]).T, nodes=nodes, unit_nodes=unodes) return Mesh( vertices=vertices, groups=[egroup], nodal_adjacency=None, facial_adjacency_groups=None)
def make_group_from_vertices(vertices, vertex_indices, order, group_factory=None): # shape: (dim, nelements, nvertices) el_vertices = vertices[:, vertex_indices] from meshmode.mesh import SimplexElementGroup, TensorProductElementGroup if group_factory is None: group_factory = SimplexElementGroup if issubclass(group_factory, SimplexElementGroup): el_origins = el_vertices[:, :, 0][:, :, np.newaxis] # ambient_dim, nelements, nspan_vectors spanning_vectors = ( el_vertices[:, :, 1:] - el_origins) nspan_vectors = spanning_vectors.shape[-1] dim = nspan_vectors # dim, nunit_nodes if dim <= 3: unit_nodes = mp.warp_and_blend_nodes(dim, order) else: unit_nodes = mp.equidistant_nodes(dim, order) unit_nodes_01 = 0.5 + 0.5*unit_nodes nodes = np.einsum( "si,des->dei", unit_nodes_01, spanning_vectors) + el_origins elif issubclass(group_factory, TensorProductElementGroup): nelements, nvertices = vertex_indices.shape dim = 0 while True: if nvertices == 2**dim: break if nvertices < 2**dim: raise ValueError("invalid number of vertices for tensor-product " "elements, must be power of two") dim += 1 from modepy.quadrature.jacobi_gauss import legendre_gauss_lobatto_nodes from modepy.nodes import tensor_product_nodes unit_nodes = tensor_product_nodes(dim, legendre_gauss_lobatto_nodes(order)) # shape: (dim, nnodes) unit_nodes_01 = 0.5 + 0.5*unit_nodes _, nnodes = unit_nodes.shape from pytools import generate_nonnegative_integer_tuples_below as gnitb id_tuples = list(gnitb(2, dim)) assert len(id_tuples) == nvertices vdm = np.empty((nvertices, nvertices)) for i, vertex_tuple in enumerate(id_tuples): for j, func_tuple in enumerate(id_tuples): vertex_ref = np.array(vertex_tuple, dtype=np.float64) vdm[i, j] = np.prod(vertex_ref**func_tuple) # shape: (dim, nelements, nvertices) coeffs = np.empty((dim, nelements, nvertices)) for d in range(dim): coeffs[d] = la.solve(vdm, el_vertices[d].T).T vdm_nodes = np.zeros((nnodes, nvertices)) for j, func_tuple in enumerate(id_tuples): vdm_nodes[:, j] = np.prod( unit_nodes_01 ** np.array(func_tuple).reshape(-1, 1), axis=0) nodes = np.einsum("ij,dej->dei", vdm_nodes, coeffs) else: raise ValueError("unsupported value for 'group_factory': %s" % group_factory) # make contiguous nodes = nodes.copy() return group_factory( order, vertex_indices, nodes, unit_nodes=unit_nodes)
n = 8 ball_radius = 0.05 link_radius = 0.02 from pytools import generate_nonnegative_integer_tuples_summing_to_at_most \ as gnitstam node_tuples = list(gnitstam(n, 3)) faces = [[nt for nt in node_tuples if nt[0] == 0], [nt for nt in node_tuples if nt[1] == 0], [nt for nt in node_tuples if nt[2] == 0], [nt for nt in node_tuples if sum(nt) == n]] from modepy.tools import unit_to_barycentric, barycentric_to_equilateral nodes = [(n[0], n[2], n[1]) for n in barycentric_to_equilateral( unit_to_barycentric(mp.warp_and_blend_nodes(3, n, node_tuples))).T] id_to_node = dict(list(zip(node_tuples, nodes))) def get_ball_radius(nid): in_faces = len([f for f in faces if nid in f]) if in_faces >= 2: return ball_radius * 1.333 else: return ball_radius def get_ball_color(nid): in_faces = len([f for f in faces if nid in f]) if in_faces >= 2: return (1, 0, 1)
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_curve_mesh(curve_f, element_boundaries, order, unit_nodes=None, node_vertex_consistency_tolerance=None, closed=True, return_parametrization_points=False): """ :arg curve_f: A callable representing a parametrization for a curve, accepting a vector of point locations and returning an array of shape *(2, npoints)*. :arg element_boundaries: a vector of element boundary locations in :math:`[0,1]`, in order. 0 must be the first entry, 1 the last one. :arg closed: if *True*, the curve is assumed closed and the first and last of the *element_boundaries* must match. :arg unit_nodes: if given, the unit nodes to use. Must have shape ``(dim, nnodes)``. :returns: a :class:`meshmode.mesh.Mesh`, or if *return_parametrization_points* is *True*, a tuple ``(mesh, par_points)``, where *par_points* is an array of parametrization points. """ assert element_boundaries[0] == 0 assert element_boundaries[-1] == 1 nelements = len(element_boundaries) - 1 if unit_nodes is None: unit_nodes = mp.warp_and_blend_nodes(1, order) nodes_01 = 0.5 * (unit_nodes + 1) wrap = nelements if not closed: wrap += 1 vertices = curve_f(element_boundaries)[:, :wrap] vertex_indices = np.vstack([ np.arange(0, nelements, dtype=np.int32), np.arange(1, nelements + 1, dtype=np.int32) % wrap ]).T assert vertices.shape[1] == np.max(vertex_indices) + 1 if closed: start_end_par = np.array([0, 1], dtype=np.float64) start_end_curve = curve_f(start_end_par) assert la.norm(start_end_curve[:, 0] - start_end_curve[:, 1]) < 1.0e-12 el_lengths = np.diff(element_boundaries) el_starts = element_boundaries[:-1] # (el_nr, node_nr) t = el_starts[:, np.newaxis] + el_lengths[:, np.newaxis] * nodes_01 t = t.ravel() nodes = curve_f(t).reshape(vertices.shape[0], nelements, -1) from meshmode.mesh import Mesh, SimplexElementGroup egroup = SimplexElementGroup(order, vertex_indices=vertex_indices, nodes=nodes, unit_nodes=unit_nodes) mesh = Mesh( vertices=vertices, groups=[egroup], node_vertex_consistency_tolerance=node_vertex_consistency_tolerance, is_conforming=True) if return_parametrization_points: return mesh, t else: return mesh
from __future__ import absolute_import import matplotlib.pyplot as pt import numpy as np import modepy as mp dims = 3 n = 10 unit = mp.warp_and_blend_nodes(dims, n) if 0: from modepy.tools import estimate_lebesgue_constant lebesgue = estimate_lebesgue_constant(n, unit, visualize=True) from modepy.tools import unit_to_barycentric, barycentric_to_equilateral equi = barycentric_to_equilateral(unit_to_barycentric(unit)) if dims == 2: pt.plot(equi[0], equi[1], "o") from modepy.tools import EQUILATERAL_VERTICES uv = list(EQUILATERAL_VERTICES[2]) uv.append(uv[0]) uv = np.array(uv) pt.plot(uv[:, 0], uv[:, 1], "") pt.gca().set_aspect("equal") pt.show() elif dims == 3: import mayavi.mlab as mlab mlab.points3d(
from pytools import generate_nonnegative_integer_tuples_summing_to_at_most \ as gnitstam node_tuples = list(gnitstam(n, 3)) faces = [ [nt for nt in node_tuples if nt[0] == 0], [nt for nt in node_tuples if nt[1] == 0], [nt for nt in node_tuples if nt[2] == 0], [nt for nt in node_tuples if sum(nt) == n] ] from modepy.tools import unit_to_barycentric, barycentric_to_equilateral nodes = [(n[0],n[2], n[1]) for n in barycentric_to_equilateral( unit_to_barycentric( mp.warp_and_blend_nodes(3, n, node_tuples))).T] id_to_node = dict(list(zip(node_tuples, nodes))) def get_ball_radius(nid): in_faces = len([f for f in faces if nid in f]) if in_faces >= 2: return ball_radius * 1.333 else: return ball_radius def get_ball_color(nid): in_faces = len([f for f in faces if nid in f]) if in_faces >= 2: return (1,0,1) else: return (0,0,1)
import numpy as np import modepy as mp n = 17 # use this total degree dimensions = 2 # Get a basis of orthonormal functions, and their derivatives. basis = mp.simplex_onb(dimensions, n) grad_basis = mp.grad_simplex_onb(dimensions, n) nodes = mp.warp_and_blend_nodes(dimensions, n) x, y = nodes # We want to compute the x derivative of this function: f = np.sin(5*x+y) df_dx = 5*np.cos(5*x+y) # The (generalized) Vandermonde matrix transforms coefficients into # nodal values. So we can find basis coefficients by applying its # inverse: f_coefficients = np.linalg.solve( mp.vandermonde(basis, nodes), f) # Now linearly combine the (x-)derivatives of the basis using # f_coefficients to compute the numerical derivatives. df_dx_num = np.dot( mp.vandermonde(grad_basis, nodes)[0], f_coefficients)
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
import numpy as np import modepy as mp n = 17 # use this total degree dimensions = 2 # Get a basis of orthonormal functions, and their derivatives. basis = mp.simplex_onb(dimensions, n) grad_basis = mp.grad_simplex_onb(dimensions, n) nodes = mp.warp_and_blend_nodes(dimensions, n) x, y = nodes # We want to compute the x derivative of this function: f = np.sin(5 * x + y) df_dx = 5 * np.cos(5 * x + y) # The (generalized) Vandermonde matrix transforms coefficients into # nodal values. So we can find basis coefficients by applying its # inverse: f_coefficients = np.linalg.solve(mp.vandermonde(basis, nodes), f) # Now linearly combine the (x-)derivatives of the basis using # f_coefficients to compute the numerical derivatives. df_dx_num = np.dot(mp.vandermonde(grad_basis, nodes)[0], f_coefficients) assert np.linalg.norm(df_dx - df_dx_num) < 1e-5
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 test_sanity_single_element(ctx_getter, dim, order, visualize=False): pytest.importorskip("pytential") cl_ctx = ctx_getter() queue = cl.CommandQueue(cl_ctx) from modepy.tools import unit_vertices vertices = unit_vertices(dim).T.copy() center = np.empty(dim, np.float64) center.fill(-0.5) import modepy as mp from meshmode.mesh import SimplexElementGroup, Mesh, BTAG_ALL mg = SimplexElementGroup( order=order, vertex_indices=np.arange(dim + 1, dtype=np.int32).reshape(1, -1), nodes=mp.warp_and_blend_nodes(dim, order).reshape(dim, 1, -1), dim=dim) mesh = Mesh(vertices, [mg], nodal_adjacency=None, facial_adjacency_groups=None) from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ PolynomialWarpAndBlendGroupFactory vol_discr = Discretization(cl_ctx, mesh, PolynomialWarpAndBlendGroupFactory(order + 3)) # {{{ volume calculation check vol_x = vol_discr.nodes().with_queue(queue) vol_one = vol_x[0].copy() vol_one.fill(1) from pytential import norm, integral # noqa from pytools import factorial true_vol = 1 / factorial(dim) * 2**dim comp_vol = integral(vol_discr, queue, vol_one) rel_vol_err = abs(true_vol - comp_vol) / true_vol assert rel_vol_err < 1e-12 # }}} # {{{ boundary discretization from meshmode.discretization.connection import make_face_restriction bdry_connection = make_face_restriction( vol_discr, PolynomialWarpAndBlendGroupFactory(order + 3), BTAG_ALL) bdry_discr = bdry_connection.to_discr # }}} # {{{ visualizers from meshmode.discretization.visualization import make_visualizer #vol_vis = make_visualizer(queue, vol_discr, 4) bdry_vis = make_visualizer(queue, bdry_discr, 4) # }}} from pytential import bind, sym bdry_normals = bind(bdry_discr, sym.normal(dim))(queue).as_vector(dtype=object) if visualize: bdry_vis.write_vtk_file("boundary.vtu", [("bdry_normals", bdry_normals)]) from pytential import bind, sym normal_outward_check = bind( bdry_discr, sym.normal(dim) | (sym.nodes(dim) + 0.5 * sym.ones_vec(dim)), )(queue).as_scalar() > 0 assert normal_outward_check.get().all(), normal_outward_check.get()
def run(actx, *, ambient_dim: int = 3, resolution: int = None, target_order: int = 4, tmax: float = 1.0, timestep: float = 1.0e-2, group_factory_name: str = "warp_and_blend", visualize: bool = True): if ambient_dim not in (2, 3): raise ValueError(f"unsupported dimension: {ambient_dim}") mesh_order = target_order radius = 1.0 # {{{ geometry # {{{ element groups import modepy as mp import meshmode.discretization.poly_element as poly # NOTE: picking the same unit nodes for the mesh and the discr saves # a bit of work when reconstructing after a time step if group_factory_name == "warp_and_blend": group_factory_cls = poly.PolynomialWarpAndBlendGroupFactory unit_nodes = mp.warp_and_blend_nodes(ambient_dim - 1, mesh_order) elif group_factory_name == "quadrature": group_factory_cls = poly.InterpolatoryQuadratureSimplexGroupFactory if ambient_dim == 2: unit_nodes = mp.LegendreGaussQuadrature( mesh_order, force_dim_axis=True).nodes else: unit_nodes = mp.VioreanuRokhlinSimplexQuadrature(mesh_order, 2).nodes else: raise ValueError(f"unknown group factory: '{group_factory_name}'") # }}} # {{{ discretization import meshmode.mesh.generation as gen if ambient_dim == 2: nelements = 8192 if resolution is None else resolution mesh = gen.make_curve_mesh( lambda t: radius * gen.ellipse(1.0, t), np.linspace(0.0, 1.0, nelements + 1), order=mesh_order, unit_nodes=unit_nodes) else: nrounds = 4 if resolution is None else resolution mesh = gen.generate_icosphere(radius, uniform_refinement_rounds=nrounds, order=mesh_order, unit_nodes=unit_nodes) from meshmode.discretization import Discretization discr0 = Discretization(actx, mesh, group_factory_cls(target_order)) logger.info("ndofs: %d", discr0.ndofs) logger.info("nelements: %d", discr0.mesh.nelements) # }}} if visualize: from meshmode.discretization.visualization import make_visualizer vis = make_visualizer(actx, discr0, vis_order=target_order, # NOTE: setting this to True will add some unnecessary # resampling in Discretization.nodes for the vis_discr underneath force_equidistant=False) # }}} # {{{ ode def velocity_field(nodes, alpha=1.0): return make_obj_array([ alpha * nodes[0], -alpha * nodes[1], 0.0 * nodes[0] ][:ambient_dim]) def source(t, x): discr = reconstruct_discr_from_nodes(actx, discr0, x) u = velocity_field(thaw(discr.nodes(), actx)) # {{{ # NOTE: these are just here because this was at some point used to # profile some more operators (turned out well!) from meshmode.discretization import num_reference_derivative x = thaw(discr.nodes()[0], actx) gradx = sum( num_reference_derivative(discr, (i,), x) for i in range(discr.dim)) intx = sum(actx.np.sum(xi * wi) for xi, wi in zip(x, discr.quad_weights())) assert gradx is not None assert intx is not None # }}} return u # }}} # {{{ evolve maxiter = int(tmax // timestep) + 1 dt = tmax / maxiter + 1.0e-15 x = thaw(discr0.nodes(), actx) t = 0.0 if visualize: filename = f"moving-geometry-{0:09d}.vtu" plot_solution(actx, vis, filename, discr0, t, x) for n in range(1, maxiter + 1): x = advance(actx, dt, t, x, source) t += dt if visualize: discr = reconstruct_discr_from_nodes(actx, discr0, x) vis = make_visualizer(actx, discr, vis_order=target_order) # vis = vis.copy_with_same_connectivity(actx, discr) filename = f"moving-geometry-{n:09d}.vtu" plot_solution(actx, vis, filename, discr, t, x) logger.info("[%05d/%05d] t = %.5e/%.5e dt = %.5e", n, maxiter, t, tmax, dt)
def test_sanity_single_element(ctx_getter, dim, order, visualize=False): pytest.importorskip("pytential") cl_ctx = ctx_getter() queue = cl.CommandQueue(cl_ctx) from modepy.tools import UNIT_VERTICES vertices = UNIT_VERTICES[dim].T.copy() center = np.empty(dim, np.float64) center.fill(-0.5) import modepy as mp from meshmode.mesh import SimplexElementGroup, Mesh mg = SimplexElementGroup( order=order, vertex_indices=np.arange(dim+1, dtype=np.int32).reshape(1, -1), nodes=mp.warp_and_blend_nodes(dim, order).reshape(dim, 1, -1), dim=dim) mesh = Mesh(vertices, [mg]) from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ PolynomialWarpAndBlendGroupFactory vol_discr = Discretization(cl_ctx, mesh, PolynomialWarpAndBlendGroupFactory(order+3)) # {{{ volume calculation check vol_x = vol_discr.nodes().with_queue(queue) vol_one = vol_x[0].copy() vol_one.fill(1) from pytential import norm, integral # noqa from pytools import factorial true_vol = 1/factorial(dim) * 2**dim comp_vol = integral(vol_discr, queue, vol_one) rel_vol_err = abs(true_vol - comp_vol) / true_vol assert rel_vol_err < 1e-12 # }}} # {{{ boundary discretization from meshmode.discretization.connection import make_boundary_restriction bdry_mesh, bdry_discr, bdry_connection = make_boundary_restriction( queue, vol_discr, PolynomialWarpAndBlendGroupFactory(order + 3)) # }}} # {{{ visualizers from meshmode.discretization.visualization import make_visualizer #vol_vis = make_visualizer(queue, vol_discr, 4) bdry_vis = make_visualizer(queue, bdry_discr, 4) # }}} from pytential import bind, sym bdry_normals = bind(bdry_discr, sym.normal())(queue).as_vector(dtype=object) if visualize: bdry_vis.write_vtk_file("boundary.vtu", [ ("bdry_normals", bdry_normals) ]) from pytential import bind, sym normal_outward_check = bind(bdry_discr, sym.normal() | (sym.Nodes() + 0.5*sym.ones_vec(dim)), )(queue).as_scalar() > 0 assert normal_outward_check.get().all(), normal_outward_check.get()
def make_curve_mesh( curve_f: Callable[[np.ndarray], np.ndarray], element_boundaries: np.ndarray, order: int, *, unit_nodes: Optional[np.ndarray] = None, node_vertex_consistency_tolerance: Optional[Union[float, bool]] = None, closed: bool = True, return_parametrization_points: bool = False): """ :param curve_f: parametrization for a curve, accepting a vector of point locations and returning an array of shape ``(2, npoints)``. :param element_boundaries: a vector of element boundary locations in :math:`[0, 1]`, in order. :math:`0` must be the first entry, :math:`1` the last one. :param order: order of the (simplex) elements. If *unit_nodes* is also provided, the orders should match. :param unit_nodes: if given, the unit nodes to use. Must have shape ``(2, nnodes)``. :param node_vertex_consistency_tolerance: passed to the :class:`~meshmode.mesh.Mesh` constructor. If *False*, no checks are performed. :param closed: if *True*, the curve is assumed closed and the first and last of the *element_boundaries* must match. :param return_parametrization_points: if *True*, the parametrization points at which all the nodes in the mesh were evaluated are also returned. :returns: a :class:`~meshmode.mesh.Mesh`, or if *return_parametrization_points* is *True*, a tuple ``(mesh, par_points)``, where *par_points* is an array of parametrization points. """ assert element_boundaries[0] == 0 assert element_boundaries[-1] == 1 nelements = len(element_boundaries) - 1 if unit_nodes is None: unit_nodes = mp.warp_and_blend_nodes(1, order) nodes_01 = 0.5 * (unit_nodes + 1) wrap = nelements if not closed: wrap += 1 vertices = curve_f(element_boundaries)[:, :wrap] vertex_indices = np.vstack([ np.arange(0, nelements, dtype=np.int32), np.arange(1, nelements + 1, dtype=np.int32) % wrap ]).T assert vertices.shape[1] == np.max(vertex_indices) + 1 if closed: start_end_par = np.array([0, 1], dtype=np.float64) start_end_curve = curve_f(start_end_par) assert la.norm(start_end_curve[:, 0] - start_end_curve[:, 1]) < 1.0e-12 el_lengths = np.diff(element_boundaries) el_starts = element_boundaries[:-1] # (el_nr, node_nr) t = el_starts[:, np.newaxis] + el_lengths[:, np.newaxis] * nodes_01 t = t.ravel() nodes = curve_f(t).reshape(vertices.shape[0], nelements, -1) from meshmode.mesh import Mesh, SimplexElementGroup egroup = SimplexElementGroup(order, vertex_indices=vertex_indices, nodes=nodes, unit_nodes=unit_nodes) mesh = Mesh( vertices=vertices, groups=[egroup], node_vertex_consistency_tolerance=node_vertex_consistency_tolerance, is_conforming=True) if return_parametrization_points: return mesh, t else: return mesh
def make_group_from_vertices(vertices, vertex_indices, order, group_cls=None, unit_nodes=None, group_factory=None): if group_factory is not None: from warnings import warn warn("'group_factory' is deprecated, use 'group_cls' instead", DeprecationWarning, stacklevel=2) if group_cls is not None: raise ValueError("cannot set both 'group_cls' and 'group_factory'") group_cls = group_factory # shape: (ambient_dim, nelements, nvertices) ambient_dim = vertices.shape[0] el_vertices = vertices[:, vertex_indices] from meshmode.mesh import SimplexElementGroup, TensorProductElementGroup if group_cls is None: group_cls = SimplexElementGroup if issubclass(group_cls, SimplexElementGroup): if order < 1: raise ValueError("can't represent simplices with mesh order < 1") el_origins = el_vertices[:, :, 0][:, :, np.newaxis] # ambient_dim, nelements, nspan_vectors spanning_vectors = (el_vertices[:, :, 1:] - el_origins) nspan_vectors = spanning_vectors.shape[-1] dim = nspan_vectors # dim, nunit_nodes if unit_nodes is None: if dim <= 3: unit_nodes = mp.warp_and_blend_nodes(dim, order) else: unit_nodes = mp.equidistant_nodes(dim, order) unit_nodes_01 = 0.5 + 0.5 * unit_nodes nodes = np.einsum("si,des->dei", unit_nodes_01, spanning_vectors) + el_origins elif issubclass(group_cls, TensorProductElementGroup): nelements, nvertices = vertex_indices.shape dim = nvertices.bit_length() - 1 if nvertices != 2**dim: raise ValueError("invalid number of vertices for tensor-product " "elements, must be power of two") if unit_nodes is None: from modepy.quadrature.jacobi_gauss import legendre_gauss_lobatto_nodes unit_nodes = mp.tensor_product_nodes( dim, legendre_gauss_lobatto_nodes(order)) # shape: (dim, nnodes) unit_nodes_01 = 0.5 + 0.5 * unit_nodes _, nnodes = unit_nodes.shape from pytools import generate_nonnegative_integer_tuples_below as gnitb id_tuples = list(gnitb(2, dim)) assert len(id_tuples) == nvertices vdm = np.empty((nvertices, nvertices)) for i, vertex_tuple in enumerate(id_tuples): for j, func_tuple in enumerate(id_tuples): vertex_ref = np.array(vertex_tuple, dtype=np.float64) vdm[i, j] = np.prod(vertex_ref**func_tuple) # shape: (ambient_dim, nelements, nvertices) coeffs = np.empty((ambient_dim, nelements, nvertices)) for d in range(ambient_dim): coeffs[d] = la.solve(vdm, el_vertices[d].T).T vdm_nodes = np.zeros((nnodes, nvertices)) for j, func_tuple in enumerate(id_tuples): vdm_nodes[:, j] = np.prod(unit_nodes_01**np.array(func_tuple).reshape( -1, 1), axis=0) nodes = np.einsum("ij,dej->dei", vdm_nodes, coeffs) else: raise ValueError(f"unsupported value for 'group_cls': {group_cls}") # make contiguous nodes = nodes.copy() return group_cls(order, vertex_indices, nodes, unit_nodes=unit_nodes)
import matplotlib.pyplot as pt import numpy as np import modepy as mp nodes = mp.warp_and_blend_nodes(2, 10) pt.plot(nodes[0], nodes[1], "x") tri = np.array([(-1, -1), (1, -1), (-1, 1), (-1, -1)]).T pt.plot(nodes[0], nodes[1], "x") pt.plot(tri[0], tri[1], "-b") pt.gca().set_aspect("equal") pt.show()
import matplotlib.pyplot as pt import numpy as np import modepy as mp dims = 3 n = 10 unit = mp.warp_and_blend_nodes(dims, n) if 0: from modepy.tools import estimate_lebesgue_constant lebesgue = estimate_lebesgue_constant(n, unit, visualize=True) from modepy.tools import unit_to_barycentric, barycentric_to_equilateral equi = barycentric_to_equilateral(unit_to_barycentric(unit)) if dims == 2: pt.plot(equi[0], equi[1], "o") from modepy.tools import EQUILATERAL_VERTICES uv = list(EQUILATERAL_VERTICES[2]) uv.append(uv[0]) uv = np.array(uv) pt.plot(uv[:, 0], uv[:, 1], "") pt.gca().set_aspect("equal") pt.show() elif dims == 3: import mayavi.mlab as mlab mlab.points3d(equi[0], equi[1], equi[2]) mlab.orientation_axes()