def generate_torus_and_cycle_vertices(r_major, r_minor, n_major=20, n_minor=10, order=1): a = r_major b = r_minor u, v = np.mgrid[0:2 * np.pi:2 * np.pi / n_major, 0:2 * np.pi:2 * np.pi / n_minor] # https://web.archive.org/web/20160410151837/https://www.math.hmc.edu/~gu/curves_and_surfaces/surfaces/torus.html # noqa x = np.cos(u) * (a + b * np.cos(v)) y = np.sin(u) * (a + b * np.cos(v)) z = b * np.sin(v) vertices = (np.vstack((x[np.newaxis], y[np.newaxis], z[np.newaxis])).transpose(0, 2, 1).copy().reshape(3, -1)) def idx(i, j): return (i % n_major) + (j % n_minor) * n_major vertex_indices = ([(idx(i, j), idx(i + 1, j), idx(i, j + 1)) for i in range(n_major) for j in range(n_minor)] + [(idx(i + 1, j), idx(i + 1, j + 1), idx(i, j + 1)) for i in range(n_major) for j in range(n_minor)]) vertex_indices = np.array(vertex_indices, dtype=np.int32) grp = make_group_from_vertices(vertices, vertex_indices, order) # ambient_dim, nelements, nunit_nodes nodes = grp.nodes.copy() major_theta = np.arctan2(nodes[1], nodes[0]) rvec = np.array( [np.cos(major_theta), np.sin(major_theta), np.zeros_like(major_theta)]) # ^ # | # --------------+----. # / | \ # / | _-- \ # | |.^ | | y # | +------+---> # | x | # \ / # \ / # ------------------' x = np.sum(nodes * rvec, axis=0) - a minor_theta = np.arctan2(nodes[2], x) nodes[0] = np.cos(major_theta) * (a + b * np.cos(minor_theta)) nodes[1] = np.sin(major_theta) * (a + b * np.cos(minor_theta)) nodes[2] = b * np.sin(minor_theta) from meshmode.mesh import Mesh return (Mesh(vertices, [grp.copy(nodes=nodes)], is_conforming=True), [idx(i, 0) for i in range(n_major)], [idx(0, j) for j in range(n_minor)])
def test_sanity_single_element(ctx_factory, dim, order, visualize=False): pytest.importorskip("pytential") cl_ctx = ctx_factory() 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], is_conforming=True) 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) ]) 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 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
def refine(self, refine_flags): """ :arg refine_flags: a :class:`numpy.ndarray` of dtype bool of length ``mesh.nelements`` indicating which elements should be split. """ if len(refine_flags) != self.last_mesh.nelements: raise ValueError("length of refine_flags does not match " "element count of last generated mesh") #vertices and groups for next generation nvertices = len(self.last_mesh.vertices[0]) groups = [] midpoint_already = set() grpn = 0 totalnelements = 0 for grp in self.last_mesh.groups: iel_base = grp.element_nr_base nelements = 0 for iel_grp in range(grp.nelements): nelements += 1 vertex_indices = grp.vertex_indices[iel_grp] if refine_flags[iel_base + iel_grp]: cur_dim = len(grp.vertex_indices[iel_grp]) - 1 nelements += len(self.simplex_result[cur_dim]) - 1 for i in range(len(vertex_indices)): for j in range(i + 1, len(vertex_indices)): i_index = vertex_indices[i] j_index = vertex_indices[j] index_tuple = (i_index, j_index) if i_index < j_index else ( j_index, i_index) if index_tuple not in midpoint_already and \ self.pair_map[index_tuple].midpoint is None: nvertices += 1 midpoint_already.add(index_tuple) groups.append( np.empty([nelements, len(grp.vertex_indices[0])], dtype=np.int32)) grpn += 1 totalnelements += nelements vertices = np.empty([len(self.last_mesh.vertices), nvertices]) new_hanging_vertex_element = [[] for i in range(nvertices)] # def remove_element_from_connectivity(vertices, new_hanging_vertex_elements, to_remove): # #print(vertices) # import itertools # if len(vertices) == 2: # min_vertex = min(vertices[0], vertices[1]) # max_vertex = max(vertices[0], vertices[1]) # ray = self.pair_map[(min_vertex, max_vertex)] # self.remove_from_subtree(ray, new_hanging_vertex_elements, to_remove) # return # # cur_dim = len(vertices)-1 # element_rays = [] # midpoints = [] # split_possible = True # for i in range(len(vertices)): # for j in range(i+1, len(vertices)): # min_vertex = min(vertices[i], vertices[j]) # max_vertex = max(vertices[i], vertices[j]) # element_rays.append(self.pair_map[(min_vertex, max_vertex)]) # if element_rays[len(element_rays)-1].midpoint is not None: # midpoints.append(element_rays[len(element_rays)-1].midpoint) # else: # split_possible = False #for node in element_rays: #self.remove_from_subtree(node, new_hanging_vertex_elements, to_remove) #if split_possible: # if split_possible: # node_tuple_to_coord = {} # for node_index, node_tuple in enumerate(self.index_to_node_tuple[cur_dim]): # node_tuple_to_coord[node_tuple] = vertices[node_index] # for midpoint_index, midpoint_tuple in enumerate(self.index_to_midpoint_tuple[cur_dim]): # node_tuple_to_coord[midpoint_tuple] = midpoints[midpoint_index] # for i in range(len(self.simplex_result[cur_dim])): # next_vertices = [] # for j in range(len(self.simplex_result[cur_dim][i])): # next_vertices.append(node_tuple_to_coord[self.simplex_node_tuples[cur_dim][self.simplex_result[cur_dim][i][j]]]) # all_rays_present = True # for v1 in range(len(next_vertices)): # for v2 in range(v1+1, len(next_vertices)): # vertex_tuple = (min(next_vertices[v1], next_vertices[v2]), max(next_vertices[v1], next_vertices[v2])) # if vertex_tuple not in self.pair_map: # all_rays_present = False # if all_rays_present: # remove_element_from_connectivity(next_vertices, new_hanging_vertex_elements, to_remove) # else: # split_possible = False # if not split_possible: # next_vertices_list = list(itertools.combinations(vertices, len(vertices)-1)) # for next_vertices in next_vertices_list: # remove_element_from_connectivity(next_vertices, new_hanging_vertex_elements, to_remove) # {{{ Add element to connectivity def add_element_to_connectivity(vertices, new_hanging_vertex_elements, to_add): if len(vertices) == 2: min_vertex = min(vertices[0], vertices[1]) max_vertex = max(vertices[0], vertices[1]) ray = self.pair_map[(min_vertex, max_vertex)] self.add_to_subtree(ray, new_hanging_vertex_elements, to_add) return cur_dim = len(vertices) - 1 element_rays = [] midpoints = [] split_possible = True for i in range(len(vertices)): for j in range(i + 1, len(vertices)): min_vertex = min(vertices[i], vertices[j]) max_vertex = max(vertices[i], vertices[j]) element_rays.append(self.pair_map[(min_vertex, max_vertex)]) if element_rays[len(element_rays) - 1].midpoint is not None: midpoints.append(element_rays[len(element_rays) - 1].midpoint) else: split_possible = False #for node in element_rays: #self.add_to_subtree(node, new_hanging_vertex_elements, to_add) if split_possible: node_tuple_to_coord = {} for node_index, node_tuple in enumerate( self.index_to_node_tuple[cur_dim]): node_tuple_to_coord[node_tuple] = vertices[node_index] for midpoint_index, midpoint_tuple in enumerate( self.index_to_midpoint_tuple[cur_dim]): node_tuple_to_coord[midpoint_tuple] = midpoints[ midpoint_index] for i in range(len(self.simplex_result[cur_dim])): next_vertices = [] for j in range(len(self.simplex_result[cur_dim][i])): next_vertices.append(node_tuple_to_coord[ self.simplex_node_tuples[cur_dim][ self.simplex_result[cur_dim][i][j]]]) all_rays_present = True for v1 in range(len(next_vertices)): for v2 in range(v1 + 1, len(next_vertices)): vertex_tuple = (min(next_vertices[v1], next_vertices[v2]), max(next_vertices[v1], next_vertices[v2])) if vertex_tuple not in self.pair_map: all_rays_present = False if all_rays_present: add_element_to_connectivity( next_vertices, new_hanging_vertex_elements, to_add) else: split_possible = False if not split_possible: next_vertices_list = list( itertools.combinations(vertices, len(vertices) - 1)) for next_vertices in next_vertices_list: add_element_to_connectivity(next_vertices, new_hanging_vertex_elements, to_add) # for node in element_rays: # self.add_element_to_connectivity(node, new_hanging_vertex_elements, to_add) # leaves = self.get_subtree(node) # for leaf in leaves: # if to_add not in leaf.adjacent_elements: # leaf.adjacent_elements.append(to_add) # if to_add not in new_hanging_vertex_elements[leaf.left_vertex]: # new_hanging_vertex_elements[leaf.left_vertex].append(to_add) # if to_add not in new_hanging_vertex_elements[leaf.right_vertex]: # new_hanging_vertex_elements[leaf.right_vertex].append(to_add) # next_element_rays = [] # for i in range(len(element_rays)): # for j in range(i+1, len(element_rays)): # if element_rays[i].midpoint is not None and element_rays[j].midpoint is not None: # min_midpoint = min(element_rays[i].midpoint, element_rays[j].midpoint) # max_midpoint = max(element_rays[i].midpoint, element_rays[j].midpoint) # vertex_pair = (min_midpoint, max_midpoint) # if vertex_pair in self.pair_map: # next_element_rays.append(self.pair_map[vertex_pair]) # cur_next_rays = [] # if element_rays[i].left_vertex == element_rays[j].left_vertex: # cur_next_rays = [element_rays[i].left, element_rays[j].left, self.pair_map[vertex_pair]] # if element_rays[i].right_vertex == element_rays[j].right_vertex: # cur_next_rays = [element_rays[i].right, element_rays[j].right, self.pair_map[vertex_pair]] # if element_rays[i].left_vertex == element_rays[j].right_vertex: # cur_next_rays = [element_rays[i].left, element_rays[j].right, self.pair_map[vertex_pair]] # if element_rays[i].right_vertex == element_rays[j].left_vertex: # cur_next_rays = [element_rays[i].right, element_rays[j].left, self.pair_map[vertex_pair]] # assert (cur_next_rays != []) # #print cur_next_rays # add_element_to_connectivity(cur_next_rays, new_hanging_vertex_elements, to_add) # else: # return # else: # return # add_element_to_connectivity(next_element_rays, new_hanging_vertex_elements, to_add) # }}} # {{{ Add hanging vertex element def add_hanging_vertex_el(v_index, el): assert not (v_index == 37 and el == 48) new_hanging_vertex_element[v_index].append(el) # }}} # def remove_ray_el(ray, el): # ray.remove(el) # {{{ Check adjacent elements def check_adjacent_elements(groups, new_hanging_vertex_elements, nelements_in_grp): for grp in groups: iel_base = 0 for iel_grp in range(nelements_in_grp): vertex_indices = grp[iel_grp] for i in range(len(vertex_indices)): for j in range(i + 1, len(vertex_indices)): min_index = min(vertex_indices[i], vertex_indices[j]) max_index = max(vertex_indices[i], vertex_indices[j]) cur_node = self.pair_map[(min_index, max_index)] #print iel_base+iel_grp, cur_node.left_vertex, cur_node.right_vertex #if (iel_base + iel_grp) not in cur_node.adjacent_elements: #print min_index, max_index #print iel_base + iel_grp, cur_node.left_vertex, cur_node.right_vertex, cur_node.adjacent_elements #assert (4 in new_hanging_vertex_elements[cur_node.left_vertex] or 4 in new_hanging_vertex_elements[cur_node.right_vertex]) assert ((iel_base + iel_grp) in cur_node.adjacent_elements) assert ((iel_base + iel_grp) in new_hanging_vertex_elements[ cur_node.left_vertex]) assert ((iel_base + iel_grp) in new_hanging_vertex_elements[ cur_node.right_vertex]) # }}} for i in range(len(self.last_mesh.vertices)): for j in range(len(self.last_mesh.vertices[i])): vertices[i, j] = self.last_mesh.vertices[i, j] import copy if i == 0: new_hanging_vertex_element[j] = copy.deepcopy( self.hanging_vertex_element[j]) grpn = 0 for grp in self.last_mesh.groups: for iel_grp in range(grp.nelements): for i in range(len(grp.vertex_indices[iel_grp])): groups[grpn][iel_grp][i] = grp.vertex_indices[iel_grp][i] grpn += 1 grpn = 0 vertices_index = len(self.last_mesh.vertices[0]) nelements_in_grp = grp.nelements del self.group_refinement_records[:] for grp_idx, grp in enumerate(self.last_mesh.groups): iel_base = grp.element_nr_base # List of lists mapping element number to new element number(s). element_mapping = [] tesselation = None # {{{ get midpoint coordinates for vertices midpoints_to_find = [] resampler = None for iel_grp in range(grp.nelements): if refine_flags[iel_base + iel_grp]: # if simplex if len(grp.vertex_indices[iel_grp]) == grp.dim + 1: midpoints_to_find.append(iel_grp) if not resampler: from meshmode.mesh.refinement.resampler import ( SimplexResampler) resampler = SimplexResampler() tesselation = _Tesselation( self.simplex_result[grp.dim], self.simplex_node_tuples[grp.dim]) else: raise NotImplementedError( "unimplemented: midpoint finding" "for non simplex elements") if midpoints_to_find: midpoints = resampler.get_midpoints(grp, tesselation, midpoints_to_find) midpoint_order = resampler.get_vertex_pair_to_midpoint_order( grp.dim) del midpoints_to_find # }}} for iel_grp in range(grp.nelements): element_mapping.append([iel_grp]) if refine_flags[iel_base + iel_grp]: midpoint_vertices = [] vertex_indices = grp.vertex_indices[iel_grp] # if simplex if len(vertex_indices) == grp.dim + 1: for i in range(len(vertex_indices)): for j in range(i + 1, len(vertex_indices)): min_index = min(vertex_indices[i], vertex_indices[j]) max_index = max(vertex_indices[i], vertex_indices[j]) cur_node = self.pair_map[(min_index, max_index)] if cur_node.midpoint is None: cur_node.midpoint = vertices_index import copy cur_node.left = TreeRayNode( min_index, vertices_index, copy.deepcopy( cur_node.adjacent_elements)) cur_node.left.parent = cur_node cur_node.right = TreeRayNode( max_index, vertices_index, copy.deepcopy( cur_node.adjacent_elements)) cur_node.right.parent = cur_node vertex_pair1 = (min_index, vertices_index) vertex_pair2 = (max_index, vertices_index) self.pair_map[vertex_pair1] = cur_node.left self.pair_map[ vertex_pair2] = cur_node.right midpoint_idx = midpoint_order[(i, j)] vertices[:, vertices_index] = \ midpoints[iel_grp][:, midpoint_idx] midpoint_vertices.append(vertices_index) vertices_index += 1 else: cur_midpoint = cur_node.midpoint midpoint_vertices.append(cur_midpoint) #generate new rays cur_dim = len(grp.vertex_indices[0]) - 1 for i in range(len(midpoint_vertices)): for j in range(i + 1, len(midpoint_vertices)): min_index = min(midpoint_vertices[i], midpoint_vertices[j]) max_index = max(midpoint_vertices[i], midpoint_vertices[j]) vertex_pair = (min_index, max_index) if vertex_pair in self.pair_map: continue self.pair_map[vertex_pair] = TreeRayNode( min_index, max_index, []) node_tuple_to_coord = {} for node_index, node_tuple in enumerate( self.index_to_node_tuple[cur_dim]): node_tuple_to_coord[ node_tuple] = grp.vertex_indices[iel_grp][ node_index] for midpoint_index, midpoint_tuple in enumerate( self.index_to_midpoint_tuple[cur_dim]): node_tuple_to_coord[ midpoint_tuple] = midpoint_vertices[ midpoint_index] for i in range(len(self.simplex_result[cur_dim])): if i == 0: iel = iel_grp else: iel = nelements_in_grp + i - 1 element_mapping[-1].append(iel) for j in range(len( self.simplex_result[cur_dim][i])): groups[grpn][iel][j] = \ node_tuple_to_coord[self.simplex_node_tuples[cur_dim][self.simplex_result[cur_dim][i][j]]] nelements_in_grp += len( self.simplex_result[cur_dim]) - 1 #assuming quad otherwise else: #quadrilateral raise NotImplementedError("unimplemented: " "support for quad elements") # node_tuple_to_coord = {} # for node_index, node_tuple in enumerate(self.index_to_node_tuple[cur_dim]): # node_tuple_to_coord[node_tuple] = grp.vertex_indices[iel_grp][node_index] # def generate_all_tuples(cur_list): # if len(cur_list[len(cur_list)-1]) self.group_refinement_records.append( _GroupRefinementRecord(tesselation, element_mapping)) #clear connectivity data for grp in self.last_mesh.groups: iel_base = grp.element_nr_base for iel_grp in range(grp.nelements): for i in range(len(grp.vertex_indices[iel_grp])): for j in range(i + 1, len(grp.vertex_indices[iel_grp])): min_vert = min(grp.vertex_indices[iel_grp][i], grp.vertex_indices[iel_grp][j]) max_vert = max(grp.vertex_indices[iel_grp][i], grp.vertex_indices[iel_grp][j]) vertex_pair = (min_vert, max_vert) root_ray = self.get_root(self.pair_map[vertex_pair]) if root_ray not in self.seen_tuple: self.seen_tuple[root_ray] = True cur_tree = self.get_subtree(root_ray) for node in cur_tree: node.adjacent_elements = [] new_hanging_vertex_element[ node.left_vertex] = [] new_hanging_vertex_element[ node.right_vertex] = [] self.seen_tuple.clear() nelements_in_grp = grp.nelements for grp in groups: for iel_grp in range(len(grp)): add_verts = [] for i in range(len(grp[iel_grp])): add_verts.append(grp[iel_grp][i]) add_element_to_connectivity(add_verts, new_hanging_vertex_element, iel_base + iel_grp) #assert ray connectivity #check_adjacent_elements(groups, new_hanging_vertex_element, nelements_in_grp) self.hanging_vertex_element = new_hanging_vertex_element # {{{ make new groups new_mesh_el_groups = [] for refinement_record, group, prev_group in zip( self.group_refinement_records, groups, self.last_mesh.groups): is_simplex = len( prev_group.vertex_indices[0]) == prev_group.dim + 1 ambient_dim = len(prev_group.nodes) nelements = len(group) nunit_nodes = len(prev_group.unit_nodes[0]) nodes = np.empty((ambient_dim, nelements, nunit_nodes), dtype=prev_group.nodes.dtype) element_mapping = refinement_record.element_mapping to_resample = [ elem for elem in range(len(element_mapping)) if len(element_mapping[elem]) > 1 ] if to_resample: # if simplex if is_simplex: from meshmode.mesh.refinement.resampler import SimplexResampler resampler = SimplexResampler() new_nodes = resampler.get_tesselated_nodes( prev_group, refinement_record.tesselation, to_resample) else: raise NotImplementedError( "unimplemented: node resampling for non simplex elements" ) for elem, mapped_elems in enumerate(element_mapping): if len(mapped_elems) == 1: # No resampling required, just copy over nodes[:, mapped_elems[0]] = prev_group.nodes[:, elem] n = nodes[:, mapped_elems[0]] else: nodes[:, mapped_elems] = new_nodes[elem] if is_simplex: new_mesh_el_groups.append( type(prev_group)(order=prev_group.order, vertex_indices=group, nodes=nodes, unit_nodes=prev_group.unit_nodes)) else: raise NotImplementedError("unimplemented: support for creating" "non simplex element groups") # }}} from meshmode.mesh import Mesh refine_flags = refine_flags.astype(np.bool) self.previous_mesh = self.last_mesh self.last_mesh = Mesh(vertices, new_mesh_el_groups, nodal_adjacency=self.generate_nodal_adjacency( totalnelements, nvertices, groups), vertex_id_dtype=self.last_mesh.vertex_id_dtype, element_id_dtype=self.last_mesh.element_id_dtype, is_conforming=(self.last_mesh.is_conforming and (refine_flags.all() or (~refine_flags).all()))) return self.last_mesh
def refine(self, refine_flags): """ :arg refine_flags: a :class:`numpy.ndarray` of dtype bool of length ``mesh.nelements`` indicating which elements should be split. """ mesh = self._current_mesh refine_flags = np.asarray(refine_flags, dtype=np.bool) if len(refine_flags) != mesh.nelements: raise ValueError("length of refine_flags does not match " "element count of last generated mesh") perform_vertex_updates = mesh.vertices is not None new_el_groups = [] group_refinement_records = [] additional_vertices = [] if perform_vertex_updates: inew_vertex = mesh.nvertices for igrp, group in enumerate(mesh.groups): bisection_info = self._get_bisection_tesselation_info( type(group), group.dim) # {{{ compute counts and index arrays grp_flags = refine_flags[group. element_nr_base:group.element_nr_base + group.nelements] nchildren = len(bisection_info.children) nchild_elements = np.ones(group.nelements, dtype=mesh.element_id_dtype) nchild_elements[grp_flags] = nchildren child_el_indices = np.empty(group.nelements + 1, dtype=mesh.element_id_dtype) child_el_indices[0] = 0 child_el_indices[1:] = np.cumsum(nchild_elements) unrefined_el_new_indices = child_el_indices[:-1][~grp_flags] refining_el_old_indices, = np.where(grp_flags) new_nelements = child_el_indices[-1] # }}} group_refinement_records.append( _GroupRefinementRecord(tesselation=bisection_info, element_mapping=[ list( range( child_el_indices[iel], child_el_indices[iel] + nchild_elements[iel])) for iel in range(group.nelements) ])) # {{{ get new vertices together if perform_vertex_updates: midpoints = bisection_info.resampler.get_midpoints( group, bisection_info, refining_el_old_indices) new_vertex_indices = np.empty( (new_nelements, group.vertex_indices.shape[1]), dtype=mesh.vertex_id_dtype) new_vertex_indices.fill(-17) # copy over unchanged vertices new_vertex_indices[unrefined_el_new_indices] = \ group.vertex_indices[~grp_flags] for old_iel in refining_el_old_indices: new_iel_base = child_el_indices[old_iel] refining_vertices = np.empty(len( bisection_info.ref_vertices), dtype=mesh.vertex_id_dtype) refining_vertices.fill(-17) # carry over old vertices refining_vertices[bisection_info.orig_vertex_indices] = \ group.vertex_indices[old_iel] for imidpoint, (iref_midpoint, (v1, v2)) in enumerate( zip(bisection_info.midpoint_indices, bisection_info.midpoint_vertex_pairs)): global_v1 = group.vertex_indices[old_iel, v1] global_v2 = group.vertex_indices[old_iel, v2] if global_v1 > global_v2: global_v1, global_v2 = global_v2, global_v1 try: global_midpoint = self.global_vertex_pair_to_midpoint[ global_v1, global_v2] except KeyError: global_midpoint = inew_vertex additional_vertices.append( midpoints[old_iel][:, imidpoint]) inew_vertex += 1 refining_vertices[iref_midpoint] = global_midpoint assert (refining_vertices >= 0).all() new_vertex_indices[new_iel_base:new_iel_base+nchildren] = \ refining_vertices[bisection_info.children] assert (new_vertex_indices >= 0).all() else: new_vertex_indices = None # }}} # {{{ get new nodes together new_nodes = np.empty( (mesh.ambient_dim, new_nelements, group.nunit_nodes), dtype=group.nodes.dtype) new_nodes.fill(float("nan")) # copy over unchanged nodes new_nodes[:, unrefined_el_new_indices] = group.nodes[:, ~grp_flags] tesselated_nodes = bisection_info.resampler.get_tesselated_nodes( group, bisection_info, refining_el_old_indices) for old_iel in refining_el_old_indices: new_iel_base = child_el_indices[old_iel] new_nodes[:, new_iel_base:new_iel_base+nchildren, :] = \ tesselated_nodes[old_iel] assert (~np.isnan(new_nodes)).all() # }}} new_el_groups.append( type(group)(order=group.order, vertex_indices=new_vertex_indices, nodes=new_nodes, unit_nodes=group.unit_nodes)) if perform_vertex_updates: new_vertices = np.empty( (mesh.ambient_dim, mesh.nvertices + len(additional_vertices)), mesh.vertices.dtype) new_vertices[:, :mesh.nvertices] = mesh.vertices new_vertices[:, mesh.nvertices:] = np.array(additional_vertices).T else: new_vertices = None from meshmode.mesh import Mesh new_mesh = Mesh(new_vertices, new_el_groups, is_conforming=(mesh.is_conforming and (refine_flags.all() or (~refine_flags).all()))) self.group_refinement_records = group_refinement_records self._current_mesh = new_mesh self._previous_mesh = mesh return new_mesh
def partition_mesh(mesh, part_per_element, part_num): """ :arg mesh: A :class:`~meshmode.mesh.Mesh` to be partitioned. :arg part_per_element: A :class:`numpy.ndarray` containing one integer per element of *mesh* indicating which part of the partitioned mesh the element is to become a part of. :arg part_num: The part number of the mesh to return. :returns: A tuple ``(part_mesh, part_to_global)``, where *part_mesh* is a :class:`~meshmode.mesh.Mesh` that is a partition of mesh, and *part_to_global* is a :class:`numpy.ndarray` mapping element numbers on *part_mesh* to ones in *mesh*. .. versionadded:: 2017.1 """ assert len(part_per_element) == mesh.nelements, ( "part_per_element must have shape (mesh.nelements,)") # Contains the indices of the elements requested. queried_elems = np.where(np.array(part_per_element) == part_num)[0] global_elem_to_part_elem = _compute_global_elem_to_part_elem(part_per_element, {part_num}, mesh.element_id_dtype) # Create new mesh groups that mimick the original mesh's groups but only contain # the local partition's elements part_mesh_groups, global_group_to_part_group, required_vertex_indices =\ _filter_mesh_groups(mesh.groups, queried_elems, mesh.vertex_id_dtype) part_vertices = np.zeros((mesh.ambient_dim, len(required_vertex_indices))) for dim in range(mesh.ambient_dim): part_vertices[dim] = mesh.vertices[dim][required_vertex_indices] part_mesh_group_elem_base = [0 for _ in part_mesh_groups] el_nr = 0 for i_part_grp, grp in enumerate(part_mesh_groups): part_mesh_group_elem_base[i_part_grp] = el_nr el_nr += grp.nelements local_to_local_adj_groups = _create_local_to_local_adjacency_groups(mesh, global_elem_to_part_elem, part_mesh_groups, global_group_to_part_group, part_mesh_group_elem_base) nonlocal_adj_data = _collect_nonlocal_adjacency_data(mesh, np.array(part_per_element), global_elem_to_part_elem, part_mesh_groups, global_group_to_part_group, part_mesh_group_elem_base) bdry_data = _collect_bdry_data(mesh, global_elem_to_part_elem, part_mesh_groups, global_group_to_part_group, part_mesh_group_elem_base) group_neighbor_parts = [adj.neighbor_parts for adj in nonlocal_adj_data if adj is not None] all_neighbor_parts = set(np.unique(np.concatenate(group_neighbor_parts))) if\ group_neighbor_parts else set() boundary_tags = mesh.boundary_tags[:] btag_to_index = {tag: i for i, tag in enumerate(boundary_tags)} def boundary_tag_bit(boundary_tag): from meshmode.mesh import _boundary_tag_bit return _boundary_tag_bit(boundary_tags, btag_to_index, boundary_tag) from meshmode.mesh import BTAG_PARTITION for i_neighbor_part in all_neighbor_parts: part_tag = BTAG_PARTITION(i_neighbor_part) boundary_tags.append(part_tag) btag_to_index[part_tag] = len(boundary_tags)-1 inter_partition_adj_groups = _create_inter_partition_adjacency_groups(mesh, part_per_element, part_mesh_groups, all_neighbor_parts, nonlocal_adj_data, bdry_data, boundary_tag_bit) # Combine local and inter-partition/boundary adjacency groups part_facial_adj_groups = local_to_local_adj_groups for igrp, facial_adj in enumerate(inter_partition_adj_groups): part_facial_adj_groups[igrp][None] = facial_adj from meshmode.mesh import Mesh part_mesh = Mesh( part_vertices, part_mesh_groups, facial_adjacency_groups=part_facial_adj_groups, boundary_tags=boundary_tags, is_conforming=mesh.is_conforming) return part_mesh, queried_elems
def merge_disjoint_meshes(meshes, skip_tests=False, single_group=False): if not meshes: raise ValueError("must pass at least one mesh") from pytools import is_single_valued if not is_single_valued(mesh.ambient_dim for mesh in meshes): raise ValueError("all meshes must share the same ambient dimension") # {{{ assemble combined vertex array ambient_dim = meshes[0].ambient_dim nvertices = sum( mesh.vertices.shape[-1] for mesh in meshes) vert_dtype = np.find_common_type( [mesh.vertices.dtype for mesh in meshes], []) vertices = np.empty( (ambient_dim, nvertices), vert_dtype) current_vert_base = 0 vert_bases = [] for mesh in meshes: mesh_nvert = mesh.vertices.shape[-1] vertices[:, current_vert_base:current_vert_base+mesh_nvert] = \ mesh.vertices vert_bases.append(current_vert_base) current_vert_base += mesh_nvert # }}} # {{{ assemble new groups list nodal_adjacency = None facial_adjacency_groups = None if single_group: grp_cls = None order = None unit_nodes = None nodal_adjacency = None facial_adjacency_groups = None for mesh in meshes: if mesh._nodal_adjacency is not None: nodal_adjacency = False if mesh._facial_adjacency_groups is not None: facial_adjacency_groups = False for group in mesh.groups: if grp_cls is None: grp_cls = type(group) order = group.order unit_nodes = group.unit_nodes else: assert type(group) == grp_cls assert group.order == order assert np.array_equal(unit_nodes, group.unit_nodes) vertex_indices = np.vstack([ group.vertex_indices + vert_base for mesh, vert_base in zip(meshes, vert_bases) for group in mesh.groups]) nodes = np.hstack([ group.nodes for mesh in meshes for group in mesh.groups]) if not nodes.flags.c_contiguous: # hstack stopped producing C-contiguous arrays in numpy 1.14 nodes = nodes.copy(order="C") new_groups = [ grp_cls(order, vertex_indices, nodes, unit_nodes=unit_nodes)] else: new_groups = [] nodal_adjacency = None facial_adjacency_groups = None for mesh, vert_base in zip(meshes, vert_bases): if mesh._nodal_adjacency is not None: nodal_adjacency = False if mesh._facial_adjacency_groups is not None: facial_adjacency_groups = False for group in mesh.groups: new_vertex_indices = group.vertex_indices + vert_base new_group = group.copy(vertex_indices=new_vertex_indices) new_groups.append(new_group) # }}} from meshmode.mesh import Mesh return Mesh(vertices, new_groups, skip_tests=skip_tests, nodal_adjacency=nodal_adjacency, facial_adjacency_groups=facial_adjacency_groups, is_conforming=all( mesh.is_conforming for mesh in meshes))
def get_mesh(self): el_type_hist = {} for el_type in self.element_types: el_type_hist[el_type] = el_type_hist.get(el_type, 0) + 1 if not el_type_hist: raise RuntimeError("empty mesh in gmsh input") groups = self.groups = [] ambient_dim = self.points.shape[-1] mesh_bulk_dim = max(el_type.dimensions for el_type in six.iterkeys(el_type_hist)) # {{{ build vertex numbering # map set of face vertex indices to list of tags associated to face face_vertex_indices_to_tags = {} vertex_gmsh_index_to_mine = {} for element, (el_vertices, el_type) in enumerate( zip(self.element_vertices, self.element_types)): for gmsh_vertex_nr in el_vertices: if gmsh_vertex_nr not in vertex_gmsh_index_to_mine: vertex_gmsh_index_to_mine[gmsh_vertex_nr] = \ len(vertex_gmsh_index_to_mine) if self.tags: el_tag_indexes = [ self.gmsh_tag_index_to_mine[t] for t in self.element_markers[element] ] # record tags of boundary dimension el_tags = [ self.tags[i][0] for i in el_tag_indexes if self.tags[i][1] == mesh_bulk_dim - 1 ] el_grp_verts = { vertex_gmsh_index_to_mine[e] for e in el_vertices } face_vertex_indices = frozenset(el_grp_verts) if face_vertex_indices not in face_vertex_indices_to_tags: face_vertex_indices_to_tags[face_vertex_indices] = [] face_vertex_indices_to_tags[face_vertex_indices] += el_tags # }}} # {{{ build vertex array gmsh_vertex_indices, my_vertex_indices = \ list(zip(*six.iteritems(vertex_gmsh_index_to_mine))) vertices = np.empty((ambient_dim, len(vertex_gmsh_index_to_mine)), dtype=np.float64) vertices[:, np.array(my_vertex_indices, np.intp)] = \ self.points[np.array(gmsh_vertex_indices, np.intp)].T # }}} from meshmode.mesh import (Mesh, SimplexElementGroup, TensorProductElementGroup) bulk_el_types = set() for group_el_type, ngroup_elements in six.iteritems(el_type_hist): if group_el_type.dimensions != mesh_bulk_dim: continue bulk_el_types.add(group_el_type) nodes = np.empty( (ambient_dim, ngroup_elements, el_type.node_count()), np.float64) el_vertex_count = group_el_type.vertex_count() vertex_indices = np.empty((ngroup_elements, el_vertex_count), np.int32) i = 0 for element, (el_vertices, el_nodes, el_type) in enumerate( zip(self.element_vertices, self.element_nodes, self.element_types)): if el_type is not group_el_type: continue nodes[:, i] = self.points[el_nodes].T vertex_indices[i] = [ vertex_gmsh_index_to_mine[v_nr] for v_nr in el_vertices ] i += 1 unit_nodes = (np.array(group_el_type.lexicographic_node_tuples(), dtype=np.float64).T / group_el_type.order) * 2 - 1 if isinstance(group_el_type, GmshSimplexElementBase): group = SimplexElementGroup(group_el_type.order, vertex_indices, nodes, unit_nodes=unit_nodes) if group.dim == 2: from meshmode.mesh.processing import flip_simplex_element_group group = flip_simplex_element_group( vertices, group, np.ones(ngroup_elements, np.bool)) elif isinstance(group_el_type, GmshTensorProductElementBase): gmsh_vertex_tuples = type(group_el_type)( order=1).gmsh_node_tuples() gmsh_vertex_tuples_loc_dict = dict( (gvt, i) for i, gvt in enumerate(gmsh_vertex_tuples)) from pytools import (generate_nonnegative_integer_tuples_below as gnitb) vertex_shuffle = np.array([ gmsh_vertex_tuples_loc_dict[vt] for vt in gnitb(2, group_el_type.dimensions) ]) group = TensorProductElementGroup( group_el_type.order, vertex_indices[:, vertex_shuffle], nodes, unit_nodes=unit_nodes) else: raise NotImplementedError("gmsh element type: %s" % type(group_el_type).__name__) groups.append(group) # FIXME: This is heuristic. if len(bulk_el_types) == 1: is_conforming = True else: is_conforming = mesh_bulk_dim < 3 # construct boundary tags for mesh from meshmode.mesh import BTAG_ALL, BTAG_REALLY_ALL boundary_tags = [BTAG_ALL, BTAG_REALLY_ALL] if self.tags: boundary_tags += [ tag for tag, dim in self.tags if dim == mesh_bulk_dim - 1 ] # compute facial adjacency for Mesh if there is tag information facial_adjacency_groups = None if is_conforming and self.tags: from meshmode.mesh import _compute_facial_adjacency_from_vertices facial_adjacency_groups = _compute_facial_adjacency_from_vertices( groups, boundary_tags, np.int32, np.int8, face_vertex_indices_to_tags) return Mesh(vertices, groups, is_conforming=is_conforming, facial_adjacency_groups=facial_adjacency_groups, boundary_tags=boundary_tags, **self.mesh_construction_kwargs)
def generate_box_mesh(axis_coords, order=1, coord_dtype=np.float64, group_factory=None): """Create a semi-structured mesh. :param axis_coords: a tuple with a number of entries corresponding to the number of dimensions, with each entry a numpy array specifying the coordinates to be used along that axis. :param group_factory: One of :class:`meshmode.mesh.SimplexElementGroup` or :class:`meshmode.mesh.TensorProductElementGroup`. .. versionchanged:: 2017.1 *group_factory* parameter added. """ for iaxis, axc in enumerate(axis_coords): if len(axc) < 2: raise ValueError("need at least two points along axis %d" % (iaxis+1)) dim = len(axis_coords) shape = tuple(len(axc) for axc in axis_coords) from pytools import product nvertices = product(shape) vertex_indices = np.arange(nvertices).reshape(*shape, order="F") vertices = np.empty((dim,)+shape, dtype=coord_dtype) for idim in range(dim): vshape = (shape[idim],) + (1,)*idim vertices[idim] = axis_coords[idim].reshape(*vshape) vertices = vertices.reshape(dim, -1) from meshmode.mesh import SimplexElementGroup, TensorProductElementGroup if group_factory is None: group_factory = SimplexElementGroup if issubclass(group_factory, SimplexElementGroup): is_tp = False elif issubclass(group_factory, TensorProductElementGroup): is_tp = True else: raise ValueError("unsupported value for 'group_factory': %s" % group_factory) el_vertices = [] if dim == 1: for i in range(shape[0]-1): # a--b a = vertex_indices[i] b = vertex_indices[i+1] el_vertices.append((a, b,)) elif dim == 2: for i in range(shape[0]-1): for j in range(shape[1]-1): # c--d # | | # a--b a = vertex_indices[i, j] b = vertex_indices[i+1, j] c = vertex_indices[i, j+1] d = vertex_indices[i+1, j+1] if is_tp: el_vertices.append((a, b, c, d)) else: el_vertices.append((a, b, c)) el_vertices.append((d, c, b)) elif dim == 3: for i in range(shape[0]-1): for j in range(shape[1]-1): for k in range(shape[2]-1): a000 = vertex_indices[i, j, k] a001 = vertex_indices[i, j, k+1] a010 = vertex_indices[i, j+1, k] a011 = vertex_indices[i, j+1, k+1] a100 = vertex_indices[i+1, j, k] a101 = vertex_indices[i+1, j, k+1] a110 = vertex_indices[i+1, j+1, k] a111 = vertex_indices[i+1, j+1, k+1] if is_tp: el_vertices.append( (a000, a001, a010, a011, a100, a101, a110, a111)) else: el_vertices.append((a000, a100, a010, a001)) el_vertices.append((a101, a100, a001, a010)) el_vertices.append((a101, a011, a010, a001)) el_vertices.append((a100, a010, a101, a110)) el_vertices.append((a011, a010, a110, a101)) el_vertices.append((a011, a111, a101, a110)) else: raise NotImplementedError("box meshes of dimension %d" % dim) el_vertices = np.array(el_vertices, dtype=np.int32) grp = make_group_from_vertices( vertices.reshape(dim, -1), el_vertices, order, group_factory=group_factory) from meshmode.mesh import Mesh return Mesh(vertices, [grp], nodal_adjacency=None, facial_adjacency_groups=None)
def partition_mesh(mesh, part_per_element, part_num): """ :arg mesh: A :class:`~meshmode.mesh.Mesh` to be partitioned. :arg part_per_element: A :class:`numpy.ndarray` containing one integer per element of *mesh* indicating which part of the partitioned mesh the element is to become a part of. :arg part_num: The part number of the mesh to return. :returns: A tuple ``(part_mesh, part_to_global)``, where *part_mesh* is a :class:`~meshmode.mesh.Mesh` that is a partition of mesh, and *part_to_global* is a :class:`numpy.ndarray` mapping element numbers on *part_mesh* to ones in *mesh*. .. versionadded:: 2017.1 """ assert len(part_per_element) == mesh.nelements, ( "part_per_element must have shape (mesh.nelements,)") # Contains the indices of the elements requested. queried_elems = np.where(np.array(part_per_element) == part_num)[0] num_groups = len(mesh.groups) new_indices = [] new_nodes = [] # The set of vertex indices we need. # NOTE: There are two methods for producing required_indices. # Optimizations may come from further exploring these options. #index_set = np.array([], dtype=int) index_sets = np.array([], dtype=set) skip_groups = [] num_prev_elems = 0 start_idx = 0 for group_num in range(num_groups): mesh_group = mesh.groups[group_num] # Find the index of first element in the next group. end_idx = len(queried_elems) for idx in range(start_idx, len(queried_elems)): if queried_elems[idx] - num_prev_elems >= mesh_group.nelements: end_idx = idx break if start_idx == end_idx: skip_groups.append(group_num) new_indices.append(np.array([])) new_nodes.append(np.array([])) num_prev_elems += mesh_group.nelements continue elems = queried_elems[start_idx:end_idx] - num_prev_elems new_indices.append(mesh_group.vertex_indices[elems]) new_nodes.append( np.zeros((mesh.ambient_dim, end_idx - start_idx, mesh_group.nunit_nodes))) for i in range(mesh.ambient_dim): for j in range(start_idx, end_idx): elems = queried_elems[j] - num_prev_elems new_idx = j - start_idx new_nodes[group_num][i, new_idx, :] = mesh_group.nodes[i, elems, :] #index_set = np.append(index_set, new_indices[group_num].ravel()) index_sets = np.append(index_sets, set(new_indices[group_num].ravel())) num_prev_elems += mesh_group.nelements start_idx = end_idx # A sorted np.array of vertex indices we need (without duplicates). #required_indices = np.unique(np.sort(index_set)) required_indices = np.array(list(set.union(*index_sets))) new_vertices = np.zeros((mesh.ambient_dim, len(required_indices))) for dim in range(mesh.ambient_dim): new_vertices[dim] = mesh.vertices[dim][required_indices] # Our indices need to be in range [0, len(mesh.nelements)]. for group_num in range(num_groups): if group_num not in skip_groups: for i in range(len(new_indices[group_num])): for j in range(len(new_indices[group_num][0])): original_index = new_indices[group_num][i, j] new_indices[group_num][i, j] = np.where( required_indices == original_index)[0] new_mesh_groups = [] for group_num, mesh_group in enumerate(mesh.groups): if group_num not in skip_groups: new_mesh_groups.append( type(mesh_group)(mesh_group.order, new_indices[group_num], new_nodes[group_num], unit_nodes=mesh_group.unit_nodes)) from meshmode.mesh import BTAG_ALL, BTAG_PARTITION boundary_tags = [BTAG_PARTITION(n) for n in np.unique(part_per_element)] from meshmode.mesh import Mesh part_mesh = Mesh(new_vertices, new_mesh_groups, facial_adjacency_groups=None, boundary_tags=boundary_tags, is_conforming=mesh.is_conforming) adj_data = [[] for _ in range(len(part_mesh.groups))] for igrp, grp in enumerate(part_mesh.groups): elem_base = grp.element_nr_base boundary_adj = part_mesh.facial_adjacency_groups[igrp][None] boundary_elems = boundary_adj.elements boundary_faces = boundary_adj.element_faces p_meshwide_elems = queried_elems[boundary_elems + elem_base] parent_igrps = find_group_indices(mesh.groups, p_meshwide_elems) for adj_idx, elem in enumerate(boundary_elems): face = boundary_faces[adj_idx] tag = -boundary_adj.neighbors[adj_idx] assert tag >= 0, "Expected boundary tag in adjacency group." parent_igrp = parent_igrps[adj_idx] parent_elem_base = mesh.groups[parent_igrp].element_nr_base parent_elem = p_meshwide_elems[adj_idx] - parent_elem_base parent_adj = mesh.facial_adjacency_groups[parent_igrp] for parent_facial_group in parent_adj.values(): indices, = np.nonzero( parent_facial_group.elements == parent_elem) for idx in indices: if (parent_facial_group.neighbors[idx] >= 0 and parent_facial_group.element_faces[idx] == face): rank_neighbor = (parent_facial_group.neighbors[idx] + parent_elem_base) n_face = parent_facial_group.neighbor_faces[idx] n_part_num = part_per_element[rank_neighbor] tag = tag & ~part_mesh.boundary_tag_bit(BTAG_ALL) tag = tag | part_mesh.boundary_tag_bit( BTAG_PARTITION(n_part_num)) boundary_adj.neighbors[adj_idx] = -tag # Find the neighbor element from the other partition. n_meshwide_elem = np.count_nonzero( part_per_element[:rank_neighbor] == n_part_num) adj_data[igrp].append( (elem, face, n_part_num, n_meshwide_elem, n_face)) connected_mesh = part_mesh.copy() from meshmode.mesh import InterPartitionAdjacencyGroup for igrp, adj in enumerate(adj_data): if adj: bdry = connected_mesh.facial_adjacency_groups[igrp][None] # Initialize connections n_parts = np.zeros_like(bdry.elements) n_parts.fill(-1) global_n_elems = np.copy(n_parts) n_faces = np.copy(n_parts) # Sort both sets of elements so that we can quickly merge # the two data structures bdry_perm = np.lexsort([bdry.element_faces, bdry.elements]) elems = bdry.elements[bdry_perm] faces = bdry.element_faces[bdry_perm] neighbors = bdry.neighbors[bdry_perm] adj_elems, adj_faces, adj_n_parts, adj_gl_n_elems, adj_n_faces =\ np.array(adj).T adj_perm = np.lexsort([adj_faces, adj_elems]) adj_elems = adj_elems[adj_perm] adj_faces = adj_faces[adj_perm] adj_n_parts = adj_n_parts[adj_perm] adj_gl_n_elems = adj_gl_n_elems[adj_perm] adj_n_faces = adj_n_faces[adj_perm] # Merge interpartition adjacency data with FacialAdjacencyGroup adj_idx = 0 for bdry_idx in range(len(elems)): if adj_idx >= len(adj_elems): break if (adj_elems[adj_idx] == elems[bdry_idx] and adj_faces[adj_idx] == faces[bdry_idx]): n_parts[bdry_idx] = adj_n_parts[adj_idx] global_n_elems[bdry_idx] = adj_gl_n_elems[adj_idx] n_faces[bdry_idx] = adj_n_faces[adj_idx] adj_idx += 1 connected_mesh.facial_adjacency_groups[igrp][None] =\ InterPartitionAdjacencyGroup(elements=elems, element_faces=faces, neighbors=neighbors, igroup=bdry.igroup, ineighbor_group=None, neighbor_partitions=n_parts, global_neighbors=global_n_elems, neighbor_faces=n_faces) return connected_mesh, queried_elems
def import_firedrake_mesh(fdrake_mesh, cells_to_use=None, normals=None, no_normals_warn=None): """ Create a :class:`meshmode.mesh.Mesh` from a `firedrake.mesh.MeshGeometry` with the same cells/elements, vertices, nodes, mesh order, and facial adjacency. The vertex and node coordinates will be the same, as well as the cell/element ordering. However, :mod:`firedrake` does not require elements to be positively oriented, so any negative elements are flipped as in :func:`meshmode.mesh.processing.flip_simplex_element_group`. The flipped cells/elements are identified by the returned *firedrake_orient* array :arg fdrake_mesh: `firedrake.mesh.MeshGeometry`. This mesh **must** be in a space of ambient dimension 1, 2, or 3 and have co-dimension of 0 or 1. It must use a simplex as a reference element. In the case of a 2-dimensional mesh embedded in 3-space, the method ``fdrake_mesh.init_cell_orientations`` must have been called. In the case of a 1-dimensional mesh embedded in 2-space, see parameters *normals* and *no_normals_warn*. Finally, the ``coordinates`` attribute must have a function space whose *finat_element* associates a degree of freedom with each vertex. In particular, this means that the vertices of the mesh must have well-defined coordinates. For those unfamiliar with :mod:`firedrake`, you can verify this by looking at .. code-block:: python coords_fspace = fdrake_mesh.coordinates.function_space() vertex_entity_dofs = coords_fspace.finat_element.entity_dofs()[0] for entity, dof_list in vertex_entity_dofs.items(): assert len(dof_list) > 0 :arg cells_to_use: *cells_to_use* is primarily intended for use internally by :func:`~meshmode.interop.firedrake.connection.\ build_connection_from_firedrake`. *cells_to_use* must be either 1. *None*, in which case this argument is ignored, or 2. a numpy array of unique firedrake cell indexes. In case (2.), only cells whose index appears in *cells_to_use* are included in the resultant mesh, and their index in *cells_to_use* becomes the element index in the resultant mesh element group. Any faces or vertices which do not touch a cell in *cells_to_use* are also ignored. Note that in this latter case, some faces that are not boundaries in *fdrake_mesh* may become boundaries in the returned mesh. These "induced" boundaries are marked with :class:`~meshmode.mesh.BTAG_INDUCED_BOUNDARY` instead of :class:`~meshmode.mesh.BTAG_ALL`. :arg normals: **Only** used if *fdrake_mesh* is a 1-surface embedded in 2-space. In this case, - If *None* then all elements are assumed to be positively oriented. - Else, should be a list/array whose *i*\\ th entry is the normal for the *i*\\ th element (*i*\\ th in *mesh.coordinate.function_space()*'s *cell_node_list*) :arg no_normals_warn: If *True* (the default), raises a warning if *fdrake_mesh* is a 1-surface embedded in 2-space and *normals* is *None*. :return: A tuple *(meshmode mesh, firedrake_orient)*. ``firedrake_orient < 0`` is *True* for any negatively oriented firedrake cell (which was flipped by meshmode) and False for any positively oriented firedrake cell (which was not flipped by meshmode). """ # Type validation from firedrake.mesh import MeshGeometry if not isinstance(fdrake_mesh, MeshGeometry): raise TypeError("'fdrake_mesh_topology' must be an instance of " "firedrake.mesh.MeshGeometry, " "not '%s'." % type(fdrake_mesh)) if cells_to_use is not None: if not isinstance(cells_to_use, np.ndarray): raise TypeError("'cells_to_use' must be a np.ndarray or " "*None*") assert len(cells_to_use.shape) == 1 assert np.size(np.unique(cells_to_use)) == np.size(cells_to_use), \ ":arg:`cells_to_use` must have unique entries" assert np.all( np.logical_and(cells_to_use >= 0, cells_to_use < fdrake_mesh.num_cells())) assert fdrake_mesh.ufl_cell().is_simplex(), "Mesh must use simplex cells" gdim = fdrake_mesh.geometric_dimension() tdim = fdrake_mesh.topological_dimension() assert gdim in [1, 2, 3], "Mesh must be in space of ambient dim 1, 2, or 3" assert gdim - tdim in [0, 1], "Mesh co-dimension must be 0 or 1" # firedrake meshes are not guaranteed be fully instantiated until # the .init() method is called. In particular, the coordinates function # may not be accessible if we do not call init(). If the mesh has # already been initialized, nothing will change. For more details # on why we need a second initialization, see # this pull request: # https://github.com/firedrakeproject/firedrake/pull/627 # which details how Firedrake implements a mesh's coordinates # as a function on that very same mesh fdrake_mesh.init() # Get all the nodal information we can from the topology bdy_tags = _get_firedrake_boundary_tags(fdrake_mesh, tag_induced_boundary=cells_to_use is not None) with ProcessLogger( logger, "Retrieving vertex indices and computing " "NodalAdjacency from firedrake mesh"): vertex_indices, nodal_adjacency = \ _get_firedrake_nodal_info(fdrake_mesh, cells_to_use=cells_to_use) # If only using some cells, vertices may need new indices as many # will be removed if cells_to_use is not None: vert_ndx_new2old = np.unique(vertex_indices.flatten()) vert_ndx_old2new = dict( zip( vert_ndx_new2old, np.arange(np.size(vert_ndx_new2old), dtype=vertex_indices.dtype))) vertex_indices = \ np.vectorize(vert_ndx_old2new.__getitem__)(vertex_indices) with ProcessLogger( logger, "Building (possibly) unflipped " "SimplexElementGroup from firedrake unit nodes/nodes"): # Grab the mesh reference element and cell dimension coord_finat_elt = fdrake_mesh.coordinates.function_space( ).finat_element cell_dim = fdrake_mesh.cell_dimension() # Get finat unit nodes and map them onto the meshmode reference simplex finat_unit_nodes = get_finat_element_unit_nodes(coord_finat_elt) fd_ref_to_mm = get_affine_reference_simplex_mapping(cell_dim, True) finat_unit_nodes = fd_ref_to_mm(finat_unit_nodes) # Now grab the nodes coords = fdrake_mesh.coordinates cell_node_list = coords.function_space().cell_node_list if cells_to_use is not None: cell_node_list = cell_node_list[cells_to_use] nodes = np.real(coords.dat.data[cell_node_list]) # Add extra dim in 1D for shape (nelements, nunit_nodes, dim) if tdim == 1: nodes = np.reshape(nodes, nodes.shape + (1, )) # Transpose nodes to have shape (dim, nelements, nunit_nodes) nodes = np.transpose(nodes, (2, 0, 1)) # make a group (possibly with some elements that need to be flipped) unflipped_group = SimplexElementGroup(coord_finat_elt.degree, vertex_indices, nodes, dim=cell_dim, unit_nodes=finat_unit_nodes) # Next get the vertices (we'll need these for the orientations) with ProcessLogger(logger, "Obtaining vertex coordinates"): coord_finat = fdrake_mesh.coordinates.function_space().finat_element # unit_vertex_indices are the element-local indices of the nodes # which coincide with the vertices, i.e. for element *i*, # vertex 0's coordinates would be nodes[i][unit_vertex_indices[0]]. # This assumes each vertex has some node which coincides with it... # which is normally fine to assume for firedrake meshes. unit_vertex_indices = [] # iterate through the dofs associated to each vertex on the # reference element for _, dofs in sorted(coord_finat.entity_dofs()[0].items()): assert len(dofs) == 1, \ "The function space of the mesh coordinates must have" \ " exactly one degree of freedom associated with " \ " each vertex in order to determine vertex coordinates" dof, = dofs unit_vertex_indices.append(dof) # Now get the vertex coordinates as *(dim, nvertices)*-shaped array if cells_to_use is not None: nvertices = np.size(vert_ndx_new2old) else: nvertices = fdrake_mesh.num_vertices() vertices = np.ndarray((gdim, nvertices), dtype=nodes.dtype) recorded_verts = set() for icell, cell_vertex_indices in enumerate(vertex_indices): for local_vert_id, global_vert_id in enumerate( cell_vertex_indices): if global_vert_id not in recorded_verts: recorded_verts.add(global_vert_id) local_node_nr = unit_vertex_indices[local_vert_id] vertices[:, global_vert_id] = nodes[:, icell, local_node_nr] # Use the vertices to compute the orientations and flip the group with ProcessLogger(logger, "Computing cell orientations"): orient = _get_firedrake_orientations(fdrake_mesh, unflipped_group, vertices, cells_to_use=cells_to_use, normals=normals, no_normals_warn=no_normals_warn) with ProcessLogger(logger, "Flipping group"): from meshmode.mesh.processing import flip_simplex_element_group group = flip_simplex_element_group(vertices, unflipped_group, orient < 0) # Now, any flipped element had its 0 vertex and 1 vertex exchanged. # This changes the local facet nr, so we need to create and then # fix our facial adjacency groups. To do that, we need to figure # out which local facet numbers switched. face_vertex_indices = group.face_vertex_indices() # face indices of the faces not containing vertex 0 and not # containing vertex 1, respectively no_zero_face_ndx, no_one_face_ndx = None, None for iface, face in enumerate(face_vertex_indices): if 0 not in face: no_zero_face_ndx = iface elif 1 not in face: no_one_face_ndx = iface with ProcessLogger( logger, "Building (possibly) unflipped " "FacialAdjacencyGroups"): unflipped_facial_adjacency_groups = \ _get_firedrake_facial_adjacency_groups(fdrake_mesh, cells_to_use=cells_to_use) # applied below to take elements and element_faces # (or neighbors and neighbor_faces) and flip in any faces that need to # be flipped. def flip_local_face_indices(faces, elements): faces = np.copy(faces) neg_elements = np.full(elements.shape, False) # To handle neighbor case, we only need to flip at elements # who have a neighbor, i.e. where neighbors is not a negative # bitmask of bdy tags neg_elements[elements >= 0] = (orient[elements[elements >= 0]] < 0) no_zero = np.logical_and(neg_elements, faces == no_zero_face_ndx) no_one = np.logical_and(neg_elements, faces == no_one_face_ndx) faces[no_zero], faces[no_one] = no_one_face_ndx, no_zero_face_ndx return faces # Create new facial adjacency groups that have been flipped with ProcessLogger(logger, "Flipping FacialAdjacencyGroups"): facial_adjacency_groups = [] for igroup, fagrps in enumerate(unflipped_facial_adjacency_groups): facial_adjacency_groups.append({}) for ineighbor_group, fagrp in fagrps.items(): new_element_faces = flip_local_face_indices( fagrp.element_faces, fagrp.elements) new_neighbor_faces = flip_local_face_indices( fagrp.neighbor_faces, fagrp.neighbors) new_fagrp = FacialAdjacencyGroup( igroup=igroup, ineighbor_group=ineighbor_group, elements=fagrp.elements, element_faces=new_element_faces, neighbors=fagrp.neighbors, neighbor_faces=new_neighbor_faces) facial_adjacency_groups[igroup][ineighbor_group] = new_fagrp return (Mesh(vertices, [group], boundary_tags=bdy_tags, nodal_adjacency=nodal_adjacency, facial_adjacency_groups=facial_adjacency_groups), orient)
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.INTERIOR_FACES` to indicate interior faces, or :class:`meshmode.discretization.connection.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 = FRESTR_INTERIOR_FACES from warnings import warn warn( "passing *None* for boundary_tag is deprecated--pass " "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 FRESTR_INTERIOR_FACES: 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 FRESTR_ALL_FACES: group_boundary_faces.extend( (iel, iface) for iface in range(grp.mesh_el_group.nfaces) for iel in range(grp.nelements)) else: bdry_grp = fagrp_map.get(None) if bdry_grp is not None: nb_el_bits = -bdry_grp.neighbors face_relevant_flags = (nb_el_bits & btag_bit) != 0 group_boundary_faces.extend( zip(bdry_grp.elements[face_relevant_flags], bdry_grp.element_faces[face_relevant_flags])) # }}} grp_face_vertex_indices = mgrp.face_vertex_indices() grp_vertex_unit_coordinates = mgrp.vertex_unit_coordinates() batch_base = 0 # group by face_id for face_id in range(mgrp.nfaces): batch_boundary_el_numbers_in_grp = np.array([ ibface_el for ibface_el, ibface_face in group_boundary_faces if ibface_face == face_id ], dtype=np.intp) # {{{ Preallocate arrays for mesh group nbatch_elements = len(batch_boundary_el_numbers_in_grp) if per_face_groups or face_id == 0: if per_face_groups: ngroup_bdry_elements = nbatch_elements else: ngroup_bdry_elements = len(group_boundary_faces) vertex_indices = np.empty( (ngroup_bdry_elements, mgrp.dim + 1 - 1), mgrp.vertex_indices.dtype) bdry_unit_nodes = mp.warp_and_blend_nodes( mgrp.dim - 1, mgrp.order) bdry_unit_nodes_01 = (bdry_unit_nodes + 1) * 0.5 vol_basis = mp.simplex_onb(mgrp.dim, mgrp.order) nbdry_unit_nodes = bdry_unit_nodes_01.shape[-1] nodes = np.empty((discr.ambient_dim, ngroup_bdry_elements, nbdry_unit_nodes), dtype=np.float64) # }}} new_el_numbers = batch_base + np.arange(nbatch_elements) if not per_face_groups: batch_base += nbatch_elements # {{{ no per-element axes in these computations # Find boundary vertex indices loc_face_vertices = list(grp_face_vertex_indices[face_id]) # Find unit nodes for boundary element face_vertex_unit_coordinates = \ grp_vertex_unit_coordinates[loc_face_vertices] # Find A, b such that A [e_1 e_2] + b = [r_1 r_2] # (Notation assumes that the volume is 3D and the face is 2D. # Code does not.) b = face_vertex_unit_coordinates[0] A = ( # noqa face_vertex_unit_coordinates[1:] - face_vertex_unit_coordinates[0]).T face_unit_nodes = (np.dot(A, bdry_unit_nodes_01).T + b).T resampling_mat = mp.resampling_matrix(vol_basis, face_unit_nodes, mgrp.unit_nodes) # }}} # {{{ build information for mesh element group # Find vertex_indices glob_face_vertices = mgrp.vertex_indices[ batch_boundary_el_numbers_in_grp][:, loc_face_vertices] vertex_indices[new_el_numbers] = \ vol_to_bdry_vertices[glob_face_vertices] # Find nodes nodes[:, new_el_numbers, :] = np.einsum( "ij,dej->dei", resampling_mat, mgrp.nodes[:, batch_boundary_el_numbers_in_grp, :]) # }}} connection_data[igrp, face_id] = _ConnectionBatchData( group_source_element_indices=batch_boundary_el_numbers_in_grp, group_target_element_indices=new_el_numbers, A=A, b=b, ) is_last_face = face_id + 1 == mgrp.nfaces if per_face_groups or is_last_face: bdry_mesh_group = SimplexElementGroup( mgrp.order, vertex_indices, nodes, unit_nodes=bdry_unit_nodes) bdry_mesh_groups.append(bdry_mesh_group) bdry_mesh = Mesh(bdry_vertices, bdry_mesh_groups) from meshmode.discretization import Discretization bdry_discr = Discretization(discr.cl_context, bdry_mesh, group_factory) with cl.CommandQueue(discr.cl_context) as queue: connection = _build_boundary_connection(queue, discr, bdry_discr, connection_data, per_face_groups) logger.info("building face restriction: done") return connection
def make_face_restriction(actx, discr, group_factory, boundary_tag, per_face_groups=False): """Create a mesh, a discretization and a connection to restrict a function on *discr* to its values on the edges of element faces denoted by *boundary_tag*. :arg boundary_tag: The boundary tag for which to create a face restriction. May be :class:`FACE_RESTR_INTERIOR` to indicate interior faces, or :class:`FACE_RESTR_ALL` to make a discretization consisting of all (interior and boundary) faces. :arg per_face_groups: If *True*, the resulting discretization is guaranteed to have groups organized as:: (grp0, face0), (grp0, face1), ... (grp0, faceN), (grp1, face0), (grp1, face1), ... (grp1, faceN), ... each with the elements in the same order as the originating group. If *False*, volume and boundary groups correspond with each other one-to-one, and an interpolation batch is created per face. :return: a :class:`meshmode.discretization.connection.DirectDiscretizationConnection` representing the new connection. The new boundary discretization can be obtained from the :attr:`meshmode.discretization.connection.DirectDiscretizationConnection.to_discr` attribute of the return value, and the corresponding new boundary mesh from that. """ if boundary_tag is None: boundary_tag = FACE_RESTR_INTERIOR from warnings import warn warn( "passing *None* for boundary_tag is deprecated--pass " "FACE_RESTR_INTERIOR instead", DeprecationWarning, stacklevel=2) logger.info("building face restriction: start") # {{{ gather boundary vertices bdry_vertex_vol_nrs = _get_face_vertices(discr.mesh, boundary_tag) vol_to_bdry_vertices = np.empty(discr.mesh.vertices.shape[-1], discr.mesh.vertices.dtype) vol_to_bdry_vertices.fill(-1) vol_to_bdry_vertices[bdry_vertex_vol_nrs] = np.arange( len(bdry_vertex_vol_nrs), dtype=np.intp) bdry_vertices = discr.mesh.vertices[:, bdry_vertex_vol_nrs] # }}} from meshmode.mesh import Mesh, _ModepyElementGroup bdry_mesh_groups = [] connection_data = {} if boundary_tag not in [FACE_RESTR_ALL, FACE_RESTR_INTERIOR]: btag_bit = discr.mesh.boundary_tag_bit(boundary_tag) for igrp, (grp, fagrp_map) in enumerate( zip(discr.groups, discr.mesh.facial_adjacency_groups)): mgrp = grp.mesh_el_group if not isinstance(mgrp, _ModepyElementGroup): raise NotImplementedError( "can only take boundary of " "meshes based on SimplexElementGroup and " "TensorProductElementGroup") # {{{ pull together per-group face lists group_boundary_faces = [] if boundary_tag is FACE_RESTR_INTERIOR: for fagrp in fagrp_map.values(): if fagrp.ineighbor_group is None: # boundary faces -> not looking for those continue group_boundary_faces.extend( zip(fagrp.elements, fagrp.element_faces)) elif boundary_tag is FACE_RESTR_ALL: group_boundary_faces.extend( (iel, iface) for iface in range(grp.mesh_el_group.nfaces) for iel in range(grp.nelements)) else: bdry_grp = fagrp_map.get(None) if bdry_grp is not None: nb_el_bits = -bdry_grp.neighbors face_relevant_flags = (nb_el_bits & btag_bit) != 0 group_boundary_faces.extend( zip(bdry_grp.elements[face_relevant_flags], bdry_grp.element_faces[face_relevant_flags])) # }}} batch_base = 0 # group by face_index for face in mgrp._modepy_faces: batch_boundary_el_numbers_in_grp = np.array([ ibface_el for ibface_el, ibface_face in group_boundary_faces if ibface_face == face.face_index ], dtype=np.intp) # {{{ preallocate arrays for mesh group nbatch_elements = len(batch_boundary_el_numbers_in_grp) if per_face_groups or face.face_index == 0: if per_face_groups: ngroup_bdry_elements = nbatch_elements else: ngroup_bdry_elements = len(group_boundary_faces) # make up some not-terrible nodes for the boundary Mesh space = mp.space_for_shape(face, mgrp.order) bdry_unit_nodes = mp.edge_clustered_nodes_for_space( space, face) vol_basis = mp.basis_for_space(mgrp._modepy_space, mgrp._modepy_shape).functions vertex_indices = np.empty( (ngroup_bdry_elements, face.nvertices), mgrp.vertex_indices.dtype) nbdry_unit_nodes = bdry_unit_nodes.shape[-1] nodes = np.empty((discr.ambient_dim, ngroup_bdry_elements, nbdry_unit_nodes), dtype=np.float64) # }}} new_el_numbers = batch_base + np.arange(nbatch_elements) if not per_face_groups: batch_base += nbatch_elements # {{{ no per-element axes in these computations face_unit_nodes = face.map_to_volume(bdry_unit_nodes) resampling_mat = mp.resampling_matrix(vol_basis, face_unit_nodes, mgrp.unit_nodes) # }}} # {{{ build information for mesh element group # Find vertex_indices glob_face_vertices = mgrp.vertex_indices[ batch_boundary_el_numbers_in_grp][:, face.volume_vertex_indices] vertex_indices[new_el_numbers] = \ vol_to_bdry_vertices[glob_face_vertices] # Find nodes nodes[:, new_el_numbers, :] = np.einsum( "ij,dej->dei", resampling_mat, mgrp.nodes[:, batch_boundary_el_numbers_in_grp, :]) # }}} connection_data[igrp, face.face_index] = _ConnectionBatchData( group_source_element_indices=batch_boundary_el_numbers_in_grp, group_target_element_indices=new_el_numbers, face=face, ) is_last_face = face.face_index + 1 == mgrp.nfaces if per_face_groups or is_last_face: bdry_mesh_group = type(mgrp)(mgrp.order, vertex_indices, nodes, unit_nodes=bdry_unit_nodes) bdry_mesh_groups.append(bdry_mesh_group) bdry_mesh = Mesh(bdry_vertices, bdry_mesh_groups) bdry_discr = discr.copy(actx=actx, mesh=bdry_mesh, group_factory=group_factory) connection = _build_boundary_connection(actx, discr, bdry_discr, connection_data, per_face_groups) logger.info("building face restriction: done") return connection
def partition_mesh(mesh, part_per_element, part_num): """ :arg mesh: A :class:`meshmode.mesh.Mesh` to be partitioned. :arg part_per_element: A :class:`numpy.ndarray` containing one integer per element of *mesh* indicating which part of the partitioned mesh the element is to become a part of. :arg part_num: The part number of the mesh to return. :returns: A tuple ``(part_mesh, part_to_global)``, where *part_mesh* is a :class:`meshmode.mesh.Mesh` that is a partition of mesh, and *part_to_global* is a :class:`numpy.ndarray` mapping element numbers on *part_mesh* to ones in *mesh*. .. versionadded:: 2017.1 """ assert len(part_per_element) == mesh.nelements, ( "part_per_element must have shape (mesh.nelements,)") # Contains the indices of the elements requested. queried_elems = np.where(np.array(part_per_element) == part_num)[0] num_groups = len(mesh.groups) new_indices = [] new_nodes = [] # The set of vertex indices we need. # NOTE: There are two methods for producing required_indices. # Optimizations may come from further exploring these options. #index_set = np.array([], dtype=int) index_sets = np.array([], dtype=set) skip_groups = [] num_prev_elems = 0 start_idx = 0 for group_num in range(num_groups): mesh_group = mesh.groups[group_num] # Find the index of first element in the next group. end_idx = len(queried_elems) for idx in range(start_idx, len(queried_elems)): if queried_elems[idx] - num_prev_elems >= mesh_group.nelements: end_idx = idx break if start_idx == end_idx: skip_groups.append(group_num) new_indices.append(np.array([])) new_nodes.append(np.array([])) num_prev_elems += mesh_group.nelements continue elems = queried_elems[start_idx:end_idx] - num_prev_elems new_indices.append(mesh_group.vertex_indices[elems]) new_nodes.append( np.zeros( (mesh.ambient_dim, end_idx - start_idx, mesh_group.nunit_nodes))) for i in range(mesh.ambient_dim): for j in range(start_idx, end_idx): elems = queried_elems[j] - num_prev_elems new_idx = j - start_idx new_nodes[group_num][i, new_idx, :] = mesh_group.nodes[i, elems, :] #index_set = np.append(index_set, new_indices[group_num].ravel()) index_sets = np.append(index_sets, set(new_indices[group_num].ravel())) num_prev_elems += mesh_group.nelements start_idx = end_idx # A sorted np.array of vertex indices we need (without duplicates). #required_indices = np.unique(np.sort(index_set)) required_indices = np.array(list(set.union(*index_sets))) new_vertices = np.zeros((mesh.ambient_dim, len(required_indices))) for dim in range(mesh.ambient_dim): new_vertices[dim] = mesh.vertices[dim][required_indices] # Our indices need to be in range [0, len(mesh.nelements)]. for group_num in range(num_groups): if group_num not in skip_groups: for i in range(len(new_indices[group_num])): for j in range(len(new_indices[group_num][0])): original_index = new_indices[group_num][i, j] new_indices[group_num][i, j] = np.where( required_indices == original_index)[0] new_mesh_groups = [] for group_num, mesh_group in enumerate(mesh.groups): if group_num not in skip_groups: new_mesh_groups.append( type(mesh_group)( mesh_group.order, new_indices[group_num], new_nodes[group_num], unit_nodes=mesh_group.unit_nodes)) from meshmode.mesh import BTAG_ALL, BTAG_PARTITION boundary_tags = [BTAG_PARTITION(n) for n in np.unique(part_per_element)] from meshmode.mesh import Mesh part_mesh = Mesh( new_vertices, new_mesh_groups, facial_adjacency_groups=None, boundary_tags=boundary_tags, is_conforming=mesh.is_conforming) adj_data = [[] for _ in range(len(part_mesh.groups))] for igrp, grp in enumerate(part_mesh.groups): elem_base = grp.element_nr_base boundary_adj = part_mesh.facial_adjacency_groups[igrp][None] boundary_elems = boundary_adj.elements boundary_faces = boundary_adj.element_faces p_meshwide_elems = queried_elems[boundary_elems + elem_base] parent_igrps = find_group_indices(mesh.groups, p_meshwide_elems) for adj_idx, elem in enumerate(boundary_elems): face = boundary_faces[adj_idx] tag = -boundary_adj.neighbors[adj_idx] assert tag >= 0, "Expected boundary tag in adjacency group." parent_igrp = parent_igrps[adj_idx] parent_elem_base = mesh.groups[parent_igrp].element_nr_base parent_elem = p_meshwide_elems[adj_idx] - parent_elem_base parent_adj = mesh.facial_adjacency_groups[parent_igrp] for parent_facial_group in parent_adj.values(): indices, = np.nonzero(parent_facial_group.elements == parent_elem) for idx in indices: if (parent_facial_group.neighbors[idx] >= 0 and parent_facial_group.element_faces[idx] == face): rank_neighbor = (parent_facial_group.neighbors[idx] + parent_elem_base) n_face = parent_facial_group.neighbor_faces[idx] n_part_num = part_per_element[rank_neighbor] tag = tag & ~part_mesh.boundary_tag_bit(BTAG_ALL) tag = tag | part_mesh.boundary_tag_bit( BTAG_PARTITION(n_part_num)) boundary_adj.neighbors[adj_idx] = -tag # Find the neighbor element from the other partition. n_meshwide_elem = np.count_nonzero( part_per_element[:rank_neighbor] == n_part_num) adj_data[igrp].append((elem, face, n_part_num, n_meshwide_elem, n_face)) connected_mesh = part_mesh.copy() from meshmode.mesh import InterPartitionAdjacencyGroup for igrp, adj in enumerate(adj_data): if adj: bdry = connected_mesh.facial_adjacency_groups[igrp][None] # Initialize connections n_parts = np.zeros_like(bdry.elements) n_parts.fill(-1) global_n_elems = np.copy(n_parts) n_faces = np.copy(n_parts) # Sort both sets of elements so that we can quickly merge # the two data structures bdry_perm = np.lexsort([bdry.element_faces, bdry.elements]) elems = bdry.elements[bdry_perm] faces = bdry.element_faces[bdry_perm] neighbors = bdry.neighbors[bdry_perm] adj_elems, adj_faces, adj_n_parts, adj_gl_n_elems, adj_n_faces =\ np.array(adj).T adj_perm = np.lexsort([adj_faces, adj_elems]) adj_elems = adj_elems[adj_perm] adj_faces = adj_faces[adj_perm] adj_n_parts = adj_n_parts[adj_perm] adj_gl_n_elems = adj_gl_n_elems[adj_perm] adj_n_faces = adj_n_faces[adj_perm] # Merge interpartition adjacency data with FacialAdjacencyGroup adj_idx = 0 for bdry_idx in range(len(elems)): if adj_idx >= len(adj_elems): break if (adj_elems[adj_idx] == elems[bdry_idx] and adj_faces[adj_idx] == faces[bdry_idx]): n_parts[bdry_idx] = adj_n_parts[adj_idx] global_n_elems[bdry_idx] = adj_gl_n_elems[adj_idx] n_faces[bdry_idx] = adj_n_faces[adj_idx] adj_idx += 1 connected_mesh.facial_adjacency_groups[igrp][None] =\ InterPartitionAdjacencyGroup(elements=elems, element_faces=faces, neighbors=neighbors, igroup=bdry.igroup, ineighbor_group=None, neighbor_partitions=n_parts, global_neighbors=global_n_elems, neighbor_faces=n_faces) return connected_mesh, queried_elems