#!/usr/bin/env python3
import lzma,collections,sys,random,time,math

def parse(p):
 sizes=collections.Counter(); clauses=[]; nvar=0
 for line in lzma.open(p,'rt'):
  if line.startswith('c'): continue
  if line.startswith('p'): nvar=int(line.split()[2]); continue
  c=list(map(int,line.split()))[:-1]; sizes[len(c)]+=1; clauses.append(c)
 n=max(k for k,v in sizes.items() if k==v and k>1); prim=n*n
 edges=set(); cols=[]
 for c in clauses:
  if len(c)==2 and c[0]<0 and c[1]<0 and -c[0]<=prim and -c[1]<=prim:
   a,b=-c[0],-c[1]; ra,ca=divmod(a-1,n); rb,cb=divmod(b-1,n); ca+=1; cb+=1
   if ca==cb and ra!=rb:
    edges.add(tuple(sorted((ra,rb)))); cols.append(ca)
 return n,min(cols),list(edges)

def cm_order(n,adj,rev=False):
 seen=[False]*n; order=[]
 starts=sorted(range(n), key=lambda x: len(adj[x]))
 for s in starts:
  if seen[s]: continue
  q=[s]; seen[s]=True
  for v in q:
   order.append(v)
   ns=sorted([u for u in adj[v] if not seen[u]], key=lambda x: len(adj[x]))
   for u in ns: seen[u]=True; q.append(u)
 if rev: order=order[::-1]
 return order

def label_from_order(order, flip=False):
 n=len(order); pos=[0]*n; lo=1; hi=n
 for i,v in enumerate(order):
  if (i%2==0) ^ flip: pos[v]=lo; lo+=1
  else: pos[v]=hi; hi-=1
 return pos

def score(pos,edges,w):
 bad=0; cost=0; md=10**9
 for u,v in edges:
  d=abs(pos[u]-pos[v]); md=min(md,d)
  if d<w: bad+=1; cost+=(w-d)*(w-d)
 return bad,cost,md

def improve(pos,adj,edges,w,maxt=3):
 n=len(pos); at=[0]*(n+1)
 for v,p in enumerate(pos): at[p]=v
 def vcost(v):
  s=0; b=0
  pv=pos[v]
  for u in adj[v]:
   d=abs(pv-pos[u])
   if d<w: b+=1; s+=(w-d)*(w-d)
  return s,b
 cost=sum(max(0,w-abs(pos[u]-pos[v]))**2 for u,v in edges)
 bad=sum(1 for u,v in edges if abs(pos[u]-pos[v])<w)
 T=max(1.0,w*w/4); end=time.time()+maxt; it=0
 while time.time()<end and bad:
  it+=1
  if random.random()<0.7:
   # pick endpoint of bad edge
   bes=[e for e in random.sample(edges,min(len(edges),50)) if abs(pos[e[0]]-pos[e[1]])<w]
   v=random.choice(random.choice(bes)) if bes else random.randrange(n)
  else: v=random.randrange(n)
  q=random.randrange(1,n+1); u=at[q]
  if u==v: continue
  old=set([v,u])|set(adj[v])|set(adj[u])
  oldcost=sum(max(0,w-abs(pos[a]-pos[b]))**2 for a in old for b in adj[a] if a<b and b in old or False)
  # easier compute affected unique edges
  aff=set()
  for x in (v,u):
   for y in adj[x]: aff.add(tuple(sorted((x,y))))
  oc=sum(max(0,w-abs(pos[a]-pos[b]))**2 for a,b in aff)
  ob=sum(1 for a,b in aff if abs(pos[a]-pos[b])<w)
  pv,pu=pos[v],pos[u]; pos[v],pos[u]=pu,pv; at[pv],at[pu]=u,v
  nc=sum(max(0,w-abs(pos[a]-pos[b]))**2 for a,b in aff)
  nb=sum(1 for a,b in aff if abs(pos[a]-pos[b])<w)
  delta=nc-oc
  if delta<=0 or random.random()<math.exp(-delta/T):
   cost+=delta; bad+=nb-ob
  else:
   pos[v],pos[u]=pv,pu; at[pv],at[pu]=v,u
  T*=0.99995
 return pos,score(pos,edges,w),it

for p in sys.argv[1:]:
 n,w,edges=parse(p); adj=[set() for _ in range(n)]
 for u,v in edges: adj[u].add(v); adj[v].add(u)
 best=None; bp=None
 for rev in [0,1]:
  order=cm_order(n,adj,rev)
  for flip in [0,1]:
   pos=label_from_order(order,flip)
   sc=score(pos,edges,w)
   if best is None or sc<best: best=sc; bp=pos[:]
 print(p.split('/')[-1], 'n',n,'w',w,'m',len(edges),'init',best)
 pos,sc,it=improve(bp,adj,edges,w,5)
 print(' after',sc,'it',it)
