a collection of generative art scripts
at main 325 lines 8.6 kB view raw
1 2import math 3 4tau = math.pi * 2 5 6mod = 12 7 8valid_states = set() 9collapsed_states = set() 10collapse_map = {} 11 12def contains(L,R,P): 13 l_positions = range(mod-L, mod + 1) 14 r_positions = range(1, R+1) 15 valid_P = set(l_positions) | set(r_positions) 16 return P in valid_P 17 18# Step 0: build valid states 19for L in range(0, mod): 20 for R in range(0, mod): 21 if L + R < mod - 1: 22 l_positions = range(mod-L, mod + 1) 23 r_positions = range(1, R+1) 24 valid_P = set(l_positions) | set(r_positions) 25 26 collapsed_states.add((L, R, L + R + 1)) 27 for P in valid_P: 28 valid_states.add((L, R, P)) 29 collapse_map[(L, R, P)] = (L, R, L + R + 1) 30 elif L + R == mod - 1: 31 for P in range(1, mod): 32 valid_states.add(("done", P)) 33 34print("Number of valid states:", len(valid_states)) 35 36#print(valid_states) 37 38# Step 1: build directed edges 39edges = {} # key: state, value: list of next states 40collapsed_edges = {} 41 42for state in valid_states: 43 if state[0] == "done": 44 edges[state] = (state, []) # terminal 45 continue 46 47 L, R, P = state 48 out = [] 49 50 # clockwise move 51 P1 = (P - 2) % mod + 1 52 53 L1 = L if contains(L,R,P1) else L+1 54 55 if L1 + R < mod - 1: 56 if (L1, R, P1) in valid_states: 57 out.append((L1, R, P1)) 58 else: 59 print("AAAAAAAA", (L1, R, P1), (L, R, P)) 60 else: 61 out.append(("done", P1)) 62 63 # counterclockwise move 64 P2 = (P % mod) + 1 65 66 R1 = R if contains(L,R,P2) else R+1 67 if L + R1 < mod - 1: 68 if (L, R1, P2) in valid_states: 69 out.append((L, R1, P2)) 70 else: 71 print("BBBBBBBB", (L, R1, P2), (L, R, P)) 72 else: 73 out.append(("done", P2)) 74 75 edges[state] = (state, out) 76 77# Sanity check 78outdegrees = [len(v) for (u,v) in edges.values()] 79terminal_count = sum(1 for (u,v) in edges.values() if len(v) == 0) 80print("Max outdegree:", max(outdegrees)) # should be 2 81print("Min outdegree (non-terminal):", 82 min([d for s,d in zip(edges.keys(), outdegrees) if s[0] != "done"])) # should be 2 83print("Number of terminal states:", terminal_count) # 12 84 85 86 87 88for state in valid_states: 89 outs = edges[state] 90 if len(outs[1]) < 2: 91 collapsed_edges[state] = outs 92 continue 93 outs_of_outs = [edges[out] for out in outs[1]] 94 dests_of_ooo = [dests for src, dests in outs_of_outs] 95 is_isomorph = [state in dests for dests in dests_of_ooo] 96 srcs_of_ooo = [src for src, dests in outs_of_outs] 97 if is_isomorph == [True, True]: 98 pass 99 elif state in collapse_map: 100 collapsed = collapse_map[state] 101 for dest, is_iso in zip([o for o in outs[1]], is_isomorph): 102 if not is_iso: 103 if dest in collapse_map: 104 if collapsed in collapsed_edges: 105 collapsed_edges[collapsed][1].append(collapse_map[dest]) 106 else: 107 collapsed_edges[collapsed] = (collapsed, [collapse_map[dest]]) 108 else: 109 if collapsed in collapsed_edges: 110 collapsed_edges[collapsed][1].append(dest) 111 else: 112 collapsed_edges[collapsed] = (collapsed, [dest]) 113 else: 114 print(state, outs, "b") 115 116#print(collapsed_states) 117#print(collapsed_edges) 118 119 120 121import networkx as nx 122import matplotlib 123matplotlib.use("Agg") 124import matplotlib.pyplot as plt 125 126G = nx.DiGraph() 127 128# Add nodes and edges 129 130collapse = False 131 132drawn_edges = collapsed_edges if collapse else edges 133 134 135def collapsed_rep(state): 136 if state[0] == "done": 137 return state 138 return collapse_map.get(state, state) 139 140for src, outs in drawn_edges.items(): 141 G.add_node(src) 142 for dst in outs[1]: 143 #print(src, dst) 144 G.add_edge(src, dst) 145 146import matplotlib.cm as cm 147import matplotlib.colors as mcolors 148 149reps = [collapsed_rep(s) for s in G.nodes] 150unique_reps = list(dict.fromkeys(reps)) # stable order 151 152cmap = cm.get_cmap("tab20", len(unique_reps)) 153rep_to_color = { 154 r: mcolors.to_hex(cmap(i)) 155 for i, r in enumerate(unique_reps) 156} 157 158# Build layout 159pos = {} 160 161def collapsed_pos(state): 162 if state[0] == "done": 163 _, P = state 164 x = (mod+1) * (mod) * mod 165 y = (mod+1) * mod * (P + mod // 2) + mod // 2 166 else: 167 (L, R, P) = state 168 x = (mod+1) * (mod) * P 169 y = (mod+1) * mod * ((P)/2) + (mod+1) * (mod * (mod - L) - mod//2) 170 return (x,y) 171 172if collapse: 173 for state in G.nodes: 174 px, py = collapsed_pos(state) 175 pos[state] = py, px 176else: 177 for state in G.nodes: 178 #if state[0] == "done": 179 # _, P = state 180 # x = (mod+1) * (P + (mod//2)) % mod 181 # y = (mod - 1) * (mod + 1) 182 #else: 183 # L, R, P = state 184 # x = (mod+1) * (P + (mod//2)) % mod 185 # y = (mod+1) * (L + R) + R 186 if state[0] == "done": 187 px, py = collapsed_pos(state) 188 pos[state] = py, px 189 elif state in collapse_map: 190 px, py = collapsed_pos(collapse_map[state]) 191 p = state[2] 192 start = mod - state[0] 193 end = state[1] 194 p_rel = p - start if p >= start else p + state[0] 195 portion = 0.5 196 if state[0] + end > 0: 197 portion = (p_rel) / (state[0] + end) 198 pos[state] = ( 199 #py + (mod // 2 * math.cos(portion * tau / 2)) * (mod - 2), 200 py + (portion - 0.5) * mod * (mod - 2), 201 px + (mod * 0.4) * mod * math.sin(portion * tau / 2) 202 ) 203 print(px, py, state[0]) 204 else: 205 print(state, "aaa") 206 pos[state] = (0, 0) 207 208#pos[(0,0,mod)] = ((mod+1) * (mod + (mod // 2)) % mod, (mod - 1) * (mod + 1)) 209 210# Labels 211labels = {} 212collapsed_labels = {} 213for state in G.nodes: 214 if state[0] == "done": 215 labels[state] = f"end at {state[1]}" 216 collapsed_labels[state] = f"end at {state[1]}" 217 else: 218 L, R, P = state 219 if state in collapsed_states: 220 nL = 12 - L 221 nR = R if R != 0 else 12 222 collapsed_labels[state] = f"{nL} to {nR}" 223 else: 224 collapsed_labels[state] = f"({L}, {R}, {P})" 225 labels[state] = f"{P}" #f"({L}, {R}, {P}" 226 227spring = False 228 229done_states = [("done",n) for n in range(1,mod)] 230 231init_state = (0, 0, 1) if collapse else (0,0,mod) 232 233fixed_states = done_states + [init_state] 234 235if spring: 236 pos = nx.spring_layout( 237 G, 238 pos=pos, # your existing layout 239 fixed=fixed_states, # or list of nodes to keep fixed 240 #center=(6 * 13, 11 * 13 / 2), 241 k=1, 242 iterations=10000 243 ) 244else: 245 #pos = nx.kamada_kawai_layout(G) 246 #pos = nx.bfs_layout(G, init_state) 247 #pos = nx.planar_layout(G) 248 pass 249 250node_colors = "lightblue" 251 252if not collapse: 253 node_colors = [rep_to_color[collapsed_rep(s)] for s in G.nodes] 254else: 255 node_colors = [rep_to_color[s] for s in G.nodes] 256 257frame_count = 512 258 259probs = {} 260 261for state in valid_states: 262 probs[state] = 0 263 264probs[(0,0,mod)] = 1 265 266 267for frame_index in range(frame_count): 268 # Draw 269 sizes = [] 270 if collapse: 271 for s in G.nodes: 272 size = 0 273 for key in collapse_map: 274 if s == collapse_map[key]: 275 size += probs[key] 276 #print(s, key, collapse_map[key]) 277 if s in done_states: 278 size += probs[s] 279 sizes.append(30 + size * 50000) 280 else: 281 sizes = [30 + probs[s] * 50000 for s in G.nodes] 282 283 plt.figure(figsize=(14, 10)) 284 nx.draw( 285 G, 286 pos, 287 node_size=sizes, 288 node_color=node_colors, 289 edge_color="gray", 290 arrowsize=10 if collapse else 5, 291 with_labels=False 292 ) 293 294 used_labels = collapsed_labels if collapse else labels 295 296 for state in done_states: 297 p = probs[state] * 100 298 used_labels[state] = f"{state[1]}: {p:.2f}%" 299 300 nx.draw_networkx_labels(G, pos, used_labels, font_size=6) 301 302 303 plt.tight_layout() 304 305 plt.savefig(f"{frame_index:06d}.png", dpi=100, bbox_inches="tight") 306 plt.close() 307 308 probs_next = {} 309 for state in valid_states: 310 if not state in done_states: 311 probs_next[state] = 0 312 for state in done_states: 313 probs_next[state] = probs[state] 314 for state in valid_states: 315 if state in done_states: 316 continue 317 else: 318 for dst in edges[state][1]: 319 probs_next[dst] += probs[state] / 2 320 321 probs = probs_next 322 323 324#plt.tight_layout() 325#plt.show()