import sys
import argparse
from collections import deque
from dataclasses import dataclass, field
from typing import FrozenSet, Optional, List, Tuple, Dict
@dataclass(frozen=True)
class Chunk:
tau: int hop_count: int
@dataclass(frozen=True)
class State:
site: str
hop: int
def successors(state: State, N: int, max_hops: int) -> List[Tuple[str, State]]:
site = state.site
h = state.hop
results = []
if site == "ext":
for i in range(N):
results.append((
f"O_ext(→W{i})",
State(site=f"M_ext_{i}", hop=0)
))
return results
if site.startswith("M_ext_"):
i = int(site.split("_")[-1])
results.append((
f"O_push-local(ext→Q{i})",
State(site=f"Q_{i}", hop=h)
))
return results
if site.startswith("M_") and not site.startswith("M_ext"):
parts = site.split("_")
i, j = int(parts[1]), int(parts[2])
results.append((
f"O_push-local(M{i}{j}→Q{j})",
State(site=f"Q_{j}", hop=h)
))
if h < max_hops:
for j2 in range(N):
if j2 != i and j2 != j: results.append((
f"O_hop(M{i}{j}→M{i}{j2}, h={h}→{h+1})",
State(site=f"M_{i}_{j2}", hop=h+1)
))
results.append((
f"O_park(M{i}{j}→W, h={h})",
State(site="W", hop=h)
))
return results
if site.startswith("Q_"):
i = int(site.split("_")[1])
results.append((
f"O_dispatch(Q{i}→E{i})",
State(site=f"E_{i}", hop=h)
))
for j in range(N):
if j != i:
results.append((
f"O_mailbox(Q{i}→M{i}{j})",
State(site=f"M_{i}_{j}", hop=0)
))
return results
if site == "W":
for i in range(N):
results.append((
f"O_drain(W→Q{i})",
State(site=f"Q_{i}", hop=h)
))
return results
if site.startswith("E_"):
i = int(site.split("_")[1])
results.append((
f"O_finish(E{i}→sink)",
State(site="sink", hop=h)
))
results.append((
f"O_requeue(E{i}→Q{i})",
State(site=f"Q_{i}", hop=h)
))
return results
if site == "sink":
return []
raise ValueError(f"Unknown site: {site}")
LIVE_SITES = lambda site: site not in ("ext", "sink")
def particle_count(state: State) -> int:
if state.site == "sink":
return 0
if state.site == "ext":
return -1 return 1
def check_conservation(parent: State, op: str, child: State) -> Optional[str]:
phi_before = particle_count(parent)
phi_after = particle_count(child)
if phi_before == -1:
return None
delta = phi_after - phi_before
if delta == 0:
return None if delta == -1 and child.site == "sink":
return None return (f"CONSERVATION VIOLATED: {parent.site} →[{op}]→ {child.site}: "
f"ΔΦ = {delta}")
def check_hop_bound(state: State, max_hops: int) -> Optional[str]:
if state.hop > max_hops:
return (f"LIVELOCK VIOLATION: hop_count={state.hop} > "
f"max_hops={max_hops} at site {state.site}")
return None
def check_deadlock(state: State, N: int, max_hops: int) -> Optional[str]:
if state.site == "sink":
return None succs = successors(state, N, max_hops)
if not succs:
return (f"DEADLOCK: state {state} has no successors "
f"but is not the sink")
return None
def explore(N: int, verbose: bool = False) -> Dict[str, int]:
max_hops = N // 2
init = State(site="ext", hop=0)
visited: set = set()
queue = deque([init])
errors: List[str] = []
stats = {
"states_visited": 0,
"transitions": 0,
"sink_reached": 0,
"errors": 0,
"max_hop_seen": 0,
}
print(f"\n{'='*60}")
print(f"Exploring N={N}, max_hops={max_hops}")
print(f"{'='*60}")
while queue:
state = queue.popleft()
if state in visited:
continue
visited.add(state)
stats["states_visited"] += 1
err = check_hop_bound(state, max_hops)
if err:
errors.append(err)
print(f" ✗ {err}")
err = check_deadlock(state, N, max_hops)
if err:
errors.append(err)
print(f" ✗ {err}")
if state.site == "sink":
stats["sink_reached"] += 1
if verbose:
print(f" [SINK reached] {state}")
continue
for op, child in successors(state, N, max_hops):
stats["transitions"] += 1
stats["max_hop_seen"] = max(stats["max_hop_seen"], child.hop)
err = check_conservation(state, op, child)
if err:
errors.append(err)
print(f" ✗ {err}")
if verbose:
print(f" {state.site}(h={state.hop}) "
f"--[{op}]--> {child.site}(h={child.hop})")
if child not in visited:
queue.append(child)
stats["errors"] = len(errors)
status = "✓ ALL PROPERTIES HOLD" if not errors else f"✗ {len(errors)} VIOLATIONS"
print(f"\n States visited : {stats['states_visited']}")
print(f" Transitions : {stats['transitions']}")
print(f" Sink reached : {stats['sink_reached']}")
print(f" Max hop seen : {stats['max_hop_seen']} (≤ max_hops={max_hops})")
print(f" Errors : {stats['errors']}")
print(f" Result : {status}")
return stats
def lyapunov_report(N: int):
max_hops = N // 2
print(f"\n Lyapunov trace (N={N}, max_hops={max_hops}):")
h = 0
while h <= max_hops:
V = max_hops - h
fate = "→ O_hop (V decreases)" if h < max_hops else "→ O_park FORCED (V=0, absorbing)"
print(f" h={h:2d} V={V:2d} {fate}")
h += 1
def main():
parser = argparse.ArgumentParser(description="DTA-V3 FSM verifier")
parser.add_argument("--verbose", action="store_true",
help="Print every state transition")
args = parser.parse_args()
print("DTA-V3 Scheduler FSM Finite Verifier")
print("Formal model: main.tex, chapter 'Formal Mathematical Model'")
print("Properties checked:")
print(" [1] No Task Loss — Φ conserved on every live transition")
print(" [2] Deadlock Freedom — every non-sink state has ≥1 successor")
print(" [3] Livelock Freedom — hop_count never exceeds max_hops")
all_ok = True
for N in [2, 3, 4, 8]:
stats = explore(N, verbose=args.verbose)
lyapunov_report(N)
if stats["errors"] > 0:
all_ok = False
print("\n" + "="*60)
if all_ok:
print("RESULT: All three correctness properties verified for N ∈ {2,3,4,8}.")
print(" — Φ is conserved on every live transition (no task loss).")
print(" — Every non-sink state has at least one successor (no deadlock).")
print(" — hop_count never exceeds max_hops (livelock freedom / Lyapunov).")
else:
print("RESULT: One or more violations found. See output above.")
sys.exit(1)
if __name__ == "__main__":
main()