Research source · python

check_learned_contexts_independent.py

tools/research/check_learned_contexts_independent.py

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

File fingerprint

SHA-256: b2f4c925270d4aa2dafec2f0fe8dcb3b06a58b2c4895d8ff02ec371de859b123

#!/usr/bin/env python3
"""Independent finite semantics audit for learned-context contraction.

Python strings, containers and arithmetic are semantic oracles, not native
operations or timing measurements. No external dependencies.
"""
from collections import deque
from itertools import product
from random import Random


def words(depth):
    return [''.join(bits) for n in range(depth + 1)
            for bits in product('01', repeat=n)]


def rolling_cut(v, w):
    remaining = list(v) + ['!']
    window, prefix = deque(), []
    for bit in w:
        if remaining:
            remaining.pop()
        else:
            prefix.append(window.popleft())
        window.append(bit)
    if remaining:
        return None
    return ''.join(prefix), ''.join(window)


def incremental(records, cap, degree, span):
    contexts, rows = [], {}
    for v, w in records:
        if not v or not w:
            return None
        matches = [j for j, p in enumerate(contexts) if w.startswith(p)]
        if matches:
            j = matches[0]
            y = w[len(contexts[j]):]
        else:
            cut = rolling_cut(v, w)
            if cut is None or len(contexts) == cap:
                return None
            p, y = cut
            j = len(contexts)
            contexts.append(p)
        if not y or abs(len(v).bit_length() - len(y).bit_length()) > span:
            return None
        receipt = rows.setdefault(v, [])
        if (j, y) not in receipt:
            receipt.append((j, y))
            if len(receipt) > degree:
                return None
    return contexts, rows


def reference(records, cap, degree, span):
    """Direct definition with slicing and literal row sets, no receipt state."""
    contexts, rows = [], {}
    for v, w in records:
        if not v or not w:
            return None
        p = next((p for p in contexts if w.startswith(p)), None)
        if p is None:
            if len(contexts) >= cap or len(w) <= len(v):
                return None
            p = w[:len(w) - len(v) - 1]
            contexts.append(p)
        residual = w[len(p):]
        if not residual:
            return None
        lo, hi = sorted((len(v), len(residual)))
        # Compare dyadic classes by repeated halving independently of bit_length.
        a = b = 0
        while lo > 1:
            lo //= 2
            a += 1
        while hi > 1:
            hi //= 2
            b += 1
        if b - a > span:
            return None
        rows.setdefault(v, set()).add(w)
        if len(rows[v]) > degree:
            return None
    return contexts, rows


def main():
    rng = Random(207)
    pool = [('0', '000'), ('1', '100'), ('0', '001'),
            ('00', '1011'), ('1', '00'), ('01', '1111')]
    sequences = [list(xs) for n in range(4) for xs in product(pool, repeat=n)]
    sequences += [[], [('0', '0')], [('', '00')], [('0', '')],
                  [('0', '000'), ('1', '0')],
                  [('0', '000'), ('1', '101'), ('00', '100')],
                  [('0', '0010'), ('1', '00011')],
                  [('1', '00011'), ('0', '0010')]]
    for _ in range(300):
        aliases = ['0', '1', '00', '01']
        seq = [(rng.choice(aliases), rng.choice(words(5)[1:]))
               for _ in range(rng.randrange(1, 10))]
        if seq:
            seq += [seq[0]]  # A later context must not reclassify a duplicate.
        sequences.append(seq)
    checks = accepted = rejected = snapshots = query_checks = 0
    queries = words(6)
    for records in sequences:
        for cap in (1, 2, 3):
            for degree in (1, 2, 3):
                for span in (1, 2):
                    checks += 1
                    got = incremental(records, cap, degree, span)
                    expected = reference(records, cap, degree, span)
                    assert (got is None) == (expected is None), (records, cap, degree, span)
                    if got is None:
                        rejected += 1
                        continue
                    accepted += 1
                    contexts, receipts = got
                    assert contexts == expected[0]
                    for v, entries in receipts.items():
                        assert {contexts[j] + y for j, y in entries} == expected[1][v]
                    names = sorted({v for v, _ in records}) + ['111111']
                    versions = []
                    for n in range(len(names) + 1):
                        selected = frozenset(names[:n])
                        lanes = [set() for _ in contexts]
                        for v in selected:
                            for j, y in receipts.get(v, []):
                                lanes[j].add(y)
                        versions.append((selected, tuple(frozenset(lane) for lane in lanes)))
                    for selected, lanes in reversed(versions):
                        snapshots += 1
                        literal = {w for v, w in records if v in selected}
                        for q in queries:
                            answer = any(q.startswith(p) and q[len(p):] in lane
                                         for p, lane in zip(contexts, lanes))
                            assert answer == (q in literal), (records, selected, q)
                            query_checks += 1
    # A late shorter prefix can overlap an old context but cannot steal old words.
    overlap = [('0', '0000'), ('00', '0110'), ('0', '0000')]
    contexts, rows = incremental(overlap, 2, 2, 1)
    assert contexts == ['00', '0'] and rows['0'] == [(0, '00')]
    # The first-record cut is an order-sensitive checked contract.
    assert incremental([('0', '0010'), ('1', '00011')], 1, 1, 1) is not None
    assert incremental([('1', '00011'), ('0', '0010')], 1, 1, 1) is None
    print({'contract_cases': checks, 'accepted': accepted, 'rejected': rejected,
           'retained_selection_snapshots': snapshots, 'query_checks': query_checks,
           'late_overlap_control': 1, 'order_sensitive_control': 1})


if __name__ == '__main__':
    main()