def connectivity_for_element_group(self, grp): from pytools import ( generate_nonnegative_integer_tuples_summing_to_at_most as gnitstam, generate_nonnegative_integer_tuples_below as gnitb) from meshmode.mesh import TensorProductElementGroup, SimplexElementGroup if isinstance(grp.mesh_el_group, SimplexElementGroup): node_tuples = list(gnitstam(grp.order, grp.dim)) from modepy.tools import simplex_submesh el_connectivity = np.array( simplex_submesh(node_tuples), dtype=np.intp) vtk_cell_type = self.simplex_cell_types[grp.dim] elif isinstance(grp.mesh_el_group, TensorProductElementGroup): node_tuples = list(gnitb(grp.order+1, grp.dim)) node_tuple_to_index = dict( (nt, i) for i, nt in enumerate(node_tuples)) def add_tuple(a, b): return tuple(ai+bi for ai, bi in zip(a, b)) el_offsets = { 1: [(0,), (1,)], 2: [(0, 0), (1, 0), (1, 1), (0, 1)], 3: [ (0, 0, 0), (1, 0, 0), (1, 1, 0), (0, 1, 0), (0, 0, 1), (1, 0, 1), (1, 1, 1), (0, 1, 1), ] }[grp.dim] el_connectivity = np.array([ [ node_tuple_to_index[add_tuple(origin, offset)] for offset in el_offsets] for origin in gnitb(grp.order, grp.dim)]) vtk_cell_type = self.tensor_cell_types[grp.dim] else: raise NotImplementedError("visualization for element groups " "of type '%s'" % type(grp.mesh_el_group).__name__) assert len(node_tuples) == grp.nunit_dofs return el_connectivity, vtk_cell_type
def translate_from(self, src_expansion, src_coeff_exprs, src_rscale, dvec, tgt_rscale): if not isinstance(src_expansion, type(self)): raise RuntimeError("do not know how to translate %s to " "Taylor multipole expansion" % type(src_expansion).__name__) if not self.use_rscale: src_rscale = 1 tgt_rscale = 1 logger.info("building translation operator: %s(%d) -> %s(%d): start" % (type(src_expansion).__name__, src_expansion.order, type(self).__name__, self.order)) from sumpy.tools import mi_factorial src_mi_to_index = dict((mi, i) for i, mi in enumerate( src_expansion.get_coefficient_identifiers())) for i, mi in enumerate(src_expansion.get_coefficient_identifiers()): src_coeff_exprs[i] *= mi_factorial(mi) result = [0] * len(self.get_full_coefficient_identifiers()) from pytools import generate_nonnegative_integer_tuples_below as gnitb for i, tgt_mi in enumerate( self.get_full_coefficient_identifiers()): tgt_mi_plus_one = tuple(mi_i + 1 for mi_i in tgt_mi) for src_mi in gnitb(tgt_mi_plus_one): try: src_index = src_mi_to_index[src_mi] except KeyError: # Omitted coefficients: not life-threatening continue contrib = src_coeff_exprs[src_index] for idim in range(self.dim): n = tgt_mi[idim] k = src_mi[idim] assert n >= k from sympy import binomial contrib *= (binomial(n, k) * sym.UnevaluatedExpr(dvec[idim]/tgt_rscale)**(n-k)) result[i] += ( contrib * sym.UnevaluatedExpr(src_rscale/tgt_rscale)**sum(src_mi)) result[i] /= mi_factorial(tgt_mi) logger.info("building translation operator: done") return ( self.derivative_wrangler.get_stored_mpole_coefficients_from_full( result, tgt_rscale))
def nd_quad_submesh(node_tuples): """Return a list of tuples of indices into the node list that generate a tesselation of the reference element. :arg node_tuples: A list of tuples *(i, j, ...)* of integers indicating node positions inside the unit element. The returned list references indices in this list. :func:`pytools.generate_nonnegative_integer_tuples_below` may be used to generate *node_tuples*. See also :func:`modepy.tools.simplex_submesh`. """ from pytools import single_valued, add_tuples dims = single_valued(len(nt) for nt in node_tuples) node_dict = dict((ituple, idx) for idx, ituple in enumerate(node_tuples)) from pytools import generate_nonnegative_integer_tuples_below as gnitb result = [] for current in node_tuples: try: result.append( tuple(node_dict[add_tuples(current, offset)] for offset in gnitb(2, dims))) except KeyError: pass return result
def nd_quad_submesh(node_tuples): """Return a list of tuples of indices into the node list that generate a tesselation of the reference element. :arg node_tuples: A list of tuples *(i, j, ...)* of integers indicating node positions inside the unit element. The returned list references indices in this list. :func:`pytools.generate_nonnegative_integer_tuples_below` may be used to generate *node_tuples*. See also :func:`modepy.tools.simplex_submesh`. """ from pytools import single_valued, add_tuples dims = single_valued(len(nt) for nt in node_tuples) node_dict = dict( (ituple, idx) for idx, ituple in enumerate(node_tuples)) from pytools import generate_nonnegative_integer_tuples_below as gnitb result = [] for current in node_tuples: try: result.append(tuple( node_dict[add_tuples(current, offset)] for offset in gnitb(2, dims))) except KeyError: pass return result
def lexicographic_node_tuples(self): """Generate tuples enumerating the node indices present in this element. Each tuple has a length equal to the dimension of the element. The tuples constituents are non-negative integers whose sum is less than or equal to the order of the element. """ from pytools import generate_nonnegative_integer_tuples_below as gnitb result = list(gnitb(self.order + 1, self.dimensions)) assert len(result) == self.node_count() return result
def tensor_product_basis(dims, basis_1d): """Adapt any iterable *basis_1d* of 1D basis functions into a *dims*-dimensional tensor product basis. :returns: a tuple of callables representing a *dims*-dimensional basis .. versionadded:: 2017.1 """ from pytools import generate_nonnegative_integer_tuples_below as gnitb return tuple( _TensorProductBasisFunction(order, [basis_1d[i] for i in order]) for order in gnitb(len(basis_1d), dims))
def test_nd_quad_submesh(dims): from meshmode.mesh.tools import nd_quad_submesh from pytools import generate_nonnegative_integer_tuples_below as gnitb node_tuples = list(gnitb(3, dims)) for i, nt in enumerate(node_tuples): print(i, nt) assert len(node_tuples) == 3**dims elements = nd_quad_submesh(node_tuples) for e in elements: print(e) assert len(elements) == 2**dims
def test_hypercube_submesh(dims): from modepy.tools import hypercube_submesh from pytools import generate_nonnegative_integer_tuples_below as gnitb node_tuples = list(gnitb(3, dims)) for i, nt in enumerate(node_tuples): logger.info("[%4d] nodes %s", i, nt) assert len(node_tuples) == 3**dims elements = hypercube_submesh(node_tuples) for e in elements: logger.info("element: %s", e) assert len(elements) == 2**dims
def grad_tensor_product_basis(dims, basis_1d, grad_basis_1d): """Provides derivatives for each of the basis functions generated by :func:`tensor_product_basis`. :returns: a :class:`tuple` of callables, where each one returns a *dims*-dimensional :class:`tuple`, one for each derivative. .. versionadded:: 2020.2 """ from pytools import ( wandering_element, generate_nonnegative_integer_tuples_below as gnitb) func = (basis_1d, grad_basis_1d) return tuple( _TensorProductGradientBasisFunction(order, [ [func[i][k] for i, k in zip(iderivative, order)] for iderivative in wandering_element(dims) ]) for order in gnitb(len(basis_1d), dims))
def _(shape: Hypercube, node_tuples): from pytools import single_valued, add_tuples dims = single_valued(len(nt) for nt in node_tuples) # NOTE: nodes use "first coordinate varies faster" (see node_tuples_for_space) from pytools import generate_nonnegative_integer_tuples_below as gnitb vertex_node_tuples = [nt[::-1] for nt in gnitb(2, dims)] result = [] node_dict = {ituple: idx for idx, ituple in enumerate(node_tuples)} for current in node_tuples: try: result.append(tuple( node_dict[add_tuples(current, offset)] for offset in vertex_node_tuples )) except KeyError: pass return result
def _vis_connectivity(self): """ :return: a list of :class:`_VisConnectivityGroup` instances. """ # Assume that we're using modepy's default node ordering. from pytools import ( generate_nonnegative_integer_tuples_summing_to_at_most as gnitstam, generate_nonnegative_integer_tuples_below as gnitb) from meshmode.mesh import TensorProductElementGroup, SimplexElementGroup result = [] from pyvisfile.vtk import (VTK_LINE, VTK_TRIANGLE, VTK_TETRA, VTK_QUAD, VTK_HEXAHEDRON) subel_nr_base = 0 for group in self.vis_discr.groups: if isinstance(group.mesh_el_group, SimplexElementGroup): node_tuples = list(gnitstam(group.order, group.dim)) from modepy.tools import submesh el_connectivity = np.array(submesh(node_tuples), dtype=np.intp) vtk_cell_type = { 1: VTK_LINE, 2: VTK_TRIANGLE, 3: VTK_TETRA, }[group.dim] elif isinstance(group.mesh_el_group, TensorProductElementGroup): node_tuples = list(gnitb(group.order + 1, group.dim)) node_tuple_to_index = dict( (nt, i) for i, nt in enumerate(node_tuples)) def add_tuple(a, b): return tuple(ai + bi for ai, bi in zip(a, b)) el_offsets = { 1: [(0, ), (1, )], 2: [(0, 0), (1, 0), (1, 1), (0, 1)], 3: [ (0, 0, 0), (1, 0, 0), (1, 1, 0), (0, 1, 0), (0, 0, 1), (1, 0, 1), (1, 1, 1), (0, 1, 1), ] }[group.dim] el_connectivity = np.array([[ node_tuple_to_index[add_tuple(origin, offset)] for offset in el_offsets ] for origin in gnitb(group.order, group.dim)]) vtk_cell_type = { 1: VTK_LINE, 2: VTK_QUAD, 3: VTK_HEXAHEDRON, }[group.dim] else: raise NotImplementedError("visualization for element groups " "of type '%s'" % type(group.mesh_el_group).__name__) assert len(node_tuples) == group.nunit_nodes vis_connectivity = ( group.node_nr_base + np.arange(0, group.nelements * group.nunit_nodes, group.nunit_nodes)[:, np.newaxis, np.newaxis] + el_connectivity).astype(np.intp) vgrp = _VisConnectivityGroup(vis_connectivity=vis_connectivity, vtk_cell_type=vtk_cell_type, subelement_nr_base=subel_nr_base) result.append(vgrp) subel_nr_base += vgrp.nsubelements return result
def make_group_from_vertices(vertices, vertex_indices, order, group_cls=None, unit_nodes=None, group_factory=None): if group_factory is not None: from warnings import warn warn("'group_factory' is deprecated, use 'group_cls' instead", DeprecationWarning, stacklevel=2) if group_cls is not None: raise ValueError("cannot set both 'group_cls' and 'group_factory'") group_cls = group_factory # shape: (ambient_dim, nelements, nvertices) ambient_dim = vertices.shape[0] el_vertices = vertices[:, vertex_indices] from meshmode.mesh import SimplexElementGroup, TensorProductElementGroup if group_cls is None: group_cls = SimplexElementGroup if issubclass(group_cls, SimplexElementGroup): if order < 1: raise ValueError("can't represent simplices with mesh order < 1") el_origins = el_vertices[:, :, 0][:, :, np.newaxis] # ambient_dim, nelements, nspan_vectors spanning_vectors = (el_vertices[:, :, 1:] - el_origins) nspan_vectors = spanning_vectors.shape[-1] dim = nspan_vectors # dim, nunit_nodes if unit_nodes is None: if dim <= 3: unit_nodes = mp.warp_and_blend_nodes(dim, order) else: unit_nodes = mp.equidistant_nodes(dim, order) unit_nodes_01 = 0.5 + 0.5 * unit_nodes nodes = np.einsum("si,des->dei", unit_nodes_01, spanning_vectors) + el_origins elif issubclass(group_cls, TensorProductElementGroup): nelements, nvertices = vertex_indices.shape dim = nvertices.bit_length() - 1 if nvertices != 2**dim: raise ValueError("invalid number of vertices for tensor-product " "elements, must be power of two") if unit_nodes is None: from modepy.quadrature.jacobi_gauss import legendre_gauss_lobatto_nodes unit_nodes = mp.tensor_product_nodes( dim, legendre_gauss_lobatto_nodes(order)) # shape: (dim, nnodes) unit_nodes_01 = 0.5 + 0.5 * unit_nodes _, nnodes = unit_nodes.shape from pytools import generate_nonnegative_integer_tuples_below as gnitb id_tuples = list(gnitb(2, dim)) assert len(id_tuples) == nvertices vdm = np.empty((nvertices, nvertices)) for i, vertex_tuple in enumerate(id_tuples): for j, func_tuple in enumerate(id_tuples): vertex_ref = np.array(vertex_tuple, dtype=np.float64) vdm[i, j] = np.prod(vertex_ref**func_tuple) # shape: (ambient_dim, nelements, nvertices) coeffs = np.empty((ambient_dim, nelements, nvertices)) for d in range(ambient_dim): coeffs[d] = la.solve(vdm, el_vertices[d].T).T vdm_nodes = np.zeros((nnodes, nvertices)) for j, func_tuple in enumerate(id_tuples): vdm_nodes[:, j] = np.prod(unit_nodes_01**np.array(func_tuple).reshape( -1, 1), axis=0) nodes = np.einsum("ij,dej->dei", vdm_nodes, coeffs) else: raise ValueError(f"unsupported value for 'group_cls': {group_cls}") # make contiguous nodes = nodes.copy() return group_cls(order, vertex_indices, nodes, unit_nodes=unit_nodes)
def test_dof_array_arithmetic_same_as_numpy(actx_factory): actx = actx_factory() from meshmode.mesh.generation import generate_regular_rect_mesh mesh = generate_regular_rect_mesh( a=(-0.5,)*2, b=(0.5,)*2, n=(3,)*2, order=1) discr = Discretization(actx, mesh, PolynomialWarpAndBlendGroupFactory(3)) def get_real(ary): return ary.real def get_imag(ary): return ary.real import operator from pytools import generate_nonnegative_integer_tuples_below as gnitb from random import uniform, randrange for op_func, n_args, use_integers in [ (operator.add, 2, False), (operator.sub, 2, False), (operator.mul, 2, False), (operator.truediv, 2, False), (operator.pow, 2, False), # FIXME pyopencl.Array doesn't do mod. #(operator.mod, 2, True), #(operator.mod, 2, False), #(operator.imod, 2, True), #(operator.imod, 2, False), # FIXME: Two outputs #(divmod, 2, False), (operator.iadd, 2, False), (operator.isub, 2, False), (operator.imul, 2, False), (operator.itruediv, 2, False), (operator.and_, 2, True), (operator.xor, 2, True), (operator.or_, 2, True), (operator.iand, 2, True), (operator.ixor, 2, True), (operator.ior, 2, True), (operator.ge, 2, False), (operator.lt, 2, False), (operator.gt, 2, False), (operator.eq, 2, True), (operator.ne, 2, True), (operator.pos, 1, False), (operator.neg, 1, False), (operator.abs, 1, False), (get_real, 1, False), (get_imag, 1, False), ]: for is_array_flags in gnitb(2, n_args): if sum(is_array_flags) == 0: # all scalars, no need to test continue if is_array_flags[0] == 0 and op_func in [ operator.iadd, operator.isub, operator.imul, operator.itruediv, operator.iand, operator.ixor, operator.ior, ]: # can't do in place operations with a scalar lhs continue args = [ (0.5+np.random.rand(discr.ndofs) if not use_integers else np.random.randint(3, 200, discr.ndofs)) if is_array_flag else (uniform(0.5, 2) if not use_integers else randrange(3, 200)) for is_array_flag in is_array_flags] # {{{ get reference numpy result # make a copy for the in place operators ref_args = [ arg.copy() if isinstance(arg, np.ndarray) else arg for arg in args] ref_result = op_func(*ref_args) # }}} # {{{ test DOFArrays actx_args = [ unflatten(actx, discr, actx.from_numpy(arg)) if isinstance(arg, np.ndarray) else arg for arg in args] actx_result = actx.to_numpy(flatten(op_func(*actx_args))) assert np.allclose(actx_result, ref_result) # }}} # {{{ test object arrays of DOFArrays # It would be very nice if comparisons on object arrays behaved # consistently with everything else. Alas, they do not. Instead: # # 0.5 < obj_array(DOFArray) -> obj_array([True]) # # because hey, 0.5 < DOFArray returned something truthy. if op_func not in [ operator.eq, operator.ne, operator.le, operator.lt, operator.ge, operator.gt, operator.iadd, operator.isub, operator.imul, operator.itruediv, operator.iand, operator.ixor, operator.ior, # All Python objects are real-valued, right? get_imag, ]: obj_array_args = [ make_obj_array([arg]) if isinstance(arg, DOFArray) else arg for arg in actx_args] obj_array_result = actx.to_numpy( flatten(op_func(*obj_array_args)[0])) assert np.allclose(obj_array_result, ref_result)
def _(space: QN): from pytools import \ generate_nonnegative_integer_tuples_below as gnitb return tuple( [tp[::-1] for tp in gnitb(space.order + 1, space.spatial_dim)])
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] boundary_tags = tuple(boundary_tags) # 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 make_group_from_vertices(vertices, vertex_indices, order, group_factory=None): # shape: (dim, nelements, nvertices) el_vertices = vertices[:, vertex_indices] from meshmode.mesh import SimplexElementGroup, TensorProductElementGroup if group_factory is None: group_factory = SimplexElementGroup if issubclass(group_factory, SimplexElementGroup): el_origins = el_vertices[:, :, 0][:, :, np.newaxis] # ambient_dim, nelements, nspan_vectors spanning_vectors = ( el_vertices[:, :, 1:] - el_origins) nspan_vectors = spanning_vectors.shape[-1] dim = nspan_vectors # dim, nunit_nodes if dim <= 3: unit_nodes = mp.warp_and_blend_nodes(dim, order) else: unit_nodes = mp.equidistant_nodes(dim, order) unit_nodes_01 = 0.5 + 0.5*unit_nodes nodes = np.einsum( "si,des->dei", unit_nodes_01, spanning_vectors) + el_origins elif issubclass(group_factory, TensorProductElementGroup): nelements, nvertices = vertex_indices.shape dim = 0 while True: if nvertices == 2**dim: break if nvertices < 2**dim: raise ValueError("invalid number of vertices for tensor-product " "elements, must be power of two") dim += 1 from modepy.quadrature.jacobi_gauss import legendre_gauss_lobatto_nodes from modepy.nodes import tensor_product_nodes unit_nodes = tensor_product_nodes(dim, legendre_gauss_lobatto_nodes(order)) # shape: (dim, nnodes) unit_nodes_01 = 0.5 + 0.5*unit_nodes _, nnodes = unit_nodes.shape from pytools import generate_nonnegative_integer_tuples_below as gnitb id_tuples = list(gnitb(2, dim)) assert len(id_tuples) == nvertices vdm = np.empty((nvertices, nvertices)) for i, vertex_tuple in enumerate(id_tuples): for j, func_tuple in enumerate(id_tuples): vertex_ref = np.array(vertex_tuple, dtype=np.float64) vdm[i, j] = np.prod(vertex_ref**func_tuple) # shape: (dim, nelements, nvertices) coeffs = np.empty((dim, nelements, nvertices)) for d in range(dim): coeffs[d] = la.solve(vdm, el_vertices[d].T).T vdm_nodes = np.zeros((nnodes, nvertices)) for j, func_tuple in enumerate(id_tuples): vdm_nodes[:, j] = np.prod( unit_nodes_01 ** np.array(func_tuple).reshape(-1, 1), axis=0) nodes = np.einsum("ij,dej->dei", vdm_nodes, coeffs) else: raise ValueError("unsupported value for 'group_factory': %s" % group_factory) # make contiguous nodes = nodes.copy() return group_factory( order, vertex_indices, nodes, unit_nodes=unit_nodes)
def _vis_connectivity(self): """ :return: a list of :class:`_VisConnectivityGroup` instances. """ # Assume that we're using modepy's default node ordering. from pytools import ( generate_nonnegative_integer_tuples_summing_to_at_most as gnitstam, generate_nonnegative_integer_tuples_below as gnitb) from meshmode.mesh import TensorProductElementGroup, SimplexElementGroup result = [] from pyvisfile.vtk import ( VTK_LINE, VTK_TRIANGLE, VTK_TETRA, VTK_QUAD, VTK_HEXAHEDRON) subel_nr_base = 0 for group in self.vis_discr.groups: if isinstance(group.mesh_el_group, SimplexElementGroup): node_tuples = list(gnitstam(group.order, group.dim)) from modepy.tools import submesh el_connectivity = np.array( submesh(node_tuples), dtype=np.intp) vtk_cell_type = { 1: VTK_LINE, 2: VTK_TRIANGLE, 3: VTK_TETRA, }[group.dim] elif isinstance(group.mesh_el_group, TensorProductElementGroup): node_tuples = list(gnitb(group.order+1, group.dim)) node_tuple_to_index = dict( (nt, i) for i, nt in enumerate(node_tuples)) def add_tuple(a, b): return tuple(ai+bi for ai, bi in zip(a, b)) el_offsets = { 1: [(0,), (1,)], 2: [(0, 0), (1, 0), (1, 1), (0, 1)], 3: [ (0, 0, 0), (1, 0, 0), (1, 1, 0), (0, 1, 0), (0, 0, 1), (1, 0, 1), (1, 1, 1), (0, 1, 1), ] }[group.dim] el_connectivity = np.array([ [ node_tuple_to_index[add_tuple(origin, offset)] for offset in el_offsets] for origin in gnitb(group.order, group.dim)]) vtk_cell_type = { 1: VTK_LINE, 2: VTK_QUAD, 3: VTK_HEXAHEDRON, }[group.dim] else: raise NotImplementedError("visualization for element groups " "of type '%s'" % type(group.mesh_el_group).__name__) assert len(node_tuples) == group.nunit_nodes vis_connectivity = ( group.node_nr_base + np.arange( 0, group.nelements*group.nunit_nodes, group.nunit_nodes )[:, np.newaxis, np.newaxis] + el_connectivity).astype(np.intp) vgrp = _VisConnectivityGroup( vis_connectivity=vis_connectivity, vtk_cell_type=vtk_cell_type, subelement_nr_base=subel_nr_base) result.append(vgrp) subel_nr_base += vgrp.nsubelements return result
def translate_from(self, src_expansion, src_coeff_exprs, src_rscale, dvec, tgt_rscale, sac=None, _fast_version=True): if not isinstance(src_expansion, type(self)): raise RuntimeError("do not know how to translate %s to " "Taylor multipole expansion" % type(src_expansion).__name__) if not self.use_rscale: src_rscale = 1 tgt_rscale = 1 logger.info("building translation operator: %s(%d) -> %s(%d): start" % (type(src_expansion).__name__, src_expansion.order, type(self).__name__, self.order)) from sumpy.tools import mi_factorial src_mi_to_index = {mi: i for i, mi in enumerate( src_expansion.get_coefficient_identifiers())} tgt_mi_to_index = {mi: i for i, mi in enumerate( self.get_full_coefficient_identifiers())} # This algorithm uses the observation that M2M coefficients # have the following form in 2D # # $T_{m, n} = \sum_{i\le m, j\le n} C_{i, j} # d_x^i d_y^j \binom{m}{i} \binom{n}{j}$ # and can be rewritten as follows. # # Let $Y_{m, n} = \sum_{i\le m} C_{i, n} d_x^i \binom{m}{i}$. # # Then, $T_{m, n} = \sum_{j\le n} Y_{m, j} d_y^j \binom{n}{j}$. # # $Y_{m, n}$ are $p^2$ temporary variables that are # reused for different M2M coefficients and costs $p$ per variable. # Total cost for calculating $Y_{m, n}$ is $p^3$ and similar # for $T_{m, n}$. For compressed Taylor series this can be done # more efficiently. # Let's take the example u_xy + u_x + u_y = 0. # In the diagram below, C depicts a non zero source coefficient. # We divide these into two hyperplanes. # # C C 0 # C 0 C 0 0 0 # C 0 0 = C 0 0 + 0 0 0 # C 0 0 0 C 0 0 0 0 0 0 0 # C C C C C C 0 0 0 0 0 C C C C # # The calculations done when naively translating first hyperplane of the # source coefficients (C) to target coefficients (T) are shown # below in the graph. Each connection represents a O(1) calculation, # and the arrows go "up and to the right". # # ┌─→C T # │ ↑ # │┌→C→0←─────┐-> T T # ││ ↑ ↑ │ # ││ ┌─┘┌────┐│ # ││↱C→0↲0←─┐││ T T T # │││└───⬏ │││ # └└└C→0 0 0│││ T T T T # └───⬏ ↑│││ # └─────┘│││ # └──────┘││ # └───────┘│ # └────────┘ # # By using temporaries (Y), this can be reduced as shown below. # # ┌→C Y T # │ ↑ # │↱C 0 -> Y→0 -> T T # ││↑ # ││C 0 0 Y→0 0 T T T # ││↑ └───⬏ # └└C 0 0 0 Y 0 0 0 T T T T # └───⬏ ↑ # └─────┘ # # Note that in the above calculation data is propagated upwards # in the first pass and then rightwards in the second pass. # Data propagation with zeros are not shown as they are not calculated. # If the propagation was done rightwards first and upwards second # number of calculations are higher as shown below. # # C ┌→Y T # │ ↑ # C→0 -> │↱Y↱Y -> T T # ││↑│↑ # C→0 0 ││Y│Y Y T T T # └───⬏ ││↑│↑ ↑ # C→0 0 0 └└Y└Y Y Y T T T T # └───⬏ ↑ # └─────┘ # # For the second hyperplane, data is propogated rightwards first # and then upwards second which is opposite to that of the first # hyperplane. # # 0 0 0 # # 0 0 -> 0↱0 -> 0 T # │↑ # 0 0 0 0│0 0 0 T T # │↑ ↑ # 0 C→C→C 0└Y Y Y 0 T T T # └───⬏ # # In other words, we're better off computing the translation # one dimension at a time. If the coefficient-identifying multi-indices # in the source expansion have the form (0, m) and (n, 0), where m>=0, n>=1, # then we calculate the output from (0, m) with the second # dimension as the fastest varying dimension and then calculate # the output from (n, 0) with the first dimension as the fastest # varying dimension. tgt_hyperplanes = \ self.expansion_terms_wrangler._split_coeffs_into_hyperplanes() result = [0] * len(self.get_full_coefficient_identifiers()) # axis morally iterates over 'hyperplane directions' for axis in range(self.dim): # {{{ index gymnastics # First, let's write source coefficients in target coefficient # indices. If target order is lower than source order, then # we will discard higher order terms from source coefficients. cur_dim_input_coeffs = \ [0] * len(self.get_full_coefficient_identifiers()) for d, mis in tgt_hyperplanes: # Only consider hyperplanes perpendicular to *axis*. if d != axis: continue for mi in mis: # When target order is higher than source order, we assume # that the higher order source coefficients were zero. if mi not in src_mi_to_index: continue src_idx = src_mi_to_index[mi] tgt_idx = tgt_mi_to_index[mi] cur_dim_input_coeffs[tgt_idx] = src_coeff_exprs[src_idx] * \ sym.UnevaluatedExpr(src_rscale/tgt_rscale)**sum(mi) if all(coeff == 0 for coeff in cur_dim_input_coeffs): continue # }}} # {{{ translation # As explained above using the unicode art, we use the orthogonal axis # as the last dimension to vary to reduce the number of operations. dims = list(range(axis)) + \ list(range(axis+1, self.dim)) + [axis] # d is the axis along which we translate. for d in dims: # We build the full target multipole and then compress it # at the very end. cur_dim_output_coeffs = \ [0] * len(self.get_full_coefficient_identifiers()) for i, tgt_mi in enumerate( self.get_full_coefficient_identifiers()): # Calling this input_mis instead of src_mis because we # converted the source coefficients to target coefficient # indices beforehand. for mi_i in range(tgt_mi[d]+1): input_mi = mi_set_axis(tgt_mi, d, mi_i) contrib = cur_dim_input_coeffs[tgt_mi_to_index[input_mi]] for n, k, dist in zip(tgt_mi, input_mi, dvec): assert n >= k contrib /= factorial(n-k) contrib *= \ sym.UnevaluatedExpr(dist/tgt_rscale)**(n-k) cur_dim_output_coeffs[i] += contrib # cur_dim_output_coeffs is the input in the next iteration cur_dim_input_coeffs = cur_dim_output_coeffs # }}} for i in range(len(cur_dim_output_coeffs)): result[i] += cur_dim_output_coeffs[i] # {{{ simpler, functionally equivalent code if not _fast_version: src_mi_to_index = dict((mi, i) for i, mi in enumerate( src_expansion.get_coefficient_identifiers())) result = [0] * len(self.get_full_coefficient_identifiers()) for i, mi in enumerate(src_expansion.get_coefficient_identifiers()): src_coeff_exprs[i] *= mi_factorial(mi) from pytools import generate_nonnegative_integer_tuples_below as gnitb for i, tgt_mi in enumerate( self.get_full_coefficient_identifiers()): tgt_mi_plus_one = tuple(mi_i + 1 for mi_i in tgt_mi) for src_mi in gnitb(tgt_mi_plus_one): try: src_index = src_mi_to_index[src_mi] except KeyError: # Omitted coefficients: not life-threatening continue contrib = src_coeff_exprs[src_index] for idim in range(self.dim): n = tgt_mi[idim] k = src_mi[idim] assert n >= k from sympy import binomial contrib *= (binomial(n, k) * sym.UnevaluatedExpr(dvec[idim]/tgt_rscale)**(n-k)) result[i] += (contrib * sym.UnevaluatedExpr(src_rscale/tgt_rscale)**sum(src_mi)) result[i] /= mi_factorial(tgt_mi) # }}} logger.info("building translation operator: done") return ( self.expansion_terms_wrangler.get_stored_mpole_coefficients_from_full( result, tgt_rscale, sac=sac))
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)