Research source · python

verify_indexed_prefix_repair.py

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

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

File fingerprint

SHA-256: 72451afc3f9f4d785e1b445aa790e438bc7642dbb08160b74a6481032937cc7d

#!/usr/bin/env python3
"""Symbolic model of the prefix-repair clauses under explicit module contracts.

Ranks below denote n+offset for arbitrary n >= 0. Lengths count elements at
that rank, not letters in their flattened words. This is not OCaml extraction.
"""
from dataclasses import dataclass
from pathlib import Path
import argparse
import hashlib
import json


@dataclass(frozen=True)
class Nat:
    constant: int
    variables: tuple = ()

    def __add__(self, other):
        return Nat(self.constant + other.constant,
                   tuple(sorted(self.variables + other.variables)))

    def at_least(self, k):
        # Every listed variable ranges independently over the natural numbers.
        return self.constant >= k


@dataclass(frozen=True)
class Buffer:
    rank: int
    word: tuple
    length: Nat


@dataclass(frozen=True)
class Chain:
    rank: int
    word: tuple


@dataclass(frozen=True)
class Small:
    prefix: Buffer

    def __post_init__(self):
        assert self.prefix.length.at_least(3)

    @property
    def rank(self):
        return self.prefix.rank + 1

    @property
    def word(self):
        return self.prefix.word


@dataclass(frozen=True)
class Big:
    prefix: Buffer
    child: Chain
    suffix: Buffer

    def __post_init__(self):
        assert self.prefix.rank == self.suffix.rank
        assert self.child.rank == self.prefix.rank + 1
        assert self.prefix.length.at_least(3)
        assert self.suffix.length.at_least(3)

    @property
    def rank(self):
        return self.prefix.rank + 1

    @property
    def word(self):
        return self.prefix.word + self.child.word + self.suffix.word


def semi_push(stored, child):
    assert isinstance(stored, (Small, Big)), "expected stored value, not buffer"
    assert stored.rank == child.rank
    return Chain(child.rank, stored.word + child.word)


def semi_concat(left, right):
    assert left.rank == right.rank
    return Chain(left.rank, left.word + right.word)


def inject(prefix, items):
    assert prefix.rank == items.rank
    return Buffer(prefix.rank, prefix.word + items.word,
                  prefix.length + items.length)


def extract_prefix(stored, child, mutation=None):
    # Direct clause transcription, with primitives interpreted by contracts.
    if isinstance(stored, Small):
        return stored.prefix, child
    child = semi_push(Small(stored.suffix), child)
    child = (semi_concat(child, stored.child) if mutation == "swap_concat"
             else semi_concat(stored.child, child))
    return stored.prefix, child


def make_green_prefix(p, p2, child, three, remainder, short, mutation=None):
    # The has3p contract, including its exhaustive short/long partition.
    assert three.rank == remainder.rank == p2.rank == p.rank
    assert three.length == Nat(3)
    assert p2.word == three.word + remainder.word
    assert p2.length == three.length + remainder.length
    assert p.length.at_least(5)
    if short:
        assert not remainder.length.variables
        assert 0 <= remainder.length.constant < 3
        p = inject(p, three)
        return inject(p, remainder), child
    assert remainder.length.at_least(3)
    p = inject(p, three)
    child = semi_push(remainder if mutation == "omit_small" else Small(remainder),
                      child)
    return p, child


def check_case(kind, short_size=None, mutation=None):
    p = Buffer(0, ("P",), Nat(5, ("p",)))
    three = Buffer(0, ("U",), Nat(3))
    short = short_size is not None
    rest = Buffer(0, (() if short_size == 0 else ("V",)) if short else ("R",),
                  Nat(short_size) if short else Nat(3, ("r",)))
    p2 = inject(three, rest)
    tail = Chain(1, ("D",))
    stored = (Small(p2) if kind == "Small" else
              Big(p2, Chain(1, ("T",)),
                  Buffer(0, ("S",), Nat(3, ("s",)))))
    # Independent pre-state contract of pop_green: old = stored followed by tail.
    expected = p.word + stored.word + tail.word
    extracted, child = extract_prefix(stored, tail, mutation)
    newp, newchild = make_green_prefix(p, extracted, child, three, rest,
                                      short, mutation)
    assert newp.rank == 0 and newchild.rank == 1
    assert newp.length.at_least(8)
    assert newp.word + newchild.word == expected, "fringe equation failed"
    return {"stored": kind, "remainder": short_size if short else "3+r",
            "normal_form": list(expected),
            "output_ranks": ["n", "n+1"],
            "prefix_length_lower_bound": newp.length.constant}


SOURCE_HASHES = {
    "lib/cadeque_core.ml": "2a7e36a50e9cec5f3cc06226922ab26e039e1fa0dc1188e879e594f320ae003e",
    "theory/Cadeque/Core.v": "32340da1cedd75eddee2e3205fee4ae4d99f71e347e807b993d528f3e6f1875f",
    "theory/Cadeque/Types.v": "4beb995bd9634c6cc275aa053e009065a7ff699e2ad7b04fd2821df97bcd9e23",
    "theory/Cadeque/Models.v": "d19612a21f4e506aedb31657393c437a07d1f2f8e5ed4ebcea4e408d6b05402f",
}


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--source", type=Path)
    args = parser.parse_args()
    cases = [check_case(kind, size) for kind in ("Small", "Big")
             for size in (0, 1, 2, None)]
    negative = []
    for mutation in ("swap_concat", "omit_small"):
        try:
            check_case("Big", None, mutation)
        except AssertionError as e:
            negative.append({"mutation": mutation, "rejected": str(e)})
        else:
            raise AssertionError(f"mutation escaped: {mutation}")
    try:
        Small(Buffer(0, ("V",), Nat(2)))
    except AssertionError:
        negative.append({"mutation": "repackage_two_items",
                         "rejected": "Small requires at least three entries"})
    else:
        raise AssertionError("undersized Small escaped")
    checked = {}
    if args.source:
        for name, expected in SOURCE_HASHES.items():
            actual = hashlib.sha256((args.source / name).read_bytes()).hexdigest()
            assert actual == expected, f"source drift: {name}"
            checked[name] = actual
    print(json.dumps({
        "scope": "Symbolic equations and rank/occupancy checks for a handwritten "
                 "model of extract_prefix and make_green_prefix under pop/buffer/"
                 "semi-chain contracts. No executable OCaml refinement, general "
                 "indexed-type checker, or proof-assistant verification.",
        "rank_quantifier": "arbitrary natural n; runtime rank counters are not modeled",
        "word_quantifier": "arbitrary substituted word blocks, not sampled letters",
        "source_commit": "503c5d3ff7f9a20bad1cd7a8f8d7abf4e635d3a0",
        "checked_source_hashes": checked,
        "checker_sha256": hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
        "cases": cases,
        "negative_controls": negative
    }, indent=2))


if __name__ == "__main__":
    main()