Research source · python

check_single_hole_reuse.py

tools/research/check_single_hole_reuse.py

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

File fingerprint

SHA-256: 8e15a6c3fe47ae0442b36e6d940ddb2d10c6334f42796115acd0416da66cfb35

#!/usr/bin/env python3
"""Finite audit of the single-hole export and pending union (section 11).

Python integers model unary stack lengths and are not native arithmetic costs.
The counter operations below act on run heads; the proof implements their
length changes by unary stack push/pop. No production code is claimed.
"""
from dataclasses import dataclass, replace
from random import Random
import json
from check_name_funded_ball_reuse import Receipt, step, Q, B, deficit


def encode(n):
    result = []
    for bit in map(int, bin(n)[:1:-1] if n else ''):
        if result and result[-1][0] == bit:
            result[-1] = (bit, result[-1][1]+1)
        else:
            result.append((bit, 1))
    return tuple(result)


def decode(runs):
    out = offset = 0
    for bit, length in runs:
        assert bit in (0, 1) and length > 0
        if bit:
            out += ((1 << length)-1) << offset
        offset += length
    assert not runs or runs[-1][0] == 1
    assert all(x[0] != y[0] for x, y in zip(runs, runs[1:]))
    return out


def inc(r):
    def zero_start(t):
        (_, k), *tail = t
        tail = tuple(tail)
        if k > 1:
            return ((1, 1), (0, k-1)) + tail
        assert tail and tail[0][0] == 1
        return ((1, tail[0][1]+1),) + tail[1:]
    if not r:
        return ((1, 1),)
    if r[0][0] == 0:
        return zero_start(r)
    return ((0, r[0][1]),) + (zero_start(r[1:]) if r[1:] else ((1, 1),))


def dec(r):
    assert r
    def one_start(t):
        (_, k), *tail = t
        tail = tuple(tail)
        if k > 1:
            return ((0, 1), (1, k-1)) + tail
        if not tail:
            return ()
        assert tail[0][0] == 0
        return ((0, tail[0][1]+1),) + tail[1:]
    if r[0][0] == 1:
        return one_start(r)
    head = ((1, r[0][1]),)
    return head if r[1:] == ((1, 1),) else head + one_start(r[1:])


@dataclass(frozen=True)
class Signed:
    sign: int = 0
    magnitude: int = 0
    runs: tuple = ()

    def unit(self, direction):
        if not self.sign or self.sign == direction:
            result = Signed(direction, self.magnitude+1, inc(self.runs))
        elif self.magnitude == 1:
            result = Signed()
        else:
            result = Signed(self.sign, self.magnitude-1, dec(self.runs))
        assert result.sign*result.magnitude == self.sign*self.magnitude+direction
        assert decode(result.runs) == result.magnitude
        return result


@dataclass(frozen=True)
class Row:
    widths: frozenset = frozenset()
    h: int = 0
    gap: int = 0
    total: Signed = Signed()
    delta: Signed = Signed()
    outer: int = 0
    inner: int = 0
    emitted: int = 0
    done: bool = True


def record_width(old, n):
    a = b = old.delta
    candidate_sum = old.total
    gap = old.gap
    outer, inner, emitted, done = old.outer, old.inner, old.emitted, old.done
    worker_steps = 0
    for position in range(1, n+1):
        for _ in range(8):
            if done:
                break
            worker_steps += 1
            if inner:
                inner -= 1
                emitted += 1
                a = a.unit(1); b = b.unit(1)
            elif outer:
                outer -= 1
                inner = outer
            else:
                done = True
        b = b.unit(-1)
        candidate_sum = candidate_sum.unit(1)
        if position > old.h:
            gap += 1
    fresh = n not in old.widths  # inspected only at the record delimiter
    total, delta = (candidate_sum, b) if fresh else (old.total, a)
    if fresh:
        assert gap > 0
        gap -= 1
    if n > old.h:
        outer = inner = n
        emitted = 0
        done = False
        delta = Signed(-1, total.magnitude, total.runs)
    new = Row(old.widths | {n}, max(old.h, n), gap, total, delta,
              outer, inner, emitted, done)
    assert worker_steps <= 8*n
    assert total.magnitude == sum(new.widths)
    assert gap == new.h-len(new.widths)
    assert delta.sign*delta.magnitude == emitted-total.magnitude
    if done:
        assert emitted == new.h*(new.h+1)//2
        missing = set(range(1, new.h+1))-new.widths
        assert delta.sign*delta.magnitude == sum(missing)
    return new


def service(s, full, budget):
    for _ in range(budget):
        if full or (not s.queue and s.active is None):
            break
        active = s.active
        completion = active is not None and not (active.left and active.right)
        unequal = False
        if completion:
            sign = 1 if active.right else -1 if active.left else active.last
            unequal = bool(sign)
        s, _ = step(s)
        if unequal:
            full = True
            s = replace(s, queue=(), active=None)
    return s, full


def select(s, full, name, hole):
    s, full = service(s, full, Q*len(name))
    if name in s.seen:
        return s, full
    s = replace(s, seen=s.seen | {name})
    if full or hole is None:
        return s, full
    if hole == 0:
        return replace(s, queue=(), active=None), True
    if not s.candidate:
        return replace(s, candidate=hole), False
    return replace(s, queue=s.queue+(hole,)), False


def query(s, full, h, selected, count):
    saved_hole = s.candidate
    answers = 0
    for n in range(1, count+1):
        s, full = service(s, full, 4*B)
        selected_any = bool(saved_hole) or full
        answer = selected_any and n <= h and (full or n != saved_hole)
        oracle = any(n <= h and (hole == 0 or n != hole) for hole in selected)
        assert answer == oracle
        if saved_hole and n >= saved_hole:
            assert full or (not s.queue and s.active is None)
        answers += 1
    return answers


def main():
    rng = Random(18311)
    counter_cases = 0
    for n in list(range(8192)) + [2**k+d for k in (64, 256, 1024, 4096)
                                 for d in (-1, 0, 1)]:
        assert decode(inc(encode(n))) == n+1
        if n:
            assert decode(dec(encode(n))) == n-1
        counter_cases += 1
    signed = Signed()
    for _ in range(20000):
        signed = signed.unit(rng.choice((-1, 1)))
    # Arbitrary intermediate gaps, duplicates, order and maximum resets.
    width_records = 0
    for _ in range(200):
        row = Row()
        for _ in range(50):
            row = record_width(row, rng.randrange(1, 30))
            width_records += 1
    literal_records = exported = 0
    for h in range(1, 8):
        for trial in range(6):
            holes = [rng.randrange(h) for _ in range(4)]  # zero means full
            records = [(u, format(i, '0%db' % j))
                       for u, hole in enumerate(holes)
                       for j in range(1, h+1) if j != hole
                       for i in range(2**j)]
            rng.shuffle(records)
            records += records[:20]
            rows = [Row() for _ in range(4)]
            seen = [set() for _ in range(4)]
            counts = [{} for _ in range(4)]
            for u, word in records:
                rows[u] = record_width(rows[u], len(word))
                if word not in seen[u]:
                    seen[u].add(word)
                    n = len(word)
                    counts[u][n] = counts[u].get(n, 0)+1
                row = rows[u]
                if counts[u].get(row.h, 0) == 2**row.h:
                    assert row.done  # forced own-row future service, at first Full
                literal_records += 1
            for u, row in enumerate(rows):
                assert row.done and row.h == h and row.gap <= 1
                assert row.delta.sign*row.delta.magnitude == holes[u]
                exported += 1
    queries = snapshots = 0
    for _ in range(150):
        h = rng.randrange(2, 80)
        table = {bin(i)[2:]: rng.choice([None, 0]+list(range(1, h)))
                 for i in range(1, 20)}
        s, full, chosen = Receipt(), False, set()
        for _ in range(40):
            name = rng.choice(list(table))
            hole = table[name]
            s, full = select(s, full, name, hole)
            if hole is not None:
                chosen.add(hole)
            queries += query(s, full, h, chosen, h+2)
            snapshots += 1
    unfinished = 0
    for exponent in (64, 256, 1024, 4096):
        m = 1 << exponent
        for distinct_last in (False, True):
            s, full = Receipt(), False
            chosen = set()
            for i in range(1, 160):
                hole = m+int(distinct_last and i == 159)
                chosen.add(hole)
                s, full = select(s, full, bin(i)[2:], hole)
            unfinished += bool(s.queue or s.active)
            work, probe, settled = 0, s, full
            while not settled and (probe.queue or probe.active):
                probe, settled = service(probe, settled, 1)
                work += 1
            assert work <= deficit(m) <= B*m
            assert (work+4*B-1)//(4*B) <= m
            queries += query(s, full, m+3, chosen, 1200)
            snapshots += 1
    assert unfinished > 0
    # First moment alone cannot export two arbitrary holes.
    assert sum((1, 4)) == sum((2, 3)) and set((1, 4)) != set((2, 3))
    print(json.dumps(dict(status='passed', unsigned_counter_cases=counter_cases,
                         signed_unit_updates=20000, width_records=width_records,
                         literal_records=literal_records, completed_row_exports=exported,
                         retained_query_receipts=snapshots, query_prefix_answers=queries,
                         unfinished_large_receipts=unfinished), indent=2))


if __name__ == '__main__':
    main()