import sys,lzma,random,time,collections,os

def parse(path):
    opener=lzma.open if path.endswith('.xz') else open
    with opener(path,'rt') as f:
        clauses=[]; N=0; blocks=[]
        for line in f:
            if not line or line[0] in 'c%\n': continue
            if line[0]=='p': N=int(line.split()[2]); continue
            lits=list(map(int,line.split()[:-1])); clauses.append(lits)
            if len(lits)>2 and all(x>0 for x in lits): blocks.append(lits)
    # sort blocks by min var
    blocks.sort(key=lambda b:min(b)); n=len(blocks); d=len(blocks[0])
    mapv={v:(i,j) for i,b in enumerate(blocks) for j,v in enumerate(b)}
    masks=[[None]*n for _ in range(n)]
    neigh=[set() for _ in range(n)]
    for lits in clauses:
        if len(lits)==2 and lits[0]<0 and lits[1]<0:
            a,b=-lits[0],-lits[1]
            if a not in mapv or b not in mapv: continue
            i,ai=mapv[a]; j,bj=mapv[b]
            if i==j: continue
            if i>j: i,j=j,i; ai,bj=bj,ai
            if masks[i][j] is None: masks[i][j]=[0]*d
            masks[i][j][ai]|=1<<bj
            neigh[i].add(j); neigh[j].add(i)
    edges=[]
    for i in range(n):
      for j in range(i+1,n):
        if masks[i][j] is not None: edges.append((i,j))
    return N,blocks,n,d,masks,[list(s) for s in neigh],edges

def violates(masks,i,ai,j,bj):
    if i<j:
      m=masks[i][j]
      return m is not None and ((m[ai]>>bj)&1)
    else:
      m=masks[j][i]
      return m is not None and ((m[bj]>>ai)&1)

def solve(data,limit=10,seed=1):
    N,blocks,n,d,masks,neigh,edges=data
    rnd=random.Random(seed); end=time.time()+limit
    best=10**9; steps=0; rest=0
    while time.time()<end:
      rest+=1
      A=[rnd.randrange(d) for _ in range(n)]
      varconf=[0]*n; bad=[]; pos={}
      def add(e):
        pos[e]=len(bad); bad.append(e); varconf[e[0]]+=1; varconf[e[1]]+=1
      def rem(e):
        k=pos.pop(e); last=bad.pop()
        if k<len(bad): bad[k]=last; pos[last]=k
        varconf[e[0]]-=1; varconf[e[1]]-=1
      for e in edges:
        if violates(masks,e[0],A[e[0]],e[1],A[e[1]]): add(e)
      temp=0.01
      noimp=0
      while time.time()<end:
        steps+=1
        if not bad: return A,steps,rest,best
        if len(bad)<best: best=len(bad); noimp=0
        else: noimp+=1
        if noimp>20000+200*n: break
        e=bad[rnd.randrange(len(bad))]
        x=e[0] if rnd.randrange(2)==0 else e[1]
        # occasionally choose most conflicted among bad edge endpoints/neighborhood
        if rnd.random()<0.35:
          x=e[0] if varconf[e[0]]>=varconf[e[1]] else e[1]
        old=A[x]
        bestvals=[]; bestdelta=10**9
        cur=varconf[x]
        for val in range(d):
          if val==old: continue
          newc=0
          for y in neigh[x]:
            if violates(masks,x,val,y,A[y]): newc+=1
          delta=newc-cur
          if delta<bestdelta: bestdelta=delta; bestvals=[val]
          elif delta==bestdelta: bestvals.append(val)
        if bestdelta>0 and rnd.random()>0.02: # still move, but random noise may choose any
          pass
        new=rnd.choice(bestvals if rnd.random()>0.02 else list(range(d)))
        if new==old: continue
        # update incident edges
        for y in neigh[x]:
          e2=(x,y) if x<y else (y,x)
          was=e2 in pos
          now=violates(masks,x,new,y,A[y])
          if was and not now: rem(e2)
          elif (not was) and now: add(e2)
        A[x]=new
    return None,steps,rest,best

if __name__=='__main__':
  for p in sys.argv[1:]:
    data=parse(p); t=time.time(); sol,steps,rest,best=solve(data,30,1)
    print(os.path.basename(p), 'sol',sol is not None,'time',time.time()-t,'steps',steps,'rest',rest,'best',best)
