Research source · python

verify_grid_closure.py

site/public/research-artifacts/foundations/verify_grid_closure.py

126 lines. Source is displayed for inspection; it is not executed by this page.

File fingerprint

SHA-256: be7e7316e723ae0075f1944a1aff461a00fc686c20294ba4c4564a2cbd2f1ea6

#!/usr/bin/env python3
"""Small exact checks for grid closure and persistent partial assignments.
Python tuples model immutable nodes. This is not a local-machine timing test.
"""
import itertools as it
import json
import random


def edges(m):
    return [(r*m+c, r*m+c+1) for r in range(m) for c in range(m-1)] + [
        (r*m+c, (r+1)*m+c) for r in range(m-1) for c in range(m)]


def independent_cross_matching(es, prefix, b):
    adj = [set() for _ in range(b)]
    for u, v in es:
        adj[u].add(v); adj[v].add(u)
    remaining = [(u, v) if u in prefix else (v, u) for u, v in es
                 if (u in prefix) != (v in prefix)]
    chosen = []
    while remaining:
        u, v = remaining[0]
        chosen.append((u, v))
        forbidden = {u, v} | adj[u] | adj[v]
        remaining = [(a, z) for a, z in remaining if a not in forbidden and z not in forbidden]
    return chosen


def check_cuts(m):
    b, es = m*m, edges(m)
    minimum = 1 << b
    count = 0
    for p in it.combinations(range(b), b//2):
        prefix = set(p)
        suffix = [v for v in range(b) if v not in prefix]
        crossing = sum((u in prefix) != (v in prefix) for u, v in es)
        assert crossing * 3 >= m
        matching = independent_cross_matching(es, prefix, b)
        assert len(matching) * 40 >= crossing
        residuals = set()
        for values in it.product((0, 1), repeat=len(p)):
            a = dict(zip(p, values))
            result = 0
            for j, rest in enumerate(it.product((0, 1), repeat=len(suffix))):
                x = a | dict(zip(suffix, rest))
                result |= int(all(x[u] or x[v] for u, v in es)) << j
            residuals.add(result)
        assert len(residuals) >= 2 ** len(matching)
        minimum = min(minimum, len(residuals))
        count += 1
    return {'m': m, 'balanced_cuts': count, 'minimum_distinct_residuals': minimum}


class Trees:
    def __init__(self, ell):
        self.ell, self.allocations = ell, 0

    def node(self, left, right, value):
        self.allocations += 1
        return (left, right, value)

    def build(self, depth, value):
        if depth == 0:
            return self.node(None, None, value)
        return self.node(self.build(depth-1, value), self.build(depth-1, value), bool(value) if value is not None else None)

    def get(self, root, key):
        for bit in key:
            root = root[bit]
        return root[2]

    def put(self, root, key, value, aggregate=False):
        if not key:
            return self.node(None, None, value)
        bit, rest = key[0], key[1:]
        children = [root[0], root[1]]
        children[bit] = self.put(children[bit], rest, value, aggregate)
        return self.node(*children, (children[0][2] or children[1][2]) if aggregate else None)


def persistent_checks(m, trials=250):
    b, es = m*m, edges(m)
    ell = max(1, (b-1).bit_length())
    keys = [tuple(int(c) for c in format(v, f'0{ell}b')) for v in range(b)]
    incident = [[] for _ in range(b)]
    horizontal, vertical = {}, {}
    for u, v in es:
        group = 0 if v == u+1 else 1
        (horizontal if group == 0 else vertical)[u] = v
        incident[u].append((group, u, v)); incident[v].append((group, u, v))
    tr = Trees(ell)
    assignment = tr.build(ell, None)
    flags = (tr.build(ell, False), tr.build(ell, False))
    versions = [(assignment, flags, (None,)*b)]
    rng = random.Random(9200+m)
    max_nodes = 0
    for _ in range(trials):
        root, flagroots, oracle = rng.choice(versions)
        v, value = rng.randrange(b), rng.choice((None, 0, 1))
        before = tr.allocations
        newroot = tr.put(root, keys[v], value)
        newflags = list(flagroots)
        for group, u, w in incident[v]:
            bad = tr.get(newroot, keys[u]) == 0 and tr.get(newroot, keys[w]) == 0
            newflags[group] = tr.put(newflags[group], keys[u], bad, True)
        allocated = tr.allocations-before
        assert allocated <= 5*(ell+1)
        max_nodes = max(max_nodes, allocated)
        neworacle = list(oracle); neworacle[v] = value
        versions.append((newroot, tuple(newflags), tuple(neworacle)))
        # Recheck every retained version; no old updates may be lost or overwritten.
        for r, f, a in versions:
            assert tuple(tr.get(r, keys[x]) for x in range(b)) == a
            expected = not any(a[u] == 0 and a[w] == 0 for u, w in es)
            assert (not (f[0][2] or f[1][2])) == expected
            completed = [1 if x is None else x for x in a]
            assert expected == all(completed[u] or completed[w] for u, w in es)
    return {'m': m, 'updates': trials, 'retained_versions': len(versions),
            'max_new_tree_nodes_per_update': max_nodes, 'node_bound': 5*(ell+1)}


if __name__ == '__main__':
    print(json.dumps({'cuts': [check_cuts(m) for m in (2, 3)],
                      'persistent': [persistent_checks(m) for m in (2, 3, 5)],
                      'passed': True, 'status': 'finite exact checks, not formal verification or runtime proof'}, indent=2))