Research source · python
check_regular_stack_certificates.py
site/public/research-artifacts/foundations/check_regular_stack_certificates.py
295 lines. Source is displayed for inspection; it is not executed by this page.
File fingerprint
SHA-256: b39f7365d23dec5e3b53f9428cc8d35adb988bac7ed22ff27f845cf1f6851d91
#!/usr/bin/env python3
"""Exact DFA checker for regular-stack-certificates.md.
Default: emit example instances, exact decisions and bounded implementation audits.
--check FILE: check one JSON instance (use an example's `instance` object).
States are integers; stack and input symbols are strings. Stack top is first.
No saturation, synthesis, VPA determinization or scaffold generator is included.
"""
import argparse
from collections import deque
from itertools import product
import json
class DFA:
def __init__(self, alphabet, spec):
self.alphabet = tuple(alphabet)
self.index = {a: i for i, a in enumerate(alphabet)}
self.start = spec['start']
self.final = set(spec['final'])
self.table = spec['transitions']
n = len(self.table)
if not n or type(self.start) is not int or not 0 <= self.start < n:
raise ValueError('invalid DFA start')
if any(type(x) is not int or not 0 <= x < n for x in self.final):
raise ValueError('invalid DFA final state')
if any(len(row) != len(alphabet) or any(type(x) is not int or not 0 <= x < n for x in row) for row in self.table):
raise ValueError('DFA must be complete over the stack alphabet')
def step(self, state, a):
return self.table[state][self.index[a]]
def accepts(self, word):
state = self.start
for a in word:
state = self.step(state, a)
return state in self.final
def nonempty(self):
seen, todo = {self.start}, [self.start]
while todo:
s = todo.pop()
if s in self.final:
return True
for t in self.table[s]:
if t not in seen:
seen.add(t)
todo.append(t)
return False
def spec(transitions, final, start=0):
return {'start': start, 'final': list(final), 'transitions': transitions}
def empty_spec(alphabet):
return spec([[0] * len(alphabet)], [])
def singleton(word, alphabet):
n = len(word)
return spec([[i + 1 if i < n and a == word[i] else n + 1 for a in alphabet]
for i in range(n + 2)], [n])
def image(op, arg, word):
if op == 'push':
return (arg,) + tuple(word)
if op == 'pop' and word:
return tuple(word[1:])
return tuple(word)
def branch_failure(old, target, e, top, clock_zero, op, arg):
"""Exact emptiness of D intersect preimage(complement(target)).
Returns an old-stack tuple, or None after exhausting the finite product.
top=None is the empty-stack case. Length cap retains the first-symbol flag.
"""
cap = max(e, 0) + 1
target_start = target.step(target.start, arg) if op == 'push' else target.start
start = old.start, target_start, 0
todo, seen = deque([(start, ())]), {start}
while todo:
(s, t, length), word = todo.popleft()
top_ok = (length == 0) if top is None else (length > 0)
clock_ok = (length == e) if clock_zero else (length > e)
if top_ok and clock_ok and s in old.final and t not in target.final:
return word
if top is None:
continue
for a in old.alphabet:
if length == 0 and a != top:
continue
nt = t if op == 'pop' and length == 0 else target.step(t, a)
nxt = old.step(s, a), nt, min(cap, length + 1)
if nxt not in seen:
seen.add(nxt)
todo.append((nxt, word + (a,)))
return None
def coordinate_failure(A, e):
if e <= 0:
return None
todo, seen = deque([((A.start, 0), ())]), {(A.start, 0)}
while todo:
(s, n), word = todo.popleft()
if n < e and s in A.final:
return word
if n >= e:
continue
for a in A.alphabet:
nxt = A.step(s, a), n + 1
if nxt not in seen:
seen.add(nxt)
todo.append((nxt, word + (a,)))
return None
def new_e(e, top, clock_zero, op, c):
d = 1 if op == 'push' else (-1 if op == 'pop' and top is not None else 0)
k = 0 if c == -1 and clock_zero else c
return e + d - k
def check(instance):
P, C = instance['source'], instance['clock']
alphabet = P['stack_alphabet']
if not alphabet or len(set(alphabet)) != len(alphabet) or not all(isinstance(a, str) for a in alphabet):
raise ValueError('invalid stack alphabet')
Q, R, letters = set(P['states']), set(C['states']), set(P['alphabet'])
if not all(type(s) is int for s in Q | R) or not all(isinstance(a, str) for a in letters):
raise ValueError('states must be integers and input letters strings')
if not P['initial'] or not set(P['initial']) <= Q or C['initial'] not in R:
raise ValueError('invalid initialization')
table = {}
for r, a, nr, c in C['table']:
if r not in R or nr not in R or a not in letters or type(c) is not int or c not in (-1, 0, 1) or (r, a) in table:
raise ValueError('invalid or duplicate clock rule')
table[r, a] = nr, c
if set(table) != set(product(R, letters)):
raise ValueError('clock must be complete')
for rule in P['rules']:
if rule['q'] not in Q or rule['next'] not in Q or rule['letter'] not in letters:
raise ValueError('invalid source rule')
if not set(rule['guard']) <= set(alphabet) | {None}:
raise ValueError('invalid top guard; null denotes empty')
if rule['op'] not in ('push', 'pop', 'keep') or (rule['op'] == 'push' and rule['symbol'] not in alphabet):
raise ValueError('invalid stack operation')
rows = {}
for row in instance['rows']:
key = row['q'], row['r'], row['e']
if key in rows or key[0] not in Q or key[1] not in R or type(key[2]) is not int:
raise ValueError('invalid or duplicate row')
rows[key] = DFA(alphabet, row['dfa'])
empty = DFA(alphabet, empty_spec(alphabet))
for key, A in sorted(rows.items()):
w = coordinate_failure(A, key[2])
if w is not None:
return {'valid': False, 'reason': 'negative implicit clock', 'mode': list(key), 'stack': list(w)}
for q in sorted(set(P['initial'])):
if not rows.get((q, C['initial'], 0), empty).accepts(()):
return {'valid': False, 'reason': 'initialization', 'q': q}
for (q, r, e), A in sorted(rows.items()):
for i, rule in enumerate(P['rules']):
if rule['q'] != q:
continue
nr, c = table[r, rule['letter']]
for top, clock_zero in product(rule['guard'], (True, False)):
ne = new_e(e, top, clock_zero, rule['op'], c)
key = rule['next'], nr, ne
target = rows.get(key, empty)
w = branch_failure(A, target, e, top, clock_zero, rule['op'], rule.get('symbol'))
if w is not None:
return {'valid': False, 'reason': 'closure', 'mode': [q, r, e],
'stack': list(w), 'rule_index': i, 'clock_zero': clock_zero,
'target_mode': list(key), 'target_stack': list(image(rule['op'], rule.get('symbol'), w))}
return {'valid': True, 'bound': max(abs(e) for (_, _, e), A in rows.items() if A.nonempty())}
def rule(q, a, guard, op, nxt, symbol=None):
return {'q': q, 'letter': a, 'guard': guard, 'op': op, 'next': nxt, 'symbol': symbol}
def row(q, e, dfa, r=0):
return {'q': q, 'r': r, 'e': e, 'dfa': dfa}
def make_instance(rules, outputs, rows, states=(0,)):
return {'source': {'states': list(states), 'initial': [0], 'alphabet': list(outputs),
'stack_alphabet': ['A', 'B'], 'rules': rules},
'clock': {'states': [0], 'initial': 0,
'table': [[0, a, 0, c] for a, c in outputs.items()]}, 'rows': rows}
def examples():
G = ['A', 'B']
any_top = [None] + G
epsilon = singleton((), G)
abstar = spec([[1, 2], [2, 0], [2, 2]], [0])
babstar = spec([[3, 1], [2, 3], [3, 1], [3, 3]], [1])
paired = make_instance([
rule(0, 'a', any_top, 'push', 1, 'B'), rule(1, 'c', ['B'], 'push', 0, 'A'),
rule(0, 'b', ['A'], 'pop', 2), rule(2, 'd', ['B'], 'pop', 0),
rule(0, 'x', any_top, 'keep', 0), rule(0, 'x', ['B'], 'push', 0, 'B')],
{'a': 1, 'c': 1, 'b': -1, 'd': -1, 'x': 0},
[row(0, 0, abstar), row(1, 0, babstar), row(2, 0, babstar)], (0, 1, 2))
bottom = make_instance([rule(0, 'x', any_top, 'pop', 0), rule(0, 'x', any_top, 'keep', 0)],
{'x': 0}, [row(0, 0, epsilon)])
once = make_instance([rule(0, 'x', [None], 'push', 0, 'A'), rule(0, 'x', G, 'keep', 0)],
{'x': 0}, [row(0, 0, epsilon), row(0, 1, singleton(('A',), G))])
ahead = json.loads(json.dumps(bottom))
ahead['clock'] = {'states': [0, 1], 'initial': 0, 'table': [[0, 'x', 1, 1], [1, 'x', 1, 0]]}
ahead['rows'] = [row(0, 0, epsilon), row(0, -1, epsilon, r=1)]
allwords = json.loads(json.dumps(paired))
allwords['rows'] = [row(q, 0, spec([[0, 0]], [0])) for q in range(3)]
coarse = json.loads(json.dumps(paired))
coarse['rows'] = [row(0, 0, spec([[1, 2], [1, 1], [2, 2]], [0, 1])),
row(1, 0, spec([[2, 1], [1, 1], [2, 2]], [1])),
row(2, 0, spec([[2, 1], [1, 1], [2, 2]], [1]))]
unbounded = make_instance([rule(0, 'x', any_top, 'push', 0, 'A'), rule(0, 'x', any_top, 'keep', 0)],
{'x': 0}, [row(0, 0, epsilon), row(0, 1, singleton(('A',), G))])
malformed = json.loads(json.dumps(bottom))
malformed['rows'].append(row(0, 1, epsilon))
long_failure = make_instance([rule(0, 'a', any_top, 'push', 0, 'A')], {'a': 1},
[row(0, 0, spec([[i + 1 if i < 12 else 13, 13] for i in range(14)], range(13)))])
return [('alternating_symbol_pairs', paired, True), ('bottom_pop_keep', bottom, True),
('guarded_push_once', once, True), ('clock_ahead', ahead, True),
('forgot_all_symbols', allwords, False), ('kept_only_top', coarse, False),
('unbounded_push_keep_candidate', unbounded, False),
('invalid_clock_coordinate', malformed, False), ('failure_at_length_twelve', long_failure, False)]
def audit():
# Exhaust the small unary DFA family; direct membership is the separate oracle.
family = [DFA(['A'], spec([[a], [b]], [i for i in range(2) if mask >> i & 1]))
for a, b, mask in product(range(2), range(2), range(4))]
total = 0
for old, target, e, top, zero, op in product(family, family, (-1, 0, 1), (None, 'A'), (True, False), ('push', 'pop', 'keep')):
w = branch_failure(old, target, e, top, zero, op, 'A')
oracle = None
for n in range(17):
v = ('A',) * n
if (v[0] if v else None) != top or not ((n == e) if zero else (n > e)):
continue
if old.accepts(v) and not target.accepts(image(op, 'A', v)):
oracle = v
break
assert w == oracle
total += 1
coordinate_cases = 0
for n in range(6):
for w in product(('A', 'B'), repeat=n):
for e, c, op in product(range(-3, 4), (-1, 0, 1), ('push', 'pop', 'keep')):
H = n - e
if H < 0:
continue
ne = new_e(e, w[0] if w else None, H == 0, op, c)
assert ne == len(image(op, 'A', w)) - max(0, H + c)
coordinate_cases += 1
return {'unary_dfa_descriptions': len(family), 'exact_product_vs_direct_cases': total,
'unary_ranges': {'DFA_states': 2, 'start': 0, 'transitions_and_finals': 'all',
'e': [-1, 1], 'top_cases': ['empty', 'A'], 'clock_cases': ['zero', 'positive'],
'operations': ['push A', 'pop', 'keep'], 'direct_lengths': [0, 16]},
'coordinate_cases': coordinate_cases,
'coordinate_ranges': {'alphabet': ['A', 'B'], 'stack_length': [0, 5], 'e': [-3, 3],
'clock_output': [-1, 0, 1], 'operations': ['push A', 'pop', 'keep'],
'restriction': 'nonnegative implicit clock'},
'status': 'bounded implementation corroboration passed'}
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--check', metavar='JSON')
args = parser.parse_args()
if args.check:
with open(args.check, encoding='utf-8') as f:
result = check(json.load(f))
print(json.dumps(result, indent=2, sort_keys=True))
raise SystemExit(0 if result['valid'] else 1)
results = []
for name, instance, expected in examples():
result = check(instance)
assert result['valid'] == expected, (name, result)
results.append({'name': name, 'instance': instance, 'exact_check': result})
assert len(results[-1]['exact_check']['stack']) == 12
print(json.dumps({'note': 'regular-stack-certificates.md', 'examples': results,
'exact_scope': 'finite-product decisions of all regular certificate obligations',
'finite_audit': audit(), 'not_implemented': ['saturation', 'synthesis',
'VPA determinization', 'scaffold generation', 'proof assistant']}, indent=2, sort_keys=True))
if __name__ == '__main__':
main()