{"action":"created","comment":{"url":"https://api.github.com/repos/OP2/PyOP2/pulls/comments/238819280","pull_request_review_id":181455397,"id":238819280,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIzODgxOTI4MA==","diff_hunk":"@@ -0,0 +1,816 @@\n+from abc import ABCMeta, abstractmethod\n+from collections import OrderedDict, namedtuple\n+import numpy\n+\n+from pyop2.codegen.representation import (Index, FixedIndex, RuntimeIndex,\n+                                          MultiIndex, Extent, Indexed,\n+                                          BitShift, BitwiseNot, BitwiseAnd,\n+                                          Conditional, Comparison, DummyInstruction,\n+                                          LogicalNot, LogicalAnd, LogicalOr,\n+                                          Argument, Literal, NamedLiteral,\n+                                          Materialise, Accumulate, FunctionCall, When,\n+                                          Symbol, Zero, Sum, Product, view)\n+from pyop2.codegen.representation import (PackInst, UnpackInst, KernelInst)\n+\n+from pyop2.utils import cached_property\n+from pyop2.datatypes import IntType\n+from pyop2.op2 import ON_BOTTOM, ON_TOP, ON_INTERIOR_FACETS, ALL, Subset, DecoratedMap\n+from pyop2.op2 import READ, INC, WRITE\n+from loopy.types import OpaqueType\n+from functools import reduce\n+\n+\n+class PetscMat(OpaqueType):\n+\n+    def __init__(self):\n+        super(PetscMat, self).__init__(name=\"Mat\")\n+\n+\n+class SparseArray(namedtuple(\"SparseArray\", (\"values\", \"dof\", \"offset\"))):\n+\n+    @cached_property\n+    def nrows(self):\n+        extent, = self.offset.shape\n+        return extent\n+\n+\n+class Map(object):\n+\n+    __slots__ = (\"values\", \"offset\", \"boundary_masks\", \"interior_horizontal\",\n+                 \"variable\", \"vector_bc\", \"implicit_bcs\", \"layer_bounds\",\n+                 \"variable_entity_masks\", \"prefetch\")\n+\n+    def __init__(self, map_, interior_horizontal, layer_bounds, variable_entity_masks,\n+                 values=None, offset=None, boundary_masks=None):\n+        self.variable = map_.iterset._extruded and not map_.iterset.constant_layers\n+        self.vector_bc = map_.vector_index\n+        self.implicit_bcs = map_.implicit_bcs\n+        self.variable_entity_masks = variable_entity_masks\n+        self.layer_bounds = layer_bounds\n+        self.interior_horizontal = interior_horizontal\n+        self.prefetch = {}\n+        if values is not None:\n+            self.values = values\n+            if map_.offset is not None:\n+                assert offset is not None\n+            self.offset = offset\n+            if map_.boundary_masks is not None:\n+                assert boundary_masks is not None\n+            self.boundary_masks = boundary_masks\n+            return\n+\n+        offset = map_.offset\n+        iterset = map_.iterset\n+        boundary_masks = map_.boundary_masks\n+        shape = (None, ) + map_.shape[1:]\n+        values = Argument(shape, dtype=map_.dtype, pfx=\"map\")\n+        if offset is not None:\n+            offset = NamedLiteral(offset, name=values.name + \"_offset\")\n+\n+        if boundary_masks is not None:\n+            v = {}\n+            for method, (section, indices, (*_, bottom, top)) in boundary_masks.items():\n+                if iterset.constant_layers:\n+                    vals = []\n+                    for location, p in [(\"bottom\", bottom),\n+                                        (\"top\", top)]:\n+                        dof = section.getDof(p)\n+                        off = section.getOffset(p)\n+                        name = values.name + (\"_%s_%s_indices\" % (method, location))\n+                        vals.append(NamedLiteral(indices[off:off+dof], name))\n+                    v[method] = tuple(vals)\n+                else:\n+                    name = values.name + (\"_%s\" % method)\n+                    indices = NamedLiteral(indices, name + \"_indices\")\n+                    chart = section.getChart()\n+                    off = numpy.asarray(list(section.getOffset(p) for p in range(*chart)), dtype=IntType)\n+                    dof = numpy.asarray(list(section.getDof(p) for p in range(*chart)),\n+                                        dtype=IntType)\n+                    off = NamedLiteral(off, name + \"_offset\")\n+                    dof = NamedLiteral(dof, name + \"_dof\")\n+                    v[method] = SparseArray(indices, dof, off)\n+            boundary_masks = v\n+        self.values = values\n+        self.offset = offset\n+        self.boundary_masks = boundary_masks\n+\n+    @property\n+    def shape(self):\n+        return self.values.shape\n+\n+    @property\n+    def dtype(self):\n+        return self.values.dtype\n+\n+    def indexed(self, multiindex, layer=None):\n+        n, i, f = multiindex\n+        if layer is not None and self.offset is not None:\n+            key = f.extent\n+            if key is None:\n+                key = 1\n+\n+            if key not in self.prefetch:\n+                bottom_layer, _ = self.layer_bounds\n+                offset_extent, = self.offset.shape\n+                j = Index(offset_extent)\n+                base = Indexed(self.values, (n, j))\n+                if f.extent:\n+                    k = Index(f.extent)\n+                else:\n+                    k = Index(1)\n+                offset = Sum(Sum(layer, Product(Literal(numpy.int32(-1)), bottom_layer)), k)\n+                offset = Product(offset, Indexed(self.offset, (j,)))\n+                self.prefetch[key] = Materialise(PackInst(), Sum(base, offset), MultiIndex(k, j))\n+\n+            return Indexed(self.prefetch[key], (f, i)), (f, i)\n+        else:\n+            assert f.extent == 1 or f.extent is None\n+            base = Indexed(self.values, (n, i))\n+            return base, (f, i)\n+\n+    def indexed_vector(self, n, shape, layer=None):\n+        shape = self.shape[1:] + shape\n+        if self.interior_horizontal:\n+            shape = (2, ) + shape\n+        else:\n+            shape = (1, ) + shape\n+        f, i, j = (Index(e) for e in shape)\n+        base, (f, i) = self.indexed((n, i, f), layer=layer)\n+        discard = Comparison(\"<\", base, Zero((), self.dtype))\n+        if self.vector_bc is not None:\n+            # Exposition:\n+            # Vector-index bcs are encoded in the high bits of the map.\n+            # The incoming value is:\n+            # input := ~(row + sum_i 2**(nbit - i))\n+            # Where i are the components to zero\n+            # The actual row is then:\n+            # row := (~input) & (~(sum_{k<3} 2**(nbit - k)))\n+            # And the high bits that are non-zero tell us which\n+            # values to mask out.\n+            nbits = Literal(self.dtype.type(self.dtype.itemsize*8 - 2))\n+            mask = Literal(self.dtype.type(sum(2**(nbits.value - i) for i in range(3))))\n+            flipped = BitwiseNot(base)\n+            base = Conditional(discard,\n+                               BitwiseAnd(flipped, BitwiseNot(mask)),\n+                               base)\n+            expr = LogicalNot(BitwiseAnd(flipped, mask))\n+            expr = LogicalOr(expr,\n+                             BitwiseAnd(flipped,\n+                                        BitShift(\"<<\", Literal(self.dtype.type(1)),\n+                                                 Sum(nbits,\n+                                                     Product(Literal(self.dtype.type(-1)), j)))))\n+            discard = LogicalAnd(discard, expr)\n+\n+        init = Conditional(discard, Literal(self.dtype.type(-1)), Sum(Product(base, Literal(numpy.int32(j.extent))), j))\n+        pack = Materialise(PackInst(), init, MultiIndex(f, i, j))\n+        multiindex = tuple(Index(e) for e in pack.shape)\n+        return Indexed(pack, multiindex), multiindex\n+\n+    def indexed_implicit(self, n, layer=None):\n+        if layer is None:\n+            raise ValueError(\"Implicit bcs and no layers?!\")\n+        shape = self.shape[1:]\n+        if self.interior_horizontal:\n+            shape = (2, ) + shape\n+        else:\n+            shape = (1, ) + shape\n+        f, i = (Index(e) for e in shape)\n+        base, (f, i) = self.indexed((n, i, f), layer=layer)\n+\n+        expressions = [PackInst(), base, MultiIndex(f, i)]\n+        if self.variable:\n+            for location, method in self.implicit_bcs:\n+                index_array = self.boundary_masks[method]\n+                # For facets\n+                if self.interior_horizontal:\n+                    f = Index(2)\n+                else:\n+                    f = FixedIndex(0)\n+                bottom_mask, top_mask = self.variable_entity_masks\n+\n+                idx, = bottom_mask.multiindex\n+                idx = Sum(idx, f)\n+                if location == \"bottom\":\n+                    mask = Indexed(bottom_mask.aggregate, (idx, ))\n+                else:\n+                    mask = Indexed(top_mask.aggregate, (idx, ))\n+\n+                if all(index_array.offset.value == 0):\n+                    # No need to do this if there are no boundary dofs\n+                    continue\n+                bit = Index(index_array.nrows)\n+                when = BitwiseAnd(mask, BitShift(\"<<\", Literal(numpy.int64(1)), bit))\n+                off = Materialise(PackInst(), Indexed(index_array.offset, (bit, )), MultiIndex())\n+                dof = Materialise(PackInst(), Indexed(index_array.dof, (bit, )), MultiIndex())\n+                k = RuntimeIndex(off, Sum(off, dof),\n+                                 LogicalAnd(\n+                                     Comparison(\"<=\", Zero((), numpy.int32), off),\n+                                     Comparison(\"<=\", Zero((), numpy.int32), dof)))\n+\n+                index = Indexed(index_array.values, (k, ))\n+\n+                expr = When(when, Literal(self.dtype.type(-1)))\n+                indices = MultiIndex(f, index)\n+                expressions.append(expr)\n+                expressions.append(indices)\n+        else:\n+            for location, method in self.implicit_bcs:\n+                i = Index()\n+                bottom, top = self.boundary_masks[method]\n+                idx = FixedIndex(0)\n+                if location == \"bottom\":\n+                    indices = bottom\n+                    bound = self.layer_bounds[0]\n+                else:\n+                    indices = top\n+                    bound = Sum(self.layer_bounds[1], Literal(IntType.type(-1)))\n+                    if self.interior_horizontal:\n+                        idx = FixedIndex(1)\n+\n+                index = Indexed(indices, (i, ))\n+                when = Comparison(\"==\", layer, bound)\n+\n+                expr = When(when, Literal(self.dtype.type(-1)))\n+                indices = MultiIndex(idx, index)\n+                expressions.append(expr)\n+                expressions.append(indices)\n+        pack = Materialise(*expressions)\n+        multiindex = tuple(Index(e) for e in pack.shape)\n+        return Indexed(pack, multiindex), multiindex\n+\n+\n+class Pack(metaclass=ABCMeta):\n+\n+    @abstractmethod\n+    def kernel_arg(self, loop_indices=None):\n+        pass\n+\n+    @abstractmethod\n+    def pack(self, loop_indices=None):\n+        pass\n+\n+    @abstractmethod\n+    def emit_unpack_instruction(self, *,\n+                                loop_indices=None):\n+        pass\n+\n+\n+class GlobalPack(Pack):\n+\n+    def __init__(self, outer, access):\n+        self.outer = outer\n+        self.access = access\n+\n+    def kernel_arg(self, loop_indices=None):\n+        return Indexed(self.outer, (Index(e) for e in self.outer.shape))\n+\n+    # TODO: do we make a temporary and zero it?\n+    def pack(self, loop_indices=None):\n+        return None\n+\n+    def emit_unpack_instruction(self, *,\n+                                loop_indices=None):\n+        yield None\n+\n+\n+class DatPack(Pack):\n+    def __init__(self, outer, access, map_=None, interior_horizontal=False,\n+                 view_index=None, layer_bounds=None):\n+        self.outer = outer\n+        self.map_ = map_\n+        self.access = access\n+        self.interior_horizontal = interior_horizontal\n+        self.view_index = view_index\n+        self.layer_bounds = layer_bounds\n+\n+    def _rvalue(self, multiindex, loop_indices=None):\n+        f, i, *j = multiindex\n+        try:\n+            n, layer = loop_indices\n+        except ValueError:\n+            n, = loop_indices\n+            layer = None\n+        if self.view_index is not None:\n+            j = tuple(j) + tuple(FixedIndex(i) for i in self.view_index)\n+        map_, (f, i) = self.map_.indexed((n, i, f), layer=layer)\n+        return Indexed(self.outer,\n+                       MultiIndex(map_, *j))\n+\n+    def pack(self, loop_indices=None):\n+        if self.map_ is None:\n+            return None\n+\n+        if hasattr(self, \"_pack\"):\n+            return self._pack\n+\n+        if self.interior_horizontal:\n+            shape = (2, )\n+        else:\n+            shape = (1, )\n+\n+        shape = shape + self.map_.shape[1:]\n+        if self.view_index is None:\n+            shape = shape + self.outer.shape[1:]\n+\n+        if self.access in {INC, WRITE}:\n+            val = Zero((), self.outer.dtype)\n+            multiindex = MultiIndex(*(Index(e) for e in shape))\n+            self._pack = Materialise(PackInst(), val, multiindex)\n+        else:\n+            multiindex = MultiIndex(*(Index(e) for e in shape))\n+            self._pack = Materialise(PackInst(),\n+                                     self._rvalue(multiindex, loop_indices=loop_indices),\n+                                     multiindex)\n+        return self._pack\n+\n+    def kernel_arg(self, loop_indices=None):\n+        if self.map_ is None:\n+            if loop_indices is None:\n+                raise ValueError(\"Need iteration index\")\n+            try:\n+                n, layer = loop_indices\n+            except ValueError:\n+                n, = loop_indices\n+            # Direct dats on extruded sets never get a layer index\n+            # (they're defined on the \"base\" set, effectively).\n+            # FIXME: is this a bug?\n+            shape = self.outer.shape\n+            if self.view_index is None:\n+                multiindex = (n, ) + tuple(Index(e) for e in shape[1:])\n+            else:\n+                multiindex = (n, ) + tuple(FixedIndex(i) for i in self.view_index)\n+            return Indexed(self.outer, multiindex)\n+        else:\n+            pack = self.pack(loop_indices)\n+            shape = pack.shape\n+            return Indexed(pack, (Index(e) for e in shape))\n+\n+    def emit_unpack_instruction(self, *,\n+                                loop_indices=None):\n+        pack = self.pack(loop_indices)\n+        if pack is None:\n+            yield None\n+        elif self.access is READ:\n+            yield None\n+        elif self.access is INC:\n+            multiindex = tuple(Index(e) for e in pack.shape)\n+            rvalue = self._rvalue(multiindex, loop_indices=loop_indices)\n+            yield Accumulate(UnpackInst(),\n+                             rvalue,\n+                             Sum(rvalue, view(pack, tuple((0, i) for i in multiindex))))\n+        else:\n+            multiindex = tuple(Index(e) for e in pack.shape)\n+            yield Accumulate(UnpackInst(),\n+                             self._rvalue(multiindex, loop_indices=loop_indices),\n+                             view(pack, tuple((0, i) for i in multiindex)))\n+\n+\n+class MixedDatPack(Pack):\n+    def __init__(self, packs, access, dtype, interior_horizontal):\n+        self.packs = packs\n+        self.access = access\n+        self.dtype = dtype\n+        self.interior_horizontal = interior_horizontal\n+\n+    def pack(self, loop_indices=None):\n+        if hasattr(self, \"_pack\"):\n+            return self._pack\n+\n+        flat_shape = numpy.sum(tuple(numpy.prod(p.map_.shape[1:] + p.outer.shape[1:]) for p in self.packs))\n+\n+        if self.interior_horizontal:\n+            _shape = (2,)\n+            flat_shape *= 2\n+        else:\n+            _shape = (1,)\n+\n+        if self.access in {INC, WRITE}:\n+            val = Zero((), self.dtype)\n+            multiindex = MultiIndex(Index(flat_shape))\n+            self._pack = Materialise(PackInst(), val, multiindex)\n+        else:\n+            multiindex = MultiIndex(Index(flat_shape))\n+            val = Zero((), self.dtype)\n+            expressions = []\n+            offset = 0\n+            for p in self.packs:\n+                shape = _shape + p.map_.shape[1:] + p.outer.shape[1:]\n+                mi = MultiIndex(*(Index(e) for e in shape))\n+                expr = p._rvalue(mi, loop_indices)\n+                extents = [numpy.prod(shape[i+1:], dtype=numpy.int32) for i in range(len(shape))]\n+                index = reduce(Sum, [Product(i, Literal(IntType.type(e), casting=False)) for i, e in zip(mi, extents)], Literal(IntType.type(0), casting=False))\n+                indices = MultiIndex(Sum(index, Literal(IntType.type(offset, casting=False))),)\n+                offset += numpy.prod(shape, dtype=numpy.int32)\n+                expressions.append(expr)\n+                expressions.append(indices)\n+\n+            self._pack = Materialise(PackInst(), val, multiindex, *expressions)\n+\n+        return self._pack\n+\n+    def kernel_arg(self, loop_indices=None):\n+        pack = self.pack(loop_indices)\n+        shape = pack.shape\n+        return Indexed(pack, (Index(e) for e in shape))\n+\n+    def emit_unpack_instruction(self, *,\n+                                loop_indices=None):\n+        pack = self.pack(loop_indices)\n+\n+        if self.access is READ:\n+            yield None\n+        else:\n+            if self.interior_horizontal:\n+                _shape = (2,)\n+            else:\n+                _shape = (1,)\n+            offset = 0\n+            for p in self.packs:\n+                shape = _shape + p.map_.shape[1:] + p.outer.shape[1:]\n+                mi = MultiIndex(*(Index(e) for e in shape))\n+                rvalue = p._rvalue(mi, loop_indices)\n+                extents = [numpy.prod(shape[i+1:], dtype=numpy.int32) for i in range(len(shape))]\n+                index = reduce(Sum, [Product(i, Literal(IntType.type(e), casting=False)) for i, e in zip(mi, extents)], Literal(IntType.type(0), casting=False))\n+                indices = MultiIndex(Sum(index, Literal(IntType.type(offset), casting=False)),)\n+                rhs = Indexed(pack, indices)\n+                offset += numpy.prod(shape, dtype=numpy.int32)\n+\n+                if self.access is INC:\n+                    rhs = Sum(rvalue, rhs)\n+\n+                yield Accumulate(UnpackInst(), rvalue, rhs)\n+\n+\n+class MatPack(Pack):\n+    def __init__(self, outer, access, maps, dims, dtype, interior_horizontal=False):\n+        self.outer = outer\n+        self.access = access\n+        self.maps = maps\n+        self.dims = dims\n+        self.dtype = dtype\n+        self.interior_horizontal = interior_horizontal\n+\n+    def pack(self, loop_indices=None):\n+        if hasattr(self, \"_pack\"):\n+            return self._pack\n+        ((rdim, cdim), ), = self.dims\n+        rmap, cmap = self.maps\n+        if self.interior_horizontal:\n+            shape = (2, )\n+        else:\n+            shape = (1, )\n+        rshape = shape + rmap.shape[1:] + (rdim, )\n+        cshape = shape + cmap.shape[1:] + (cdim, )\n+        if self.access in {WRITE, INC}:\n+            val = Zero((), self.dtype)\n+            multiindex = MultiIndex(*(Index(e) for e in (rshape + cshape)))\n+            pack = Materialise(PackInst(), val, multiindex)\n+            self._pack = pack\n+            return pack\n+        else:\n+            raise ValueError(\"Unexpected access type\")\n+\n+    def kernel_arg(self, loop_indices=None):\n+        pack = self.pack(loop_indices=loop_indices)\n+        return Indexed(pack, tuple(Index(e) for e in pack.shape))\n+\n+    def emit_unpack_instruction(self, *,\n+                                loop_indices=None):\n+        ((rdim, cdim), ), = self.dims\n+        rmap, cmap = self.maps\n+        try:\n+            n, layer = loop_indices\n+        except ValueError:\n+            n, = loop_indices\n+            layer = None\n+        vector = rmap.vector_bc or cmap.vector_bc\n+        if vector:\n+            maps = [map_.indexed_vector(n, (dim, ), layer=layer)\n+                    for map_, dim in zip(self.maps, (rdim, cdim))]\n+        else:\n+            maps = []\n+            for map_ in self.maps:\n+                if map_.implicit_bcs:\n+                    maps.append(map_.indexed_implicit(n, layer=layer))\n+                else:\n+                    i = Index()\n+                    if self.interior_horizontal:\n+                        f = Index(2)\n+                    else:\n+                        f = Index(1)\n+                    maps.append(map_.indexed((n, i, f), layer=layer))\n+        (rmap, cmap), (rindices, cindices) = zip(*maps)\n+\n+        pack = self.pack(loop_indices=loop_indices)\n+        if vector:\n+            free_indices = rindices + cindices\n+            pack = Indexed(pack, free_indices)\n+            name = \"MatSetValuesLocal\"\n+        else:\n+            # TODO: What is this?","path":"pyop2/codegen/builder.py","position":510,"original_position":510,"commit_id":"a761af0c9df815e49856e0a34957c43267c96017","original_commit_id":"a761af0c9df815e49856e0a34957c43267c96017","user":{"login":"tj-sun","id":9328006,"node_id":"MDQ6VXNlcjkzMjgwMDY=","avatar_url":"https://avatars2.githubusercontent.com/u/9328006?v=4","gravatar_id":"","url":"https://api.github.com/users/tj-sun","html_url":"https://github.com/tj-sun","followers_url":"https://api.github.com/users/tj-sun/followers","following_url":"https://api.github.com/users/tj-sun/following{/other_user}","gists_url":"https://api.github.com/users/tj-sun/gists{/gist_id}","starred_url":"https://api.github.com/users/tj-sun/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tj-sun/subscriptions","organizations_url":"https://api.github.com/users/tj-sun/orgs","repos_url":"https://api.github.com/users/tj-sun/repos","events_url":"https://api.github.com/users/tj-sun/events{/privacy}","received_events_url":"https://api.github.com/users/tj-sun/received_events","type":"User","site_admin":false},"body":"Yes, forgot to remove the line.","created_at":"2018-12-04T20:07:57Z","updated_at":"2018-12-04T20:07:57Z","html_url":"https://github.com/OP2/PyOP2/pull/555#discussion_r238819280","pull_request_url":"https://api.github.com/repos/OP2/PyOP2/pulls/555","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/OP2/PyOP2/pulls/comments/238819280"},"html":{"href":"https://github.com/OP2/PyOP2/pull/555#discussion_r238819280"},"pull_request":{"href":"https://api.github.com/repos/OP2/PyOP2/pulls/555"}},"in_reply_to_id":237557819},"pull_request":{"url":"https://api.github.com/repos/OP2/PyOP2/pulls/555","id":233214382,"node_id":"MDExOlB1bGxSZXF1ZXN0MjMzMjE0Mzgy","html_url":"https://github.com/OP2/PyOP2/pull/555","diff_url":"https://github.com/OP2/PyOP2/pull/555.diff","patch_url":"https://github.com/OP2/PyOP2/pull/555.patch","issue_url":"https://api.github.com/repos/OP2/PyOP2/issues/555","number":555,"state":"open","locked":false,"title":"loopy codegen","user":{"login":"tj-sun","id":9328006,"node_id":"MDQ6VXNlcjkzMjgwMDY=","avatar_url":"https://avatars2.githubusercontent.com/u/9328006?v=4","gravatar_id":"","url":"https://api.github.com/users/tj-sun","html_url":"https://github.com/tj-sun","followers_url":"https://api.github.com/users/tj-sun/followers","following_url":"https://api.github.com/users/tj-sun/following{/other_user}","gists_url":"https://api.github.com/users/tj-sun/gists{/gist_id}","starred_url":"https://api.github.com/users/tj-sun/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tj-sun/subscriptions","organizations_url":"https://api.github.com/users/tj-sun/orgs","repos_url":"https://api.github.com/users/tj-sun/repos","events_url":"https://api.github.com/users/tj-sun/events{/privacy}","received_events_url":"https://api.github.com/users/tj-sun/received_events","type":"User","site_admin":false},"body":"This pull request aims to\r\n- support loopy kernel for parloops\r\n- rewrite wrapper code generation in `sequential.py` (based on Lawrence's code)\r\n- JITModule caching without code generation","created_at":"2018-11-23T14:23:14Z","updated_at":"2018-12-04T20:07:57Z","closed_at":null,"merged_at":null,"merge_commit_sha":"b13582476e4cb0beafea2fc289b6fb50d7122686","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/OP2/PyOP2/pulls/555/commits","review_comments_url":"https://api.github.com/repos/OP2/PyOP2/pulls/555/comments","review_comment_url":"https://api.github.com/repos/OP2/PyOP2/pulls/comments{/number}","comments_url":"https://api.github.com/repos/OP2/PyOP2/issues/555/comments","statuses_url":"https://api.github.com/repos/OP2/PyOP2/statuses/a761af0c9df815e49856e0a34957c43267c96017","head":{"label":"OP2:kaushik","ref":"kaushik","sha":"a761af0c9df815e49856e0a34957c43267c96017","user":{"login":"OP2","id":1544411,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE1NDQ0MTE=","avatar_url":"https://avatars3.githubusercontent.com/u/1544411?v=4","gravatar_id":"","url":"https://api.github.com/users/OP2","html_url":"https://github.com/OP2","followers_url":"https://api.github.com/users/OP2/followers","following_url":"https://api.github.com/users/OP2/following{/other_user}","gists_url":"https://api.github.com/users/OP2/gists{/gist_id}","starred_url":"https://api.github.com/users/OP2/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/OP2/subscriptions","organizations_url":"https://api.github.com/users/OP2/orgs","repos_url":"https://api.github.com/users/OP2/repos","events_url":"https://api.github.com/users/OP2/events{/privacy}","received_events_url":"https://api.github.com/users/OP2/received_events","type":"Organization","site_admin":false},"repo":{"id":4648505,"node_id":"MDEwOlJlcG9zaXRvcnk0NjQ4NTA1","name":"PyOP2","full_name":"OP2/PyOP2","private":false,"owner":{"login":"OP2","id":1544411,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE1NDQ0MTE=","avatar_url":"https://avatars3.githubusercontent.com/u/1544411?v=4","gravatar_id":"","url":"https://api.github.com/users/OP2","html_url":"https://github.com/OP2","followers_url":"https://api.github.com/users/OP2/followers","following_url":"https://api.github.com/users/OP2/following{/other_user}","gists_url":"https://api.github.com/users/OP2/gists{/gist_id}","starred_url":"https://api.github.com/users/OP2/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/OP2/subscriptions","organizations_url":"https://api.github.com/users/OP2/orgs","repos_url":"https://api.github.com/users/OP2/repos","events_url":"https://api.github.com/users/OP2/events{/privacy}","received_events_url":"https://api.github.com/users/OP2/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/OP2/PyOP2","description":"Framework for performance-portable parallel computations on unstructured meshes","fork":false,"url":"https://api.github.com/repos/OP2/PyOP2","forks_url":"https://api.github.com/repos/OP2/PyOP2/forks","keys_url":"https://api.github.com/repos/OP2/PyOP2/keys{/key_id}","collaborators_url":"https://api.github.com/repos/OP2/PyOP2/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/OP2/PyOP2/teams","hooks_url":"https://api.github.com/repos/OP2/PyOP2/hooks","issue_events_url":"https://api.github.com/repos/OP2/PyOP2/issues/events{/number}","events_url":"https://api.github.com/repos/OP2/PyOP2/events","assignees_url":"https://api.github.com/repos/OP2/PyOP2/assignees{/user}","branches_url":"https://api.github.com/repos/OP2/PyOP2/branches{/branch}","tags_url":"https://api.github.com/repos/OP2/PyOP2/tags","blobs_url":"https://api.github.com/repos/OP2/PyOP2/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/OP2/PyOP2/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/OP2/PyOP2/git/refs{/sha}","trees_url":"https://api.github.com/repos/OP2/PyOP2/git/trees{/sha}","statuses_url":"https://api.github.com/repos/OP2/PyOP2/statuses/{sha}","languages_url":"https://api.github.com/repos/OP2/PyOP2/languages","stargazers_url":"https://api.github.com/repos/OP2/PyOP2/stargazers","contributors_url":"https://api.github.com/repos/OP2/PyOP2/contributors","subscribers_url":"https://api.github.com/repos/OP2/PyOP2/subscribers","subscription_url":"https://api.github.com/repos/OP2/PyOP2/subscription","commits_url":"https://api.github.com/repos/OP2/PyOP2/commits{/sha}","git_commits_url":"https://api.github.com/repos/OP2/PyOP2/git/commits{/sha}","comments_url":"https://api.github.com/repos/OP2/PyOP2/comments{/number}","issue_comment_url":"https://api.github.com/repos/OP2/PyOP2/issues/comments{/number}","contents_url":"https://api.github.com/repos/OP2/PyOP2/contents/{+path}","compare_url":"https://api.github.com/repos/OP2/PyOP2/compare/{base}...{head}","merges_url":"https://api.github.com/repos/OP2/PyOP2/merges","archive_url":"https://api.github.com/repos/OP2/PyOP2/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/OP2/PyOP2/downloads","issues_url":"https://api.github.com/repos/OP2/PyOP2/issues{/number}","pulls_url":"https://api.github.com/repos/OP2/PyOP2/pulls{/number}","milestones_url":"https://api.github.com/repos/OP2/PyOP2/milestones{/number}","notifications_url":"https://api.github.com/repos/OP2/PyOP2/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/OP2/PyOP2/labels{/name}","releases_url":"https://api.github.com/repos/OP2/PyOP2/releases{/id}","deployments_url":"https://api.github.com/repos/OP2/PyOP2/deployments","created_at":"2012-06-13T08:28:09Z","updated_at":"2018-11-09T14:18:22Z","pushed_at":"2018-12-04T13:08:13Z","git_url":"git://github.com/OP2/PyOP2.git","ssh_url":"git@github.com:OP2/PyOP2.git","clone_url":"https://github.com/OP2/PyOP2.git","svn_url":"https://github.com/OP2/PyOP2","homepage":"op2.github.com/PyOP2","size":12004,"stargazers_count":44,"watchers_count":44,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":20,"mirror_url":null,"archived":false,"open_issues_count":12,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":20,"open_issues":12,"watchers":44,"default_branch":"master"}},"base":{"label":"OP2:master","ref":"master","sha":"df3f03a371654444b3bc7deade0759f8ee9314ce","user":{"login":"OP2","id":1544411,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE1NDQ0MTE=","avatar_url":"https://avatars3.githubusercontent.com/u/1544411?v=4","gravatar_id":"","url":"https://api.github.com/users/OP2","html_url":"https://github.com/OP2","followers_url":"https://api.github.com/users/OP2/followers","following_url":"https://api.github.com/users/OP2/following{/other_user}","gists_url":"https://api.github.com/users/OP2/gists{/gist_id}","starred_url":"https://api.github.com/users/OP2/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/OP2/subscriptions","organizations_url":"https://api.github.com/users/OP2/orgs","repos_url":"https://api.github.com/users/OP2/repos","events_url":"https://api.github.com/users/OP2/events{/privacy}","received_events_url":"https://api.github.com/users/OP2/received_events","type":"Organization","site_admin":false},"repo":{"id":4648505,"node_id":"MDEwOlJlcG9zaXRvcnk0NjQ4NTA1","name":"PyOP2","full_name":"OP2/PyOP2","private":false,"owner":{"login":"OP2","id":1544411,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE1NDQ0MTE=","avatar_url":"https://avatars3.githubusercontent.com/u/1544411?v=4","gravatar_id":"","url":"https://api.github.com/users/OP2","html_url":"https://github.com/OP2","followers_url":"https://api.github.com/users/OP2/followers","following_url":"https://api.github.com/users/OP2/following{/other_user}","gists_url":"https://api.github.com/users/OP2/gists{/gist_id}","starred_url":"https://api.github.com/users/OP2/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/OP2/subscriptions","organizations_url":"https://api.github.com/users/OP2/orgs","repos_url":"https://api.github.com/users/OP2/repos","events_url":"https://api.github.com/users/OP2/events{/privacy}","received_events_url":"https://api.github.com/users/OP2/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/OP2/PyOP2","description":"Framework for performance-portable parallel computations on unstructured meshes","fork":false,"url":"https://api.github.com/repos/OP2/PyOP2","forks_url":"https://api.github.com/repos/OP2/PyOP2/forks","keys_url":"https://api.github.com/repos/OP2/PyOP2/keys{/key_id}","collaborators_url":"https://api.github.com/repos/OP2/PyOP2/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/OP2/PyOP2/teams","hooks_url":"https://api.github.com/repos/OP2/PyOP2/hooks","issue_events_url":"https://api.github.com/repos/OP2/PyOP2/issues/events{/number}","events_url":"https://api.github.com/repos/OP2/PyOP2/events","assignees_url":"https://api.github.com/repos/OP2/PyOP2/assignees{/user}","branches_url":"https://api.github.com/repos/OP2/PyOP2/branches{/branch}","tags_url":"https://api.github.com/repos/OP2/PyOP2/tags","blobs_url":"https://api.github.com/repos/OP2/PyOP2/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/OP2/PyOP2/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/OP2/PyOP2/git/refs{/sha}","trees_url":"https://api.github.com/repos/OP2/PyOP2/git/trees{/sha}","statuses_url":"https://api.github.com/repos/OP2/PyOP2/statuses/{sha}","languages_url":"https://api.github.com/repos/OP2/PyOP2/languages","stargazers_url":"https://api.github.com/repos/OP2/PyOP2/stargazers","contributors_url":"https://api.github.com/repos/OP2/PyOP2/contributors","subscribers_url":"https://api.github.com/repos/OP2/PyOP2/subscribers","subscription_url":"https://api.github.com/repos/OP2/PyOP2/subscription","commits_url":"https://api.github.com/repos/OP2/PyOP2/commits{/sha}","git_commits_url":"https://api.github.com/repos/OP2/PyOP2/git/commits{/sha}","comments_url":"https://api.github.com/repos/OP2/PyOP2/comments{/number}","issue_comment_url":"https://api.github.com/repos/OP2/PyOP2/issues/comments{/number}","contents_url":"https://api.github.com/repos/OP2/PyOP2/contents/{+path}","compare_url":"https://api.github.com/repos/OP2/PyOP2/compare/{base}...{head}","merges_url":"https://api.github.com/repos/OP2/PyOP2/merges","archive_url":"https://api.github.com/repos/OP2/PyOP2/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/OP2/PyOP2/downloads","issues_url":"https://api.github.com/repos/OP2/PyOP2/issues{/number}","pulls_url":"https://api.github.com/repos/OP2/PyOP2/pulls{/number}","milestones_url":"https://api.github.com/repos/OP2/PyOP2/milestones{/number}","notifications_url":"https://api.github.com/repos/OP2/PyOP2/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/OP2/PyOP2/labels{/name}","releases_url":"https://api.github.com/repos/OP2/PyOP2/releases{/id}","deployments_url":"https://api.github.com/repos/OP2/PyOP2/deployments","created_at":"2012-06-13T08:28:09Z","updated_at":"2018-11-09T14:18:22Z","pushed_at":"2018-12-04T13:08:13Z","git_url":"git://github.com/OP2/PyOP2.git","ssh_url":"git@github.com:OP2/PyOP2.git","clone_url":"https://github.com/OP2/PyOP2.git","svn_url":"https://github.com/OP2/PyOP2","homepage":"op2.github.com/PyOP2","size":12004,"stargazers_count":44,"watchers_count":44,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":20,"mirror_url":null,"archived":false,"open_issues_count":12,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":20,"open_issues":12,"watchers":44,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/OP2/PyOP2/pulls/555"},"html":{"href":"https://github.com/OP2/PyOP2/pull/555"},"issue":{"href":"https://api.github.com/repos/OP2/PyOP2/issues/555"},"comments":{"href":"https://api.github.com/repos/OP2/PyOP2/issues/555/comments"},"review_comments":{"href":"https://api.github.com/repos/OP2/PyOP2/pulls/555/comments"},"review_comment":{"href":"https://api.github.com/repos/OP2/PyOP2/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/OP2/PyOP2/pulls/555/commits"},"statuses":{"href":"https://api.github.com/repos/OP2/PyOP2/statuses/a761af0c9df815e49856e0a34957c43267c96017"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"tj-sun"}}