def test_circle_mesh(visualize=False): from meshmode.mesh.io import generate_gmsh, FileSource logger.info("BEGIN GEN") mesh = generate_gmsh( FileSource("circle.step"), 2, order=2, force_ambient_dim=2, other_options=["-string", "Mesh.CharacteristicLengthMax = 0.05;"], target_unit="MM", ) logger.info("END GEN") logger.info("nelements: %d", mesh.nelements) from meshmode.mesh.processing import affine_map mesh = affine_map(mesh, A=3 * np.eye(2)) if visualize: from meshmode.mesh.visualization import draw_2d_mesh draw_2d_mesh(mesh, fill=None, draw_vertex_numbers=False, draw_nodal_adjacency=True, set_bounding_box=True) import matplotlib.pyplot as pt pt.axis("equal") pt.savefig("circle_mesh", dpi=300)
def test_rect_mesh(visualize=False): mesh = mgen.generate_regular_rect_mesh(nelements_per_axis=(4, 4)) if visualize: from meshmode.mesh.visualization import draw_2d_mesh draw_2d_mesh(mesh, fill=None, draw_nodal_adjacency=True) import matplotlib.pyplot as pt pt.show()
def test_rect_mesh(do_plot=False): from meshmode.mesh.generation import generate_regular_rect_mesh mesh = generate_regular_rect_mesh() if do_plot: from meshmode.mesh.visualization import draw_2d_mesh draw_2d_mesh(mesh, fill=None, draw_nodal_adjacency=True) import matplotlib.pyplot as pt pt.show()
def test_rect_mesh(do_plot=False): from meshmode.mesh.generation import generate_regular_rect_mesh mesh = generate_regular_rect_mesh() if do_plot: from meshmode.mesh.visualization import draw_2d_mesh draw_2d_mesh(mesh, fill=None, draw_connectivity=True) import matplotlib.pyplot as pt pt.show()
def main(): from meshmode.mesh.generation import ( # noqa make_curve_mesh, starfish) mesh1 = make_curve_mesh(starfish, np.linspace(0, 1, 20), 4) from meshmode.mesh.processing import affine_map, merge_disjoint_meshes mesh2 = affine_map(mesh1, b=np.array([2, 3])) mesh = merge_disjoint_meshes((mesh1, mesh2)) from meshmode.mesh.visualization import draw_2d_mesh draw_2d_mesh(mesh, set_bounding_box=True) import matplotlib.pyplot as pt pt.show()
def test_circle_mesh(do_plot=False): from meshmode.mesh.io import generate_gmsh, FileSource print("BEGIN GEN") mesh = generate_gmsh( FileSource("circle.step"), 2, order=2, force_ambient_dim=2, other_options=[ "-string", "Mesh.CharacteristicLengthMax = 0.05;"] ) print("END GEN") print(mesh.nelements) from meshmode.mesh.processing import affine_map mesh = affine_map(mesh, A=3*np.eye(2)) if do_plot: from meshmode.mesh.visualization import draw_2d_mesh draw_2d_mesh(mesh, fill=None, draw_connectivity=True, set_bounding_box=True) import matplotlib.pyplot as pt pt.show()
def test_circle_mesh(do_plot=False): from meshmode.mesh.io import generate_gmsh, FileSource print("BEGIN GEN") mesh = generate_gmsh( FileSource("circle.step"), 2, order=2, force_ambient_dim=2, other_options=[ "-string", "Mesh.CharacteristicLengthMax = 0.05;"] ) print("END GEN") print(mesh.nelements) from meshmode.mesh.processing import affine_map mesh = affine_map(mesh, A=3*np.eye(2)) if do_plot: from meshmode.mesh.visualization import draw_2d_mesh draw_2d_mesh(mesh, fill=None, draw_nodal_adjacency=True, set_bounding_box=True) import matplotlib.pyplot as pt pt.show()
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)
def main(): # If can't import firedrake, do nothing # # filename MUST include "firedrake" (i.e. match *firedrake*.py) in order # to be run during CI try: import firedrake # noqa : F401 except ImportError: return 0 from meshmode.interop.firedrake import build_connection_from_firedrake from firedrake import (UnitSquareMesh, FunctionSpace, SpatialCoordinate, Function, cos) # Create a firedrake mesh and interpolate cos(x+y) onto it fd_mesh = UnitSquareMesh(10, 10) fd_fspace = FunctionSpace(fd_mesh, "DG", 2) spatial_coord = SpatialCoordinate(fd_mesh) fd_fntn = Function(fd_fspace).interpolate(cos(sum(spatial_coord))) # Make connections cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext(queue) fd_connection = build_connection_from_firedrake(actx, fd_fspace) fd_bdy_connection = \ build_connection_from_firedrake(actx, fd_fspace, restrict_to_boundary="on_boundary") # Plot the meshmode meshes that the connections connect to import matplotlib.pyplot as plt from meshmode.mesh.visualization import draw_2d_mesh fig, (ax1, ax2) = plt.subplots(1, 2) ax1.set_title("FiredrakeConnection") plt.sca(ax1) draw_2d_mesh(fd_connection.discr.mesh, draw_vertex_numbers=False, draw_element_numbers=False, set_bounding_box=True) ax2.set_title("FiredrakeConnection 'on_boundary'") plt.sca(ax2) draw_2d_mesh(fd_bdy_connection.discr.mesh, draw_vertex_numbers=False, draw_element_numbers=False, set_bounding_box=True) plt.show() # Plot fd_fntn using unrestricted FiredrakeConnection from meshmode.discretization.visualization import make_visualizer discr = fd_connection.discr vis = make_visualizer(actx, discr, discr.groups[0].order + 3) field = fd_connection.from_firedrake(fd_fntn, actx=actx) fig = plt.figure() ax1 = fig.add_subplot(1, 2, 1, projection="3d") ax1.set_title("cos(x+y) in\nFiredrakeConnection") vis.show_scalar_in_matplotlib_3d(field, do_show=False) # Now repeat using FiredrakeConnection restricted to "on_boundary" bdy_discr = fd_bdy_connection.discr bdy_vis = make_visualizer(actx, bdy_discr, bdy_discr.groups[0].order + 3) bdy_field = fd_bdy_connection.from_firedrake(fd_fntn, actx=actx) ax2 = fig.add_subplot(1, 2, 2, projection="3d") plt.sca(ax2) ax2.set_title("cos(x+y) in\nFiredrakeConnection 'on_boundary'") bdy_vis.show_scalar_in_matplotlib_3d(bdy_field, do_show=False) import matplotlib.cm as cm fig.colorbar(cm.ScalarMappable()) plt.show()
def test_mesh_multiple_groups(actx_factory, ambient_dim, visualize=False): actx = actx_factory() order = 4 mesh = mgen.generate_regular_rect_mesh(a=(-0.5, ) * ambient_dim, b=(0.5, ) * ambient_dim, nelements_per_axis=(8, ) * ambient_dim, order=order) assert len(mesh.groups) == 1 from meshmode.mesh.processing import split_mesh_groups element_flags = np.any( mesh.vertices[0, mesh.groups[0].vertex_indices] < 0.0, axis=1).astype(np.int64) mesh = split_mesh_groups(mesh, element_flags) assert len(mesh.groups) == 2 # pylint: disable=no-member assert mesh.facial_adjacency_groups assert mesh.nodal_adjacency if visualize and ambient_dim == 2: from meshmode.mesh.visualization import draw_2d_mesh draw_2d_mesh(mesh, draw_vertex_numbers=False, draw_element_numbers=True, draw_face_numbers=False, set_bounding_box=True) import matplotlib.pyplot as plt plt.savefig("test_mesh_multiple_groups_2d_elements.png", dpi=300) from meshmode.discretization import Discretization discr = Discretization(actx, mesh, PolynomialWarpAndBlendGroupFactory(order)) if visualize: group_id = discr.empty(actx, dtype=np.int32) for igrp, vec in enumerate(group_id): vec.fill(igrp) from meshmode.discretization.visualization import make_visualizer vis = make_visualizer(actx, discr, vis_order=order) vis.write_vtk_file("mesh_multiple_groups.vtu", [("group_id", group_id)], overwrite=True) # check face restrictions from meshmode.discretization.connection import ( make_face_restriction, make_face_to_all_faces_embedding, make_opposite_face_connection, check_connection) for boundary_tag in [BTAG_ALL, FACE_RESTR_INTERIOR, FACE_RESTR_ALL]: conn = make_face_restriction( actx, discr, group_factory=PolynomialWarpAndBlendGroupFactory(order), boundary_tag=boundary_tag, per_face_groups=False) check_connection(actx, conn) bdry_f = conn.to_discr.zeros(actx) + 1 if boundary_tag == FACE_RESTR_INTERIOR: opposite = make_opposite_face_connection(actx, conn) check_connection(actx, opposite) op_bdry_f = opposite(bdry_f) error = flat_norm(bdry_f - op_bdry_f, np.inf) assert error < 1.0e-11, error if boundary_tag == FACE_RESTR_ALL: embedding = make_face_to_all_faces_embedding( actx, conn, conn.to_discr) check_connection(actx, embedding) em_bdry_f = embedding(bdry_f) error = flat_norm(bdry_f - em_bdry_f) assert error < 1.0e-11, error # check some derivatives (nb: flatten is a generator) import pytools ref_axes = pytools.flatten([[i] for i in range(ambient_dim)]) from meshmode.discretization import num_reference_derivative x = thaw(discr.nodes(), actx) num_reference_derivative(discr, ref_axes, x[0])
def make_opposite_face_connection(volume_to_bdry_conn): """Given a boundary restriction connection *volume_to_bdry_conn*, return a :class:`DirectDiscretizationConnection` that performs data exchange across opposite faces. """ vol_discr = volume_to_bdry_conn.from_discr vol_mesh = vol_discr.mesh bdry_discr = volume_to_bdry_conn.to_discr # make sure we were handed a volume-to-boundary connection for i_tgrp, conn_grp in enumerate(volume_to_bdry_conn.groups): for batch in conn_grp.batches: assert batch.from_group_index == i_tgrp assert batch.to_element_face is not None ngrps = len(volume_to_bdry_conn.groups) assert ngrps == len(vol_discr.groups) assert ngrps == len(bdry_discr.groups) # One interpolation batch in this connection corresponds # to a key (i_tgt_grp,) (i_src_grp, i_face_tgt,) with cl.CommandQueue(vol_discr.cl_context) as queue: # a list of batches for each group groups = [[] for i_tgt_grp in range(ngrps)] for i_src_grp in range(ngrps): src_grp_el_lookup = _make_bdry_el_lookup_table( queue, volume_to_bdry_conn, i_src_grp) for i_tgt_grp in range(ngrps): vbc_tgt_grp_batches = volume_to_bdry_conn.groups[ i_tgt_grp].batches adj = vol_mesh.facial_adjacency_groups[i_tgt_grp][i_src_grp] for i_face_tgt in range(vol_mesh.groups[i_tgt_grp].nfaces): vbc_tgt_grp_face_batch = _find_ibatch_for_face( vbc_tgt_grp_batches, i_face_tgt) # {{{ index wrangling # Assert that the adjacency group and the restriction # interpolation batch and the adjacency group have the same # element ordering. adj_tgt_flags = adj.element_faces == i_face_tgt assert (np.array_equal( adj.elements[adj_tgt_flags], vbc_tgt_grp_face_batch.from_element_indices.get( queue=queue))) # find to_element_indices tgt_bdry_element_indices = ( vbc_tgt_grp_face_batch.to_element_indices.get( queue=queue)) # find from_element_indices src_vol_element_indices = adj.neighbors[adj_tgt_flags] src_element_faces = adj.neighbor_faces[adj_tgt_flags] src_bdry_element_indices = src_grp_el_lookup[ src_vol_element_indices, src_element_faces] # }}} # {{{ visualization (for debugging) if 0: print("TVE", adj.elements[adj_tgt_flags]) print("TBE", tgt_bdry_element_indices) print("FVE", src_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() # }}} groups[i_tgt_grp].extend( _make_cross_face_batches(queue, bdry_discr, bdry_discr, i_tgt_grp, i_src_grp, tgt_bdry_element_indices, src_bdry_element_indices)) from meshmode.discretization.connection import ( DirectDiscretizationConnection, DiscretizationConnectionElementGroup) return DirectDiscretizationConnection( from_discr=bdry_discr, to_discr=bdry_discr, groups=[ DiscretizationConnectionElementGroup(batches=batches) for batches in groups ], is_surjective=True)
def make_opposite_face_connection(actx, volume_to_bdry_conn): """Given a boundary restriction connection *volume_to_bdry_conn*, return a :class:`DirectDiscretizationConnection` that performs data exchange across opposite faces. """ vol_discr = volume_to_bdry_conn.from_discr vol_mesh = vol_discr.mesh bdry_discr = volume_to_bdry_conn.to_discr # make sure we were handed a volume-to-boundary connection for i_tgrp, conn_grp in enumerate(volume_to_bdry_conn.groups): for batch in conn_grp.batches: assert batch.from_group_index == i_tgrp assert batch.to_element_face is not None ngrps = len(volume_to_bdry_conn.groups) assert ngrps == len(vol_discr.groups) assert ngrps == len(bdry_discr.groups) # One interpolation batch in this connection corresponds # to a key (i_tgt_grp,) (i_src_grp, i_face_tgt,) # a list of batches for each group groups = [[] for i_tgt_grp in range(ngrps)] for i_src_grp in range(ngrps): src_grp_el_lookup = _make_bdry_el_lookup_table(actx, volume_to_bdry_conn, i_src_grp) for i_tgt_grp in range(ngrps): vbc_tgt_grp_batches = volume_to_bdry_conn.groups[i_tgt_grp].batches adj = vol_mesh.facial_adjacency_groups[i_tgt_grp][i_src_grp] for i_face_tgt in range(vol_mesh.groups[i_tgt_grp].nfaces): vbc_tgt_grp_face_batch = _find_ibatch_for_face( vbc_tgt_grp_batches, i_face_tgt) # {{{ index wrangling # The elements in the adjacency group will be a subset of # the elements in the restriction interpolation batch: # Imagine an inter-group boundary. The volume-to-boundary # connection will include all faces as targets, whereas # there will be separate adjacency groups for intra- and # inter-group connections. adj_tgt_flags = adj.element_faces == i_face_tgt adj_els = adj.elements[adj_tgt_flags] if adj_els.size == 0: # NOTE: this case can happen for inter-group boundaries # when all elements are adjacent on the same face # index, so all other ones will be empty continue vbc_els = thaw_to_numpy( actx, vbc_tgt_grp_face_batch.from_element_indices) if len(adj_els) == len(vbc_els): # Same length: assert (below) that the two use the same # ordering. vbc_used_els = slice(None) else: # Genuine subset: figure out an index mapping. vbc_els_sort_idx = np.argsort(vbc_els) vbc_used_els = vbc_els_sort_idx[np.searchsorted( vbc_els, adj_els, sorter=vbc_els_sort_idx)] assert np.array_equal(vbc_els[vbc_used_els], adj_els) # find to_element_indices tgt_bdry_element_indices = thaw_to_numpy( actx, vbc_tgt_grp_face_batch.to_element_indices)[vbc_used_els] # find from_element_indices src_vol_element_indices = adj.neighbors[adj_tgt_flags] src_element_faces = adj.neighbor_faces[adj_tgt_flags] src_bdry_element_indices = src_grp_el_lookup[ src_vol_element_indices, src_element_faces] # }}} # {{{ visualization (for debugging) if 0: print("TVE", adj.elements[adj_tgt_flags]) print("TBE", tgt_bdry_element_indices) print("FVE", src_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() # }}} batches = _make_cross_face_batches(actx, bdry_discr, bdry_discr, i_tgt_grp, i_src_grp, tgt_bdry_element_indices, src_bdry_element_indices) groups[i_tgt_grp].extend(batches) from meshmode.discretization.connection import ( DirectDiscretizationConnection, DiscretizationConnectionElementGroup) return DirectDiscretizationConnection( from_discr=bdry_discr, to_discr=bdry_discr, groups=[ DiscretizationConnectionElementGroup(batches=batches) for batches in groups ], is_surjective=True)
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)
def test_box_boundary_tags(dim, nelem, mesh_type, group_cls, visualize=False): if group_cls is TensorProductElementGroup and mesh_type is not None: pytest.skip("mesh type not supported on tensor product elements") from meshmode.mesh import is_boundary_tag_empty from meshmode.mesh import check_bc_coverage if dim == 1: a = (0, ) b = (1, ) nelements_per_axis = (nelem, ) btag_to_face = {"btag_test_1": ["+x"], "btag_test_2": ["-x"]} elif dim == 2: a = (0, -1) b = (1, 1) nelements_per_axis = (nelem, ) * 2 btag_to_face = { "btag_test_1": ["+x", "-y"], "btag_test_2": ["+y", "-x"] } elif dim == 3: a = (0, -1, -1) b = (1, 1, 1) nelements_per_axis = (nelem, ) * 3 btag_to_face = { "btag_test_1": ["+x", "-y", "-z"], "btag_test_2": ["+y", "-x", "+z"] } mesh = mgen.generate_regular_rect_mesh( a=a, b=b, nelements_per_axis=nelements_per_axis, order=3, boundary_tag_to_face=btag_to_face, group_cls=group_cls, mesh_type=mesh_type) if visualize and dim == 2: from meshmode.mesh.visualization import draw_2d_mesh draw_2d_mesh(mesh, draw_element_numbers=False, draw_vertex_numbers=False) import matplotlib.pyplot as plt plt.show() # correct answer if dim == 1: num_on_bdy = 1 elif group_cls is TensorProductElementGroup: num_on_bdy = dim * nelem**(dim - 1) elif group_cls is SimplexElementGroup: num_on_bdy = dim * (dim - 1) * nelem**(dim - 1) else: raise AssertionError() assert not is_boundary_tag_empty(mesh, "btag_test_1") assert not is_boundary_tag_empty(mesh, "btag_test_2") check_bc_coverage(mesh, ["btag_test_1", "btag_test_2"]) # check how many elements are marked on each boundary num_marked_bdy_1 = 0 num_marked_bdy_2 = 0 btag_1_bit = mesh.boundary_tag_bit("btag_test_1") btag_2_bit = mesh.boundary_tag_bit("btag_test_2") for igrp in range(len(mesh.groups)): bdry_fagrp = mesh.facial_adjacency_groups[igrp].get(None, None) if bdry_fagrp is None: continue for nbrs in bdry_fagrp.neighbors: if (-nbrs) & btag_1_bit: num_marked_bdy_1 += 1 if (-nbrs) & btag_2_bit: num_marked_bdy_2 += 1 # raise errors if wrong number of elements marked if num_marked_bdy_1 != num_on_bdy: raise ValueError("%i marked on custom boundary 1, should be %i" % (num_marked_bdy_1, num_on_bdy)) if num_marked_bdy_2 != num_on_bdy: raise ValueError("%i marked on custom boundary 2, should be %i" % (num_marked_bdy_2, num_on_bdy))
def make_opposite_face_connection(volume_to_bdry_conn): """Given a boundary restriction connection *volume_to_bdry_conn*, return a :class:`DirectDiscretizationConnection` that performs data exchange across opposite faces. """ vol_discr = volume_to_bdry_conn.from_discr vol_mesh = vol_discr.mesh bdry_discr = volume_to_bdry_conn.to_discr # make sure we were handed a volume-to-boundary connection for i_tgrp, conn_grp in enumerate(volume_to_bdry_conn.groups): for batch in conn_grp.batches: assert batch.from_group_index == i_tgrp assert batch.to_element_face is not None ngrps = len(volume_to_bdry_conn.groups) assert ngrps == len(vol_discr.groups) assert ngrps == len(bdry_discr.groups) # One interpolation batch in this connection corresponds # to a key (i_tgt_grp,) (i_src_grp, i_face_tgt,) with cl.CommandQueue(vol_discr.cl_context) as queue: # a list of batches for each group groups = [[] for i_tgt_grp in range(ngrps)] for i_src_grp in range(ngrps): src_grp_el_lookup = _make_bdry_el_lookup_table( queue, volume_to_bdry_conn, i_src_grp) for i_tgt_grp in range(ngrps): vbc_tgt_grp_batches = volume_to_bdry_conn.groups[i_tgt_grp].batches adj = vol_mesh.facial_adjacency_groups[i_tgt_grp][i_src_grp] for i_face_tgt in range(vol_mesh.groups[i_tgt_grp].nfaces): vbc_tgt_grp_face_batch = _find_ibatch_for_face( vbc_tgt_grp_batches, i_face_tgt) # {{{ index wrangling # Assert that the adjacency group and the restriction # interpolation batch and the adjacency group have the same # element ordering. adj_tgt_flags = adj.element_faces == i_face_tgt assert (np.array_equal( adj.elements[adj_tgt_flags], vbc_tgt_grp_face_batch.from_element_indices .get(queue=queue))) # find to_element_indices tgt_bdry_element_indices = ( vbc_tgt_grp_face_batch.to_element_indices .get(queue=queue)) # find from_element_indices src_vol_element_indices = adj.neighbors[adj_tgt_flags] src_element_faces = adj.neighbor_faces[adj_tgt_flags] src_bdry_element_indices = src_grp_el_lookup[ src_vol_element_indices, src_element_faces] # }}} # {{{ visualization (for debugging) if 0: print("TVE", adj.elements[adj_tgt_flags]) print("TBE", tgt_bdry_element_indices) print("FVE", src_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() # }}} groups[i_tgt_grp].extend(_make_cross_face_batches(queue, bdry_discr, bdry_discr, i_tgt_grp, i_src_grp, tgt_bdry_element_indices, src_bdry_element_indices)) from meshmode.discretization.connection import ( DirectDiscretizationConnection, DiscretizationConnectionElementGroup) return DirectDiscretizationConnection( from_discr=bdry_discr, to_discr=bdry_discr, groups=[ DiscretizationConnectionElementGroup(batches=batches) for batches in groups], is_surjective=True)