From 0a6f2fba45d667ee889960abe2fa7624fdb11d6f Mon Sep 17 00:00:00 2001
From: Andreas Kloeckner <inform@tiker.net>
Date: Sat, 21 Jun 2014 18:37:19 -0500
Subject: [PATCH] Make boundary restriction work, add unit test

---
 meshmode/discretization/connection.py   | 103 ++++++++++++++++++++----
 meshmode/discretization/poly_element.py |  16 +++-
 meshmode/mesh/__init__.py               |   4 +-
 test/blob-2d.step                       |  95 ++++++++++++++++++++++
 test/test_meshmode.py                   |  91 +++++++++++++++++++++
 5 files changed, 287 insertions(+), 22 deletions(-)
 create mode 100644 test/blob-2d.step
 create mode 100644 test/test_meshmode.py

diff --git a/meshmode/discretization/connection.py b/meshmode/discretization/connection.py
index bbdfd21..974a18c 100644
--- a/meshmode/discretization/connection.py
+++ b/meshmode/discretization/connection.py
@@ -26,7 +26,7 @@ import numpy as np
 import modepy as mp
 import pyopencl as cl
 import pyopencl.array  # noqa
-from pytools import memoize_method, memoize_method_nested
+from pytools import memoize_method, memoize_method_nested, Record
 
 
 __doc__ = """
@@ -34,6 +34,8 @@ __doc__ = """
 
 .. autofunction:: make_same_mesh_connection
 
+.. autofunction:: make_boundary_restriction
+
 Implementation details
 ^^^^^^^^^^^^^^^^^^^^^^
 
@@ -54,13 +56,13 @@ class InterpolationBatch(object):
 
     .. attribute:: source_element_indices
 
-        A :class:`numpy.ndarray` of length ``nelements``, containing the
+        A :class:`pyopencl.array.Arrays` of length ``nelements``, containing the
         element index from which this "*to*" element's data will be
         interpolated.
 
     .. attribute:: target_element_indices
 
-        A :class:`numpy.ndarray` of length ``nelements``, containing the
+        A :class:`pyopencl.array.Arrays` of length ``nelements``, containing the
         element index to which this "*to*" element's data will be
         interpolated.
 
@@ -72,6 +74,7 @@ class InterpolationBatch(object):
         of the *from* reference element) from which the node
         locations of this element should be interpolated.
     """
+
     def __init__(self, source_element_indices,
             target_element_indices, result_unit_nodes):
         self.source_element_indices = source_element_indices
@@ -136,7 +139,18 @@ class DiscretizationConnection(object):
                     0<=k<nelements and
                     0<=i<n_to_nodes and
                     0<=j<n_from_nodes}""",
-                "result[k,i] = sum(j, resample_mat[i, j] * vec[k, j])",
+                "result[target_element_indices[k], i] \
+                    = sum(j, resample_mat[i, j] \
+                    * vec[source_element_indices[k], j])",
+                [
+                    lp.GlobalArg("result", None,
+                        shape="nelements_result, n_to_nodes"),
+                    lp.GlobalArg("vec", None,
+                        shape="nelements_vec, n_from_nodes"),
+                    lp.ValueArg("nelements_result", np.int32),
+                    lp.ValueArg("nelements_vec", np.int32),
+                    "...",
+                    ],
                 name="oversample")
 
             knl = lp.split_iname(knl, "i", 16, inner_tag="l.0")
@@ -153,9 +167,12 @@ class DiscretizationConnection(object):
         for i_grp, (sgrp, tgrp, cgrp) in enumerate(
                 zip(self.to_discr.groups, self.from_discr.groups, self.groups)):
             for i_batch, batch in enumerate(cgrp.batches):
-                knl()(queue,
-                        resample_mat=self._resample_matrix(i_grp, i_batch),
-                        result=sgrp.view(result), vec=tgrp.view(vec))
+                if len(batch.source_element_indices):
+                    knl()(queue,
+                            resample_mat=self._resample_matrix(i_grp, i_batch),
+                            result=sgrp.view(result), vec=tgrp.view(vec),
+                            source_element_indices=batch.source_element_indices,
+                            target_element_indices=batch.target_element_indices)
 
         return result
 
@@ -193,7 +210,47 @@ def make_same_mesh_connection(queue, to_discr, from_discr):
 
 # {{{ boundary restriction constructor
 
-def make_boundary_extractor(queue, discr, group_factory):
+class _ConnectionBatchData(Record):
+    pass
+
+
+def _build_boundary_connection(queue, vol_discr, bdry_discr, connection_data):
+    connection_groups = []
+    for igrp, (vol_grp, bdry_grp) in enumerate(
+            zip(vol_discr.groups, bdry_discr.groups)):
+        connection_batches = []
+        mgrp = vol_grp.mesh_el_group
+
+        for face_id in xrange(len(mgrp.face_vertex_indices())):
+            data = connection_data[igrp, face_id]
+
+            bdry_unit_nodes_01 = (bdry_grp.unit_nodes + 1)*0.5
+            result_unit_nodes = (np.dot(data.A, bdry_unit_nodes_01).T + data.b).T
+
+            connection_batches.append(
+                    InterpolationBatch(
+                        source_element_indices=cl.array.to_device(
+                            queue,
+                            vol_grp.mesh_el_group.element_nr_base
+                            + data.group_source_element_indices)
+                        .with_queue(None),
+                        target_element_indices=cl.array.to_device(
+                            queue,
+                            bdry_grp.mesh_el_group.element_nr_base
+                            + data.group_target_element_indices)
+                        .with_queue(None),
+                        result_unit_nodes=result_unit_nodes,
+                        ))
+
+        connection_groups.append(
+                DiscretizationConnectionElementGroup(
+                    connection_batches))
+
+    return DiscretizationConnection(
+            vol_discr, bdry_discr, connection_groups)
+
+
+def make_boundary_restriction(queue, discr, group_factory):
     """
     :return: a tuple ``(bdry_mesh, bdry_discr, connection)``
     """
@@ -205,6 +262,7 @@ def make_boundary_extractor(queue, discr, group_factory):
 
     for igrp, mgrp in enumerate(discr.mesh.groups):
         grp_face_vertex_indices = mgrp.face_vertex_indices()
+
         for iel_grp in xrange(mgrp.nelements):
             for fid, loc_face_vertices in enumerate(grp_face_vertex_indices):
                 face_vertices = frozenset(
@@ -237,6 +295,8 @@ def make_boundary_extractor(queue, discr, group_factory):
 
     from meshmode.mesh import Mesh, SimplexElementGroup
     bdry_mesh_groups = []
+    connection_data = {}
+
     for igrp, grp in enumerate(discr.groups):
         mgrp = grp.mesh_el_group
         group_boundary_faces = [
@@ -274,7 +334,7 @@ def make_boundary_extractor(queue, discr, group_factory):
         batch_base = 0
 
         for face_id in xrange(len(grp_face_vertex_indices)):
-            batch_boundary_el_numbers_in_vol = np.array(
+            batch_boundary_el_numbers_in_grp = np.array(
                     [
                         ibface_el
                         for ibface_el, ibface_face in group_boundary_faces
@@ -283,7 +343,7 @@ def make_boundary_extractor(queue, discr, group_factory):
 
             new_el_numbers = np.arange(
                     batch_base,
-                    batch_base + len(batch_boundary_el_numbers_in_vol))
+                    batch_base + len(batch_boundary_el_numbers_in_grp))
 
             # {{{ no per-element axes in these computations
 
@@ -309,9 +369,11 @@ def make_boundary_extractor(queue, discr, group_factory):
 
             # }}}
 
+            # {{{ build information for mesh element group
+
             # Find vertex_indices
             glob_face_vertices = mgrp.vertex_indices[
-                    batch_boundary_el_numbers_in_vol][:, loc_face_vertices]
+                    batch_boundary_el_numbers_in_grp][:, loc_face_vertices]
             vertex_indices[new_el_numbers] = \
                     vol_to_bdry_vertices[glob_face_vertices]
 
@@ -319,9 +381,18 @@ def make_boundary_extractor(queue, discr, group_factory):
             nodes[:, new_el_numbers, :] = np.einsum(
                     "ij,dej->dei",
                     resampling_mat,
-                    mgrp.nodes[:, batch_boundary_el_numbers_in_vol, :])
+                    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,
+                    )
 
-            batch_base += len(batch_boundary_el_numbers_in_vol)
+            batch_base += len(batch_boundary_el_numbers_in_grp)
 
         bdry_mesh_group = SimplexElementGroup(
                 mgrp.order, vertex_indices, nodes, unit_nodes=bdry_unit_nodes)
@@ -333,10 +404,8 @@ def make_boundary_extractor(queue, discr, group_factory):
     bdry_discr = Discretization(
             discr.cl_context, bdry_mesh, group_factory)
 
-    # FIXME
-    connection = None
-
-    return bdry_mesh, bdry_discr, connection
+    return bdry_mesh, bdry_discr, _build_boundary_connection(
+            queue, discr, bdry_discr, connection_data)
 
 # }}}
 
diff --git a/meshmode/discretization/poly_element.py b/meshmode/discretization/poly_element.py
index 43445c6..c15a1b1 100644
--- a/meshmode/discretization/poly_element.py
+++ b/meshmode/discretization/poly_element.py
@@ -96,7 +96,13 @@ class QuadratureSimplexElementGroup(PolynomialSimplexElementGroupBase):
     @property
     @memoize_method
     def unit_nodes(self):
-        return self._quadrature_rule().nodes
+        result = self._quadrature_rule().nodes
+        if len(result.shape) == 1:
+            result = np.array([result])
+
+        dim2, nunit_nodes = result.shape
+        assert dim2 == self.mesh_el_group.dim
+        return result
 
     @property
     @memoize_method
@@ -108,8 +114,12 @@ class PolynomialWarpAndBlendElementGroup(PolynomialSimplexElementGroupBase):
     @property
     @memoize_method
     def unit_nodes(self):
-        dims = self.mesh_el_group.dim
-        return mp.warp_and_blend_nodes(dims, self.order)
+        dim = self.mesh_el_group.dim
+        result = mp.warp_and_blend_nodes(dim, self.order)
+
+        dim2, nunit_nodes = result.shape
+        assert dim2 == dim
+        return result
 
     @property
     @memoize_method
diff --git a/meshmode/mesh/__init__.py b/meshmode/mesh/__init__.py
index 91f3a7b..ef2c104 100644
--- a/meshmode/mesh/__init__.py
+++ b/meshmode/mesh/__init__.py
@@ -80,7 +80,7 @@ class MeshElementGroup(Record):
             vertex_indices=vertex_indices,
             nodes=nodes,
             unit_nodes=unit_nodes,
-            element_nr_base=None, node_nr_base=None)
+            element_nr_base=element_nr_base, node_nr_base=node_nr_base)
 
     @property
     def dim(self):
@@ -217,7 +217,7 @@ class Mesh(Record):
             el_nr += ng.nelements
             node_nr += ng.nnodes
 
-        Record.__init__(self, vertices=vertices, groups=groups)
+        Record.__init__(self, vertices=vertices, groups=new_groups)
 
     @property
     def ambient_dim(self):
diff --git a/test/blob-2d.step b/test/blob-2d.step
new file mode 100644
index 0000000..f0b8bf0
--- /dev/null
+++ b/test/blob-2d.step
@@ -0,0 +1,95 @@
+ISO-10303-21;
+HEADER;
+FILE_DESCRIPTION(('FreeCAD Model'),'2;1');
+FILE_NAME('/home/andreas/own/graphics/freecad/blob.step',
+  '2014-06-16T23:10:06',('FreeCAD'),('FreeCAD'),
+  'Open CASCADE STEP processor 6.5','FreeCAD','Unknown');
+FILE_SCHEMA(('AUTOMOTIVE_DESIGN_CC2 { 1 2 10303 214 -1 1 5 4 }'));
+ENDSEC;
+DATA;
+#1 = APPLICATION_PROTOCOL_DEFINITION('committee draft',
+  'automotive_design',1997,#2);
+#2 = APPLICATION_CONTEXT(
+  'core data for automotive mechanical design processes');
+#3 = SHAPE_DEFINITION_REPRESENTATION(#4,#10);
+#4 = PRODUCT_DEFINITION_SHAPE('','',#5);
+#5 = PRODUCT_DEFINITION('design','',#6,#9);
+#6 = PRODUCT_DEFINITION_FORMATION('','',#7);
+#7 = PRODUCT('BSpline','BSpline','',(#8));
+#8 = MECHANICAL_CONTEXT('',#2,'mechanical');
+#9 = PRODUCT_DEFINITION_CONTEXT('part definition',#2,'design');
+#10 = MANIFOLD_SURFACE_SHAPE_REPRESENTATION('',(#11,#15),#53);
+#11 = AXIS2_PLACEMENT_3D('',#12,#13,#14);
+#12 = CARTESIAN_POINT('',(0.,0.,0.));
+#13 = DIRECTION('',(0.,0.,1.));
+#14 = DIRECTION('',(1.,0.,-0.));
+#15 = SHELL_BASED_SURFACE_MODEL('',(#16));
+#16 = OPEN_SHELL('',(#17));
+#17 = ADVANCED_FACE('',(#18),#36,.T.);
+#18 = FACE_BOUND('',#19,.F.);
+#19 = EDGE_LOOP('',(#20));
+#20 = ORIENTED_EDGE('',*,*,#21,.T.);
+#21 = EDGE_CURVE('',#22,#22,#24,.T.);
+#22 = VERTEX_POINT('',#23);
+#23 = CARTESIAN_POINT('',(-4.72543090582E-04,1.64651736617E-04,0.));
+#24 = SURFACE_CURVE('',#25,(#35),.PCURVE_S1.);
+#25 = B_SPLINE_CURVE_WITH_KNOTS('',3,(#26,#27,#28,#29,#30,#31,#32,#33,
+    #34),.UNSPECIFIED.,.T.,.F.,(1,1,2,1,1,1,1,1,2,1,1),(-0.574540704647,
+    -0.371453182518,0.,0.263932510809,0.520877807931,0.808151393473,
+    1.190527579206,1.393615101335,1.765068283853,2.029000794662,
+    2.285946091784),.UNSPECIFIED.);
+#26 = CARTESIAN_POINT('',(-4.451395471938E-04,3.893567516391E-04,0.));
+#27 = CARTESIAN_POINT('',(-4.920144165846E-04,4.989734519847E-06,0.));
+#28 = CARTESIAN_POINT('',(-4.111934608223E-04,-1.366538732583E-04,0.));
+#29 = CARTESIAN_POINT('',(-1.187450244609E-04,-2.333067459084E-05,0.));
+#30 = CARTESIAN_POINT('',(2.486504062773E-04,-3.199830111205E-04,0.));
+#31 = CARTESIAN_POINT('',(1.570695404062E-04,2.640870359319E-04,0.));
+#32 = CARTESIAN_POINT('',(-1.562373623195E-04,2.334791360325E-04,0.));
+#33 = CARTESIAN_POINT('',(-4.451395471938E-04,3.893567516391E-04,0.));
+#34 = CARTESIAN_POINT('',(-4.920144165846E-04,4.989734519847E-06,0.));
+#35 = PCURVE('',#36,#41);
+#36 = PLANE('',#37);
+#37 = AXIS2_PLACEMENT_3D('',#38,#39,#40);
+#38 = CARTESIAN_POINT('',(-4.451395471938E-04,3.893567516391E-04,0.));
+#39 = DIRECTION('',(0.,0.,-1.));
+#40 = DIRECTION('',(-1.,0.,0.));
+#41 = DEFINITIONAL_REPRESENTATION('',(#42),#52);
+#42 = B_SPLINE_CURVE_WITH_KNOTS('',3,(#43,#44,#45,#46,#47,#48,#49,#50,
+    #51),.UNSPECIFIED.,.F.,.F.,(1,1,2,1,1,1,1,1,2,1,1),(-0.574540704647,
+    -0.371453182518,0.,0.263932510809,0.520877807931,0.808151393473,
+    1.190527579206,1.393615101335,1.765068283853,2.029000794662,
+    2.285946091784),.UNSPECIFIED.);
+#43 = CARTESIAN_POINT('',(0.,0.));
+#44 = CARTESIAN_POINT('',(4.687486939076E-05,-3.843670171192E-04));
+#45 = CARTESIAN_POINT('',(-3.394608637149E-05,-5.260106248974E-04));
+#46 = CARTESIAN_POINT('',(-3.263945227329E-04,-4.126874262299E-04));
+#47 = CARTESIAN_POINT('',(-6.937899534711E-04,-7.093397627596E-04));
+#48 = CARTESIAN_POINT('',(-6.022090876E-04,-1.252697157072E-04));
+#49 = CARTESIAN_POINT('',(-2.889021848743E-04,-1.558776156066E-04));
+#50 = CARTESIAN_POINT('',(0.,0.));
+#51 = CARTESIAN_POINT('',(4.687486939076E-05,-3.843670171192E-04));
+#52 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2) 
+PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
+  ) );
+#53 = ( GEOMETRIC_REPRESENTATION_CONTEXT(3) 
+GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#57)) GLOBAL_UNIT_ASSIGNED_CONTEXT(
+(#54,#55,#56)) REPRESENTATION_CONTEXT('Context #1',
+  '3D Context with UNIT and UNCERTAINTY') );
+#54 = ( LENGTH_UNIT() NAMED_UNIT(*) SI_UNIT($,.METRE.) );
+#55 = ( NAMED_UNIT(*) PLANE_ANGLE_UNIT() SI_UNIT($,.RADIAN.) );
+#56 = ( NAMED_UNIT(*) SI_UNIT($,.STERADIAN.) SOLID_ANGLE_UNIT() );
+#57 = UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(1.E-10),#54,
+  'distance_accuracy_value','confusion accuracy');
+#58 = PRODUCT_TYPE('part',$,(#7));
+#59 = MECHANICAL_DESIGN_GEOMETRIC_PRESENTATION_REPRESENTATION('',(#60),
+  #53);
+#60 = STYLED_ITEM('color',(#61),#17);
+#61 = PRESENTATION_STYLE_ASSIGNMENT((#62));
+#62 = SURFACE_STYLE_USAGE(.BOTH.,#63);
+#63 = SURFACE_SIDE_STYLE('',(#64));
+#64 = SURFACE_STYLE_FILL_AREA(#65);
+#65 = FILL_AREA_STYLE('',(#66));
+#66 = FILL_AREA_STYLE_COLOUR('',#67);
+#67 = COLOUR_RGB('',0.800000011921,0.800000011921,0.800000011921);
+ENDSEC;
+END-ISO-10303-21;
diff --git a/test/test_meshmode.py b/test/test_meshmode.py
new file mode 100644
index 0000000..a1e0d4a
--- /dev/null
+++ b/test/test_meshmode.py
@@ -0,0 +1,91 @@
+from __future__ import division
+
+__copyright__ = "Copyright (C) 2014 Andreas Kloeckner"
+
+__license__ = """
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+"""
+
+import numpy as np
+import numpy.linalg as la
+import pyopencl as cl
+import pyopencl.array  # noqa
+import pyopencl.clmath  # noqa
+
+from pyopencl.tools import (  # noqa
+        pytest_generate_tests_for_pyopencl
+        as pytest_generate_tests)
+
+
+def test_boundary_interpolation(ctx_getter):
+    cl_ctx = ctx_getter()
+    queue = cl.CommandQueue(cl_ctx)
+
+    from meshmode.mesh.io import generate_gmsh, FileSource
+    from meshmode.discretization import Discretization
+    from meshmode.discretization.poly_element import \
+            QuadratureSimplexGroupFactory
+    from pytools.convergence import EOCRecorder
+    from meshmode.discretization.connection import make_boundary_restriction
+
+    eoc_rec = EOCRecorder()
+
+    order = 4
+
+    for h in [1e-1, 3e-2, 1e-2]:
+        print "BEGIN GEN"
+        mesh = generate_gmsh(
+                FileSource("blob-2d.step"), 2, order=order,
+                force_ambient_dimension=2,
+                other_options=[
+                    "-string", "Mesh.CharacteristicLengthMax = %s;" % h]
+                )
+        print "END GEN"
+
+        vol_discr = Discretization(cl_ctx, mesh,
+                QuadratureSimplexGroupFactory(order))
+        print "h=%s -> %d elements" % (
+                h, sum(mgrp.nelements for mgrp in mesh.groups))
+
+        x = vol_discr.nodes()[0].with_queue(queue)
+        f = 0.1*cl.clmath.sin(30*x)
+
+        bdry_mesh, bdry_discr, bdry_connection = make_boundary_restriction(
+                queue, vol_discr, QuadratureSimplexGroupFactory(order))
+
+        bdry_x = bdry_discr.nodes()[0].with_queue(queue)
+        bdry_f = 0.1*cl.clmath.sin(30*bdry_x)
+        bdry_f_2 = bdry_connection(queue, f)
+
+        err = la.norm((bdry_f-bdry_f_2).get(), np.inf)
+        eoc_rec.add_data_point(h, err)
+
+    print eoc_rec
+    assert eoc_rec.order_estimate() >= order-0.5
+
+
+if __name__ == "__main__":
+    import sys
+    if len(sys.argv) > 1:
+        exec(sys.argv[1])
+    else:
+        from py.test.cmdline import main
+        main([__file__])
+
+# vim: fdm=marker
-- 
GitLab