Research source · python
verify_finite_summary_erasure.py
site/public/research-artifacts/foundations/verify_finite_summary_erasure.py
423 lines. Source is displayed for inspection; it is not executed by this page.
File fingerprint
SHA-256: edfe2f3bcb9bd001e5c79d848ca99e168996e5c5787f47bbc74486db2d0bf574
#!/usr/bin/env python3
"""Exact regular-invariant certificates for supplied finite stack summaries.
The DFA product explores every descriptor realized in a supplied regular
invariant, without a stack-depth cutoff. It checks invariant closure and the
finite local-signature criterion from finite-summary-erasure.md. It does not
implement general pushdown post* saturation or prove the theorem itself.
"""
from collections import deque
from dataclasses import dataclass
import json
from pathlib import Path
@dataclass(frozen=True)
class DFA:
alphabet: tuple
start: int
final: frozenset
delta: dict
def advance(self, state, word):
for symbol in word:
state = self.delta[state, symbol]
return state
def accepts(self, word):
return self.advance(self.start, word) in self.final
@dataclass(frozen=True)
class Monoid:
elements: tuple
identity: object
table: dict
generators: dict
def mul(self, left, right):
return self.table[left, right]
def validate(self, alphabet):
assert self.identity in self.elements
assert set(self.generators) == set(alphabet)
for x in self.elements:
assert self.mul(self.identity, x) == x
assert self.mul(x, self.identity) == x
for y in self.elements:
assert self.mul(x, y) in self.elements
for z in self.elements:
assert self.mul(self.mul(x, y), z) == self.mul(x, self.mul(y, z))
def complete_dfa(alphabet, states, start, final, transitions, sink):
delta = {}
for state in states:
for symbol in alphabet:
delta[state, symbol] = transitions.get((state, symbol), sink)
return DFA(tuple(alphabet), start, frozenset(final), delta)
def singleton_dfa(word, alphabet):
sink = len(word) + 1
states = tuple(range(sink + 1))
transitions = {}
for state in range(len(word)):
transitions[state, word[state]] = state + 1
return complete_dfa(alphabet, states, 0, {len(word)}, transitions, sink)
def pair_parity_dfa(parity, prefixed=False):
alphabet = ("X", "A")
sink = 5 if prefixed else 4
if prefixed:
# 0 expects the leading A. States 1/2 are pair boundaries of parity 0/1;
# states 3/4 have just read X at those parities.
states = tuple(range(6))
transitions = {(0, "A"): 1, (1, "X"): 3, (3, "A"): 2,
(2, "X"): 4, (4, "A"): 1}
final = {1 + parity}
return complete_dfa(alphabet, states, 0, final, transitions, sink)
# States 0/1 are pair boundaries of parity 0/1; 2/3 have just read X.
states = tuple(range(5))
transitions = {(0, "X"): 2, (2, "A"): 1,
(1, "X"): 3, (3, "A"): 0}
return complete_dfa(alphabet, states, 0, {parity}, transitions, sink)
def star_dfa(symbol):
return DFA((symbol,), 0, frozenset({0}), {(0, symbol): 0})
def inclusion_failure(left, left_start, right, right_start):
queue = deque([(left_start, right_start, ())])
seen = {(left_start, right_start)}
while queue:
x, y, word = queue.popleft()
if x in left.final and y not in right.final:
return word
for symbol in left.alphabet:
pair = (left.delta[x, symbol], right.delta[y, symbol])
if pair not in seen:
seen.add(pair)
queue.append((*pair, word + (symbol,)))
return None
def apply(word, operation):
if operation == "pop":
return word[1:] if word else word
if operation == "keep":
return word
assert operation.startswith("push:")
return (operation[5:],) + word
def invariant_check(machine, invariants):
for state in machine["initial"]:
assert invariants[state].accepts(())
obligations = 0
for (state, top, letter), rules in machine["rules"].items():
source = invariants[state]
for target, operation in rules:
destination = invariants[target]
if top is None:
if source.accepts(()):
assert destination.accepts(apply((), operation))
else:
prefix = apply((top,), operation)
failure = inclusion_failure(
source,
source.delta[source.start, top],
destination,
destination.advance(destination.start, prefix),
)
assert failure is None, (state, top, letter, target, failure)
obligations += 1
return obligations
def descriptors(dfa, monoid, projection):
# Words are traversed top first. The key retains both the whole-word
# summary and the summary after deleting the raw top.
start = (dfa.start, None, None, monoid.identity, monoid.identity)
queue = deque([(start, ())])
seen = {start}
realized = {}
while queue:
(dfa_state, raw_top, projected_top, summary, tail_summary), word = queue.popleft()
if dfa_state in dfa.final:
realized.setdefault(
(raw_top, projected_top, summary, tail_summary), word
)
for symbol in dfa.alphabet:
generator = monoid.generators[symbol]
if raw_top is None:
next_raw_top = symbol
next_projected_top = projection[symbol]
next_summary = generator
next_tail_summary = monoid.identity
else:
next_raw_top = raw_top
next_projected_top = projected_top
if next_projected_top is None and projection[symbol] is not None:
next_projected_top = projection[symbol]
next_summary = monoid.mul(summary, generator)
next_tail_summary = monoid.mul(tail_summary, generator)
key = (
dfa.delta[dfa_state, symbol],
next_raw_top,
next_projected_top,
next_summary,
next_tail_summary,
)
if key not in seen:
seen.add(key)
queue.append((key, word + (symbol,)))
return realized
def signatures(machine, state, descriptor, letter, monoid, projection, controls):
raw_top, _, summary, tail_summary = descriptor
output = set()
for target, operation in machine["rules"].get((state, raw_top, letter), ()):
if operation.startswith("push:"):
symbol = operation[5:]
next_summary = monoid.mul(monoid.generators[symbol], summary)
image = projection[symbol]
projected_operation = "keep" if image is None else "push:" + image
elif operation == "pop" and raw_top is not None:
next_summary = tail_summary
projected_operation = "pop" if projection[raw_top] is not None else "keep"
else:
next_summary = summary
projected_operation = "keep"
output.add(((controls[target], next_summary), projected_operation))
return frozenset(output)
def certificate(machine, invariants, monoid, projection, controls):
alphabet = next(iter(invariants.values())).alphabet
monoid.validate(alphabet)
closure_obligations = invariant_check(machine, invariants)
groups = {}
for state, invariant in invariants.items():
for descriptor, witness in descriptors(invariant, monoid, projection).items():
raw_top, projected_top, summary, _ = descriptor
group = (controls[state], summary, projected_top)
groups.setdefault(group, []).append((state, descriptor, witness))
failures = []
quotient_rules = {}
quotient_observations = {}
for group, cells in groups.items():
state0, descriptor0, witness0 = cells[0]
raw_top0 = descriptor0[0]
observation0 = machine["observe"](state0, raw_top0)
quotient_observations[group] = observation0
for state, descriptor, witness in cells[1:]:
if machine["observe"](state, descriptor[0]) != observation0:
failures.append({
"kind": "observation",
"group": group,
"witnesses": [[state0, list(witness0)], [state, list(witness)]],
})
for letter in machine["inputs"]:
signature0 = signatures(
machine, state0, descriptor0, letter, monoid, projection, controls
)
quotient_rules[group, letter] = signature0
for state, descriptor, witness in cells[1:]:
signature = signatures(
machine, state, descriptor, letter, monoid, projection, controls
)
if signature != signature0:
failures.append({
"kind": "signatures",
"group": group,
"input": letter,
"witnesses": [[state0, list(witness0)], [state, list(witness)]],
"signatures": [sorted(signature0), sorted(signature)],
})
return {
"failures": failures,
"groups": len(groups),
"descriptors": sum(map(len, groups.values())),
"closure_obligations": closure_obligations,
"rules": quotient_rules,
"observations": quotient_observations,
}
def trivial_monoid(alphabet):
return Monoid((0,), 0, {(0, 0): 0}, {symbol: 0 for symbol in alphabet})
def parity_monoid():
elements = (0, 1)
return Monoid(
elements,
0,
{(x, y): x ^ y for x in elements for y in elements},
{"X": 1, "A": 0},
)
def union_monoid():
elements = (0, 1)
return Monoid(
elements,
0,
{(x, y): x | y for x in elements for y in elements},
{"X": 1},
)
def rightmost_tag_monoid(alphabet):
elements = ("e", "0", "1")
table = {(x, y): y if y != "e" else x for x in elements for y in elements}
generators = {symbol: "e" for symbol in alphabet}
generators.update({"B0": "0", "B1": "1"})
return Monoid(elements, "e", table, generators)
def parity_restoration_example():
states = ("q0", "q1", "b0", "b1", "d0", "d1")
inputs = ("u", "v", "x", "a")
rules = {}
for parity in (0, 1):
q, build, drop = f"q{parity}", f"b{parity}", f"d{parity}"
if parity == 0:
rules[q, None, "u"] = {(build, "push:A")}
rules[q, "X", "u"] = {(build, "push:A")}
rules[build, "A", "v"] = {(f"q{1 - parity}", "push:X")}
rules[q, "X", "x"] = {(f"d{1 - parity}", "pop")}
rules[drop, "A", "a"] = {(q, "pop")}
machine = {
"initial": ("q0",),
"inputs": inputs,
"rules": rules,
"observe": lambda state, top: state == "q0",
}
invariants = {
"q0": pair_parity_dfa(0),
"q1": pair_parity_dfa(1),
"b0": pair_parity_dfa(0, prefixed=True),
"b1": pair_parity_dfa(1, prefixed=True),
"d0": pair_parity_dfa(0, prefixed=True),
"d1": pair_parity_dfa(1, prefixed=True),
}
projection = {"X": None, "A": "A"}
controls = {state: state[0] for state in states}
summary = certificate(machine, invariants, parity_monoid(), projection, controls)
no_summary = certificate(
machine, invariants, trivial_monoid(("X", "A")), projection, controls
)
assert not summary["failures"]
assert any(failure["kind"] == "observation" for failure in no_summary["failures"])
return {
"regular_invariants": {
"q0": "(X A)^(even)",
"q1": "(X A)^(odd)",
"b_i_and_d_i": "A followed by the corresponding parity language",
},
"summary": "parity of X frames",
"summary_certificate": "passed",
"without_summary": "rejected as required",
"closure_obligations": summary["closure_obligations"],
"realized_descriptors": summary["descriptors"],
"abstract_local_groups": summary["groups"],
}
def frame_set_negative():
machine = {
"initial": ("q",),
"inputs": ("p", "r"),
"rules": {
("q", None, "p"): {("q", "push:X")},
("q", "X", "p"): {("q", "push:X")},
("q", "X", "r"): {("q", "pop")},
},
"observe": lambda state, top: False,
}
invariants = {"q": star_dfa("X")}
result = certificate(machine, invariants, union_monoid(), {"X": None}, {"q": "q"})
failure = next(f for f in result["failures"] if f.get("input") == "r")
assert {tuple(witness[1]) for witness in failure["witnesses"]} == {("X",), ("X", "X")}
return {
"summary": "set of labels present",
"certificate": "rejected as required",
"witness": failure,
"reason": "X and XX have the same current summary but different summaries after pop",
}
def missing_bit_example():
alphabet = ("A", "B0", "B1", "C0", "C1")
words = {
"init": (), "s0": ("B0",), "s1": ("B1",),
"q0": ("A", "B0"), "q1": ("A", "B1"),
"e0": ("C0", "A", "B0"), "e1": ("C1", "A", "B1"),
}
rules = {
("init", None, "0"): {("s0", "push:B0")},
("init", None, "1"): {("s1", "push:B1")},
}
for bit in (0, 1):
rules[f"s{bit}", f"B{bit}", "a"] = {(f"q{bit}", "push:A")}
rules[f"q{bit}", "A", "z"] = {(f"e{bit}", f"push:C{bit}")}
machine = {
"initial": ("init",),
"inputs": ("0", "1", "a", "z"),
"rules": rules,
"observe": lambda state, top: False,
}
invariants = {state: singleton_dfa(word, alphabet) for state, word in words.items()}
projection = {symbol: symbol for symbol in alphabet}
controls = {state: "q" if state in ("q0", "q1") else state for state in states_from(words)}
repaired = certificate(
machine, invariants, rightmost_tag_monoid(alphabet), projection, controls
)
unrepaired = certificate(
machine, invariants, trivial_monoid(alphabet), projection, controls
)
assert not repaired["failures"]
assert any(failure.get("input") == "z" for failure in unrepaired["failures"])
return {
"summary": "rightmost B0/B1 tag",
"with_summary": "passed",
"without_summary": "rejected as required",
"reachable_configurations": len(words),
}
def states_from(mapping):
return tuple(mapping.keys())
def main():
result = {
"status": "passed",
"parity_pop_restoration": parity_restoration_example(),
"frame_set_pop_obstruction": frame_set_negative(),
"missing_bit_repair": missing_bit_example(),
"assurance": (
"Exact DFA invariant closure and finite descriptor checks, without a stack-depth cutoff; "
"the general theorem remains a written proof."
),
"not_implemented": [
"general pushdown post* saturation",
"automatic summary synthesis",
"minimal summary computation",
"scaffold generation",
"proof assistant",
],
}
output = Path(__file__).with_name("finite-summary-erasure-output.json")
output.write_text(json.dumps(result, indent=2, sort_keys=True) + "\n")
print(json.dumps(result, indent=2, sort_keys=True))
if __name__ == "__main__":
main()