Created
November 3, 2025 16:30
-
-
Save ryanwebster90/73545b64f5b40ee9b937d22e6f24ae43 to your computer and use it in GitHub Desktop.
tm visualization
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Dynamic visualization of 2-symbol TM's. After --warmup steps, bits on the tape are regrouped into bytes and color-mapped, | |
| # then the 1D tape is reshaped and truncated from the left to a 64x64 frame to be recorded every --frame-every steps for --vis frames. | |
| # ## Example usage: | |
| # https://bbchallenge.org/76708232 | |
| # python vis_tm_bytes.py | |
| # https://bbchallenge.org/43374927 | |
| # python vis_tm_bytes.py --tm=1RB0RD_1LC1LB_1RA0LB_0RE1RD_---1RA --warmup=200_000_000 | |
| # For 6-state machines, use a larger warmup (current BB(6) champion family) | |
| # python vis_tm_bytes.py --warmup=200_000_000 --vis=80_000_000 --frame-every=100_000 --tm=1RB1RA_1RC1RZ_1LD0RF_1RA0LE_0LD1RC_1RA0RE --side=64 | |
| import os, time, argparse | |
| import numpy as np | |
| import imageio | |
| import matplotlib.pyplot as plt | |
| HALT = -1 | |
| def parse_tm(spec, flip_lr=True): | |
| rows = spec.strip().split('_') | |
| n = len(rows[0]) // 3 | |
| m = len(rows) | |
| trans = {} | |
| for si, row in enumerate(rows): | |
| for a in range(n): | |
| tok = row[a*3:(a+1)*3] | |
| if tok == '---': | |
| w, mv, nx = 1, -1, HALT | |
| else: | |
| w = int(tok[0]) | |
| mv = -1 if (tok[1] == 'L') else 1 | |
| if flip_lr: mv = -mv | |
| nx = HALT if tok[2] == 'H' else (ord(tok[2]) - ord('A')) | |
| trans[(si, a)] = (w, mv, nx) | |
| return trans, m, n | |
| def step_once(S, TRANS): | |
| sym = 1 if (S['pos'] in S['ones']) else 0 | |
| w, m, nxt = TRANS[(S['state'], sym)] | |
| if w == 1: | |
| if S['pos'] not in S['ones']: | |
| S['ones'].add(S['pos']); S['sigma'] += 1 | |
| else: | |
| if S['pos'] in S['ones']: | |
| S['ones'].remove(S['pos']); S['sigma'] -= 1 | |
| S['pos'] += m | |
| S['steps'] += 1 | |
| if nxt == HALT: | |
| return True | |
| S['state'] = nxt | |
| return False | |
| def enclosed_left_anchor(ones): | |
| return min(ones) if ones else None | |
| def bits_to_bytes_MSB(bits): | |
| pad = (-len(bits)) % 8 | |
| if pad: bits += '0' * pad | |
| return bytes(int(bits[i:i+8], 2) for i in range(0, len(bits), 8)) | |
| def frame_bytes(ones, side=64): | |
| FRAMESZ = side * side | |
| if not ones: | |
| return bytes(FRAMESZ) | |
| L = enclosed_left_anchor(ones) | |
| total_bits = FRAMESZ * 8 | |
| bits = ''.join('1' if (L + i) in ones else '0' for i in range(total_bits)) | |
| by = bits_to_bytes_MSB(bits) | |
| if len(by) < FRAMESZ: | |
| by = by + bytes(FRAMESZ - len(by)) | |
| return by[:FRAMESZ] | |
| def bytes_to_img_u8(by, side=64): | |
| arr = np.frombuffer(by, dtype=np.uint8) | |
| return arr.reshape(side, side) | |
| def upscale4(img): | |
| return np.repeat(np.repeat(img, 4, axis=0), 4, axis=1) | |
| def colorize_u8(img_u8, cmap_name='turbo'): | |
| cmap = plt.get_cmap(cmap_name) | |
| return (cmap(img_u8.astype(np.float32)/255.0)[:, :, :3] * 255.0).astype(np.uint8) | |
| def sanitize(s): | |
| return ''.join(ch if ch.isalnum() else '_' for ch in s) | |
| def run( | |
| spec="1RB1LB_1LC0RE_0LD0RA_1RA0LD_---0RC", | |
| warmup_steps=50_000_000, | |
| vis_steps=200_000_000, | |
| frame_every=10_000, | |
| side=64, | |
| out_dir='', | |
| video_name='', | |
| final_png='', | |
| cmap_name='turbo', | |
| fps=30, | |
| print_every=1_000_000 | |
| ): | |
| TRANS, M, N = parse_tm(spec, flip_lr=True) | |
| tag = sanitize(spec) | |
| if not out_dir: out_dir = f'out_{tag}_{side}x{side}_x4' | |
| if not video_name: video_name = f'{tag}_{side}x{side}_x4.mp4' | |
| if not final_png: final_png = f'{tag}_{side}x{side}_x4_final.png' | |
| os.makedirs(out_dir, exist_ok=True) | |
| video_path = os.path.join(out_dir, video_name) | |
| png_path = os.path.join(out_dir, final_png) | |
| S = {'ones':set(), 'state':0, 'pos':0, 'steps':0, 'sigma':0} | |
| t0 = time.time() | |
| while S['steps'] < warmup_steps: | |
| if step_once(S, TRANS): | |
| break | |
| if print_every and (S['steps'] % print_every == 0): | |
| print(f'[warmup] steps={S["steps"]:,} sigma={S["sigma"]:,} pos={S["pos"]:,} dt={(time.time()-t0):.1f}s') | |
| print(f'[warmup] done at steps={S["steps"]:,}, sigma={S["sigma"]:,}, halted={S["steps"] < warmup_steps}') | |
| with imageio.get_writer(video_path, fps=fps, codec='libx264', quality=8, pixelformat='yuv420p') as writer: | |
| by = frame_bytes(S['ones'], side=side) | |
| img = bytes_to_img_u8(by, side=side) | |
| frame = colorize_u8(upscale4(img), cmap_name=cmap_name) | |
| writer.append_data(frame) | |
| target2 = S['steps'] + vis_steps | |
| while S['steps'] < target2: | |
| if step_once(S, TRANS): | |
| break | |
| if (S['steps'] % frame_every) == 0: | |
| by = frame_bytes(S['ones'], side=side) | |
| img = bytes_to_img_u8(by, side=side) | |
| frame = colorize_u8(upscale4(img), cmap_name=cmap_name) | |
| writer.append_data(frame) | |
| if print_every and (S['steps'] % print_every == 0): | |
| print(f'[vis] steps={S["steps"]:,} sigma={S["sigma"]:,} pos={S["pos"]:,} dt={(time.time()-t0):.1f}s') | |
| by = frame_bytes(S['ones'], side=side) | |
| img = bytes_to_img_u8(by, side=side) | |
| frame = colorize_u8(upscale4(img), cmap_name=cmap_name) | |
| writer.append_data(frame) | |
| plt.figure(figsize=(4,4)); plt.imshow(frame); plt.axis('off'); plt.tight_layout() | |
| plt.savefig(png_path, dpi=180, bbox_inches='tight'); plt.close() | |
| print(f'[done] steps={S["steps"]:,} sigma={S["sigma"]:,}') | |
| print(f'[out] video={video_path}\n[out] png={png_path}') | |
| def main(): | |
| parser = argparse.ArgumentParser(description='M-state N-symbol TM visualizer') | |
| parser.add_argument('--tm', type=str, default="1RB1LB_1LC0RE_0LD0RA_1RA0LD_---0RC") | |
| parser.add_argument('--warmup', type=int, default=50_000_000) | |
| parser.add_argument('--vis', type=int, default=10_000_000) | |
| parser.add_argument('--frame-every', type=int, default=40_000) | |
| parser.add_argument('--side', type=int, default=64) | |
| parser.add_argument('--out', type=str, default='tm_vis/') | |
| parser.add_argument('--fps', type=int, default=30) | |
| parser.add_argument('--cmap', type=str, default='turbo') | |
| parser.add_argument('--print-every', type=int, default=1_000_000) | |
| args = parser.parse_args() | |
| print('states:', parse_tm(args.tm)[1], 'symbols:', parse_tm(args.tm)[2]) | |
| run( | |
| spec=args.tm, | |
| warmup_steps=args.warmup, | |
| vis_steps=args.vis, | |
| frame_every=args.frame_every, | |
| side=args.side, | |
| out_dir=args.out, | |
| fps=args.fps, | |
| cmap_name=args.cmap, | |
| print_every=args.print_every, | |
| ) | |
| if __name__ == '__main__': | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment