def collect(extracted, ispace, min_storage):
    '\n    Find groups of aliasing expressions.\n\n    We shall introduce the following (loose) terminology:\n\n        * A ``terminal`` is the leaf of a mathematical operation. Terminals\n          can be numbers (n), literals (l), or Indexeds (I).\n        * ``R`` is the relaxation operator := ``R(n) = n``, ``R(l) = l``,\n          ``R(I) = J``, where ``J`` has the same base as ``I`` but with all\n          offsets stripped away. For example, ``R(a[i+2,j-1]) = a[i,j]``.\n        * A ``relaxed expression`` is an expression in which all of the\n          terminals are relaxed.\n\n    Now we define the concept of aliasing. We say that an expression A\n    aliases an expression B if:\n\n        * ``R(A) == R(B)``\n        * all pairwise Indexeds in A and B access memory locations at a\n          fixed constant distance along each Dimension.\n\n    For example, consider the following expressions:\n\n        * a[i+1] + b[i+1]\n        * a[i+1] + b[j+1]\n        * a[i] + c[i]\n        * a[i+2] - b[i+2]\n        * a[i+2] + b[i]\n        * a[i-1] + b[i-1]\n\n    Out of the expressions above, the following alias to `a[i] + b[i]`:\n\n        * a[i+1] + b[i+1] : same operands and operations, distance along i: 1\n        * a[i-1] + b[i-1] : same operands and operations, distance along i: -1\n\n    Whereas the following do not:\n\n        * a[i+1] + b[j+1] : because at least one index differs\n        * a[i] + c[i] : because at least one of the operands differs\n        * a[i+2] - b[i+2] : because at least one operation differs\n        * a[i+2] + b[i] : because the distances along ``i`` differ (+2 and +0)\n    '
    found = []
    for expr in extracted:
        assert (not expr.is_Equality)
        indexeds = retrieve_indexed(expr)
        bases = []
        offsets = []
        for i in indexeds:
            ii = IterationInstance(i)
            if ii.is_irregular:
                break
            base = []
            offset = []
            for (e, ai) in zip(ii, ii.aindices):
                if q_constant(e):
                    base.append(e)
                else:
                    base.append(ai)
                    offset.append((ai, (e - ai)))
            bases.append(tuple(base))
            offsets.append(LabeledVector(offset))
        if ((not indexeds) or (len(bases) == len(indexeds))):
            found.append(Candidate(expr, ispace, indexeds, bases, offsets))
    mapper = OrderedDict()
    unseen = list(found)
    while unseen:
        c = unseen.pop(0)
        group = [c]
        for u in list(unseen):
            if (not compare_ops(c.expr, u.expr)):
                continue
            if (not c.translated(u)):
                continue
            group.append(u)
            unseen.remove(u)
        group = Group(group)
        if min_storage:
            k = group.dimensions_translated
        else:
            k = group.dimensions
        mapper.setdefault(k, []).append(group)
    aliases = AliasMapper()
    queue = list(mapper.values())
    while queue:
        groups = queue.pop(0)
        while groups:
            mapper = defaultdict(int)
            for g in list(groups):
                try:
                    mapper.update({d: max(mapper[d], v) for (d, v) in g.diameter.items()})
                except ValueError:
                    groups.remove(g)
            intervalss = {d: make_rotations_table(d, v) for (d, v) in mapper.items()}
            mapper = {}
            for (d, intervals) in intervalss.items():
                impacted = [g for g in groups if (d in g.dimensions)]
                for interval in list(intervals):
                    found = {g: g.find_rotation_distance(d, interval) for g in impacted}
                    if all(((distance is not None) for distance in found.values())):
                        mapper[interval] = found
                        break
            if (len(mapper) == len(intervalss)):
                break
            smallest = len(min(groups, key=len))
            fallback = groups
            (groups, remainder) = split(groups, (lambda g: (len(g) > smallest)))
            if groups:
                queue.append(remainder)
            elif (len(remainder) > 1):
                queue.append(fallback[1:])
                groups = [fallback.pop(0)]
            else:
                break
        for g in groups:
            c = g.pivot
            distances = defaultdict(int, [(i.dim, v.get(g)) for (i, v) in mapper.items()])
            offsets = [LabeledVector([(l, (v[l] + distances[l])) for l in v.labels]) for v in c.offsets]
            subs = {i: i.function[[(l + v.fromlabel(l, 0)) for l in b]] for (i, b, v) in zip(c.indexeds, c.bases, offsets)}
            alias = uxreplace(c.expr, subs)
            aliaseds = [extracted[i.expr] for i in g]
            distances = []
            for i in g:
                distance = [o.distance(v) for (o, v) in zip(i.offsets, offsets)]
                distance = [(d, set(v)) for (d, v) in LabeledVector.transpose(*distance)]
                distances.append(LabeledVector([(d, v.pop()) for (d, v) in distance]))
            aliases.add(alias, list(mapper), aliaseds, distances)
    return aliases
Find groups of aliasing expressions.

We shall introduce the following (loose) terminology:

    * A ``terminal`` is the leaf of a mathematical operation. Terminals
      can be numbers (n), literals (l), or Indexeds (I).
    * ``R`` is the relaxation operator := ``R(n) = n``, ``R(l) = l``,
      ``R(I) = J``, where ``J`` has the same base as ``I`` but with all
      offsets stripped away. For example, ``R(a[i+2,j-1]) = a[i,j]``.
    * A ``relaxed expression`` is an expression in which all of the
      terminals are relaxed.

Now we define the concept of aliasing. We say that an expression A
aliases an expression B if:

    * ``R(A) == R(B)``
    * all pairwise Indexeds in A and B access memory locations at a
      fixed constant distance along each Dimension.

For example, consider the following expressions:

    * a[i+1] + b[i+1]
    * a[i+1] + b[j+1]
    * a[i] + c[i]
    * a[i+2] - b[i+2]
    * a[i+2] + b[i]
    * a[i-1] + b[i-1]

Out of the expressions above, the following alias to `a[i] + b[i]`:

    * a[i+1] + b[i+1] : same operands and operations, distance along i: 1
    * a[i-1] + b[i-1] : same operands and operations, distance along i: -1

Whereas the following do not:

    * a[i+1] + b[j+1] : because at least one index differs
    * a[i] + c[i] : because at least one of the operands differs
    * a[i+2] - b[i+2] : because at least one operation differs
    * a[i+2] + b[i] : because the distances along ``i`` differ (+2 and +0)
def collect(extracted, ispace, min_storage):
    '\n    Find groups of aliasing expressions.\n\n    We shall introduce the following (loose) terminology:\n\n        * A ``terminal`` is the leaf of a mathematical operation. Terminals\n          can be numbers (n), literals (l), or Indexeds (I).\n        * ``R`` is the relaxation operator := ``R(n) = n``, ``R(l) = l``,\n          ``R(I) = J``, where ``J`` has the same base as ``I`` but with all\n          offsets stripped away. For example, ``R(a[i+2,j-1]) = a[i,j]``.\n        * A ``relaxed expression`` is an expression in which all of the\n          terminals are relaxed.\n\n    Now we define the concept of aliasing. We say that an expression A\n    aliases an expression B if:\n\n        * ``R(A) == R(B)``\n        * all pairwise Indexeds in A and B access memory locations at a\n          fixed constant distance along each Dimension.\n\n    For example, consider the following expressions:\n\n        * a[i+1] + b[i+1]\n        * a[i+1] + b[j+1]\n        * a[i] + c[i]\n        * a[i+2] - b[i+2]\n        * a[i+2] + b[i]\n        * a[i-1] + b[i-1]\n\n    Out of the expressions above, the following alias to `a[i] + b[i]`:\n\n        * a[i+1] + b[i+1] : same operands and operations, distance along i: 1\n        * a[i-1] + b[i-1] : same operands and operations, distance along i: -1\n\n    Whereas the following do not:\n\n        * a[i+1] + b[j+1] : because at least one index differs\n        * a[i] + c[i] : because at least one of the operands differs\n        * a[i+2] - b[i+2] : because at least one operation differs\n        * a[i+2] + b[i] : because the distances along ``i`` differ (+2 and +0)\n    '
    found = []
    for expr in extracted:
        assert (not expr.is_Equality)
        indexeds = retrieve_indexed(expr)
        bases = []
        offsets = []
        for i in indexeds:
            ii = IterationInstance(i)
            if ii.is_irregular:
                break
            base = []
            offset = []
            for (e, ai) in zip(ii, ii.aindices):
                if q_constant(e):
                    base.append(e)
                else:
                    base.append(ai)
                    offset.append((ai, (e - ai)))
            bases.append(tuple(base))
            offsets.append(LabeledVector(offset))
        if ((not indexeds) or (len(bases) == len(indexeds))):
            found.append(Candidate(expr, ispace, indexeds, bases, offsets))
    mapper = OrderedDict()
    unseen = list(found)
    while unseen:
        c = unseen.pop(0)
        group = [c]
        for u in list(unseen):
            if (not compare_ops(c.expr, u.expr)):
                continue
            if (not c.translated(u)):
                continue
            group.append(u)
            unseen.remove(u)
        group = Group(group)
        if min_storage:
            k = group.dimensions_translated
        else:
            k = group.dimensions
        mapper.setdefault(k, []).append(group)
    aliases = AliasMapper()
    queue = list(mapper.values())
    while queue:
        groups = queue.pop(0)
        while groups:
            mapper = defaultdict(int)
            for g in list(groups):
                try:
                    mapper.update({d: max(mapper[d], v) for (d, v) in g.diameter.items()})
                except ValueError:
                    groups.remove(g)
            intervalss = {d: make_rotations_table(d, v) for (d, v) in mapper.items()}
            mapper = {}
            for (d, intervals) in intervalss.items():
                impacted = [g for g in groups if (d in g.dimensions)]
                for interval in list(intervals):
                    found = {g: g.find_rotation_distance(d, interval) for g in impacted}
                    if all(((distance is not None) for distance in found.values())):
                        mapper[interval] = found
                        break
            if (len(mapper) == len(intervalss)):
                break
            smallest = len(min(groups, key=len))
            fallback = groups
            (groups, remainder) = split(groups, (lambda g: (len(g) > smallest)))
            if groups:
                queue.append(remainder)
            elif (len(remainder) > 1):
                queue.append(fallback[1:])
                groups = [fallback.pop(0)]
            else:
                break
        for g in groups:
            c = g.pivot
            distances = defaultdict(int, [(i.dim, v.get(g)) for (i, v) in mapper.items()])
            offsets = [LabeledVector([(l, (v[l] + distances[l])) for l in v.labels]) for v in c.offsets]
            subs = {i: i.function[[(l + v.fromlabel(l, 0)) for l in b]] for (i, b, v) in zip(c.indexeds, c.bases, offsets)}
            alias = uxreplace(c.expr, subs)
            aliaseds = [extracted[i.expr] for i in g]
            distances = []
            for i in g:
                distance = [o.distance(v) for (o, v) in zip(i.offsets, offsets)]
                distance = [(d, set(v)) for (d, v) in LabeledVector.transpose(*distance)]
                distances.append(LabeledVector([(d, v.pop()) for (d, v) in distance]))
            aliases.add(alias, list(mapper), aliaseds, distances)
    return aliases