from __future__ import annotations
import argparse
import pathlib
import sys
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent))
from decode import BlockedDafsa
def canonical_form(seq: list[int]) -> list[int]:
n = len(seq)
best: list[int] | None = None
for base in (seq, seq[::-1]):
doubled = base + base
for k in range(n):
rot = doubled[k : k + n]
if best is None or rot < best:
best = rot
return best if best is not None else []
def is_canonical_ccw(seq: list[int]) -> tuple[bool, str]:
if sum(seq) <= 0:
return False, f"not CCW (turn-sum {sum(seq)} <= 0)"
if seq != canonical_form(seq):
return False, "not the dihedral-canonical (lex-min rotation/reverse) form"
return True, ""
def main(argv: list[str]) -> int:
ap = argparse.ArgumentParser(description="Verify canonical CCW form of stored rats.")
ap.add_argument("asset_dir", nargs="?", default=".")
ap.add_argument("--stride", type=int, default=1, help="check every Nth rat (sampling)")
ap.add_argument("--max", type=int, default=0, help="stop after N checks (0 = no limit)")
args = ap.parse_args(argv[1:])
asset_dir = pathlib.Path(args.asset_dir)
if not (asset_dir / "block_index.json").exists():
sys.stderr.write(
f"no block_index.json in {asset_dir}; pass the asset directory\n"
)
return 2
bd = BlockedDafsa(asset_dir)
checked = 0
violations = 0
for i, seq in enumerate(bd.iter_rats()):
if args.stride > 1 and (i % args.stride) != 0:
continue
ok, reason = is_canonical_ccw(seq)
checked += 1
if not ok:
violations += 1
if violations <= 10:
print(f"NON-CANONICAL: {seq} -- {reason}")
if args.max and checked >= args.max:
break
mode = "full" if args.stride == 1 and not args.max else f"sampled (stride={args.stride}, max={args.max})"
if violations:
print(f"{violations} violation(s) in {checked} checked rat(s) [{mode}]")
return 1
print(f"OK ({checked} rat(s) verified canonical CCW [{mode}])")
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv))