Research source · python
check_unary_guards.py
site/public/research-artifacts/scaffold-proof-review/check_unary_guards.py
257 lines. Source is displayed for inspection; it is not executed by this page.
File fingerprint
SHA-256: b94a9224a83ea6b719faa4533cf63dd6c6f43fe88dd123b3d78553e878c3c491
#!/usr/bin/env python3
"""Finite audit for Run 10's unary guard and terminal claims.
The exhaustive component checks small unary histories. It is a falsifier for
the local claims, not a proof of the parameter-uniform coherence theorem. The
general argument and its dependency boundary are recorded in run-10.md.
"""
from collections import Counter
from itertools import product
import json
from math import comb
from pathlib import Path
MISSING = "M"
SELF = "S"
EPSILON = "E"
ZERO = "0"
DESCRIPTORS = (MISSING, SELF, EPSILON, ZERO)
BASE_LABEL = -1
def histories(length, labels):
rows = tuple(product(labels, DESCRIPTORS))
return product(rows, repeat=length)
def replay(history):
edges = [None]
for node, (_, descriptor) in enumerate(history, start=1):
if descriptor == MISSING:
endpoint = None
elif descriptor == SELF:
endpoint = node
elif descriptor == EPSILON:
endpoint = node - 1
else:
endpoint = edges[node - 1]
edges.append(endpoint)
return tuple(edges)
def rooted_figure(history, root=None):
edges = replay(history)
node = len(history) if root is None else root
word = []
while node is not None:
word.append(BASE_LABEL if node == 0 else history[node - 1][0])
endpoint = edges[node]
if endpoint == node:
while len(word) >= 2 and word[-2] == word[-1]:
word.pop()
return tuple(word), SELF
node = endpoint
return tuple(word), MISSING
def strong_trace(history):
return tuple(rooted_figure(history[:length]) for length in range(1, len(history) + 1))
def terminal_kind(history):
edges = replay(history)
node = len(history)
while True:
if node == 0:
return "base"
endpoint = edges[node]
if endpoint is None:
return "missing"
if endpoint == node:
return "recursive"
node = endpoint
def causal_support(history):
edges = replay(history)
node = len(history)
labels = set()
descriptors = set()
while node is not None and node > 0:
labels.add(node - 1)
descriptors.add(node - 1)
endpoint = edges[node]
if endpoint == node:
break
node = endpoint
work = list(descriptors)
while work:
index = work.pop()
if history[index][1] != ZERO or index == 0:
continue
predecessor = index - 1
if predecessor not in descriptors:
descriptors.add(predecessor)
work.append(predecessor)
return frozenset(labels), frozenset(descriptors)
def changed_descriptor(history, index, descriptor):
target = list(history)
target[index] = (target[index][0], descriptor)
return tuple(target)
def audit_history(history, labels, totals):
edges = replay(history)
source_trace = strong_trace(history)
source_figure = rooted_figure(history)
source_terminal = terminal_kind(history)
label_support, descriptor_support = causal_support(history)
totals["histories"] += 1
totals[f"terminal_{source_terminal}"] += 1
# D uses physical endpoint equality, not behavioral equality alone.
for index, (_, descriptor) in enumerate(history):
for alternate in DESCRIPTORS:
if alternate == descriptor:
continue
target = changed_descriptor(history, index, alternate)
if replay(target)[index + 1] != edges[index + 1]:
continue
assert strong_trace(target) == source_trace
assert terminal_kind(target) == source_terminal
totals["detours"] += 1
# A is the strong-trace-preserving replacement by fresh SELF. Its
# elementary guard additionally requires the old target itself to loop.
for index, (_, descriptor) in enumerate(history):
if descriptor == SELF:
continue
target = changed_descriptor(history, index, SELF)
if strong_trace(target) != source_trace:
continue
old_target = edges[index + 1]
if old_target is None or old_target == index + 1:
continue
if edges[old_target] == old_target:
assert rooted_figure(history, old_target)[1] == SELF
assert terminal_kind(target) == source_terminal
totals["literal_absorptions"] += 1
elif rooted_figure(history, old_target)[1] == SELF:
totals["behavioral_recursive_but_not_literal"] += 1
# T cannot use the unlabelled base as its lower candidate carrier: left
# starts at one, so x is always an actual labelled row.
for left in range(1, len(history) - 1):
pair = history[left][1], history[left + 1][1]
if pair not in ((EPSILON, ZERO), (ZERO, EPSILON)):
continue
if history[left - 1][0] != history[left][0]:
continue
target = list(history)
target[left] = (target[left][0], pair[1])
target[left + 1] = (target[left + 1][0], pair[0])
target = tuple(target)
assert left >= 1
assert rooted_figure(target) == source_figure
assert terminal_kind(target) == source_terminal
totals["temporal_slides"] += 1
if strong_trace(target) != source_trace:
totals["slides_changing_prefix_trace"] += 1
# G changes only unsupported coordinates and preserves replay support.
for index, (label, descriptor) in enumerate(history):
if index not in label_support:
for alternate in labels:
if alternate == label:
continue
target = list(history)
target[index] = (alternate, descriptor)
target = tuple(target)
assert causal_support(target) == (label_support, descriptor_support)
assert rooted_figure(target) == source_figure
assert terminal_kind(target) == source_terminal
totals["garbage_label_changes"] += 1
if index not in descriptor_support:
for alternate in DESCRIPTORS:
if alternate == descriptor:
continue
target = changed_descriptor(history, index, alternate)
assert causal_support(target) == (label_support, descriptor_support)
assert rooted_figure(target) == source_figure
assert terminal_kind(target) == source_terminal
totals["garbage_descriptor_changes"] += 1
def carrier_rectangles():
checks = 0
for n in range(1, 13):
for m in range(1, n + 1):
ordinary = tuple(product(range(n - m + 1), repeat=m - 1))
weak_ordinary = sum(all(row[i] <= row[i + 1] for i in range(len(row) - 1)) for row in ordinary)
assert weak_ordinary == comb(n - 1, m - 1)
checks += 1
if m <= n - 1:
recursive = tuple(product(range(n - m), repeat=m - 1))
weak_recursive = sum(all(row[i] <= row[i + 1] for i in range(len(row) - 1)) for row in recursive)
assert weak_recursive == comb(n - 2, m - 1)
checks += 1
return {
"parameter_pairs": checks,
"ordinary_first_retained_time": 1,
"recursive_first_retained_time": 2,
"recursive_zero_carrier_case_is_singleton": True,
}
def statement_markers():
root = Path(__file__).resolve().parents[2]
objects = root / "research-artifacts/scaffold-proof-review/history/objects"
unary = (objects / "f125f710bd20360eaa12313d69da99c080c014ea4cd4b60d6d7cac897c6d463f.txt").read_text()
causal = (objects / "2b0910f423c0a56136a7d1c293c57ff5f6361ee40bf5d777aa4d1da7aaaae716.txt").read_text()
checks = {
"unlabelled_base_explicit": "distinguished unlabelled base at" in unary,
"literal_guard_explicit": "Literal physical self-looping matters" in unary,
"temporal_label_guard_explicit": "candidate carrier labels agree" in unary,
"causal_support_explicit": "least replay-closed" in unary,
"three_terminal_cases_explicit": "ordinary missing or base terminal" in unary and "recursive terminal" in unary,
"recursive_time_one_exclusion_explicit": "time $1$ is unavailable" in unary,
"causal_full_behavior_present": "equal unfoldings" in causal and "bisimilar" in causal,
}
assert all(checks.values())
return checks
totals = Counter()
for labels, maximum in (((0,), 8), ((0, 1), 5)):
for length in range(1, maximum + 1):
for history in histories(length, labels):
audit_history(history, labels, totals)
assert totals["literal_absorptions"] > 0
assert totals["behavioral_recursive_but_not_literal"] > 0
assert totals["temporal_slides"] > 0
assert totals["slides_changing_prefix_trace"] > 0
result = {
"status": "pass",
"exhaustive_small_histories": dict(sorted(totals.items())),
"carrier_rectangles": carrier_rectangles(),
"statement_markers": statement_markers(),
"proved_boundary": {
"D_uses_physical_endpoint_equality": True,
"A_requires_a_literal_old_self_loop": True,
"G_uses_replay_closed_causal_irrelevance": True,
"T_uses_equal_labelled_carriers_and_may_change_prefixes": True,
"T_cannot_cross_the_unlabelled_base": True,
"terminal_kind_is_preserved_by_every_move": True,
"recursive_termination_reserves_one_extra_time_row": True,
"full_behavior_not_same_radius_identity_is_inherited": True,
},
"scope": "Finite falsification plus exact source markers; the uniform coherence proof is argued in Run 10 and the paper.",
}
print(json.dumps(result, indent=2, sort_keys=True))