From ffe42be74e789991a2573221ea4745f9d3474b51 Mon Sep 17 00:00:00 2001
From: Andreas Kloeckner <inform@tiker.net>
Date: Tue, 8 Jul 2014 08:31:09 -0500
Subject: [PATCH] Mesh connectivity representation, ico{sahedron,sphere}
 generation

---
 examples/plot-connectivity.py            |  41 ++++++++
 meshmode/discretization/visualization.py |  61 ++++++++++++
 meshmode/mesh/__init__.py                | 121 ++++++++++++++++++++++-
 meshmode/mesh/generation.py              |  92 +++++++++--------
 meshmode/mesh/io.py                      |   2 +-
 5 files changed, 270 insertions(+), 47 deletions(-)
 create mode 100644 examples/plot-connectivity.py

diff --git a/examples/plot-connectivity.py b/examples/plot-connectivity.py
new file mode 100644
index 0000000..a888636
--- /dev/null
+++ b/examples/plot-connectivity.py
@@ -0,0 +1,41 @@
+from __future__ import division
+
+import numpy as np  # noqa
+import pyopencl as cl
+
+
+order = 3
+
+
+def main():
+    cl_ctx = cl.create_some_context()
+    queue = cl.CommandQueue(cl_ctx)
+
+    from meshmode.mesh.generation import (  # noqa
+            generate_icosphere, generate_icosahedron)
+    #mesh = generate_icosphere(1, order=order)
+    mesh = generate_icosahedron(1, order=order)
+
+    from meshmode.discretization import Discretization
+    from meshmode.discretization.poly_element import \
+            PolynomialWarpAndBlendGroupFactory
+
+    discr = Discretization(
+            cl_ctx, mesh, PolynomialWarpAndBlendGroupFactory(order))
+
+    from meshmode.discretization.visualization import make_visualizer
+    vis = make_visualizer(queue, discr, order)
+
+    vis.write_vtk_file("geometry.vtu", [
+        ("f", discr.nodes()[0]),
+        ])
+
+    from meshmode.discretization.visualization import \
+            write_mesh_connectivity_vtk_file
+
+    write_mesh_connectivity_vtk_file("connectivity.vtu",
+            mesh)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/meshmode/discretization/visualization.py b/meshmode/discretization/visualization.py
index 815df95..a2b251e 100644
--- a/meshmode/discretization/visualization.py
+++ b/meshmode/discretization/visualization.py
@@ -31,9 +31,13 @@ __doc__ = """
 .. autofunction:: make_visualizer
 
 .. autoclass:: Visualizer
+
+.. autofunction:: write_mesh_connectivity_vtk_file
 """
 
 
+# {{{ visualizer
+
 def separate_by_real_and_imag(data, real_only):
     for name, field in data:
         from pytools.obj_array import log_shape
@@ -66,6 +70,11 @@ def separate_by_real_and_imag(data, real_only):
 
 
 class Visualizer(object):
+    """
+    .. automethod:: show_scalar_in_mayavi
+    .. automethod:: write_vtk_file
+    """
+
     def __init__(self, discr, vis_discr, connection):
         self.discr = discr
         self.vis_discr = vis_discr
@@ -233,4 +242,56 @@ def make_visualizer(queue, discr, vis_order):
 
     return Visualizer(discr, vis_discr, cnx)
 
+# }}}
+
+
+# {{{ connectivity
+
+def write_mesh_connectivity_vtk_file(file_name, mesh,  compressor=None,):
+    from pyvisfile.vtk import (
+            UnstructuredGrid, DataArray,
+            AppendedDataXMLGenerator,
+            VTK_LINE,
+            VF_LIST_OF_COMPONENTS)
+
+    centroids = np.empty(
+            (mesh.ambient_dim, mesh.nelements),
+            dtype=mesh.vertices.dtype)
+
+    for grp in mesh.groups:
+        iel_base = grp.element_nr_base
+        centroids[:, iel_base:iel_base+grp.nelements] = (
+                np.sum(mesh.vertices[:, grp.vertex_indices], axis=-1)
+                / grp.vertex_indices.shape[-1])
+
+    cnx = mesh.element_connectivity
+
+    nconnections = len(cnx.neighbors)
+    connections = np.empty((nconnections, 2), dtype=np.int32)
+
+    nb_starts = cnx.neighbors_starts
+    for iel in xrange(mesh.nelements):
+        connections[nb_starts[iel]:nb_starts[iel+1], 0] = iel
+
+    connections[:, 1] = cnx.neighbors
+
+    grid = UnstructuredGrid(
+            (mesh.nelements,
+                DataArray("points",
+                    centroids,
+                    vector_format=VF_LIST_OF_COMPONENTS)),
+            cells=connections.reshape(-1),
+            cell_types=np.asarray([VTK_LINE] * nconnections,
+                dtype=np.uint8))
+
+    from os.path import exists
+    if exists(file_name):
+        raise RuntimeError("output file '%s' already exists"
+                % file_name)
+
+    with open(file_name, "w") as outf:
+        AppendedDataXMLGenerator(compressor)(grid).write(outf)
+
+# }}}
+
 # vim: foldmethod=marker
diff --git a/meshmode/mesh/__init__.py b/meshmode/mesh/__init__.py
index 7d19b1a..bef25c2 100644
--- a/meshmode/mesh/__init__.py
+++ b/meshmode/mesh/__init__.py
@@ -219,6 +219,24 @@ class SimplexElementGroup(MeshElementGroup):
 
 # {{{ mesh
 
+class ElementConnectivity(Record):
+    """
+    .. attribute:: neighbors_starts
+
+        ``element_id_t [nelments+1]``
+
+        Use together with :attr:`neighbors`.  ``neighbors_starts[iel]`` and
+        ``neighbors_starts[iel+1]`` together indicate a ranges of element indices
+        :attr:`neighbors` which are adjacent to *iel*.
+
+    .. attribute:: neighbors
+
+        ``element_id_t []``
+
+        See :attr:`neighbors_starts`.
+    """
+
+
 class Mesh(Record):
     """
     .. attribute:: vertices
@@ -229,12 +247,38 @@ class Mesh(Record):
     .. attribute:: groups
 
         A list of :class:`MeshElementGroup` instances.
+
+    .. attribute:: element_connectivity
+
+        An instance of :class:`ElementConnectivity`.
+
+        Referencing this attribute may raise
+        :exc:`meshmode.ConnectivityUnavailable`.
+
+    .. attribute:: vertex_id_dtype
+
+    .. attribute:: element_id_dtype
     """
 
-    def __init__(self, vertices, groups, skip_tests=False):
+    def __init__(self, vertices, groups, skip_tests=False,
+            element_connectivity=False,
+            vertex_id_dtype=np.int32,
+            element_id_dtype=np.int32):
         """
+        The following are keyword-only:
+
         :arg skip_tests: Skip mesh tests, in case you want to load a broken
             mesh anyhow and then fix it inside of this data structure.
+        :arg element_connectivity: One of three options:
+            *None*, in which case this information
+            will be deduced from vertex adjacency. *False*, in which case
+            this information will be marked unavailable (such as if there are
+            hanging nodes in the geometry, so that vertex adjacency does not convey
+            the full picture), and references to
+            :attr:`element_neighbors_starts` and :attr:`element_neighbors`
+            will result in exceptions. Lastly, a tuple
+            *(element_neighbors_starts, element_neighbors)*, representing the
+            correspondingly-named attributes.
         """
         el_nr = 0
         node_nr = 0
@@ -246,11 +290,28 @@ class Mesh(Record):
             el_nr += ng.nelements
             node_nr += ng.nnodes
 
-        Record.__init__(self, vertices=vertices, groups=new_groups)
+        if element_connectivity is not False and element_connectivity is not None:
+            nb_starts, nbs = element_connectivity
+            element_connectivity = ElementConnectivity(
+                    element_neighbors_starts=nb_starts,
+                    element_neighbors=nbs)
+
+            del nb_starts
+            del nbs
+
+        Record.__init__(
+                self, vertices=vertices, groups=new_groups,
+                _element_connectivity=element_connectivity,
+                vertex_id_dtype=np.dtype(vertex_id_dtype),
+                element_id_dtype=np.dtype(element_id_dtype),
+                )
 
         if not skip_tests:
             assert _test_node_vertex_consistency(self)
 
+            for g in self.groups:
+                assert g.vertex_indices.dtype == self.vertex_id_dtype
+
             from meshmode.mesh.processing import \
                     test_volume_mesh_element_orientations
 
@@ -272,9 +333,21 @@ class Mesh(Record):
     def nelements(self):
         return sum(grp.nelements for grp in self.groups)
 
-    # Design experience: Try not to add too many global data structures
-    # to the mesh. Let the element groups be responsible for that.
-    # The interpolatory discretization has these big global things.
+    @property
+    def element_connectivity(self):
+        if self._element_connectivity is False:
+            from meshmode import ConnectivityUnavailable
+            raise ConnectivityUnavailable()
+        elif self._element_connectivity is None:
+            self._element_connectivity = _compute_connectivity_from_vertices(self)
+
+        return self._element_connectivity
+
+
+    # Design experience: Try not to add too many global data structures to the
+    # mesh. Let the element groups be responsible for that at the mesh level.
+    #
+    # There are more big, global structures on the discretization level.
 
 # }}}
 
@@ -328,4 +401,42 @@ def _test_node_vertex_consistency(mesh):
 # }}}
 
 
+# {{{ vertex-based connectivity
+
+def _compute_connectivity_from_vertices(mesh):
+    # FIXME Native code would make this faster
+
+    _, nvertices = mesh.vertices.shape
+    vertex_to_element = [[] for i in xrange(nvertices)]
+
+    for grp in mesh.groups:
+        iel_base = grp.element_nr_base
+        for iel_grp in xrange(grp.nelements):
+            for ivertex in grp.vertex_indices[iel_grp]:
+                vertex_to_element[ivertex].append(iel_base + iel_grp)
+
+    element_to_element = [set() for i in xrange(mesh.nelements)]
+    for grp in mesh.groups:
+        iel_base = grp.element_nr_base
+        for iel_grp in xrange(grp.nelements):
+            for ivertex in grp.vertex_indices[iel_grp]:
+                element_to_element[iel_base + iel_grp].update(
+                        vertex_to_element[ivertex])
+
+    lengths = [len(el_list) for el_list in element_to_element]
+    neighbors_starts = np.cumsum(
+            np.array([0] + lengths, dtype=mesh.element_id_dtype))
+    from pytools import flatten
+    neighbors = np.array(
+            list(flatten(element_to_element)),
+            dtype=mesh.element_id_dtype)
+
+    assert neighbors_starts[-1] == len(neighbors)
+
+    return ElementConnectivity(
+            neighbors_starts=neighbors_starts,
+            neighbors=neighbors)
+
+# }}}
+
 # vim: foldmethod=marker
diff --git a/meshmode/mesh/generation.py b/meshmode/mesh/generation.py
index cbd4363..5df058f 100644
--- a/meshmode/mesh/generation.py
+++ b/meshmode/mesh/generation.py
@@ -138,6 +138,34 @@ def qbx_peanut(t):
 # }}}
 
 
+def make_group_from_vertices(vertices, vertex_indices, order):
+    el_vertices = vertices[:, vertex_indices]
+
+    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
+    unit_nodes = mp.warp_and_blend_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
+
+    # make contiguous
+    nodes = nodes.copy()
+
+    from meshmode.mesh import SimplexElementGroup
+    return SimplexElementGroup(
+            order, vertex_indices, nodes,
+            unit_nodes=unit_nodes)
+
+
 def make_curve_mesh(curve_f, element_boundaries, order):
     """
     :arg curve_f: A callable representing a parametrization for a curve,
@@ -175,23 +203,23 @@ def make_curve_mesh(curve_f, element_boundaries, order):
             nodes=nodes,
             unit_nodes=unodes)
 
-    return Mesh(vertices=vertices, groups=[egroup])
+    return Mesh(vertices=vertices, groups=[egroup],
+            element_connectivity=None)
 
 
-def generate_icosahedron(r):
+def generate_icosahedron(r, order):
     # http://en.wikipedia.org/w/index.php?title=Icosahedron&oldid=387737307
 
     phi = (1+5**(1/2))/2
 
     from pytools import flatten
-    pts = np.array(sorted(flatten([
+    vertices = np.array(sorted(flatten([
             (0, pm1*1, pm2*phi),
             (pm1*1, pm2*phi, 0),
             (pm1*phi, 0, pm2*1)]
             for pm1 in [-1, 1]
-            for pm2 in [-1, 1])))
+            for pm2 in [-1, 1]))).T.copy()
 
-    from meshmode.mesh import Mesh
     top_ring = [11, 7, 1, 2, 8]
     bottom_ring = [10, 9, 3, 0, 4]
     bottom_point = 6
@@ -205,55 +233,34 @@ def generate_icosahedron(r):
         tris.append([bottom_ring[i], bottom_ring[(i+1) % l], top_ring[i]])
         tris.append([top_ring[i], bottom_ring[(i+1) % l], top_ring[(i+1) % l]])
 
-    pts *= r/la.norm(pts[0])
-
-    return Mesh(pts, tris)
+    vertices *= r/la.norm(vertices[:, 0])
 
+    vertex_indices = np.array(tris, dtype=np.int32)
 
-def generate_icosphere(r, refinements, order=1):
-    mesh = generate_icosahedron(r)
+    grp = make_group_from_vertices(vertices, vertex_indices, order)
 
     from meshmode.mesh import Mesh
-    from meshmode.mesh import bisect_geometry
-    for i in range(refinements):
-        mesh, _ = bisect_geometry(mesh)
-
-        pts = mesh.vertex_coordinates
-        pt_norms = np.sum(pts**2, axis=-1)**0.5
-        pts = pts * r / pt_norms[:, np.newaxis]
-
-        mesh = Mesh(pts, mesh.elements)
-
-    if order > 1:
-        from meshmode.mesh.trianglemaps import interv_pts_on_unit
-        eq_nodes = interv_pts_on_unit(order, 0, 1)
-
-        # indices: triangle #, node #, xyz axis
-        mapped_eq_nodes = np.empty((len(mesh), len(eq_nodes), 3))
+    return Mesh(vertices, [grp],
+            element_connectivity=None)
 
-        for iel, el_nodes in enumerate(mesh.elements):
-            n1, n2, n3 = mesh.vertex_coordinates[el_nodes]
-            a = np.vstack([n2-n1, n3-n1]).T
-            b = n1
 
-            mapped_eq_nodes[iel] = np.dot(a, eq_nodes.T).T + b
+def generate_icosphere(r, order):
+    mesh = generate_icosahedron(r, order)
 
-        mn = mapped_eq_nodes
-        mapped_eq_nodes = \
-                r * mn / np.sqrt(np.sum(mn**2, axis=-1))[:, :, np.newaxis]
+    grp, = mesh.groups
 
-        reshaped_nodes = mapped_eq_nodes.reshape(-1, 3)
+    grp = grp.copy(
+            nodes=grp.nodes * r / np.sqrt(np.sum(grp.nodes**2, axis=0)))
 
-        mesh = Mesh(
-                vertex_coordinates=reshaped_nodes,
-                elements=np.arange(len(reshaped_nodes)).reshape(len(mesh), -1),
-                order=order)
-
-    return mesh
+    from meshmode.mesh import Mesh
+    return Mesh(mesh.vertices, [grp],
+            element_connectivity=None)
 
 
 def generate_torus_and_cycle_vertices(r_outer, r_inner,
         n_outer=20, n_inner=10, order=1):
+    raise NotImplementedError("not yet updated for meshmode")
+
     a = r_outer
     b = r_inner
     u, v = np.mgrid[0:2*np.pi:2*np.pi/n_outer, 0:2*np.pi:2*np.pi/n_inner]
@@ -333,6 +340,9 @@ def generate_torus_and_cycle_vertices(r_outer, r_inner,
 
 
 def generate_torus(r_outer, r_inner, n_outer=20, n_inner=10, order=1):
+    # FIXME
+    raise NotImplementedError("not yet updated for meshmode")
+
     geo, a_cycle, b_cycle = generate_torus_and_cycle_vertices(
             r_outer, r_inner, n_outer, n_inner, order)
     return geo
diff --git a/meshmode/mesh/io.py b/meshmode/mesh/io.py
index bdfbf28..dc540a8 100644
--- a/meshmode/mesh/io.py
+++ b/meshmode/mesh/io.py
@@ -167,7 +167,7 @@ class GmshMeshReceiver(GmshMeshReceiverBase):
 
             groups.append(group)
 
-        return Mesh(vertices, groups)
+        return Mesh(vertices, groups, element_connectivity=None)
 
 # }}}
 
-- 
GitLab