def flip_simplex_element_group(vertices, grp, grp_flip_flags): from meshmode.mesh import SimplexElementGroup if not isinstance(grp, SimplexElementGroup): raise NotImplementedError( "flips only supported on " "exclusively SimplexElementGroup-based meshes") # Swap the first two vertices on elements to be flipped. new_vertex_indices = grp.vertex_indices.copy() new_vertex_indices[grp_flip_flags, 0] \ = grp.vertex_indices[grp_flip_flags, 1] new_vertex_indices[grp_flip_flags, 1] \ = grp.vertex_indices[grp_flip_flags, 0] # Apply the flip matrix to the nodes. flip_matrix = get_simplex_element_flip_matrix(grp.order, grp.unit_nodes) new_nodes = grp.nodes.copy() new_nodes[:, grp_flip_flags] = np.einsum("ij,dej->dei", flip_matrix, grp.nodes[:, grp_flip_flags]) return SimplexElementGroup(grp.order, new_vertex_indices, new_nodes, unit_nodes=grp.unit_nodes)
def make_curve_mesh(curve_f, element_boundaries, order, unit_nodes=None, node_vertex_consistency_tolerance=None, return_parametrization_points=False): """ :arg curve_f: A callable representing a parametrization for a curve, accepting a vector of point locations and returning an array of shape *(2, npoints)*. :arg element_boundaries: a vector of element boundary locations in :math:`[0,1]`, in order. 0 must be the first entry, 1 the last one. :arg unit_nodes: if given, the unit nodes to use. Must have shape ``(dim, nnoodes)``. :returns: a :class:`meshmode.mesh.Mesh`, or if *return_parametrization_points* is True, a tuple ``(mesh, par_points)``, where *par_points* is an array of parametrization points. """ assert element_boundaries[0] == 0 assert element_boundaries[-1] == 1 nelements = len(element_boundaries) - 1 if unit_nodes is None: unit_nodes = mp.warp_and_blend_nodes(1, order) nodes_01 = 0.5*(unit_nodes+1) vertices = curve_f(element_boundaries) el_lengths = np.diff(element_boundaries) el_starts = element_boundaries[:-1] # (el_nr, node_nr) t = el_starts[:, np.newaxis] + el_lengths[:, np.newaxis]*nodes_01 t = t.ravel() nodes = curve_f(t).reshape(vertices.shape[0], nelements, -1) from meshmode.mesh import Mesh, SimplexElementGroup egroup = SimplexElementGroup( order, vertex_indices=np.vstack([ np.arange(nelements, dtype=np.int32), np.arange(1, nelements+1, dtype=np.int32) % nelements, ]).T, nodes=nodes, unit_nodes=unit_nodes) mesh = Mesh( vertices=vertices, groups=[egroup], nodal_adjacency=None, facial_adjacency_groups=None, node_vertex_consistency_tolerance=node_vertex_consistency_tolerance) if return_parametrization_points: return mesh, t else: return mesh
def get_discr(self, actx) -> meshmode.discretization.Discretization: """Get a discretization with nodes exactly matching the ones used by the nodal-DG code. The returned discretization contains a new :class:`~meshmode.mesh.Mesh` object constructed from the global Octave state. """ # find dim as number of vertices in the simplex - 1 etov_size = self.octave.eval("size(EToV)", verbose=False) dim = int(etov_size[0, 1] - 1) if dim == 1: unit_nodes = self.octave.eval("JacobiGL(0, 0, N)", verbose=False).T else: unit_nodes_arrays = self.octave.eval(f"Nodes{dim}D(N)", nout=dim, verbose=False) equilat_to_biunit_func_name = ("".join(self.AXES[:dim] + ["to"] + self.REF_AXES[:dim])) unit_nodes_arrays = self.octave.feval(equilat_to_biunit_func_name, *unit_nodes_arrays, nout=dim, verbose=False) unit_nodes = np.array([a.reshape(-1) for a in unit_nodes_arrays]) vertices = np.array([ self.octave.pull(f"V{self.AXES[ax].upper()}").reshape(-1) for ax in range(dim) ]) nodes = np.array( [self.octave.pull(self.AXES[ax]).T for ax in range(dim)]) vertex_indices = (self.octave.pull("EToV")).astype(np.int32) - 1 from meshmode.mesh import Mesh, SimplexElementGroup order = int(self.octave.pull("N")) egroup = SimplexElementGroup(order, vertex_indices=vertex_indices, nodes=nodes, unit_nodes=unit_nodes) mesh = Mesh(vertices=vertices, groups=[egroup], is_conforming=True) from meshmode.discretization import Discretization from meshmode.discretization.poly_element import ( PolynomialGivenNodesGroupFactory) return Discretization( actx, mesh, PolynomialGivenNodesGroupFactory(order, unit_nodes))
def flip_simplex_element_group(vertices, grp, grp_flip_flags): from modepy.tools import barycentric_to_unit, unit_to_barycentric from meshmode.mesh import SimplexElementGroup if not isinstance(grp, SimplexElementGroup): raise NotImplementedError( "flips only supported on " "exclusively SimplexElementGroup-based meshes") # Swap the first two vertices on elements to be flipped. new_vertex_indices = grp.vertex_indices.copy() new_vertex_indices[grp_flip_flags, 0] \ = grp.vertex_indices[grp_flip_flags, 1] new_vertex_indices[grp_flip_flags, 1] \ = grp.vertex_indices[grp_flip_flags, 0] # Generate a resampling matrix that corresponds to the # first two barycentric coordinates being swapped. bary_unit_nodes = unit_to_barycentric(grp.unit_nodes) flipped_bary_unit_nodes = bary_unit_nodes.copy() flipped_bary_unit_nodes[0, :] = bary_unit_nodes[1, :] flipped_bary_unit_nodes[1, :] = bary_unit_nodes[0, :] flipped_unit_nodes = barycentric_to_unit(flipped_bary_unit_nodes) flip_matrix = mp.resampling_matrix( mp.simplex_best_available_basis(grp.dim, grp.order), flipped_unit_nodes, grp.unit_nodes) flip_matrix[np.abs(flip_matrix) < 1e-15] = 0 # Flipping twice should be the identity assert la.norm( np.dot(flip_matrix, flip_matrix) - np.eye(len(flip_matrix))) < 1e-13 # Apply the flip matrix to the nodes. new_nodes = grp.nodes.copy() new_nodes[:, grp_flip_flags] = np.einsum("ij,dej->dei", flip_matrix, grp.nodes[:, grp_flip_flags]) return SimplexElementGroup(grp.order, new_vertex_indices, new_nodes, unit_nodes=grp.unit_nodes)
def group(self): if self._group is None: from meshmode.mesh import SimplexElementGroup from meshmode.mesh.processing import flip_simplex_element_group finat_element_a = self.coordinates_a.function_space_a().finat_element_a # IMPORTANT that set :attr:`_group` because # :meth:`orientations` may call :meth:`group` self._group = SimplexElementGroup( finat_element_a.analog().degree, self.vertex_indices(), self.nodes(), dim=self.cell_dimension(), unit_nodes=finat_element_a.unit_nodes()) self._group = flip_simplex_element_group(self.vertices(), self._group, self.orientations() < 0) return self._group
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 make_face_restriction(discr, group_factory, boundary_tag, per_face_groups=False): """Create a mesh, a discretization and a connection to restrict a function on *discr* to its values on the edges of element faces denoted by *boundary_tag*. :arg boundary_tag: The boundary tag for which to create a face restriction. May be :class:`meshmode.discretization.connection.FRESTR_INTERIOR_FACES` to indicate interior faces, or :class:`meshmode.discretization.connection.FRESTR_ALL_FACES` to make a discretization consisting of all (interior and boundary) faces. :arg per_face_groups: If *True*, the resulting discretization is guaranteed to have groups organized as:: (grp0, face0), (grp0, face1), ... (grp0, faceN), (grp1, face0), (grp1, face1), ... (grp1, faceN), ... each with the elements in the same order as the originating group. If *False*, volume and boundary groups correspond with each other one-to-one, and an interpolation batch is created per face. :return: a :class:`meshmode.discretization.connection.DirectDiscretizationConnection` representing the new connection. The new boundary discretization can be obtained from the :attr:`meshmode.discretization.connection.DirectDiscretizationConnection.to_discr` attribute of the return value, and the corresponding new boundary mesh from that. """ if boundary_tag is None: boundary_tag = FACE_RESTR_INTERIOR from warnings import warn warn( "passing *None* for boundary_tag is deprecated--pass " "FRESTR_INTERIOR_FACES instead", DeprecationWarning, stacklevel=2) logger.info("building face restriction: start") # {{{ gather boundary vertices bdry_vertex_vol_nrs = _get_face_vertices(discr.mesh, boundary_tag) vol_to_bdry_vertices = np.empty(discr.mesh.vertices.shape[-1], discr.mesh.vertices.dtype) vol_to_bdry_vertices.fill(-1) vol_to_bdry_vertices[bdry_vertex_vol_nrs] = np.arange( len(bdry_vertex_vol_nrs), dtype=np.intp) bdry_vertices = discr.mesh.vertices[:, bdry_vertex_vol_nrs] # }}} from meshmode.mesh import Mesh, SimplexElementGroup bdry_mesh_groups = [] connection_data = {} btag_bit = discr.mesh.boundary_tag_bit(boundary_tag) for igrp, (grp, fagrp_map) in enumerate( zip(discr.groups, discr.mesh.facial_adjacency_groups)): mgrp = grp.mesh_el_group if not isinstance(mgrp, SimplexElementGroup): raise NotImplementedError("can only take boundary of " "SimplexElementGroup-based meshes") # {{{ pull together per-group face lists group_boundary_faces = [] if boundary_tag is FACE_RESTR_INTERIOR: for fagrp in six.itervalues(fagrp_map): if fagrp.ineighbor_group is None: # boundary faces -> not looking for those continue group_boundary_faces.extend( zip(fagrp.elements, fagrp.element_faces)) elif boundary_tag is FACE_RESTR_ALL: group_boundary_faces.extend( (iel, iface) for iface in range(grp.mesh_el_group.nfaces) for iel in range(grp.nelements)) else: bdry_grp = fagrp_map.get(None) if bdry_grp is not None: nb_el_bits = -bdry_grp.neighbors face_relevant_flags = (nb_el_bits & btag_bit) != 0 group_boundary_faces.extend( zip(bdry_grp.elements[face_relevant_flags], bdry_grp.element_faces[face_relevant_flags])) # }}} grp_face_vertex_indices = mgrp.face_vertex_indices() grp_vertex_unit_coordinates = mgrp.vertex_unit_coordinates() batch_base = 0 # group by face_id for face_id in range(mgrp.nfaces): batch_boundary_el_numbers_in_grp = np.array([ ibface_el for ibface_el, ibface_face in group_boundary_faces if ibface_face == face_id ], dtype=np.intp) # {{{ preallocate arrays for mesh group nbatch_elements = len(batch_boundary_el_numbers_in_grp) if per_face_groups or face_id == 0: if per_face_groups: ngroup_bdry_elements = nbatch_elements else: ngroup_bdry_elements = len(group_boundary_faces) vertex_indices = np.empty( (ngroup_bdry_elements, mgrp.dim + 1 - 1), mgrp.vertex_indices.dtype) bdry_unit_nodes = mp.warp_and_blend_nodes( mgrp.dim - 1, mgrp.order) bdry_unit_nodes_01 = (bdry_unit_nodes + 1) * 0.5 vol_basis = mp.simplex_onb(mgrp.dim, mgrp.order) nbdry_unit_nodes = bdry_unit_nodes_01.shape[-1] nodes = np.empty((discr.ambient_dim, ngroup_bdry_elements, nbdry_unit_nodes), dtype=np.float64) # }}} new_el_numbers = batch_base + np.arange(nbatch_elements) if not per_face_groups: batch_base += nbatch_elements # {{{ no per-element axes in these computations # Find boundary vertex indices loc_face_vertices = list(grp_face_vertex_indices[face_id]) # Find unit nodes for boundary element face_vertex_unit_coordinates = \ grp_vertex_unit_coordinates[loc_face_vertices] # Find A, b such that A [e_1 e_2] + b = [r_1 r_2] # (Notation assumes that the volume is 3D and the face is 2D. # Code does not.) b = face_vertex_unit_coordinates[0] A = ( # noqa face_vertex_unit_coordinates[1:] - face_vertex_unit_coordinates[0]).T face_unit_nodes = (np.dot(A, bdry_unit_nodes_01).T + b).T resampling_mat = mp.resampling_matrix(vol_basis, face_unit_nodes, mgrp.unit_nodes) # }}} # {{{ build information for mesh element group # Find vertex_indices glob_face_vertices = mgrp.vertex_indices[ batch_boundary_el_numbers_in_grp][:, loc_face_vertices] vertex_indices[new_el_numbers] = \ vol_to_bdry_vertices[glob_face_vertices] # Find nodes nodes[:, new_el_numbers, :] = np.einsum( "ij,dej->dei", resampling_mat, mgrp.nodes[:, batch_boundary_el_numbers_in_grp, :]) # }}} connection_data[igrp, face_id] = _ConnectionBatchData( group_source_element_indices=batch_boundary_el_numbers_in_grp, group_target_element_indices=new_el_numbers, A=A, b=b, ) is_last_face = face_id + 1 == mgrp.nfaces if per_face_groups or is_last_face: bdry_mesh_group = SimplexElementGroup( mgrp.order, vertex_indices, nodes, unit_nodes=bdry_unit_nodes) bdry_mesh_groups.append(bdry_mesh_group) bdry_mesh = Mesh(bdry_vertices, bdry_mesh_groups) from meshmode.discretization import Discretization bdry_discr = Discretization(discr.cl_context, bdry_mesh, group_factory) with cl.CommandQueue(discr.cl_context) as queue: connection = _build_boundary_connection(queue, discr, bdry_discr, connection_data, per_face_groups) logger.info("building face restriction: done") return connection
def test_sanity_single_element(ctx_getter, dim, order, visualize=False): pytest.importorskip("pytential") cl_ctx = ctx_getter() queue = cl.CommandQueue(cl_ctx) from modepy.tools import unit_vertices vertices = unit_vertices(dim).T.copy() center = np.empty(dim, np.float64) center.fill(-0.5) import modepy as mp from meshmode.mesh import SimplexElementGroup, Mesh, BTAG_ALL mg = SimplexElementGroup( order=order, vertex_indices=np.arange(dim + 1, dtype=np.int32).reshape(1, -1), nodes=mp.warp_and_blend_nodes(dim, order).reshape(dim, 1, -1), dim=dim) mesh = Mesh(vertices, [mg], nodal_adjacency=None, facial_adjacency_groups=None) from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ PolynomialWarpAndBlendGroupFactory vol_discr = Discretization(cl_ctx, mesh, PolynomialWarpAndBlendGroupFactory(order + 3)) # {{{ volume calculation check vol_x = vol_discr.nodes().with_queue(queue) vol_one = vol_x[0].copy() vol_one.fill(1) from pytential import norm, integral # noqa from pytools import factorial true_vol = 1 / factorial(dim) * 2**dim comp_vol = integral(vol_discr, queue, vol_one) rel_vol_err = abs(true_vol - comp_vol) / true_vol assert rel_vol_err < 1e-12 # }}} # {{{ boundary discretization from meshmode.discretization.connection import make_face_restriction bdry_connection = make_face_restriction( vol_discr, PolynomialWarpAndBlendGroupFactory(order + 3), BTAG_ALL) bdry_discr = bdry_connection.to_discr # }}} # {{{ visualizers from meshmode.discretization.visualization import make_visualizer #vol_vis = make_visualizer(queue, vol_discr, 4) bdry_vis = make_visualizer(queue, bdry_discr, 4) # }}} from pytential import bind, sym bdry_normals = bind(bdry_discr, sym.normal(dim))(queue).as_vector(dtype=object) if visualize: bdry_vis.write_vtk_file("boundary.vtu", [("bdry_normals", bdry_normals)]) from pytential import bind, sym normal_outward_check = bind( bdry_discr, sym.normal(dim) | (sym.nodes(dim) + 0.5 * sym.ones_vec(dim)), )(queue).as_scalar() > 0 assert normal_outward_check.get().all(), normal_outward_check.get()
def 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 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 in enumerate(self.element_vertices): 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(*vertex_gmsh_index_to_mine.items())) 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 el_type_hist.items(): if group_el_type.dimensions != mesh_bulk_dim: continue bulk_el_types.add(group_el_type) nodes = np.empty( (ambient_dim, ngroup_elements, group_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 el_vertices, el_nodes, el_type in 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 import modepy as mp if isinstance(group_el_type, GmshSimplexElementBase): shape = mp.Simplex(group_el_type.dimensions) elif isinstance(group_el_type, GmshTensorProductElementBase): shape = mp.Hypercube(group_el_type.dimensions) else: raise NotImplementedError( f"gmsh element type: {type(group_el_type).__name__}") space = mp.space_for_shape(shape, group_el_type.order) unit_nodes = mp.equispaced_nodes_for_space(space, shape) 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, bool)) elif isinstance(group_el_type, GmshTensorProductElementBase): vertex_shuffle = type(group_el_type)( order=1).get_lexicographic_gmsh_node_indices() group = TensorProductElementGroup( group_el_type.order, vertex_indices[:, vertex_shuffle], nodes, unit_nodes=unit_nodes ) else: # NOTE: already checked above raise AssertionError() 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 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 _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology, cells_to_use=None): """ Return facial_adjacency_groups corresponding to the given firedrake mesh topology. Note that as we do not have geometric information, elements may need to be flipped later. :arg fdrake_mesh_topology: A :mod:`firedrake` instance of class `firedrake.mesh.MeshTopology` or `firedrake.mesh.MeshGeometry`. :arg cells_to_use: If *None*, then this argument is ignored. Otherwise, assumed to be a numpy array of unique firedrake cell ids indicating which cells of the mesh to include, as well as inducing a new cell index for those cells. Also, in this case boundary faces are tagged with :class:`meshmode.mesh.BTAG_INDUCED_BOUNDARY` if they are not a boundary face in *fdrake_mesh_topology* but become a boundary because the opposite cell is not in *cells_to_use*. Boundary faces in *fdrake_mesh_topology* are marked with :class:`BTAG_ALL`. Both are marked with :class:`BTAG_REALLY_ALL`. :return: A list of maps to :class:`FacialAdjacencyGroup`s as required by a :mod:`meshmode` :class:`Mesh`. """ top = fdrake_mesh_topology.topology # We only need one group # for interconnectivity and one for boundary connectivity. # The tricky part is moving from firedrake local facet numbering # (ordered lexicographically by the vertex excluded from the face, # search for "local facet number" in the following paper for # a reference on this... # https://spiral.imperial.ac.uk/bitstream/10044/1/28819/2/mlange-firedrake-dmplex-accepted.pdf # noqa : E501 # ) # and meshmode's facet ordering: obtained from a simplex element # group mm_simp_group = SimplexElementGroup(1, None, None, dim=top.cell_dimension()) mm_face_vertex_indices = mm_simp_group.face_vertex_indices() # map firedrake local face number to meshmode local face number fd_loc_fac_nr_to_mm = {} # Figure out which vertex is excluded to get the corresponding # firedrake local index all_local_facet_nrs = set(range(top.ufl_cell().num_vertices())) for mm_local_facet_nr, face in enumerate(mm_face_vertex_indices): fd_local_facet_nr = all_local_facet_nrs - set(face) assert len(fd_local_facet_nr) == 1 (fd_local_facet_nr,) = fd_local_facet_nr # extract id from set({id}) fd_loc_fac_nr_to_mm[fd_local_facet_nr] = mm_local_facet_nr # build a look-up table from firedrake markers to the appropriate values # in the neighbors array for the external and internal facial adjacency # groups bdy_tags = _get_firedrake_boundary_tags( top, tag_induced_boundary=cells_to_use is not None) boundary_tag_to_index = {bdy_tag: i for i, bdy_tag in enumerate(bdy_tags)} marker_to_neighbor_value = {} from meshmode.mesh import _boundary_tag_bit # None for no marker marker_to_neighbor_value[None] = \ -(_boundary_tag_bit(bdy_tags, boundary_tag_to_index, BTAG_REALLY_ALL) | _boundary_tag_bit(bdy_tags, boundary_tag_to_index, BTAG_ALL)) for marker in top.exterior_facets.unique_markers: marker_to_neighbor_value[marker] = \ -(_boundary_tag_bit(bdy_tags, boundary_tag_to_index, marker) | -marker_to_neighbor_value[None]) # {{{ build the FacialAdjacencyGroup for internal connectivity # Get the firedrake cells associated to each interior facet int_facet_cell = top.interior_facets.facet_cell # Get the firedrake local facet numbers and map them to the # meshmode local facet numbers int_fac_loc_nr = top.interior_facets.local_facet_dat.data int_fac_loc_nr = \ np.array([[fd_loc_fac_nr_to_mm[fac_nr] for fac_nr in fac_nrs] for fac_nrs in int_fac_loc_nr]) # elements neighbors element_faces neighbor_faces are as required # for a :class:`FacialAdjacencyGroup`. int_elements = int_facet_cell.flatten() int_neighbors = np.concatenate((int_facet_cell[:, 1], int_facet_cell[:, 0])) int_element_faces = int_fac_loc_nr.flatten().astype(Mesh.face_id_dtype) int_neighbor_faces = np.concatenate((int_fac_loc_nr[:, 1], int_fac_loc_nr[:, 0])) int_neighbor_faces = int_neighbor_faces.astype(Mesh.face_id_dtype) # If only using some of the cells from pyop2.datatypes import IntType if cells_to_use is not None: to_keep = np.isin(int_elements, cells_to_use) cells_to_use_inv = dict(zip(cells_to_use, np.arange(np.size(cells_to_use), dtype=IntType))) # Keep the cells that we are using and change old cell index # to new cell index int_elements = np.vectorize(cells_to_use_inv.__getitem__)( int_elements[to_keep]) int_element_faces = int_element_faces[to_keep] int_neighbors = int_neighbors[to_keep] int_neighbor_faces = int_neighbor_faces[to_keep] # For neighbor cells, change to new cell index or record # as a new boundary (if the neighbor cell is not being used) newly_created_exterior_facs = [] for ndx, icell in enumerate(int_neighbors): try: int_neighbors[ndx] = cells_to_use_inv[icell] except KeyError: newly_created_exterior_facs.append(ndx) # Make boolean array: 1 if a newly created exterior facet, 0 if # remains an interior facet newly_created_exterior_facs = np.isin(np.arange(np.size(int_elements)), newly_created_exterior_facs) new_ext_elements = int_elements[newly_created_exterior_facs] new_ext_element_faces = int_element_faces[newly_created_exterior_facs] new_ext_neighbor_tag = -(_boundary_tag_bit(bdy_tags, boundary_tag_to_index, BTAG_REALLY_ALL) | _boundary_tag_bit(bdy_tags, boundary_tag_to_index, BTAG_INDUCED_BOUNDARY)) new_ext_neighbors = np.full(new_ext_elements.shape, new_ext_neighbor_tag, dtype=IntType) new_ext_neighbor_faces = np.full(new_ext_elements.shape, 0, dtype=Mesh.face_id_dtype) # Remove any (previously) interior facets that have become exterior # facets remaining_int_facs = np.logical_not(newly_created_exterior_facs) int_elements = int_elements[remaining_int_facs] int_element_faces = int_element_faces[remaining_int_facs] int_neighbors = int_neighbors[remaining_int_facs] int_neighbor_faces = int_neighbor_faces[remaining_int_facs] interconnectivity_grp = FacialAdjacencyGroup(igroup=0, ineighbor_group=0, elements=int_elements, neighbors=int_neighbors, element_faces=int_element_faces, neighbor_faces=int_neighbor_faces) # }}} # {{{ build the FacialAdjacencyGroup for boundary faces # We can get the elements directly from exterior facets ext_elements = top.exterior_facets.facet_cell.flatten() ext_element_faces = np.array([fd_loc_fac_nr_to_mm[fac_nr] for fac_nr in top.exterior_facets.local_facet_dat.data], dtype=Mesh.face_id_dtype) ext_neighbor_faces = np.zeros(ext_element_faces.shape, dtype=Mesh.face_id_dtype) # If only using some of the cells, throw away unused cells and # move to new cell index if cells_to_use is not None: to_keep = np.isin(ext_elements, cells_to_use) ext_elements = np.vectorize(cells_to_use_inv.__getitem__)( ext_elements[to_keep]) ext_element_faces = ext_element_faces[to_keep] ext_neighbor_faces = ext_neighbor_faces[to_keep] # tag the boundary, making sure to record custom tags # (firedrake "markers") if present if top.exterior_facets.markers is not None: ext_neighbors = np.zeros(ext_elements.shape, dtype=IntType) for ifac, marker in enumerate(top.exterior_facets.markers): ext_neighbors[ifac] = marker_to_neighbor_value[marker] else: ext_neighbors = np.full(ext_elements.shape, marker_to_neighbor_value[None], dtype=IntType) # If not using all the cells, some interior facets may have become # exterior facets: if cells_to_use is not None: # Record any newly created exterior facets ext_elements = np.concatenate((ext_elements, new_ext_elements)) ext_element_faces = np.concatenate((ext_element_faces, new_ext_element_faces)) ext_neighbor_faces = np.concatenate((ext_neighbor_faces, new_ext_neighbor_faces)) ext_neighbors = np.concatenate((ext_neighbors, new_ext_neighbors)) exterior_grp = FacialAdjacencyGroup(igroup=0, ineighbor=None, elements=ext_elements, element_faces=ext_element_faces, neighbors=ext_neighbors, neighbor_faces=ext_neighbor_faces) # }}} return [{0: interconnectivity_grp, None: exterior_grp}]
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 vertex_index_gmsh_to_mine = {} for el_vertices, el_type in zip(self.element_vertices, self.element_types): for gmsh_vertex_nr in el_vertices: if gmsh_vertex_nr not in vertex_index_gmsh_to_mine: vertex_index_gmsh_to_mine[gmsh_vertex_nr] = \ len(vertex_index_gmsh_to_mine) # }}} # {{{ build vertex array gmsh_vertex_indices, my_vertex_indices = \ list(zip(*six.iteritems(vertex_index_gmsh_to_mine))) vertices = np.empty((ambient_dim, len(vertex_index_gmsh_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) for group_el_type, ngroup_elements in six.iteritems(el_type_hist): if group_el_type.dimensions != mesh_bulk_dim: continue 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 el_vertices, el_nodes, el_type in 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_index_gmsh_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__) # Gmsh seems to produce elements in the opposite orientation # of what we like. Flip them all. groups.append(group) return Mesh(vertices, groups, nodal_adjacency=None, facial_adjacency_groups=None)
def make_curve_mesh( curve_f: Callable[[np.ndarray], np.ndarray], element_boundaries: np.ndarray, order: int, *, unit_nodes: Optional[np.ndarray] = None, node_vertex_consistency_tolerance: Optional[Union[float, bool]] = None, closed: bool = True, return_parametrization_points: bool = False): """ :param curve_f: parametrization for a curve, accepting a vector of point locations and returning an array of shape ``(2, npoints)``. :param element_boundaries: a vector of element boundary locations in :math:`[0, 1]`, in order. :math:`0` must be the first entry, :math:`1` the last one. :param order: order of the (simplex) elements. If *unit_nodes* is also provided, the orders should match. :param unit_nodes: if given, the unit nodes to use. Must have shape ``(2, nnodes)``. :param node_vertex_consistency_tolerance: passed to the :class:`~meshmode.mesh.Mesh` constructor. If *False*, no checks are performed. :param closed: if *True*, the curve is assumed closed and the first and last of the *element_boundaries* must match. :param return_parametrization_points: if *True*, the parametrization points at which all the nodes in the mesh were evaluated are also returned. :returns: a :class:`~meshmode.mesh.Mesh`, or if *return_parametrization_points* is *True*, a tuple ``(mesh, par_points)``, where *par_points* is an array of parametrization points. """ assert element_boundaries[0] == 0 assert element_boundaries[-1] == 1 nelements = len(element_boundaries) - 1 if unit_nodes is None: unit_nodes = mp.warp_and_blend_nodes(1, order) nodes_01 = 0.5 * (unit_nodes + 1) wrap = nelements if not closed: wrap += 1 vertices = curve_f(element_boundaries)[:, :wrap] vertex_indices = np.vstack([ np.arange(0, nelements, dtype=np.int32), np.arange(1, nelements + 1, dtype=np.int32) % wrap ]).T assert vertices.shape[1] == np.max(vertex_indices) + 1 if closed: start_end_par = np.array([0, 1], dtype=np.float64) start_end_curve = curve_f(start_end_par) assert la.norm(start_end_curve[:, 0] - start_end_curve[:, 1]) < 1.0e-12 el_lengths = np.diff(element_boundaries) el_starts = element_boundaries[:-1] # (el_nr, node_nr) t = el_starts[:, np.newaxis] + el_lengths[:, np.newaxis] * nodes_01 t = t.ravel() nodes = curve_f(t).reshape(vertices.shape[0], nelements, -1) from meshmode.mesh import Mesh, SimplexElementGroup egroup = SimplexElementGroup(order, vertex_indices=vertex_indices, nodes=nodes, unit_nodes=unit_nodes) mesh = Mesh( vertices=vertices, groups=[egroup], node_vertex_consistency_tolerance=node_vertex_consistency_tolerance, is_conforming=True) if return_parametrization_points: return mesh, t else: return mesh