def get_ref_stiffness_transpose_mat(out_grp, in_grp): if in_grp == out_grp: from meshmode.discretization.poly_element import \ mass_matrix, diff_matrices mmat = mass_matrix(out_grp) return actx.freeze( actx.from_numpy( np.asarray( [dmat.T @ mmat.T for dmat in diff_matrices(out_grp)]))) from modepy import vandermonde basis = out_grp.basis_obj() vand = vandermonde(basis.functions, out_grp.unit_nodes) grad_vand = vandermonde(basis.gradients, in_grp.unit_nodes) vand_inv_t = np.linalg.inv(vand).T if not isinstance(grad_vand, tuple): # NOTE: special case for 1d grad_vand = (grad_vand, ) weights = in_grp.quadrature_rule().weights return actx.freeze( actx.from_numpy( np.einsum("c,bz,acz->abc", weights, vand_inv_t, grad_vand).copy() # contigify the array ))
def matrix(out_element_group, in_element_group): if out_element_group == in_element_group: return in_element_group.mass_matrix() from modepy import vandermonde vand = vandermonde(out_element_group.basis(), out_element_group.unit_nodes) o_vand = vandermonde(out_element_group.basis(), in_element_group.unit_nodes) vand_inv_t = np.linalg.inv(vand).T weights = in_element_group.weights return np.einsum("j,ik,jk->ij", weights, vand_inv_t, o_vand)
def matrix(out_element_group, in_element_group): if out_element_group == in_element_group: from meshmode.discretization.poly_element import mass_matrix return mass_matrix(in_element_group) from modepy import vandermonde basis = out_element_group.basis_obj() vand = vandermonde(basis.functions, out_element_group.unit_nodes) o_vand = vandermonde(basis.functions, in_element_group.unit_nodes) vand_inv_t = np.linalg.inv(vand).T weights = in_element_group.quadrature_rule().weights return np.einsum("j,ik,jk->ij", weights, vand_inv_t, o_vand)
def matrices(out_elem_grp, in_elem_grp): if in_elem_grp == out_elem_grp: assert in_elem_grp.is_orthogonal_basis() mmat = in_elem_grp.mass_matrix() return [dmat.T.dot(mmat.T) for dmat in in_elem_grp.diff_matrices()] from modepy import vandermonde vand = vandermonde(out_elem_grp.basis(), out_elem_grp.unit_nodes) grad_vand = vandermonde(out_elem_grp.grad_basis(), in_elem_grp.unit_nodes) vand_inv_t = np.linalg.inv(vand).T weights = in_elem_grp.weights return np.einsum('c,bz,acz->abc', weights, vand_inv_t, grad_vand)
def is_affine_simplex_group(group, abs_tol=None): if abs_tol is None: abs_tol = 1.0e-13 if not isinstance(group, SimplexElementGroup): raise TypeError("expected a 'SimplexElementGroup' not '%s'" % type(group).__name__) # get matrices basis = mp.simplex_best_available_basis(group.dim, group.order) grad_basis = mp.grad_simplex_best_available_basis(group.dim, group.order) vinv = la.inv(mp.vandermonde(basis, group.unit_nodes)) diff = mp.differentiation_matrices(basis, grad_basis, group.unit_nodes) if not isinstance(diff, tuple): diff = (diff,) # construct all second derivative matrices (including cross terms) from itertools import product mats = [] for n in product(range(group.dim), repeat=2): if n[0] > n[1]: continue mats.append(vinv.dot(diff[n[0]].dot(diff[n[1]]))) # check just the first element for a non-affine local-to-global mapping ddx_coeffs = np.einsum("aij,bj->abi", mats, group.nodes[:, 0, :]) norm_inf = np.max(np.abs(ddx_coeffs)) if norm_inf > abs_tol: return False # check all elements for a non-affine local-to-global mapping ddx_coeffs = np.einsum("aij,bcj->abci", mats, group.nodes) norm_inf = np.max(np.abs(ddx_coeffs)) return norm_inf < abs_tol
def matrices(out_elem_grp, in_elem_grp): if in_elem_grp == out_elem_grp: assert in_elem_grp.is_orthogonal_basis() mmat = in_elem_grp.mass_matrix() return [dmat.T.dot(mmat.T) for dmat in in_elem_grp.diff_matrices()] from modepy import vandermonde vand = vandermonde(out_elem_grp.basis(), out_elem_grp.unit_nodes) grad_vand = vandermonde(out_elem_grp.grad_basis(), in_elem_grp.unit_nodes) vand_inv_t = np.linalg.inv(vand).T if not isinstance(grad_vand, tuple): # NOTE: special case for 1d grad_vand = (grad_vand,) weights = in_elem_grp.weights return np.einsum("c,bz,acz->abc", weights, vand_inv_t, grad_vand)
def test_modal_coefficients_by_projection(actx_factory, quad_group_factory): group_cls = SimplexElementGroup modal_group_factory = ModalSimplexGroupFactory actx = actx_factory() order = 10 m_order = 5 # Make a regular rectangle mesh mesh = mgen.generate_regular_rect_mesh(a=(0, 0), b=(5, 3), npoints_per_axis=(10, 6), order=order, group_cls=group_cls) # Make discretizations nodal_disc = Discretization(actx, mesh, quad_group_factory(order)) modal_disc = Discretization(actx, mesh, modal_group_factory(m_order)) # Make connections one using quadrature projection nodal_to_modal_conn_quad = NodalToModalDiscretizationConnection( nodal_disc, modal_disc, allow_approximate_quad=True) def f(x): return 2 * actx.np.sin(5 * x) x_nodal = thaw(nodal_disc.nodes()[0], actx) nodal_f = f(x_nodal) # Compute modal coefficients we expect to get import modepy as mp grp, = nodal_disc.groups shape = mp.Simplex(grp.dim) space = mp.space_for_shape(shape, order=m_order) basis = mp.orthonormal_basis_for_space(space, shape) quad = grp.quadrature_rule() nodal_f_data = actx.to_numpy(nodal_f[0]) vdm = mp.vandermonde(basis.functions, quad.nodes) w_diag = np.diag(quad.weights) modal_data = [] for _, nodal_data in enumerate(nodal_f_data): # Compute modal data in each element: V.T * W * nodal_data elem_modal_f = np.dot(vdm.T, np.dot(w_diag, nodal_data)) modal_data.append(elem_modal_f) modal_data = actx.from_numpy(np.asarray(modal_data)) modal_f_expected = DOFArray(actx, data=(modal_data, )) # Map nodal coefficients using the quadrature-based projection modal_f_computed = nodal_to_modal_conn_quad(nodal_f) err = flat_norm(modal_f_expected - modal_f_computed) assert err <= 1e-13
def get_ref_mass_mat(out_grp, in_grp): if out_grp == in_grp: from meshmode.discretization.poly_element import mass_matrix return actx.freeze( actx.from_numpy(np.asarray(mass_matrix(out_grp), order="C"))) from modepy import vandermonde basis = out_grp.basis_obj() vand = vandermonde(basis.functions, out_grp.unit_nodes) o_vand = vandermonde(basis.functions, in_grp.unit_nodes) vand_inv_t = np.linalg.inv(vand).T weights = in_grp.quadrature_rule().weights return actx.freeze( actx.from_numpy( np.asarray(np.einsum("j,ik,jk->ij", weights, vand_inv_t, o_vand), order="C")))
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 matrices(out_elem_grp, in_elem_grp): if in_elem_grp == out_elem_grp: assert in_elem_grp.is_orthonormal_basis() from meshmode.discretization.poly_element import (mass_matrix, diff_matrices) mmat = mass_matrix(in_elem_grp) return [dmat.T.dot(mmat.T) for dmat in diff_matrices(in_elem_grp)] from modepy import vandermonde basis = out_elem_grp.basis_obj() vand = vandermonde(basis.functions, out_elem_grp.unit_nodes) grad_vand = vandermonde(basis.gradients, in_elem_grp.unit_nodes) vand_inv_t = np.linalg.inv(vand).T if not isinstance(grad_vand, tuple): # NOTE: special case for 1d grad_vand = (grad_vand, ) weights = in_elem_grp.quadrature_rule().weights return np.einsum("c,bz,acz->abc", weights, vand_inv_t, grad_vand)
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_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_modal_coeffs_by_projection(dim): shape = mp.Simplex(dim) space = mp.space_for_shape(shape, order=5) basis = mp.orthonormal_basis_for_space(space, shape) quad = mp.XiaoGimbutasSimplexQuadrature(10, dim) assert quad.exact_to >= 2 * space.order modal_coeffs = np.random.randn(space.space_dim) vdm = mp.vandermonde(basis.functions, quad.nodes) evaluated = vdm @ modal_coeffs modal_coeffs_2 = vdm.T @ (evaluated * quad.weights) diff = modal_coeffs - modal_coeffs_2 assert la.norm(diff, 2) < 3e-13
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 _make_cross_face_batches(queue, tgt_bdry_discr, src_bdry_discr, i_tgt_grp, i_src_grp, tgt_bdry_element_indices, src_bdry_element_indices): # FIXME: This should view-then-transfer # (but PyOpenCL doesn't do non-contiguous transfers for now). tgt_bdry_nodes = (tgt_bdry_discr.groups[i_tgt_grp].view( tgt_bdry_discr.nodes().get(queue=queue))[:, tgt_bdry_element_indices]) # FIXME: This should view-then-transfer # (but PyOpenCL doesn't do non-contiguous transfers for now). src_bdry_nodes = (src_bdry_discr.groups[i_src_grp].view( src_bdry_discr.nodes().get(queue=queue))[:, src_bdry_element_indices]) tol = 1e4 * np.finfo(tgt_bdry_nodes.dtype).eps src_mesh_grp = src_bdry_discr.mesh.groups[i_src_grp] src_grp = src_bdry_discr.groups[i_src_grp] dim = src_grp.dim ambient_dim, nelements, ntgt_unit_nodes = tgt_bdry_nodes.shape assert tgt_bdry_nodes.shape == src_bdry_nodes.shape # {{{ invert face map (using Gauss-Newton) initial_guess = np.mean(src_mesh_grp.vertex_unit_coordinates(), axis=0) src_unit_nodes = np.empty((dim, nelements, ntgt_unit_nodes)) src_unit_nodes[:] = initial_guess.reshape(-1, 1, 1) import modepy as mp vdm = mp.vandermonde(src_grp.basis(), src_grp.unit_nodes) inv_t_vdm = la.inv(vdm.T) nsrc_funcs = len(src_grp.basis()) def apply_map(unit_nodes): # unit_nodes: (dim, nelements, ntgt_unit_nodes) # basis_at_unit_nodes basis_at_unit_nodes = np.empty( (nsrc_funcs, nelements, ntgt_unit_nodes)) for i, f in enumerate(src_grp.basis()): basis_at_unit_nodes[i] = (f(unit_nodes.reshape(dim, -1)).reshape( nelements, ntgt_unit_nodes)) intp_coeffs = np.einsum("fj,jet->fet", inv_t_vdm, basis_at_unit_nodes) # If we're interpolating 1, we had better get 1 back. one_deviation = np.abs(np.sum(intp_coeffs, axis=0) - 1) assert (one_deviation < tol).all(), np.max(one_deviation) return np.einsum("fet,aef->aet", intp_coeffs, src_bdry_nodes) def get_map_jacobian(unit_nodes): # unit_nodes: (dim, nelements, ntgt_unit_nodes) # basis_at_unit_nodes dbasis_at_unit_nodes = np.empty( (dim, nsrc_funcs, nelements, ntgt_unit_nodes)) for i, df in enumerate(src_grp.grad_basis()): df_result = df(unit_nodes.reshape(dim, -1)) for rst_axis, df_r in enumerate(df_result): dbasis_at_unit_nodes[rst_axis, i] = (df_r.reshape( nelements, ntgt_unit_nodes)) dintp_coeffs = np.einsum("fj,rjet->rfet", inv_t_vdm, dbasis_at_unit_nodes) return np.einsum("rfet,aef->raet", dintp_coeffs, src_bdry_nodes) # {{{ test map applier and jacobian if 0: u = src_unit_nodes f = apply_map(u) for h in [1e-1, 1e-2]: du = h * np.random.randn(*u.shape) f_2 = apply_map(u + du) jf = get_map_jacobian(u) f2_2 = f + np.einsum("raet,ret->aet", jf, du) print(h, la.norm((f_2 - f2_2).ravel())) # }}} # {{{ visualize initial guess if 0: import matplotlib.pyplot as pt guess = apply_map(src_unit_nodes) goals = tgt_bdry_nodes from meshmode.discretization.visualization import draw_curve pt.figure(0) draw_curve(tgt_bdry_discr) pt.figure(1) draw_curve(src_bdry_discr) pt.figure(2) pt.plot(guess[0].reshape(-1), guess[1].reshape(-1), "or") pt.plot(goals[0].reshape(-1), goals[1].reshape(-1), "og") pt.plot(src_bdry_nodes[0].reshape(-1), src_bdry_nodes[1].reshape(-1), "xb") pt.show() # }}} logger.info("make_opposite_face_connection: begin gauss-newton") niter = 0 while True: resid = apply_map(src_unit_nodes) - tgt_bdry_nodes df = get_map_jacobian(src_unit_nodes) df_inv_resid = np.empty_like(src_unit_nodes) # For the 1D/2D accelerated versions, we'll use the normal # equations and Cramer's rule. If you're looking for high-end # numerics, look no further than meshmode. if dim == 1: # A is df.T ata = np.einsum("iket,jket->ijet", df, df) atb = np.einsum("iket,ket->iet", df, resid) df_inv_resid = atb / ata[0, 0] elif dim == 2: # A is df.T ata = np.einsum("iket,jket->ijet", df, df) atb = np.einsum("iket,ket->iet", df, resid) det = ata[0, 0] * ata[1, 1] - ata[0, 1] * ata[1, 0] df_inv_resid = np.empty_like(src_unit_nodes) df_inv_resid[0] = 1 / det * (ata[1, 1] * atb[0] - ata[1, 0] * atb[1]) df_inv_resid[1] = 1 / det * (-ata[0, 1] * atb[0] + ata[0, 0] * atb[1]) else: # The boundary of a 3D mesh is 2D, so that's the # highest-dimensional case we genuinely care about. # # This stinks, performance-wise, because it's not vectorized. # But we'll only hit it for boundaries of 4+D meshes, in which # case... good luck. :) for e in range(nelements): for t in range(ntgt_unit_nodes): df_inv_resid[:, e, t], _, _, _ = \ la.lstsq(df[:, :, e, t].T, resid[:, e, t]) src_unit_nodes = src_unit_nodes - df_inv_resid # {{{ visualize next guess if 0: import matplotlib.pyplot as pt guess = apply_map(src_unit_nodes) goals = tgt_bdry_nodes pt.plot(guess[0].reshape(-1), guess[1].reshape(-1), "rx") pt.plot(goals[0].reshape(-1), goals[1].reshape(-1), "go") pt.show() # }}} max_resid = np.max(np.abs(resid)) logger.debug("gauss-newton residual: %g" % max_resid) if max_resid < tol: logger.info("make_opposite_face_connection: gauss-newton: done, " "final residual: %g" % max_resid) break niter += 1 if niter > 10: raise RuntimeError( "Gauss-Newton (for finding opposite-face reference " "coordinates) did not converge") # }}} # {{{ find groups of src_unit_nodes def to_dev(ary): return cl.array.to_device(queue, ary, array_queue=None) done_elements = np.zeros(nelements, dtype=np.bool) while True: todo_elements, = np.where(~done_elements) if not len(todo_elements): return template_unit_nodes = src_unit_nodes[:, todo_elements[0], :] unit_node_dist = np.max(np.max( np.abs(src_unit_nodes[:, todo_elements, :] - template_unit_nodes.reshape(dim, 1, -1)), axis=2), axis=0) close_els = todo_elements[unit_node_dist < tol] done_elements[close_els] = True unit_node_dist = np.max(np.max( np.abs(src_unit_nodes[:, todo_elements, :] - template_unit_nodes.reshape(dim, 1, -1)), axis=2), axis=0) from meshmode.discretization.connection.direct import InterpolationBatch yield InterpolationBatch( from_group_index=i_src_grp, from_element_indices=to_dev(src_bdry_element_indices[close_els]), to_element_indices=to_dev(tgt_bdry_element_indices[close_els]), result_unit_nodes=template_unit_nodes, to_element_face=None)
def vandermonde_matrix(grp): from modepy import vandermonde vdm = vandermonde(grp.basis_obj().functions, grp.unit_nodes) return actx.from_numpy(vdm)
def _make_cross_face_batches( queue, vol_discr, bdry_discr, i_tgt_grp, i_src_grp, i_face_tgt, adj_grp, vbc_tgt_grp_face_batch, src_grp_el_lookup): # {{{ index wrangling # Assert that the adjacency group and the restriction # interpolation batch and the adjacency group have the same # element ordering. adj_grp_tgt_flags = adj_grp.element_faces == i_face_tgt assert ( np.array_equal( adj_grp.elements[adj_grp_tgt_flags], vbc_tgt_grp_face_batch.from_element_indices .get(queue=queue))) # find to_element_indices to_bdry_element_indices = ( vbc_tgt_grp_face_batch.to_element_indices .get(queue=queue)) # find from_element_indices from_vol_element_indices = adj_grp.neighbors[adj_grp_tgt_flags] from_element_faces = adj_grp.neighbor_faces[adj_grp_tgt_flags] from_bdry_element_indices = src_grp_el_lookup[ from_vol_element_indices, from_element_faces] # }}} # {{{ visualization (for debugging) if 0: print("TVE", adj_grp.elements[adj_grp_tgt_flags]) print("TBE", to_bdry_element_indices) print("FVE", from_vol_element_indices) from meshmode.mesh.visualization import draw_2d_mesh import matplotlib.pyplot as pt draw_2d_mesh(vol_discr.mesh, draw_element_numbers=True, set_bounding_box=True, draw_vertex_numbers=False, draw_face_numbers=True, fill=None) pt.figure() draw_2d_mesh(bdry_discr.mesh, draw_element_numbers=True, set_bounding_box=True, draw_vertex_numbers=False, draw_face_numbers=True, fill=None) pt.show() # }}} # {{{ invert face map (using Gauss-Newton) to_bdry_nodes = ( # FIXME: This should view-then-transfer (but PyOpenCL doesn't do # non-contiguous transfers for now). bdry_discr.groups[i_tgt_grp].view( bdry_discr.nodes().get(queue=queue)) [:, to_bdry_element_indices]) tol = 1e4 * np.finfo(to_bdry_nodes.dtype).eps from_mesh_grp = bdry_discr.mesh.groups[i_src_grp] from_grp = bdry_discr.groups[i_src_grp] dim = from_grp.dim ambient_dim, nelements, nto_unit_nodes = to_bdry_nodes.shape initial_guess = np.mean(from_mesh_grp.vertex_unit_coordinates(), axis=0) from_unit_nodes = np.empty((dim, nelements, nto_unit_nodes)) from_unit_nodes[:] = initial_guess.reshape(-1, 1, 1) import modepy as mp from_vdm = mp.vandermonde(from_grp.basis(), from_grp.unit_nodes) from_inv_t_vdm = la.inv(from_vdm.T) from_nfuncs = len(from_grp.basis()) # (ambient_dim, nelements, nfrom_unit_nodes) from_bdry_nodes = ( # FIXME: This should view-then-transfer (but PyOpenCL doesn't do # non-contiguous transfers for now). bdry_discr.groups[i_src_grp].view( bdry_discr.nodes().get(queue=queue)) [:, from_bdry_element_indices]) def apply_map(unit_nodes): # unit_nodes: (dim, nelements, nto_unit_nodes) # basis_at_unit_nodes basis_at_unit_nodes = np.empty((from_nfuncs, nelements, nto_unit_nodes)) for i, f in enumerate(from_grp.basis()): basis_at_unit_nodes[i] = ( f(unit_nodes.reshape(dim, -1)) .reshape(nelements, nto_unit_nodes)) intp_coeffs = np.einsum("fj,jet->fet", from_inv_t_vdm, basis_at_unit_nodes) # If we're interpolating 1, we had better get 1 back. one_deviation = np.abs(np.sum(intp_coeffs, axis=0) - 1) assert (one_deviation < tol).all(), np.max(one_deviation) return np.einsum("fet,aef->aet", intp_coeffs, from_bdry_nodes) def get_map_jacobian(unit_nodes): # unit_nodes: (dim, nelements, nto_unit_nodes) # basis_at_unit_nodes dbasis_at_unit_nodes = np.empty( (dim, from_nfuncs, nelements, nto_unit_nodes)) for i, df in enumerate(from_grp.grad_basis()): df_result = df(unit_nodes.reshape(dim, -1)) for rst_axis, df_r in enumerate(df_result): dbasis_at_unit_nodes[rst_axis, i] = ( df_r.reshape(nelements, nto_unit_nodes)) dintp_coeffs = np.einsum( "fj,rjet->rfet", from_inv_t_vdm, dbasis_at_unit_nodes) return np.einsum("rfet,aef->raet", dintp_coeffs, from_bdry_nodes) # {{{ test map applier and jacobian if 0: u = from_unit_nodes f = apply_map(u) for h in [1e-1, 1e-2]: du = h*np.random.randn(*u.shape) f_2 = apply_map(u+du) jf = get_map_jacobian(u) f2_2 = f + np.einsum("raet,ret->aet", jf, du) print(h, la.norm((f_2-f2_2).ravel())) # }}} # {{{ visualize initial guess if 0: import matplotlib.pyplot as pt guess = apply_map(from_unit_nodes) goals = to_bdry_nodes from meshmode.discretization.visualization import draw_curve draw_curve(bdry_discr) pt.plot(guess[0].reshape(-1), guess[1].reshape(-1), "or") pt.plot(goals[0].reshape(-1), goals[1].reshape(-1), "og") pt.plot(from_bdry_nodes[0].reshape(-1), from_bdry_nodes[1].reshape(-1), "o", color="purple") pt.show() # }}} logger.info("make_opposite_face_connection: begin gauss-newton") niter = 0 while True: resid = apply_map(from_unit_nodes) - to_bdry_nodes df = get_map_jacobian(from_unit_nodes) df_inv_resid = np.empty_like(from_unit_nodes) # For the 1D/2D accelerated versions, we'll use the normal # equations and Cramer's rule. If you're looking for high-end # numerics, look no further than meshmode. if dim == 1: # A is df.T ata = np.einsum("iket,jket->ijet", df, df) atb = np.einsum("iket,ket->iet", df, resid) df_inv_resid = atb / ata[0, 0] elif dim == 2: # A is df.T ata = np.einsum("iket,jket->ijet", df, df) atb = np.einsum("iket,ket->iet", df, resid) det = ata[0, 0]*ata[1, 1] - ata[0, 1]*ata[1, 0] df_inv_resid = np.empty_like(from_unit_nodes) df_inv_resid[0] = 1/det * (ata[1, 1] * atb[0] - ata[1, 0]*atb[1]) df_inv_resid[1] = 1/det * (-ata[0, 1] * atb[0] + ata[0, 0]*atb[1]) else: # The boundary of a 3D mesh is 2D, so that's the # highest-dimensional case we genuinely care about. # # This stinks, performance-wise, because it's not vectorized. # But we'll only hit it for boundaries of 4+D meshes, in which # case... good luck. :) for e in range(nelements): for t in range(nto_unit_nodes): df_inv_resid[:, e, t], _, _, _ = \ la.lstsq(df[:, :, e, t].T, resid[:, e, t]) from_unit_nodes = from_unit_nodes - df_inv_resid max_resid = np.max(np.abs(resid)) logger.debug("gauss-newton residual: %g" % max_resid) if max_resid < tol: logger.info("make_opposite_face_connection: gauss-newton: done, " "final residual: %g" % max_resid) break niter += 1 if niter > 10: raise RuntimeError("Gauss-Newton (for finding opposite-face reference " "coordinates) did not converge") # }}} # {{{ find groups of from_unit_nodes def to_dev(ary): return cl.array.to_device(queue, ary, array_queue=None) done_elements = np.zeros(nelements, dtype=np.bool) while True: todo_elements, = np.where(~done_elements) if not len(todo_elements): return template_unit_nodes = from_unit_nodes[:, todo_elements[0], :] unit_node_dist = np.max(np.max(np.abs( from_unit_nodes[:, todo_elements, :] - template_unit_nodes.reshape(dim, 1, -1)), axis=2), axis=0) close_els = todo_elements[unit_node_dist < tol] done_elements[close_els] = True unit_node_dist = np.max(np.max(np.abs( from_unit_nodes[:, todo_elements, :] - template_unit_nodes.reshape(dim, 1, -1)), axis=2), axis=0) from meshmode.discretization.connection import InterpolationBatch yield InterpolationBatch( from_group_index=i_src_grp, from_element_indices=to_dev(from_bdry_element_indices[close_els]), to_element_indices=to_dev(to_bdry_element_indices[close_els]), result_unit_nodes=template_unit_nodes, to_element_face=None)
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
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 __call__(self, ary): if not isinstance(ary, DOFArray): raise TypeError("non-array passed to discretization connection") if ary.shape != (len(self.from_discr.groups), ): raise ValueError("invalid shape of incoming resampling data") actx = ary.array_context @memoize_in(actx, (L2ProjectionInverseDiscretizationConnection, "conn_projection_knl")) def kproj(): return make_loopy_program( [ "{[iel]: 0 <= iel < nelements}", "{[idof_quad]: 0 <= idof_quad < n_from_nodes}" ], """ for iel <> element_dot = sum(idof_quad, ary[from_element_indices[iel], idof_quad] * basis[idof_quad] * weights[idof_quad]) result[to_element_indices[iel], ibasis] = \ result[to_element_indices[iel], ibasis] + element_dot end """, [ lp.GlobalArg("ary", None, shape=("n_from_elements", "n_from_nodes")), lp.GlobalArg( "result", None, shape=("n_to_elements", "n_to_nodes")), lp.GlobalArg("basis", None, shape="n_from_nodes"), lp.GlobalArg("weights", None, shape="n_from_nodes"), lp.ValueArg("n_from_elements", np.int32), lp.ValueArg("n_to_elements", np.int32), lp.ValueArg("n_to_nodes", np.int32), lp.ValueArg("ibasis", np.int32), "..." ], name="conn_projection_knl") @memoize_in(actx, (L2ProjectionInverseDiscretizationConnection, "conn_evaluation_knl")) def keval(): return make_loopy_program( [ "{[iel]: 0 <= iel < nelements}", "{[idof]: 0 <= idof < n_to_nodes}", "{[ibasis]: 0 <= ibasis < n_to_nodes}" ], """ result[iel, idof] = result[iel, idof] + \ sum(ibasis, vdm[idof, ibasis] * coefficients[iel, ibasis]) """, [ lp.GlobalArg("coefficients", None, shape=("nelements", "n_to_nodes")), "..." ], name="conn_evaluate_knl") # compute weights on each refinement of the reference element weights = self._batch_weights(actx) # perform dot product (on reference element) to get basis coefficients c = self.to_discr.zeros(actx, dtype=ary.entry_dtype) for igrp, (tgrp, cgrp) in enumerate( zip(self.to_discr.groups, self.conn.groups)): for ibatch, batch in enumerate(cgrp.batches): sgrp = self.from_discr.groups[batch.from_group_index] for ibasis, basis_fn in enumerate(sgrp.basis()): basis = actx.from_numpy( basis_fn(batch.result_unit_nodes).flatten()) # NOTE: batch.*_element_indices are reversed here because # they are from the original forward connection, but # we are going in reverse here. a bit confusing, but # saves on recreating the connection groups and batches. actx.call_loopy( kproj(), ibasis=ibasis, ary=ary[sgrp.index], basis=basis, weights=weights[igrp, ibatch], result=c[igrp], from_element_indices=batch.to_element_indices, to_element_indices=batch.from_element_indices) # evaluate at unit_nodes to get the vector on to_discr result = self.to_discr.zeros(actx, dtype=ary.entry_dtype) for igrp, grp in enumerate(self.to_discr.groups): from modepy import vandermonde vdm = actx.from_numpy(vandermonde(grp.basis(), grp.unit_nodes)) actx.call_loopy(keval(), result=result[grp.index], vdm=vdm, coefficients=c[grp.index]) return result