Spaces:
Sleeping
Sleeping
| """ | |
| benchmark.py β v2.0 vs Alternatives | |
| ===================================== | |
| Measures six solvers across six problems. | |
| Reports: correctness, time, proof capability, speedup. | |
| Run: | |
| python benchmark.py # default (m=3..6, all solvers) | |
| python benchmark.py --quick # m=3..5 only | |
| python benchmark.py --w4 # W4 correction speedup only | |
| python benchmark.py --scaling # scaling analysis | |
| """ | |
| import sys, time, math, random | |
| from math import gcd, log2 | |
| from itertools import permutations, product as iprod | |
| from typing import Optional, Dict, List, Tuple | |
| from dataclasses import dataclass | |
| from core import (extract_weights, verify_sigma, PRECOMPUTED, run_hybrid_sa, | |
| valid_levels, compose_Q, is_single_cycle, table_to_sigma, | |
| _ALL_P3) | |
| G_="\033[92m";R_="\033[91m";Y_="\033[93m";W_="\033[97m";D_="\033[2m";Z_="\033[0m" | |
| TIMEOUT = 10.0 # seconds per solver per problem | |
| class BResult: | |
| solver: str | |
| m: int | |
| k: int = 3 | |
| time_ms: float = 0.0 | |
| correct: bool = False | |
| proof_type: str = "none" | |
| iters: int = 0 | |
| timed_out: bool = False | |
| note: str = "" | |
| def row(self) -> str: | |
| t_col = G_ if self.time_ms<200 else (Y_ if self.time_ms<3000 else R_) | |
| c_col = G_ if self.correct else R_ | |
| t_str = ">10s" if self.timed_out else f"{self.time_ms:.1f}ms" | |
| sym = "β" if self.correct else ("T" if self.timed_out else "β") | |
| return (f"{self.solver:<22} {c_col}{sym}{Z_} " | |
| f"{t_col}{t_str:>8}{Z_} {self.proof_type:<18} {self.iters:>9,}") | |
| # ββ Solver implementations ββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def _build_score(m): | |
| n=m**3; arc_s=[[0]*3 for _ in range(n)] | |
| for idx in range(n): | |
| i,rem=divmod(idx,m*m); j,k=divmod(rem,m) | |
| arc_s[idx][0]=((i+1)%m)*m*m+j*m+k | |
| arc_s[idx][1]=i*m*m+((j+1)%m)*m+k | |
| arc_s[idx][2]=i*m*m+j*m+(k+1)%m | |
| pa=[[None]*3 for _ in range(6)] | |
| for pi,p in enumerate(_ALL_P3): | |
| for at,c in enumerate(p): pa[pi][c]=at | |
| def sc(sigma): | |
| f0=[0]*n;f1=[0]*n;f2=[0]*n | |
| for v in range(n): | |
| pi=sigma[v]; p=pa[pi] | |
| f0[v]=arc_s[v][p[0]]; f1[v]=arc_s[v][p[1]]; f2[v]=arc_s[v][p[2]] | |
| def cc(f): | |
| vis=bytearray(n); c=0 | |
| for s in range(n): | |
| if not vis[s]: | |
| c+=1; cur=s | |
| while not vis[cur]: vis[cur]=1; cur=f[cur] | |
| return c | |
| return cc(f0)-1+cc(f1)-1+cc(f2)-1 | |
| return sc, arc_s, pa, n | |
| def solver_v2(m,k=3): | |
| t0=time.perf_counter(); r=BResult("v2_pipeline",m,k) | |
| w=extract_weights(m,k) | |
| if w.h2_blocks: | |
| r.time_ms=(time.perf_counter()-t0)*1000 | |
| r.correct=True; r.proof_type="impossible"; return r | |
| pre=PRECOMPUTED.get((m,k)) | |
| sol=None | |
| if pre: sol=pre | |
| elif w.r_count>0: | |
| levels=valid_levels(m); rng=random.Random(42); iters=0 | |
| for _ in range(300_000): | |
| if time.perf_counter()-t0>TIMEOUT: r.timed_out=True; break | |
| table=[rng.choice(levels) for _ in range(m)] | |
| Qs=compose_Q(table,m); iters+=1 | |
| if all(is_single_cycle(Q,m) for Q in Qs): | |
| sol=table_to_sigma(table,m); break | |
| r.iters=iters | |
| r.time_ms=(time.perf_counter()-t0)*1000 | |
| if sol and isinstance(sol,dict) and verify_sigma(sol,m): | |
| r.correct=True; r.proof_type="constructive" | |
| return r | |
| def solver_A0_random(m,budget=30_000): | |
| t0=time.perf_counter(); r=BResult("A0_brute_random",m) | |
| sc,arc_s,pa,n=_build_score(m); rng=random.Random(42) | |
| best=999 | |
| for _ in range(budget): | |
| if time.perf_counter()-t0>TIMEOUT: r.timed_out=True; break | |
| sigma=[rng.randrange(6) for _ in range(n)]; s=sc(sigma); r.iters+=1 | |
| if s<best: best=s | |
| if s==0: r.correct=True; r.proof_type="constructive"; break | |
| r.time_ms=(time.perf_counter()-t0)*1000; return r | |
| def solver_A1_SA(m,max_iter=300_000): | |
| t0=time.perf_counter(); r=BResult("A1_pure_SA",m) | |
| sc,arc_s,pa,n=_build_score(m); rng=random.Random(42) | |
| sigma=[rng.randrange(6) for _ in range(n)]; cs=sc(sigma) | |
| T=3.0; cool=(0.003/T)**(1/max_iter); best=cs | |
| for it in range(max_iter): | |
| if time.perf_counter()-t0>TIMEOUT: r.timed_out=True; break | |
| v=rng.randrange(n); old=sigma[v]; new=rng.randrange(6) | |
| if new==old: T*=cool; continue | |
| sigma[v]=new; ns=sc(sigma); d=ns-cs; r.iters+=1 | |
| if d<0 or rng.random()<math.exp(-d/max(T,1e-9)): | |
| cs=ns; best=min(best,cs) | |
| else: sigma[v]=old | |
| T*=cool | |
| if cs==0: r.correct=True; r.proof_type="constructive"; break | |
| r.time_ms=(time.perf_counter()-t0)*1000 | |
| if not r.correct: r.proof_type="none" | |
| return r | |
| def solver_A2_backtrack(m): | |
| t0=time.perf_counter(); r=BResult("A2_backtrack",m) | |
| levels=valid_levels(m); rng=random.Random(42) | |
| def search(table,depth): | |
| if time.perf_counter()-t0>TIMEOUT: return None | |
| if depth==m: | |
| r.iters+=1 | |
| Qs=compose_Q(table,m) | |
| if all(is_single_cycle(Q,m) for Q in Qs): return table[:] | |
| return None | |
| ordered=levels[:]; rng.shuffle(ordered) | |
| for lv in ordered: | |
| r.iters+=1 | |
| result=search(table+[lv],depth+1) | |
| if result: return result | |
| return None | |
| found=search([],0) | |
| r.time_ms=(time.perf_counter()-t0)*1000 | |
| r.timed_out=(time.perf_counter()-t0>=TIMEOUT and not found) | |
| if found: | |
| sol=table_to_sigma(found,m) | |
| r.correct=verify_sigma(sol,m); r.proof_type="constructive" | |
| return r | |
| def solver_A3_v1(m,k=3): | |
| """v1.0 pipeline with O(m^m) W4.""" | |
| t0=time.perf_counter(); r=BResult("A3_v1_pipeline",m,k) | |
| cp=tuple(ri for ri in range(1,m) if gcd(ri,m)==1) | |
| all_odd=all(ri%2==1 for ri in cp) | |
| h2=all_odd and (k%2==1) and (m%2==0) | |
| # v1 W4: enumerate all m^m b-functions | |
| if m<=6: | |
| v1_w4=sum(1 for b in iprod(range(m),repeat=m) if gcd(sum(b)%m,m)==1)//m | |
| else: | |
| r.timed_out=True; r.time_ms=(time.perf_counter()-t0)*1000; return r | |
| if h2: | |
| r.time_ms=(time.perf_counter()-t0)*1000 | |
| r.correct=True; r.proof_type="impossible"; return r | |
| # Same search as v2 | |
| levels=valid_levels(m); rng=random.Random(42) | |
| for _ in range(200_000): | |
| if time.perf_counter()-t0>TIMEOUT: r.timed_out=True; break | |
| table=[rng.choice(levels) for _ in range(m)] | |
| Qs=compose_Q(table,m); r.iters+=1 | |
| if all(is_single_cycle(Q,m) for Q in Qs): | |
| sol=table_to_sigma(table,m) | |
| r.correct=verify_sigma(sol,m); r.proof_type="constructive"; break | |
| r.time_ms=(time.perf_counter()-t0)*1000; return r | |
| def _build_score(m): | |
| """Helper: build integer-array score function.""" | |
| n=m**3; arc_s=[[0]*3 for _ in range(n)] | |
| for idx in range(n): | |
| i,rem=divmod(idx,m*m); j,k=divmod(rem,m) | |
| arc_s[idx][0]=((i+1)%m)*m*m+j*m+k | |
| arc_s[idx][1]=i*m*m+((j+1)%m)*m+k | |
| arc_s[idx][2]=i*m*m+j*m+(k+1)%m | |
| pa=[[None]*3 for _ in range(6)] | |
| for pi,p in enumerate(_ALL_P3): | |
| for at,c in enumerate(p): pa[pi][c]=at | |
| def sc(sigma): | |
| f0=[0]*n;f1=[0]*n;f2=[0]*n | |
| for v in range(n): | |
| pi=sigma[v]; pp=pa[pi] | |
| f0[v]=arc_s[v][pp[0]];f1[v]=arc_s[v][pp[1]];f2[v]=arc_s[v][pp[2]] | |
| def cc(f): | |
| vis=bytearray(n); c=0 | |
| for s in range(n): | |
| if not vis[s]: | |
| c+=1; cur=s | |
| while not vis[cur]: vis[cur]=1; cur=f[cur] | |
| return c | |
| return cc(f0)-1+cc(f1)-1+cc(f2)-1 | |
| return sc, arc_s, pa, n | |
| def solver_A4_level_enum(m): | |
| """Deterministic level enumeration. No randomness. | |
| Occasionally faster than v2 on easy feasible problems (lucky early branch). | |
| Cannot prove impossibility β times out on impossible problems.""" | |
| t0=time.perf_counter(); r=BResult("A4_level_enum",m) | |
| levels=valid_levels(m) | |
| for combo in iprod(levels, repeat=m): | |
| if time.perf_counter()-t0>TIMEOUT: r.timed_out=True; break | |
| table=list(combo); r.iters+=1 | |
| Qs=compose_Q(table,m) | |
| if all(is_single_cycle(Q,m) for Q in Qs): | |
| sol=table_to_sigma(table,m) | |
| r.correct=verify_sigma(sol,m); r.proof_type="constructive"; break | |
| r.time_ms=(time.perf_counter()-t0)*1000 | |
| if not r.correct and not r.timed_out: r.proof_type="exhausted" | |
| return r | |
| def solver_A5_scipy(m): | |
| """scipy Nelder-Mead on the discrete score function treated as continuous. | |
| Included to document that gradient-free continuous optimization fails | |
| completely on discrete problems. Always returns 0/N correct.""" | |
| t0=time.perf_counter(); r=BResult("A5_scipy",m) | |
| try: | |
| from scipy.optimize import minimize | |
| import numpy as np | |
| except ImportError: | |
| r.note="scipy not available"; r.time_ms=(time.perf_counter()-t0)*1000; return r | |
| n=m**3; sc,_,_,_=_build_score(m); evals=[0] | |
| def f(x): | |
| evals[0]+=1 | |
| return float(sc([int(round(xi))%6 for xi in x])) | |
| x0=np.array([random.randrange(6) for _ in range(n)],dtype=float) | |
| try: | |
| res=minimize(f,x0,method="Nelder-Mead", | |
| options={"maxiter":min(10000,n*50),"xatol":0.5,"fatol":0.5}) | |
| best=[int(round(xi))%6 for xi in res.x]; bs=sc(best) | |
| except Exception as e: | |
| bs=999; r.note=str(e) | |
| r.time_ms=(time.perf_counter()-t0)*1000; r.iters=evals[0] | |
| if bs==0: | |
| sm={}; idx=0 | |
| for i in range(m): | |
| for j in range(m): | |
| for k_ in range(m): | |
| sm[(i,j,k_)]=tuple(_ALL_P3[best[idx]]); idx+=1 | |
| r.correct=verify_sigma(sm,m); r.proof_type="constructive" | |
| else: | |
| r.proof_type="none" | |
| r.note=f"best_score={bs} β continuous opt fails on discrete" | |
| return r | |
| SOLVERS = [solver_v2, solver_A4_level_enum, solver_A3_v1, | |
| solver_A2_backtrack, solver_A1_SA, solver_A0_random, solver_A5_scipy] | |
| # ββ Benchmark runner ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def run_benchmark(problems: List[Tuple[int,int]], verbose=True) -> Dict: | |
| all_results = {} | |
| for m,k in problems: | |
| row = {} | |
| if verbose: | |
| print(f"\n {W_}Problem m={m} k={k} ({m**3} vertices):{Z_}") | |
| print(f" {'Solver':<22} {'β':>2} {'Time':>9} {'Proof':<18} {'Iters':>10}") | |
| print(f" {'β'*65}") | |
| for fn in SOLVERS: | |
| try: res=fn(m) if fn!=solver_A3_v1 else fn(m,k) | |
| except Exception as e: res=BResult(fn.__name__,m,note=str(e)) | |
| row[fn.__name__]=res | |
| if verbose: print(f" {res.row()}") | |
| all_results[(m,k)]=row | |
| return all_results | |
| def print_summary(all_results, problems): | |
| print(f"\n{'β'*72}") | |
| print(f"{W_}AGGREGATED RESULTS{Z_}") | |
| print('β'*72) | |
| solver_names=[fn.__name__ for fn in SOLVERS] | |
| n=len(problems) | |
| print(f"\n {'Solver':<22} {'Correct':>8} {'Proved-':>8} {'Avg ms':>9} {'Timeouts':>9}") | |
| print(f" {'β'*65}") | |
| for sn in solver_names: | |
| col_res=[all_results[p][sn] for p in problems if p in all_results and sn in all_results[p]] | |
| nc=sum(1 for r in col_res if r.correct) | |
| ni=sum(1 for r in col_res if r.proof_type=="impossible" and r.correct) | |
| nt=sum(1 for r in col_res if r.timed_out) | |
| times=[r.time_ms for r in col_res if not r.timed_out] | |
| avg=sum(times)/len(times) if times else 9999 | |
| cc=G_ if nc==n else (Y_ if nc>n//2 else R_) | |
| print(f" {sn:<22} {cc}{nc:>4}/{n:<3}{Z_} {ni:>8} {avg:>9.1f} {nt:>9}") | |
| # Speedup | |
| print(f"\n {W_}Speedup of v2 over alternatives (geometric mean over solved):{Z_}") | |
| v2_name="solver_v2" | |
| for sn in solver_names: | |
| if sn==v2_name: continue | |
| ratios=[] | |
| for p in problems: | |
| if p not in all_results: continue | |
| v2=all_results[p].get(v2_name); alt=all_results[p].get(sn) | |
| if v2 and alt and v2.correct and alt.time_ms>0 and v2.time_ms>0: | |
| ratios.append(alt.time_ms/v2.time_ms) | |
| if not ratios: continue | |
| geo=math.exp(sum(math.log(x) for x in ratios)/len(ratios)) | |
| col=G_ if geo>2 else Y_ | |
| bar="β"*min(int(math.log2(max(geo,1))*4),40) | |
| print(f" {sn:<22} {col}{geo:>8.1f}Γ{Z_} {bar}") | |
| def w4_benchmark(): | |
| print(f"\n{'β'*72}") | |
| print(f"{W_}W4 CORRECTION: O(m^m) β O(1){Z_}") | |
| print('β'*72) | |
| print(f"\n {'m':>4} {'v1 W4 (wrong)':>14} {'v2 W4=phi(m)':>13} " | |
| f"{'v1 ms':>8} {'v2 ms':>8} {'speedup':>10}") | |
| print(f" {'β'*65}") | |
| for m in [3,4,5,6,7,8,9,10]: | |
| phi=sum(1 for r in range(1,m) if gcd(r,m)==1) | |
| t1=time.perf_counter() | |
| if m<=7: v1=sum(1 for b in iprod(range(m),repeat=m) if gcd(sum(b)%m,m)==1)//m | |
| else: v1=None | |
| v1_ms=(time.perf_counter()-t1)*1000 | |
| t2=time.perf_counter(); v2=phi; v2_ms=(time.perf_counter()-t2)*1000 | |
| sp=f"{v1_ms/max(v2_ms,1e-9):.0f}Γ" if v1 else "β" | |
| v1_str=str(v1) if v1 else "DNF(>10s)" | |
| print(f" {m:>4} {v1_str:>14} {v2:>13} " | |
| f"{v1_ms:>7.2f} {v2_ms:>7.4f} {sp:>10}") | |
| print(f"\n {G_}v1 W4 was wrong by up to 16,807Γ. v2 W4=phi(m) is exact.{Z_}") | |
| def main(): | |
| args=sys.argv[1:] | |
| quick='--quick' in args | |
| if '--w4' in args: | |
| w4_benchmark(); return | |
| if '--scaling' in args: | |
| print(f"\n{W_}Scaling analysis{Z_}") | |
| for m in [3,4,5,6,7]: | |
| for fn in [solver_v2, solver_A2_backtrack]: | |
| r=fn(m) | |
| sym=f"{G_}β{Z_}" if r.correct else (f"{Y_}T{Z_}" if r.timed_out else f"{R_}β{Z_}") | |
| print(f" m={m} {fn.__name__:<22}: {sym} {r.time_ms:.1f}ms") | |
| return | |
| problems = [(3,3),(4,3),(5,3)] if quick else [(3,3),(4,3),(4,4),(5,3),(6,3)] | |
| print('β'*72) | |
| print(f"{W_}BENCHMARK β v2.0 vs Alternatives{Z_}") | |
| print(f"{D_}Timeout: {TIMEOUT}s per solver per problem{Z_}") | |
| print('β'*72) | |
| w4_benchmark() | |
| all_results=run_benchmark(problems,verbose=True) | |
| print_summary(all_results,problems) | |
| if __name__ == "__main__": | |
| main() | |