Spaces:
Sleeping
Sleeping
| import math, random, time, sys, os, json | |
| from math import gcd | |
| from itertools import permutations, product as iprod | |
| from typing import Optional, List, Dict, Tuple, Any | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # UNIFIED ENGINE v2.2 (Basin Escape) | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def _build_sa(m: int, k: int=3): | |
| n = m**k | |
| arc_s = [[0]*k for _ in range(n)] | |
| m_pow = [m**i for i in range(k)] | |
| m_pow.reverse() | |
| for idx in range(n): | |
| for c in range(k): | |
| if (idx // m_pow[c]) % m == m - 1: | |
| arc_s[idx][c] = idx - (m - 1) * m_pow[c] | |
| else: | |
| arc_s[idx][c] = idx + m_pow[c] | |
| all_p = [list(p) for p in permutations(range(k))] | |
| pa = [[None]*k for _ in range(len(all_p))] | |
| for pi,p in enumerate(all_p): | |
| for at,c in enumerate(p): pa[pi][c] = at | |
| return n, arc_s, pa, all_p | |
| def _sa_score(sigma: List[int], arc_s, pa, n: int, k: int=3) -> int: | |
| total_score = 0 | |
| for c in range(k): | |
| vis = bytearray(n); comps = 0 | |
| for s in range(n): | |
| if not vis[s]: | |
| comps += 1; cur = s | |
| while not vis[cur]: | |
| vis[cur] = 1; pi = sigma[cur] | |
| cur = arc_s[cur][pa[pi][c]] | |
| total_score += comps - 1 | |
| return total_score | |
| def get_node_orbits(m: int, k: int, subgroup_generators: List[Tuple[int, ...]]) -> List[List[int]]: | |
| n = m**k; unvisited = set(range(n)); orbits = [] | |
| while unvisited: | |
| start_idx = next(iter(unvisited)); orbit = {start_idx} | |
| queue = [start_idx]; unvisited.remove(start_idx) | |
| while queue: | |
| curr_idx = queue.pop(0) | |
| curr_coords = []; val = curr_idx | |
| for _ in range(k): curr_coords.append(val % m); val //= m | |
| curr_coords.reverse() | |
| for gen in subgroup_generators: | |
| nxt_coords = [(curr_coords[d] + gen[d]) % m for d in range(k)] | |
| nxt_idx = 0 | |
| for x in nxt_coords: nxt_idx = nxt_idx * m + x | |
| if nxt_idx in unvisited: | |
| unvisited.remove(nxt_idx); orbit.add(nxt_idx); queue.append(nxt_idx) | |
| orbits.append(list(orbit)) | |
| return orbits | |
| def run_hybrid_sa(m: int, k: int=3, seed: int=0, max_iter: int=10_000_000, verbose: bool=True): | |
| n, arc_s, pa, all_p = _build_sa(m, k); nP = len(all_p) | |
| gens = [tuple([m//2]*k)] if m%2==0 else [tuple([1]*k)] | |
| orbits = get_node_orbits(m, k, gens) | |
| rng = random.Random(seed); sigma = [rng.randrange(nP) for _ in range(n)] | |
| cs = _sa_score(sigma, arc_s, pa, n, k); bs = cs; best = sigma[:] | |
| T = 2.0; cool = 0.999998; t0 = time.perf_counter(); stall = 0; reheats = 0 | |
| for it in range(max_iter): | |
| if cs == 0: break | |
| if cs <= 12: | |
| # Basin Escape v2.2: greedy descent + orbit swaps | |
| order = list(range(n)); rng.shuffle(order); fixed = False | |
| for v in order: | |
| old = sigma[v] | |
| for pi in rng.sample(range(nP), nP): | |
| if pi == old: continue | |
| sigma[v] = pi; ns = _sa_score(sigma, arc_s, pa, n, k) | |
| if ns < cs: | |
| cs = ns; fixed = True | |
| if cs < bs: bs = cs; best = sigma[:] | |
| else: sigma[v] = old | |
| if fixed: break | |
| if fixed: break | |
| if not fixed: | |
| # Try orbit-flip greedy | |
| for orbit in rng.sample(orbits, min(len(orbits), 20)): | |
| old_vals = [sigma[v] for v in orbit] | |
| for pi in rng.sample(range(nP), nP): | |
| if all(sigma[v] == pi for v in orbit): continue | |
| for v in orbit: sigma[v] = pi | |
| ns = _sa_score(sigma, arc_s, pa, n, k) | |
| if ns < cs: | |
| cs = ns; fixed = True | |
| if cs < bs: bs = cs; best = sigma[:] | |
| else: | |
| for i, v in enumerate(orbit): sigma[v] = old_vals[i] | |
| if fixed: break | |
| if fixed: break | |
| if not fixed: | |
| reheats += 1; stall = 0; sigma = best[:]; cs = bs; T = 1.0 | |
| for _ in range(max(1, int(n * 0.03))): | |
| vk = rng.randrange(n); sigma[vk] = rng.randrange(nP) | |
| cs = _sa_score(sigma, arc_s, pa, n, k) | |
| continue | |
| if rng.random() < 0.3: | |
| orbit = rng.choice(orbits); new_p = rng.randrange(nP) | |
| old_vals = [sigma[v] for v in orbit] | |
| for v in orbit: sigma[v] = new_p | |
| ns = _sa_score(sigma, arc_s, pa, n, k); d = ns - cs | |
| if d <= 0 or rng.random() < math.exp(-d / T): | |
| cs = ns | |
| if cs < bs: bs = cs; best = sigma[:]; stall = 0 | |
| else: stall += 1 | |
| else: | |
| for i, v in enumerate(orbit): sigma[v] = old_vals[i] | |
| stall += 1 | |
| else: | |
| v = rng.randrange(n); old = sigma[v]; sigma[v] = rng.randrange(nP) | |
| ns = _sa_score(sigma, arc_s, pa, n, k); d = ns - cs | |
| if d <= 0 or rng.random() < math.exp(-d / T): | |
| cs = ns | |
| if cs < bs: bs = cs; best = sigma[:]; stall = 0 | |
| else: stall += 1 | |
| else: | |
| sigma[v] = old; stall += 1 | |
| if stall > 100_000: | |
| reheats += 1; stall = 0; sigma = best[:]; cs = bs; T = 2.0 / (1.2**reheats) | |
| for _ in range(max(1, int(n * (0.05 if cs > 20 else 0.02)))): | |
| vk = rng.randrange(n); sigma[vk] = rng.randrange(nP) | |
| cs = _sa_score(sigma, arc_s, pa, n, k); continue | |
| T *= cool | |
| if verbose and (it+1) % 100_000 == 0: | |
| print(f" it={it+1:>8,} T={T:.5f} score={cs} best={bs} reh={reheats} {time.perf_counter()-t0:.1f}s") | |
| return best if bs == 0 else None, {"best": bs, "iters": it+1} | |
| def run_fiber_structured_sa(m: int, k: int, seed: int=0, max_iter: int=10_000_000, verbose: bool=True): | |
| n, arc_s, pa, all_p = _build_sa(m, k); nP = len(all_p) | |
| def get_coords(idx): | |
| coords = []; val = idx | |
| for _ in range(k): coords.append(val % m); val //= m | |
| coords.reverse(); return coords | |
| fibers = [sum(get_coords(v)) % m for v in range(n)] | |
| def get_key(v): | |
| c = get_coords(v) | |
| return (fibers[v],) + tuple(c[1:-1]) | |
| node_to_key = [get_key(v) for v in range(n)] | |
| keys = list(set(node_to_key)) | |
| rng = random.Random(seed); tab = {k_: rng.randrange(nP) for k_ in keys} | |
| def make_sigma(t): | |
| sig = [0]*n | |
| for v in range(n): sig[v] = t[node_to_key[v]] | |
| return sig | |
| sigma = make_sigma(tab); cs = _sa_score(sigma, arc_s, pa, n, k); bs = cs; bt = tab.copy() | |
| T = 2.0; cool = 0.999998; t0 = time.perf_counter(); stall = 0 | |
| for it in range(max_iter): | |
| if cs == 0: break | |
| if cs <= 20: | |
| # Basin Escape v2.2 for FiberSA | |
| rk_list = list(keys); rng.shuffle(rk_list); fixed = False | |
| for rk in rk_list: | |
| old = tab[rk] | |
| for pi in rng.sample(range(nP), nP): | |
| if pi == old: continue | |
| tab[rk] = pi; sig = make_sigma(tab) | |
| ns = _sa_score(sig, arc_s, pa, n, k) | |
| if ns < cs: | |
| cs = ns; fixed = True | |
| if cs < bs: bs = cs; bt = tab.copy() | |
| else: tab[rk] = old | |
| if fixed: break | |
| if fixed: break | |
| if not fixed: | |
| tab = bt.copy() | |
| for _ in range(max(1, int(len(keys)*0.05))): tab[rng.choice(keys)] = rng.randrange(nP) | |
| sig = make_sigma(tab); cs = _sa_score(sig, arc_s, pa, n, k) | |
| continue | |
| rk = rng.choice(keys); old = tab[rk]; tab[rk] = rng.randrange(nP) | |
| if tab[rk] == old: continue | |
| sig = make_sigma(tab); ns = _sa_score(sig, arc_s, pa, n, k); d = ns - cs | |
| if d <= 0 or rng.random() < math.exp(-d / T): | |
| cs = ns | |
| if cs < bs: bs = cs; bt = tab.copy(); stall = 0 | |
| else: stall += 1 | |
| else: tab[rk] = old; stall += 1 | |
| if stall > 50_000: | |
| stall = 0; tab = bt.copy(); cs = bs | |
| for _ in range(max(1, int(len(keys)*0.05))): tab[rng.choice(keys)] = rng.randrange(nP) | |
| sig = make_sigma(tab); cs = _sa_score(sig, arc_s, pa, n, k); continue | |
| T *= cool | |
| if verbose and (it+1) % 100_000 == 0: | |
| print(f" it={it+1:>8,} T={T:.5f} score={cs} best={bs} {time.perf_counter()-t0:.1f}s") | |
| sigma_final = make_sigma(bt) | |
| sol_dict = {} | |
| for i, pi in enumerate(sigma_final): | |
| coords = get_coords(i); sol_dict[str(tuple(coords))] = tuple(all_p[pi]) | |
| return sigma_final if bs == 0 else None, {"best": bs, "iters": it+1, "solution": sol_dict if bs==0 else None} | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # MAIN | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def main(): | |
| problem = os.environ.get("KAGGLE_PROBLEM", "P3") | |
| iters = int(os.environ.get("MAX_ITER", 10_000_000)) | |
| seed = int(os.environ.get("SEED", 42)) | |
| print(f"Problem: {problem}, Max Iters: {iters}, Seed: {seed}") | |
| if problem == "P1": | |
| sol, stats = run_fiber_structured_sa(m=4, k=4, seed=seed, max_iter=iters) | |
| elif problem == "P3": | |
| sol, stats = run_hybrid_sa(m=8, k=3, seed=seed, max_iter=iters) | |
| else: | |
| sol, stats = run_hybrid_sa(m=6, k=3, seed=seed, max_iter=iters) | |
| print(f"\nFinal Stats: {stats}") | |
| if sol: | |
| print("SOLUTION FOUND!") | |
| with open("solution.json", "w") as f: | |
| json.dump(stats.get("solution", sol), f) | |
| if __name__ == "__main__": | |
| main() | |