diff --git a/doc/misc.rst b/doc/misc.rst index 488262acbc29882a93c25d4ba137d77ac37e8660..b3fa9ffae0c88eef24b42f1109e463b0ab2021ea 100644 --- a/doc/misc.rst +++ b/doc/misc.rst @@ -3,28 +3,44 @@ Installation ============ -This command should install :mod:`meshmode`:: +This set of instructions is intended for 64-bit Linux and MacOS computers. - pip install meshmode +#. Make sure your system has the basics to build software. -(Note the extra "."!) + On Debian derivatives (Ubuntu and many more), + installing ``build-essential`` should do the trick. -You may need to run this with :command:`sudo`. -If you don't already have `pip <https://pypi.python.org/pypi/pip>`_, -run this beforehand:: + Everywhere else, just making sure you have the ``g++`` package should be + enough. - curl -O https://raw.github.com/pypa/pip/master/contrib/get-pip.py - python get-pip.py +#. Installing `miniforge for Python 3 on your respective system <https://github.com/conda-forge/miniforge>`_. -For a more manual installation, `download the source -<http://pypi.python.org/pypi/meshmode>`_, unpack it, and say:: +#. ``export CONDA=/WHERE/YOU/INSTALLED/miniforge3`` - python setup.py install + If you accepted the default location, this should work: -You may also clone its git repository:: + ``export CONDA=$HOME/miniforge3`` - git clone --recursive git://github.com/inducer/meshmode - git clone --recursive http://git.tiker.net/trees/meshmode.git +#. ``$CONDA/bin/conda create -n dgfem`` + +#. ``source $CONDA/bin/activate dgfem`` + +#. ``conda config --add channels conda-forge`` + +#. ``conda install git pip pocl islpy pyopencl`` + +#. Type the following command:: + + hash -r; for i in pymbolic cgen genpy modepy pyvisfile loopy meshmode; do python -m pip install git+https://github.com/inducer/$i.git; done + +Next time you want to use :mod:`meshmode`, just run the following command:: + + source /WHERE/YOU/INSTALLED/miniforge3/bin/activate dgfem + +You may also like to add this to a startup file (like :file:`$HOME/.bashrc`) or create an alias for it. + +After this, you should be able to run the `tests <https://github.com/inducer/meshmode/tree/master/test>`_ +or `examples <https://github.com/inducer/meshmode/tree/master/examples>`_. User-visible Changes ==================== diff --git a/meshmode/discretization/connection/opposite_face.py b/meshmode/discretization/connection/opposite_face.py index 97570b338f9938165004a46c59fb3ec1570e0cbe..e084525ba9a6eb5001548f432c25a0a8f861173e 100644 --- a/meshmode/discretization/connection/opposite_face.py +++ b/meshmode/discretization/connection/opposite_face.py @@ -342,22 +342,42 @@ def make_opposite_face_connection(volume_to_bdry_conn): # {{{ index wrangling - # Assert that the adjacency group and the restriction - # interpolation batch and the adjacency group have the same - # element ordering. + # The elements in the adjacency group will be a subset of + # the elements in the restriction interpolation batch: + # Imagine an inter-group boundary. The volume-to-boundary + # connection will include all faces as targets, whereas + # there will be separate adjacency groups for intra- and + # inter-group connections. adj_tgt_flags = adj.element_faces == i_face_tgt + adj_els = adj.elements[adj_tgt_flags] + if adj_els.size == 0: + # NOTE: this case can happen for inter-group boundaries + # when all elements are adjacent on the same face + # index, so all other ones will be empty + continue + + vbc_els = vbc_tgt_grp_face_batch.from_element_indices.get(queue) + + if len(adj_els) == len(vbc_els): + # Same length: assert (below) that the two use the same + # ordering. + vbc_used_els = slice(None) + + else: + # Genuine subset: figure out an index mapping. + vbc_els_sort_idx = np.argsort(vbc_els) + vbc_used_els = vbc_els_sort_idx[np.searchsorted( + vbc_els, adj_els, sorter=vbc_els_sort_idx + )] - assert (np.array_equal( - adj.elements[adj_tgt_flags], - vbc_tgt_grp_face_batch.from_element_indices - .get(queue=queue))) + assert np.array_equal(vbc_els[vbc_used_els], adj_els) # find to_element_indices tgt_bdry_element_indices = ( vbc_tgt_grp_face_batch.to_element_indices - .get(queue=queue)) + .get(queue=queue)[vbc_used_els]) # find from_element_indices diff --git a/meshmode/mesh/processing.py b/meshmode/mesh/processing.py index 97b9bbda9c99fd0dc1f537e6cbbbaf043bc6140c..8cb8c13f3b16d4c0d3fbc9862b45ac944e226082 100644 --- a/meshmode/mesh/processing.py +++ b/meshmode/mesh/processing.py @@ -37,6 +37,7 @@ __doc__ = """ .. autofunction:: perform_flips .. autofunction:: find_bounding_box .. autofunction:: merge_disjoint_meshes +.. autofunction:: split_mesh_groups .. autofunction:: map_mesh .. autofunction:: affine_map """ @@ -569,6 +570,64 @@ def merge_disjoint_meshes(meshes, skip_tests=False, single_group=False): # }}} +# {{{ split meshes + +def split_mesh_groups(mesh, element_flags, return_subgroup_mapping=False): + """Split all the groups in *mesh* according to the values of + *element_flags*. The element flags are expected to be integers + defining, for each group, how the elements are to be split into + subgroups. For example, a single-group mesh with flags:: + + element_flags = [0, 0, 0, 42, 42, 42, 0, 0, 0, 41, 41, 41] + + will create three subgroups. The integer flags need not be increasing + or contiguous and can repeat across different groups (i.e. they are + group-local). + + :arg element_flags: a :class:`numpy.ndarray` with + :attr:`~meshmode.mesh.Mesh.nelements` entries + indicating how the elements in a group are to be split. + + :returns: a :class:`~meshmode.mesh.Mesh` where each group has been split + according to flags in *element_flags*. If *return_subgroup_mapping* + is *True*, it also returns a mapping of + ``(group_index, subgroup) -> new_group_index``. + + """ + assert element_flags.shape == (mesh.nelements,) + + new_groups = [] + subgroup_to_group_map = {} + + for igrp, grp in enumerate(mesh.groups): + grp_flags = element_flags[ + grp.element_nr_base:grp.element_nr_base + grp.nelements] + unique_grp_flags = np.unique(grp_flags) + + for flag in unique_grp_flags: + subgroup_to_group_map[igrp, flag] = len(new_groups) + + # NOTE: making copies to maintain contiguity of the arrays + mask = grp_flags == flag + new_groups.append(grp.copy( + vertex_indices=grp.vertex_indices[mask, :].copy(), + nodes=grp.nodes[:, mask, :].copy() + )) + + from meshmode.mesh import Mesh + mesh = Mesh( + vertices=mesh.vertices, + groups=new_groups, + is_conforming=mesh.is_conforming) + + if return_subgroup_mapping: + return mesh, subgroup_to_group_map + else: + return mesh + +# }}} + + # {{{ map def map_mesh(mesh, f): # noqa diff --git a/test/test_meshmode.py b/test/test_meshmode.py index ded91fe297a14c1d2241fb67486fbc0843fa237a..047362d20c1b297e22eed758fbe633bee01a82cc 100644 --- a/test/test_meshmode.py +++ b/test/test_meshmode.py @@ -1348,6 +1348,89 @@ def test_is_affine_group_check(mesh_name): assert all(grp.is_affine for grp in mesh.groups) == is_affine +@pytest.mark.parametrize("ambient_dim", [1, 2, 3]) +def test_mesh_multiple_groups(ctx_factory, ambient_dim, visualize=False): + ctx = ctx_factory() + queue = cl.CommandQueue(ctx) + + order = 4 + + from meshmode.mesh.generation import generate_regular_rect_mesh + mesh = generate_regular_rect_mesh( + a=(-0.5,)*ambient_dim, b=(0.5,)*ambient_dim, + n=(8,)*ambient_dim, order=order) + assert len(mesh.groups) == 1 + + from meshmode.mesh.processing import split_mesh_groups + element_flags = np.any( + mesh.vertices[0, mesh.groups[0].vertex_indices] < 0.0, + axis=1).astype(np.int) + mesh = split_mesh_groups(mesh, element_flags) + + assert len(mesh.groups) == 2 + assert mesh.facial_adjacency_groups + assert mesh.nodal_adjacency + + if visualize and ambient_dim == 2: + from meshmode.mesh.visualization import draw_2d_mesh + draw_2d_mesh(mesh, + draw_vertex_numbers=False, + draw_element_numbers=True, + draw_face_numbers=False, + set_bounding_box=True) + + import matplotlib.pyplot as plt + plt.savefig("test_mesh_multiple_groups_2d_elements.png", dpi=300) + + from meshmode.discretization import Discretization + from meshmode.discretization.poly_element import \ + PolynomialWarpAndBlendGroupFactory as GroupFactory + discr = Discretization(ctx, mesh, GroupFactory(order)) + + if visualize: + group_id = discr.empty(queue, dtype=np.int) + for igrp, grp in enumerate(discr.groups): + group_id_view = grp.view(group_id) + group_id_view.fill(igrp) + + from meshmode.discretization.visualization import make_visualizer + vis = make_visualizer(queue, discr, vis_order=order) + vis.write_vtk_file("test_mesh_multiple_groups.vtu", [ + ("group_id", group_id) + ], overwrite=True) + + # check face restrictions + from meshmode.discretization.connection import ( + make_face_restriction, + make_face_to_all_faces_embedding, + make_opposite_face_connection, + check_connection) + for boundary_tag in [BTAG_ALL, FACE_RESTR_INTERIOR, FACE_RESTR_ALL]: + conn = make_face_restriction(discr, GroupFactory(order), + boundary_tag=boundary_tag, + per_face_groups=False) + check_connection(conn) + + bdry_f = conn.to_discr.empty(queue) + bdry_f.fill(1.0) + + if boundary_tag == FACE_RESTR_INTERIOR: + opposite = make_opposite_face_connection(conn) + check_connection(opposite) + + op_bdry_f = opposite(queue, bdry_f) + error = abs(cl.array.sum(bdry_f - op_bdry_f).get(queue)) + assert error < 1.0e-11, error + + if boundary_tag == FACE_RESTR_ALL: + embedding = make_face_to_all_faces_embedding(conn, conn.to_discr) + check_connection(embedding) + + em_bdry_f = embedding(queue, bdry_f) + error = abs(cl.array.sum(bdry_f - em_bdry_f).get(queue)) + assert error < 1.0e-11, error + + if __name__ == "__main__": import sys if len(sys.argv) > 1: