{"action":"created","comment":{"url":"https://api.github.com/repos/OP2/PyOP2/pulls/comments/239777477","pull_request_review_id":182653929,"id":239777477,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIzOTc3NzQ3Nw==","diff_hunk":"@@ -0,0 +1,756 @@\n+import ctypes\n+import numpy\n+\n+import loopy\n+import islpy as isl\n+import pymbolic.primitives as pym\n+\n+from collections import OrderedDict, defaultdict\n+from functools import singledispatch, reduce\n+import itertools\n+import operator\n+\n+from gem.node import traversal, Node, Memoizer, reuse_if_untouched\n+\n+from pyop2.base import READ\n+from pyop2.datatypes import as_ctypes\n+\n+from pyop2.codegen.optimise import index_merger\n+\n+from pyop2.codegen.representation import (Index, FixedIndex, RuntimeIndex,\n+                                          MultiIndex, Extent, Indexed,\n+                                          BitShift, BitwiseNot, BitwiseAnd, BitwiseOr,\n+                                          Conditional, Comparison, DummyInstruction,\n+                                          LogicalNot, LogicalAnd, LogicalOr,\n+                                          Materialise, Accumulate, FunctionCall, When,\n+                                          Argument, Variable, Literal, NamedLiteral,\n+                                          Symbol, Zero, Sum, Product)\n+from pyop2.codegen.representation import (PackInst, UnpackInst, KernelInst)\n+from pytools import ImmutableRecord\n+\n+\n+class Bag(object):\n+    pass\n+\n+\n+def symbol_mangler(kernel, name):\n+    if name in {\"ADD_VALUES\", \"INSERT_VALUES\"}:\n+        return loopy.types.to_loopy_type(numpy.int32), name\n+    return None\n+\n+\n+class PetscCallable(loopy.ScalarCallable):\n+\n+    def with_types(self, arg_id_to_dtype, kernel, callables_table):\n+        new_arg_id_to_dtype = arg_id_to_dtype.copy()\n+        return (self.copy(\n+            name_in_target=self.name,\n+            arg_id_to_dtype=new_arg_id_to_dtype), callables_table)\n+\n+    def with_descrs(self, arg_id_to_descr, callables_table):\n+        from loopy.kernel.function_interface import ArrayArgDescriptor\n+        from loopy.kernel.array import FixedStrideArrayDimTag\n+        new_arg_id_to_descr = arg_id_to_descr.copy()\n+        for i, des in arg_id_to_descr.items():\n+            # petsc takes 1D arrays as arguments\n+            if isinstance(des, ArrayArgDescriptor):\n+                dim_tags = tuple(\n+                    FixedStrideArrayDimTag(\n+                        stride=int(numpy.prod(des.shape[i+1:])),\n+                        layout_nesting_level=len(des.shape)-i-1\n+                    )\n+                    for i in range(len(des.shape))\n+                )\n+                new_arg_id_to_descr[i] = des.copy(dim_tags=dim_tags)\n+\n+        return (self.copy(arg_id_to_descr=new_arg_id_to_descr),\n+                callables_table)\n+\n+    def generate_preambles(self, target):\n+        assert isinstance(target, loopy.CTarget)\n+        yield(\"00_petsc\", \"#include <petsc.h>\")\n+        return\n+\n+\n+def petsc_function_lookup(target, identifier):\n+    if identifier == 'MatSetValuesBlockedLocal':\n+        return PetscCallable(name=identifier)\n+    elif identifier == 'MatSetValuesLocal':\n+        return PetscCallable(name=identifier)\n+    return None\n+\n+\n+class _PreambleGen(ImmutableRecord):\n+    fields = set((\"preamble\", ))\n+\n+    def __init__(self, preamble):\n+        self.preamble = preamble\n+\n+    def __call__(self, preamble_info):\n+        yield (\"0\", self.preamble)\n+\n+\n+class PyOP2KernelCallable(loopy.ScalarCallable):\n+    \"\"\"Handles PyOP2 Kernel passed in as a string\n+    \"\"\"\n+\n+    fields = set([\"name\", \"access\", \"arg_id_to_dtype\", \"arg_id_to_descr\", \"name_in_target\"])\n+    init_arg_names = (\"name\", \"access\", \"arg_id_to_dtype\", \"arg_id_to_descr\", \"name_in_target\")\n+\n+    def __init__(self, name, access, arg_id_to_dtype=None, arg_id_to_descr=None, name_in_target=None):\n+        super(PyOP2KernelCallable, self).__init__(name, arg_id_to_dtype, arg_id_to_descr, name_in_target)\n+        self.access = access\n+\n+    def with_types(self, arg_id_to_dtype, kernel, callables_table):\n+        new_arg_id_to_dtype = arg_id_to_dtype.copy()\n+        return self.copy(\n+            name_in_target=self.name,\n+            arg_id_to_dtype=new_arg_id_to_dtype), callables_table\n+\n+    def with_descrs(self, arg_id_to_descr, callables_table):\n+        from loopy.kernel.function_interface import ArrayArgDescriptor\n+        from loopy.kernel.array import FixedStrideArrayDimTag\n+        new_arg_id_to_descr = arg_id_to_descr.copy()\n+        for i, des in arg_id_to_descr.items():\n+            # 1D arrays\n+            if isinstance(des, ArrayArgDescriptor):\n+                dim_tags = tuple(\n+                    FixedStrideArrayDimTag(\n+                        stride=int(numpy.prod(des.shape[i+1:])),\n+                        layout_nesting_level=len(des.shape)-i-1\n+                    )\n+                    for i in range(len(des.shape))\n+                )\n+                new_arg_id_to_descr[i] = des.copy(dim_tags=dim_tags)\n+        return (self.copy(arg_id_to_descr=new_arg_id_to_descr), callables_table)\n+\n+    def emit_call_insn(self, insn, target, expression_to_code_mapper):\n+        # reorder arguments, e.g. a,c = f(b,d) to f(a,b,c,d)\n+        parameters = []\n+        reads = iter(insn.expression.parameters)\n+        writes = iter(insn.assignees)\n+        for ac in self.access:\n+            if ac is READ:\n+                parameters.append(next(reads))\n+            else:\n+                parameters.append(next(writes))\n+\n+        # pass layer argument if needed\n+        for layer in reads:\n+            parameters.append(layer)\n+\n+        par_dtypes = tuple(expression_to_code_mapper.infer_type(p) for p in parameters)\n+\n+        from loopy.expression import dtype_to_type_context\n+        from pymbolic.mapper.stringifier import PREC_NONE\n+        from pymbolic import var\n+\n+        c_parameters = [\n+            expression_to_code_mapper(\n+                par, PREC_NONE, dtype_to_type_context(target, par_dtype),\n+                par_dtype).expr\n+            for par, par_dtype in zip(parameters, par_dtypes)]\n+\n+        assignee_is_returned = False\n+        return var(self.name_in_target)(*c_parameters), assignee_is_returned\n+\n+\n+class PyOP2_Kernel_Lookup(object):\n+\n+    def __init__(self, name, code, access):\n+        self.name = name\n+        self.code = code\n+        self.access = access\n+\n+    def __hash__(self):\n+        return hash(self.name + self.code)\n+\n+    def __eq__(self, other):\n+        if isinstance(other, PyOP2_Kernel_Lookup):\n+            return self.name == other.name and self.code == other.code\n+        return False\n+\n+    def __call__(self, target, identifier):\n+        if identifier == self.name:\n+            return PyOP2KernelCallable(name=identifier, access=self.access)\n+        return None\n+\n+\n+@singledispatch\n+def replace_materialise(node, self):\n+    raise AssertionError(\"Unhandled node type %r\" % type(node))\n+\n+\n+replace_materialise.register(Node)(reuse_if_untouched)\n+\n+\n+@replace_materialise.register(Materialise)\n+def replace_materialise_materialise(node, self):\n+    v = Variable(node.name, node.shape, node.dtype)\n+    inits = list(map(self, node.children))\n+    label = node.label\n+    accs = []\n+    for rvalue, indices in zip(*(inits[0::2], inits[1::2])):\n+        lvalue = Indexed(v, indices)\n+        if isinstance(rvalue, When):\n+            when, rvalue = rvalue.children\n+            acc = When(when, Accumulate(label, lvalue, rvalue))\n+        else:\n+            acc = Accumulate(label, lvalue, rvalue)\n+        accs.append(acc)\n+    self.initialisers.append(tuple(accs))\n+    return v\n+\n+\n+def runtime_indices(expressions):\n+    indices = []\n+    for node in traversal(expressions):\n+        if isinstance(node, RuntimeIndex):\n+            indices.append(node.name)\n+\n+    return frozenset(indices)\n+\n+\n+def imperatives(exprs):\n+    for op in traversal(exprs):\n+        if isinstance(op, (Accumulate, FunctionCall)):\n+            yield op\n+\n+\n+def loop_nesting(instructions, deps, outer_inames, kernel_name):\n+\n+    nesting = {}\n+\n+    for insn in imperatives(instructions):\n+        if isinstance(insn, Accumulate):\n+            if isinstance(insn.children[1], (Zero, Literal)):\n+                nesting[insn] = outer_inames\n+            else:\n+                nesting[insn] = runtime_indices([insn])\n+        else:\n+            assert isinstance(insn, FunctionCall)\n+            if insn.name in [kernel_name, \"MatSetValuesBlockedLocal\", \"MatSetValuesLocal\"]:\n+                nesting[insn] = outer_inames\n+            else:\n+                nesting[insn] = runtime_indices([insn])\n+\n+    # take care of dependencies. e.g. t1[i] = A[i], t2[j] = B[t1[j]], then t2 should depends on {i, j}\n+    name_to_insn = dict((n, i) for i, (n, _) in deps.items())\n+    for insn, (name, _deps) in deps.items():\n+        s = set(_deps)\n+        while s:\n+            d = s.pop()\n+            nesting[insn] = nesting[insn] | nesting[name_to_insn[d]]\n+            s = s | set(deps[name_to_insn[d]][1]) - set([name])\n+\n+    # boost inames, if one instruction is inside inner inames (free indices),\n+    # it should be inside the outer inames as dictated by other instructions.\n+    index_nesting = defaultdict(frozenset)  # free index -> {runtime indices}\n+    for insn in instructions:\n+        if isinstance(insn, When):\n+            key = insn.children[1]\n+        else:\n+            key = insn\n+        for fi in traversal([insn]):\n+            if isinstance(fi, Index):\n+                index_nesting[fi] |= nesting[key]\n+\n+    for insn in imperatives(instructions):\n+        outer = reduce(operator.or_,\n+                       iter(index_nesting[fi] for fi in traversal([insn]) if isinstance(fi, Index)),\n+                       frozenset())\n+        nesting[insn] = nesting[insn] | outer\n+\n+    return nesting\n+\n+\n+def instruction_dependencies(instructions, initialisers):\n+\n+    deps = {}\n+    names = {}\n+    instructions_by_type = defaultdict(list)\n+    c = itertools.count()\n+    for op in imperatives(instructions):\n+        name = \"statement%d\" % next(c)\n+        names[op] = name\n+        instructions_by_type[type(op.label)].append(op)\n+        deps[op] = frozenset()\n+\n+    # read-write dependencies in packing instructions\n+    def variables(exprs):\n+        for op in traversal(exprs):\n+            if isinstance(op, (Argument, Variable)):\n+                yield op\n+\n+    def bounds(exprs):\n+        for op in traversal(exprs):\n+            if isinstance(op, RuntimeIndex):\n+                for v in variables(op.extents):\n+                    yield v\n+\n+    writers = defaultdict(list)\n+    for op in instructions_by_type[PackInst]:\n+        assert isinstance(op, Accumulate)\n+        lvalue, _ = op.children\n+        # Only writes to the outer-most variable\n+        writes = next(variables([lvalue]))\n+        if isinstance(writes, Variable):\n+            writers[writes].append(names[op])\n+\n+    for op in instructions_by_type[PackInst]:\n+        _, rvalue = op.children\n+        deps[op] |= frozenset(x for x in itertools.chain(*(\n+            writers[r] for r in itertools.chain(variables([rvalue]), bounds([op]))\n+        )))\n+        deps[op] -= frozenset(names[op])\n+\n+    # kernel instructions depends on packing instructions\n+    for op in instructions_by_type[KernelInst]:\n+        deps[op] |= frozenset(names[o] for o in instructions_by_type[PackInst])\n+\n+    # unpacking instructions depends on kernel instructions\n+    for op in instructions_by_type[UnpackInst]:\n+        deps[op] |= frozenset(names[o] for o in instructions_by_type[KernelInst])\n+\n+    # add sequential instructions\n+    for inits in initialisers:\n+        for i, parent in enumerate(inits[1:], 1):\n+            for p in imperatives([parent]):\n+                deps[p] |= frozenset(names[c] for c in imperatives(inits[:i])) - frozenset([name])\n+\n+    # add name to deps\n+    deps = dict((op, (names[op], dep)) for op, dep in deps.items())\n+    return deps\n+\n+\n+def instruction_names(instructions):\n+    c = itertools.count()\n+    names = {}\n+    for insn in traversal(instructions):\n+        names[insn] = \"statement%d\" % next(c)\n+    return names\n+\n+\n+def generate(builder, wrapper_name=None):\n+    parameters = Bag()\n+    parameters.domains = OrderedDict()\n+    parameters.assumptions = OrderedDict()\n+    parameters.wrapper_arguments = builder.wrapper_args\n+    parameters.conditions = []\n+    parameters.kernel_data = list(None for _ in parameters.wrapper_arguments)\n+    parameters.temporaries = OrderedDict()\n+    parameters.kernel_name = builder.kernel.name\n+\n+    if builder.layer_index is not None:\n+        outer_inames = frozenset([builder._loop_index.name,\n+                                  builder.layer_index.name])\n+    else:\n+        outer_inames = frozenset([builder._loop_index.name])\n+\n+    instructions = list(builder.emit_instructions())\n+\n+    # replace Materialise\n+    mapper = Memoizer(replace_materialise)\n+    mapper.initialisers = []\n+    instructions = list(mapper(i) for i in instructions)\n+\n+    # merge indices\n+    merger = index_merger(instructions)\n+    instructions = list(merger(i) for i in instructions)\n+    initialiser = list(itertools.chain(*mapper.initialisers))\n+    merger = index_merger(initialiser)\n+    initialiser = list(merger(i) for i in initialiser)\n+    instructions = instructions + initialiser\n+    mapper.initialisers = [tuple(merger(i) for i in inits) for inits in mapper.initialisers]\n+\n+    deps = instruction_dependencies(instructions, mapper.initialisers)\n+    within_inames = loop_nesting(instructions, deps, outer_inames, parameters.kernel_name)\n+\n+    # generate loopy\n+    context = Bag()\n+    context.parameters = parameters\n+    context.within_inames = within_inames\n+    context.conditions = []\n+    context.index_ordering = []\n+    context.instruction_dependencies = deps\n+\n+    statements = list(statement(insn, context) for insn in instructions)\n+    statements = list(s for s in statements if not isinstance(s, DummyInstruction))\n+\n+    domains = list(parameters.domains.values())\n+    if builder.single_cell:\n+        new_domains = []\n+        for d in domains:\n+            if d.get_dim_name(isl.dim_type.set, 0) == \"n\":\n+                # n = start\n+                new_domains.append(d.add_constraint(isl.Constraint.eq_from_names(d.space, {\"n\": 1, \"start\": -1})))\n+            else:\n+                new_domains.append(d)\n+        domains = new_domains\n+        if builder.extruded:\n+            new_domains = []\n+            for d in domains:\n+                if d.get_dim_name(isl.dim_type.set, 0) == \"layer\":\n+                    # layer = t1 - 1\n+                    t1 = builder.layer_extents[1].name\n+                    new_domains.append(d.add_constraint(isl.Constraint.eq_from_names(d.space, {\"layer\": 1, t1: -1, 1: 1})))\n+                else:\n+                    new_domains.append(d)\n+        domains = new_domains\n+\n+    assumptions, = reduce(operator.and_,\n+                          parameters.assumptions.values()).params().get_basic_sets()\n+    options = loopy.Options(check_dep_resolution=True, ignore_boostable_into=True)\n+\n+    # sometimes masks are not used, but we still need to create the function arguments\n+    for i, arg in enumerate(parameters.wrapper_arguments):\n+        if parameters.kernel_data[i] is None:\n+            arg = loopy.GlobalArg(arg.name, dtype=arg.dtype, shape=arg.shape)\n+            parameters.kernel_data[i] = arg\n+\n+    if wrapper_name is None:\n+        wrapper_name = \"wrap_%s\" % builder.kernel.name\n+\n+    wrapper = loopy.make_kernel(domains,\n+                                statements,\n+                                kernel_data=parameters.kernel_data,\n+                                target=loopy.CTarget(),\n+                                temporary_variables=parameters.temporaries,\n+                                symbol_manglers=[symbol_mangler],\n+                                options=options,\n+                                assumptions=assumptions,\n+                                lang_version=(2018, 2),\n+                                name=wrapper_name)\n+\n+    # additional assumptions\n+    wrapper = loopy.assume(wrapper, \"start < end\")\n+    if builder.extruded:\n+        t0, t1 = builder.layer_extents\n+        wrapper = loopy.assume(wrapper, \"{0} < {1}\".format(t0.name, t1.name))\n+\n+    # prioritize loops\n+    for indices in context.index_ordering:\n+        wrapper = loopy.prioritize_loops(wrapper, indices)\n+\n+    # register kernel\n+    kernel = builder.kernel\n+    headers = set(kernel._headers)\n+    headers = headers | set([\"#include <math.h>\"])\n+    preamble = \"\\n\".join(sorted(headers))\n+\n+    if isinstance(kernel._code, loopy.LoopKernel):\n+        knl = kernel._code\n+        wrapper = loopy.register_callable_kernel(wrapper, knl)\n+        from loopy.transform.callable import _match_caller_callee_argument_dimension_\n+        wrapper = _match_caller_callee_argument_dimension_(wrapper, knl.name)\n+        wrapper = loopy.inline_callable_kernel(wrapper, knl.name)\n+    else:\n+        # kernel is a string, add it to preamble\n+        from coffee.base import Node\n+        if isinstance(kernel._code, Node):\n+            code = kernel._code.gencode()\n+        else:\n+            code = kernel._code\n+        wrapper = loopy.register_function_id_to_in_knl_callable_mapper(\n+            wrapper,\n+            PyOP2_Kernel_Lookup(kernel.name, code, tuple(builder.argument_accesses)))\n+        preamble = preamble + \"\\n\" + code\n+\n+    wrapper = loopy.register_preamble_generators(wrapper, [_PreambleGen(preamble)])\n+\n+    # register petsc functions\n+    wrapper = loopy.register_function_id_to_in_knl_callable_mapper(\n+        wrapper, petsc_function_lookup)\n+\n+    return wrapper\n+\n+\n+def argtypes(kernel):\n+    args = []\n+    for arg in kernel.args:\n+        if isinstance(arg, loopy.ValueArg):\n+            args.append(as_ctypes(arg.dtype))\n+        elif isinstance(arg, loopy.ArrayArg):\n+            args.append(ctypes.c_voidp)\n+        else:\n+            raise ValueError(\"Unhandled arg type '%s'\" % type(arg))\n+    return args\n+\n+\n+@singledispatch\n+def statement(expr, context):\n+    raise AssertionError(\"Unhandled statement type '%s'\" % type(expr))\n+\n+\n+@statement.register(DummyInstruction)\n+def statement_dummy(expr, context):\n+    new_children = tuple(expression(c, context.parameters) for c in expr.children)\n+    return DummyInstruction(expr.label, new_children)\n+\n+\n+@statement.register(When)\n+def statement_when(expr, context):\n+    condition, stmt = expr.children\n+    context.conditions.append(expression(condition, context.parameters))\n+    stmt = statement(stmt, context)\n+    context.conditions.pop()\n+    return stmt\n+\n+\n+@statement.register(Accumulate)\n+def statement_assign(expr, context):\n+    lvalue, _ = expr.children\n+    if isinstance(lvalue, Indexed):\n+        context.index_ordering.append(tuple(i.name for i in lvalue.index_ordering()))\n+    lvalue, rvalue = tuple(expression(c, context.parameters) for c in expr.children)\n+    within_inames = context.within_inames[expr]\n+\n+    id, depends_on = context.instruction_dependencies[expr]\n+    predicates = frozenset(context.conditions)\n+    return loopy.Assignment(lvalue, rvalue, within_inames=within_inames,\n+                            predicates=predicates,\n+                            id=id,\n+                            depends_on=depends_on, depends_on_is_final=True)\n+\n+\n+@statement.register(FunctionCall)\n+def statement_functioncall(expr, context):\n+\n+    parameters = context.parameters\n+\n+    from loopy.symbolic import SubArrayRef\n+    from loopy.types import OpaqueType\n+\n+    free_indices = set(i.name for i in expr.free_indices)\n+    writes = []\n+    reads = []\n+    for access, child in zip(expr.access, expr.children):\n+        var = expression(child, parameters)\n+        if isinstance(var, pym.Subscript):\n+            # tensor argument\n+            indices = []\n+            sweeping_indices = []\n+            for index in var.index_tuple:\n+                indices.append(index)\n+                if isinstance(index, pym.Variable) and index.name in free_indices:\n+                    sweeping_indices.append(index)\n+            arg = SubArrayRef(tuple(sweeping_indices), var)\n+        else:\n+            # scalar argument or constant\n+            arg = var\n+        if access is READ or (isinstance(child, Argument) and isinstance(child.dtype, OpaqueType)):\n+            reads.append(arg)\n+        else:\n+            writes.append(arg)\n+\n+    within_inames = context.within_inames[expr]\n+    predicates = frozenset(context.conditions)\n+    id, depends_on = context.instruction_dependencies[expr]\n+\n+    call = pym.Call(pym.Variable(expr.name), tuple(reads))\n+\n+    return loopy.CallInstruction(tuple(writes), call,\n+                                 within_inames=within_inames,\n+                                 predicates=predicates,\n+                                 id=id,\n+                                 depends_on=depends_on, depends_on_is_final=True)\n+\n+\n+@singledispatch\n+def expression(expr, parameters):\n+    raise AssertionError(\"Unhandled expression type '%s'\" % type(expr))\n+\n+\n+@expression.register(Index)\n+def expression_index(expr, parameters):\n+    name = expr.name\n+    if name not in parameters.domains:\n+        vars = isl.make_zero_and_vars([name])\n+        zero = vars[0]\n+        domain = (vars[name].ge_set(zero) & vars[name].lt_set(zero + expr.extent))\n+        parameters.domains[name] = domain\n+    return pym.Variable(name)\n+\n+\n+@expression.register(FixedIndex)\n+def expression_fixedindex(expr, parameters):\n+    return expr.value\n+\n+\n+@expression.register(RuntimeIndex)\n+def expression_runtimeindex(expr, parameters):\n+    @singledispatch\n+    def translate(expr, vars):\n+        raise AssertionError(\"Unhandled type '%s' in domain translation\" % type(expr))\n+\n+    @translate.register(Sum)\n+    def translate_sum(expr, vars):\n+        return operator.add(*(translate(c, vars) for c in expr.children))\n+\n+    @translate.register(Argument)\n+    def translate_argument(expr, vars):\n+        expr = expression(expr, parameters)\n+        return vars[expr.name]\n+\n+    @translate.register(Variable)\n+    def translate_variable(expr, vars):\n+        return vars[expr.name]\n+\n+    @translate.register(Zero)\n+    def translate_zero(expr, vars):\n+        assert expr.shape == ()\n+        return vars[0]\n+\n+    @translate.register(LogicalAnd)\n+    def translate_logicaland(expr, vars):\n+        a, b = (translate(c, vars) for c in expr.children)\n+        return a & b\n+\n+    @translate.register(Comparison)\n+    def translate_comparison(expr, vars):\n+        a, b = (translate(c, vars) for c in expr.children)\n+        fn = {\">\": \"gt_set\",\n+              \">=\": \"ge_set\",\n+              \"==\": \"eq_set\",\n+              \"!=\": \"ne_set\",\n+              \"<\": \"lt_set\",\n+              \"<=\": \"le_set\"}[expr.operator]\n+        return getattr(a, fn)(b)\n+\n+    name = expr.name\n+    if name not in parameters.domains:\n+        lo, hi, constraint = expr.children\n+        params = list(v.name for v in traversal([lo, hi]) if isinstance(v, (Argument, Variable)))\n+        vars = isl.make_zero_and_vars([name], params)\n+        domain = (vars[name].ge_set(translate(lo, vars))\n+                  & vars[name].lt_set(translate(hi, vars)))\n+        parameters.domains[name] = domain\n+        if constraint is not None:\n+            parameters.assumptions[name] = translate(constraint, vars)\n+    return pym.Variable(name)\n+\n+\n+@expression.register(MultiIndex)\n+def expression_multiindex(expr, parameters):\n+    return tuple(expression(c, parameters) for c in expr.children)\n+\n+\n+@expression.register(Extent)\n+def expression_extent(expr, parameters):\n+    # FIXME: There will be a symbolic representation of this.\n+    multiindex, = expr.children","path":"pyop2/codegen/rep2loopy.py","position":667,"original_position":641,"commit_id":"3daae7dbe65c100812970106f1cbc8e4395ab463","original_commit_id":"a761af0c9df815e49856e0a34957c43267c96017","user":{"login":"wence-","id":1126981,"node_id":"MDQ6VXNlcjExMjY5ODE=","avatar_url":"https://avatars2.githubusercontent.com/u/1126981?v=4","gravatar_id":"","url":"https://api.github.com/users/wence-","html_url":"https://github.com/wence-","followers_url":"https://api.github.com/users/wence-/followers","following_url":"https://api.github.com/users/wence-/following{/other_user}","gists_url":"https://api.github.com/users/wence-/gists{/gist_id}","starred_url":"https://api.github.com/users/wence-/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wence-/subscriptions","organizations_url":"https://api.github.com/users/wence-/orgs","repos_url":"https://api.github.com/users/wence-/repos","events_url":"https://api.github.com/users/wence-/events{/privacy}","received_events_url":"https://api.github.com/users/wence-/received_events","type":"User","site_admin":false},"body":"Thanks.","created_at":"2018-12-07T11:30:20Z","updated_at":"2018-12-07T11:30:20Z","html_url":"https://github.com/OP2/PyOP2/pull/555#discussion_r239777477","pull_request_url":"https://api.github.com/repos/OP2/PyOP2/pulls/555","author_association":"MEMBER","_links":{"self":{"href":"https://api.github.com/repos/OP2/PyOP2/pulls/comments/239777477"},"html":{"href":"https://github.com/OP2/PyOP2/pull/555#discussion_r239777477"},"pull_request":{"href":"https://api.github.com/repos/OP2/PyOP2/pulls/555"}},"in_reply_to_id":237574319},"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\r\n\r\n\r\n- [x] implement resetting name counter as a transformation pass.\r\n- [ ] fix mixed dat with globals.","created_at":"2018-11-23T14:23:14Z","updated_at":"2018-12-07T11:30:20Z","closed_at":null,"merged_at":null,"merge_commit_sha":"caef6c6b280d6ed4c15f22f101bc0399630dc291","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/3daae7dbe65c100812970106f1cbc8e4395ab463","head":{"label":"OP2:kaushik","ref":"kaushik","sha":"3daae7dbe65c100812970106f1cbc8e4395ab463","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-06T22:20:58Z","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":12043,"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-06T22:20:58Z","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":12043,"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/3daae7dbe65c100812970106f1cbc8e4395ab463"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"wence-"}}