Research source · python

verify_bounded_threshold_catenation.py

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

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

File fingerprint

SHA-256: c0f694dec57aaab842772b5721d46ff95230668c2b66c0d740c4575ffd7225d2

#!/usr/bin/env python3
"""Finite corroboration for bounded-threshold structural probes.

This does not prove the general lemma, the OCaml implementation, or the
source-to-record compiler. With --source it also records static facts about a
checkout of VerifiedCatenableDeque.
"""

from __future__ import annotations

import argparse
import hashlib
import json
from pathlib import Path
import random
import subprocess


def pop(xs):
    return None if not xs else (xs[0], xs[1:])


def eject(xs):
    return None if not xs else (xs[:-1], xs[-1])


def has3_probe(xs):
    r1 = pop(xs)
    if r1 is None:
        return ("less", ())
    a, t1 = r1
    r2 = pop(t1)
    if r2 is None:
        return ("less", (a,))
    b, t2 = r2
    r3 = pop(t2)
    if r3 is None:
        return ("less", (a, b))
    return ("at_least", xs)


def has7p_probe(xs):
    taken = []
    cur = xs
    after_two = None
    for i in range(7):
        result = pop(cur)
        if result is None:
            return ("less", tuple(taken))
        value, cur = result
        taken.append(value)
        if i == 1:
            after_two = cur
    return ("at_least", (tuple(taken[:2]), after_two))


def has7s_probe(xs):
    taken_reversed = []
    cur = xs
    after_two = None
    for i in range(7):
        result = eject(cur)
        if result is None:
            return ("less", tuple(reversed(taken_reversed)))
        cur, value = result
        taken_reversed.append(value)
        if i == 1:
            after_two = cur
    return ("at_least", (after_two, tuple(reversed(taken_reversed[:2]))))


def has8_probe(xs):
    assert len(xs) >= 5
    first_five = xs[:5]
    cur = xs[5:]
    extra = []
    for _ in range(3):
        result = pop(cur)
        if result is None:
            return ("less", (first_five, tuple(extra)))
        value, cur = result
        extra.append(value)
    return ("at_least", xs)


def has3p8_probe(xs):
    assert len(xs) >= 8
    first_three = xs[:3]
    result = has8_probe(xs[3:])
    if result[0] == "less":
        five, vector = result[1]
        return ("less", (first_three + five, vector))
    return ("at_least", (first_three, xs[3:]))


def expected_has3(xs):
    return ("less", xs) if len(xs) < 3 else ("at_least", xs)


def expected_has7p(xs):
    if len(xs) < 7:
        return ("less", xs)
    return ("at_least", (xs[:2], xs[2:]))


def expected_has7s(xs):
    if len(xs) < 7:
        return ("less", xs)
    return ("at_least", (xs[:-2], xs[-2:]))


def expected_has8(xs):
    if len(xs) < 8:
        return ("less", (xs[:5], xs[5:]))
    return ("at_least", xs)


def expected_has3p8(xs):
    if len(xs) < 11:
        return ("less", (xs[:8], xs[8:]))
    return ("at_least", (xs[:3], xs[3:]))


def exhaustive(max_length=64):
    checks = 0
    for n in range(max_length + 1):
        xs = tuple(range(n))
        assert has3_probe(xs) == expected_has3(xs)
        checks += 1
        if n >= 1:
            assert has7p_probe(xs) == expected_has7p(xs)
            assert has7s_probe(xs) == expected_has7s(xs)
            checks += 2
        if n >= 5:
            assert has8_probe(xs) == expected_has8(xs)
            checks += 1
        if n >= 8:
            assert has3p8_probe(xs) == expected_has3p8(xs)
            checks += 1
    return {"maximum_length": max_length, "classification_checks": checks}


def branching(operations=250_000, retained=5_000, seed=20260930):
    rng = random.Random(seed)
    versions = [tuple()]
    max_length = 0
    checks = 0
    for step in range(operations):
        base = rng.choice(versions)
        old = base
        choice = rng.randrange(4)
        if choice == 0:
            new = (step,) + base
        elif choice == 1:
            new = base + (step,)
        elif choice == 2:
            result = pop(base)
            new = base if result is None else result[1]
        else:
            result = eject(base)
            new = base if result is None else result[0]
        assert base == old
        assert has3_probe(base) == expected_has3(base)
        checks += 1
        if base:
            assert has7p_probe(base) == expected_has7p(base)
            assert has7s_probe(base) == expected_has7s(base)
            checks += 2
        if len(base) >= 5:
            assert has8_probe(base) == expected_has8(base)
            checks += 1
        if len(base) >= 8:
            assert has3p8_probe(base) == expected_has3p8(base)
            checks += 1
        versions.append(new)
        if len(versions) > retained:
            del versions[rng.randrange(len(versions) - 1)]
        max_length = max(max_length, len(new))
    return {
        "operations": operations,
        "retained_versions": retained,
        "seed": seed,
        "classification_checks": checks,
        "maximum_length": max_length,
    }


def sha256(path):
    return hashlib.sha256(path.read_bytes()).hexdigest()


def count(text, needle):
    return text.count(needle)


def source_audit(root):
    root = root.resolve()
    dq = root / "lib" / "deque_core.ml"
    cq = root / "lib" / "cadeque_core.ml"
    dq_text = dq.read_text()
    cq_text = cq.read_text()
    head = subprocess.check_output(
        ["git", "rev-parse", "HEAD"], cwd=root, text=True
    ).strip()
    return {
        "checkout_head": head,
        "deque_core_sha256": sha256(dq),
        "cadeque_core_sha256": sha256(cq),
        "core_source_characters_with_colors": sum(
            (root / "lib" / name).stat().st_size
            for name in ("color_GYR.ml", "color_GYOR.ml", "deque_core.ml", "cadeque_core.ml")
        ),
        "cadeque_core_Deque_length_occurrences": count(cq_text, "Deque.length"),
        "deque_core_let_rec_occurrences": count(dq_text, "let rec"),
        "cadeque_core_let_rec_occurrences": count(cq_text, "let rec"),
        "deque_core_mutation_tokens": sum(count(dq_text, x) for x in (":=", " ref ", "Array.")),
        "cadeque_core_mutation_tokens": sum(count(cq_text, x) for x in (":=", " ref ", "Array.")),
        "maximum_structural_probe_operations": 11,
    }


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--source", type=Path)
    parser.add_argument("--output", type=Path)
    args = parser.parse_args()
    result = {
        "scope": {
            "claim": "finite corroboration, not an unrestricted proof",
            "classifiers": ["has3", "has7p", "has7s", "has8", "has3p8"],
        },
        "exhaustive": exhaustive(),
        "branching_persistence": branching(),
    }
    if args.source:
        result["source_audit"] = source_audit(args.source)
    rendered = json.dumps(result, indent=2, sort_keys=True) + "\n"
    if args.output:
        args.output.write_text(rendered)
    print(rendered, end="")


if __name__ == "__main__":
    main()