def promote(cur):
            for ((i, j, s2), nxt) in cur.down.items():
                rule[i][j] = s2
                promote(nxt)
                rule[i][j] = None
            add = {}
            for ((i, j, s2), nxt) in cur.down.items():
                rule[i][j] = s
                cur_mn = sum(
                    0 if rule[x][y] is None else rule[x][y].min_length(settings.sinput.permutations)
                    for x in range(n)
                    for y in range(m)
                )
                if cur_mn <= settings.mn_at_most and s2 in above and (i, j, s) not in cur.down and (i, j, s) not in add:
                    if all((i, j, a) in cur.down for a in above):

                        ok = True
                        if ok:
                            # make sure the current type is an allowed neighbor of all (not necessarily immediate) neighbors
                            for ci in range(i, n):
                                for cj in range(m):
                                    if (ci, cj) > (i, j):
                                        di = signum(ci - i)
                                        dj = signum(cj - j)
                                        if s not in allowed_neighbors_cpp[(rule[ci][cj], -di, -dj)]:
                                            ok = False
                                            break
                                if not ok:
                                    break
                        if ok:
                            # check if any of my immediate neighbors are neither empty nor a point
                            # in that case, we can only put a point in the current box
                            for di in range(-1, 2):
                                for dj in range(-1, 2):
                                    ci, cj = i + di, j + dj
                                    if (ci, cj) > (i, j) and 0 <= ci < n and 0 <= cj < m:
                                        if rule[ci][cj] is not None and type(rule[ci][cj]) is not PointPermutationSet:
                                            ok = False
                                            break
                                if not ok:
                                    break

                        if ok:
                            add[(i, j, s)] = intersect([cur.down[(i, j, a)] for a in above], (i, j))

                rule[i][j] = None
            for (k, v) in add.items():
                if v is not None:
                    cur.down[k] = v
        def intersect(cur, pos):
            if settings.filter_rule_incrementally and not generates_subset(GeneratingRule(rule)):
                return None

            here = TrieNode()
            here.end = all(c.end for c in cur)
            if here.end:
                si = pos[0]
                sj = None
                for y in range(m):
                    found = False
                    for x in range(si, n):
                        if rule[x][y] is not None:
                            found = True
                            break
                    if found:
                        sj = y
                        break
                assert sj is not None
                g = GeneratingRule({(x - si, y - sj): rule[x][y] for x in range(si, n) for y in range(sj, m)})
                ok = True
                if (si, sj) == (n - 1, m - 1) and not rule[si][sj].can_be_alone():
                    ok = False
                if ok:
                    res = rule_set.add_rule(g)
                    if res == RuleDeath.PERM_PROP:
                        return None
                    elif res != RuleDeath.ALIVE:
                        here.end = False

            found = False
            for (i, j, s2) in reduce(lambda x, y: x & y, [set(c.down.keys()) for c in cur]):
                rule[i][j] = s2

                ok = True
                if ok:
                    # make sure the current type is an allowed neighbor of all (not necessarily immediate) neighbors
                    for ci in range(i, n):
                        for cj in range(m):
                            if (ci, cj) > (i, j):
                                di = signum(ci - i)
                                dj = signum(cj - j)
                                if s2 not in allowed_neighbors_cpp[(rule[ci][cj], -di, -dj)]:
                                    ok = False
                                    break
                        if not ok:
                            break
                if ok and type(s2) is not PointPermutationSet:
                    # check if any of my immediate neighbors are neither empty nor a point
                    # in that case, we can only put a point in the current box
                    for di in range(-1, 2):
                        for dj in range(-1, 2):
                            ci, cj = i + di, j + dj
                            if (ci, cj) > (i, j) and 0 <= ci < n and 0 <= cj < m:
                                if rule[ci][cj] is not None and type(rule[ci][cj]) is not PointPermutationSet:
                                    ok = False
                                    break
                        if not ok:
                            break

                if ok:
                    rest = intersect([c.down[(i, j, s2)] for c in cur], (i, j))
                    if rest is not None:
                        found = True
                        here.down[(i, j, s2)] = rest
                rule[i][j] = None

            if not found and not here.end:
                return None
            return here