import sys,lzma,itertools,collections,os,subprocess,tempfile

def parse(path):
    n=0; clauses=[]
    op=lzma.open if path.endswith('.xz') else open
    with op(path,'rt') as f:
        for line in f:
            if not line.strip() or line[0]=='c': continue
            if line[0]=='p': n=int(line.split()[2]); continue
            a=tuple(int(x) for x in line.split() if x!='0')
            if a: clauses.append(a)
    return n,clauses
class DSU:
    def __init__(self,n):
        self.p=list(range(n+1)); self.x=[0]*(n+1); self.sz=[1]*(n+1); self.const={} # root -> value of root var if known
    def find(self,a):
        if self.p[a]!=a:
            r,px=self.find(self.p[a]); self.x[a]^=px; self.p[a]=r
        return self.p[a],self.x[a]
    def rel(self,a,b):
        ra,xa=self.find(a); rb,xb=self.find(b)
        if ra==rb: return xa^xb
        return None
    def unite(self,a,b,par): # value(a) xor value(b)=par
        ra,xa=self.find(a); rb,xb=self.find(b)
        if ra==rb: return (xa^xb)==par, False
        if self.sz[ra]<self.sz[rb]:
            a,b=b,a; ra,rb=rb,ra; xa,xb=xb,xa
        # rb parent ra; x[rb] = value(rb) xor value(ra)
        self.p[rb]=ra; self.x[rb]=xa^xb^par; self.sz[ra]+=self.sz[rb]
        if rb in self.const:
            cv=self.const.pop(rb) # value(rb)
            rv=cv^self.x[rb]
            if ra in self.const and self.const[ra]!=rv: return False, True
            self.const[ra]=rv
        return True, True
    def setconst(self,a,val):
        ra,xa=self.find(a); rv=val^xa
        if ra in self.const: return self.const[ra]==rv, False
        self.const[ra]=rv; return True, True
    def getconst_lit(self,lit):
        ra,xa=self.find(abs(lit))
        if ra not in self.const: return None
        return self.const[ra]^xa^(lit<0)
    def rel_lit(self,l1,l2): # value(l1) xor value(l2)
        r=self.rel(abs(l1),abs(l2))
        if r is None: return None
        return r^(l1<0)^(l2<0)
    def lit_key(self,lit):
        r,x=self.find(abs(lit)); return (r,x^(lit<0))

def detect(n,clauses):
    S=set(map(frozenset,clauses)); bins=[c for c in clauses if len(c)==2]
    by=collections.defaultdict(list)
    ands=[]; used=set()
    for a,b in bins:
        by[-a].append(b); by[-b].append(a)
    for out,ins in by.items():
        for a,b in itertools.combinations(ins,2):
            tri=frozenset((out,-a,-b)); b1=frozenset((-out,a)); b2=frozenset((-out,b))
            if tri in S and b1 in S and b2 in S:
                ands.append((out,a,b)); used.update([tri,b1,b2])
    xors=[]
    tri_groups=collections.defaultdict(list)
    for c in clauses:
        if len(c)==3: tri_groups[tuple(sorted(abs(x) for x in c))].append(c)
    for key,cs in tri_groups.items():
        if len(cs)==4:
            # determine forbidden parity of variable values
            pars=[]
            for c in cs:
                val=0
                for v in key:
                    lit=next(x for x in c if abs(x)==v)
                    falsified_value = 0 if lit>0 else 1
                    val ^= falsified_value
                pars.append(val)
            if len(set(pars))==1:
                rhs=1-pars[0]
                xors.append((key[0],key[1],key[2],rhs));
                for c in cs: used.add(frozenset(c))
    leftover=[c for c in clauses if frozenset(c) not in used]
    return ands,xors,leftover

def prove_clause_for_lit(lit):
    return ('1 x%d' if lit>0 else '1 ~x%d')%abs(lit)+' >= 1 ;\n'
def eq_clauses(l1,l2): # l1 == l2: (not l1 or l2), (l1 or not l2)
    def neg(l): return -l
    return [ [neg(l1), l2], [l1, neg(l2)] ]
def pb_clause(cl):
    return 'rup ' + ' '.join(('1 x%d'%l if l>0 else '1 ~x%d'%(-l)) for l in cl) + ' >= 1 ;\n'

def solve(path,proofpath):
    n,clauses=parse(path); ands,xors,left=detect(n,clauses)
    print('detect',len(ands),len(xors),left[:3])
    d=DSU(n); proof=[]; q=True; steps=0
    and_out={}
    while q:
        q=False; steps+=1
        # constants/equivs from gates
        for out,a,b in ands:
            co=d.getconst_lit(out); ca=d.getconst_lit(a); cb=d.getconst_lit(b)
            # out true => a,b true
            if co==1:
                for lit in (a,b):
                    if d.getconst_lit(lit)!=1:
                        ok,ch=d.setconst(abs(lit), 1^(lit<0)); assert ok
                        if ch: proof.append(pb_clause([lit])); q=True
            if ca==0 or cb==0:
                if d.getconst_lit(out)!=0:
                    ok,ch=d.setconst(abs(out), 0^(out<0)); assert ok
                    if ch: proof.append(pb_clause([-out])); q=True
            if ca==1:
                r=d.rel_lit(out,b)
                if r is None:
                    ok,ch=d.unite(abs(out),abs(b),(out<0)^(b<0)); assert ok
                    if ch:
                        for cl in eq_clauses(out,b): proof.append(pb_clause(cl))
                        q=True
            if cb==1:
                r=d.rel_lit(out,a)
                if r is None:
                    ok,ch=d.unite(abs(out),abs(a),(out<0)^(a<0)); assert ok
                    if ch:
                        for cl in eq_clauses(out,a): proof.append(pb_clause(cl))
                        q=True
            rel=d.rel_lit(a,b)
            if rel==0:
                r=d.rel_lit(out,a)
                if r is None:
                    ok,ch=d.unite(abs(out),abs(a),(out<0)^(a<0)); assert ok
                    if ch:
                        for cl in eq_clauses(out,a): proof.append(pb_clause(cl))
                        q=True
            elif rel==1:
                if d.getconst_lit(out)!=0:
                    ok,ch=d.setconst(abs(out),0^(out<0)); assert ok
                    if ch: proof.append(pb_clause([-out])); q=True
            key=tuple(sorted([d.lit_key(a),d.lit_key(b)]))
            if key in and_out:
                prev=and_out[key]
                if d.rel_lit(out,prev) is None:
                    ok,ch=d.unite(abs(out),abs(prev),(out<0)^(prev<0)); assert ok
                    if ch:
                        for cl in eq_clauses(out,prev): proof.append(pb_clause(cl))
                        q=True
            else:
                and_out[key]=out
        for x,y,z,rhs in xors:
            lits=[x,y,z]
            # Convert variable parity equation. Use literal list positive variables only: x^y^z=rhs.
            cs=[d.getconst_lit(v) for v in lits]
            # if two constants, set third
            for i in range(3):
                js=[j for j in range(3) if j!=i]
                if cs[js[0]] is not None and cs[js[1]] is not None:
                    need=rhs^cs[js[0]]^cs[js[1]]
                    if cs[i] != need:
                        ok,ch=d.setconst(lits[i],need); assert ok
                        if ch: proof.append(pb_clause([lits[i] if need else -lits[i]])); q=True
            # if one constant, unite other two: a xor b = rhs xor c
            for i in range(3):
                if cs[i] is not None:
                    a,b=[lits[j] for j in range(3) if j!=i]
                    par=rhs^cs[i]
                    if d.rel(a,b) is None:
                        ok,ch=d.unite(a,b,par); assert ok
                        if ch:
                            # a xor b=0 means a==b else a==-b
                            l2=b if par==0 else -b
                            for cl in eq_clauses(a,l2): proof.append(pb_clause(cl))
                            q=True
            # if relation known between a,b, set c
            for i,j,k in [(0,1,2),(0,2,1),(1,2,0)]:
                rel=d.rel(lits[i],lits[j])
                if rel is not None:
                    need=rhs^rel
                    if cs[k] != need:
                        ok,ch=d.setconst(lits[k],need); assert ok
                        if ch: proof.append(pb_clause([lits[k] if need else -lits[k]])); q=True
        if steps>100: break
    print('steps',steps,'proof lemmas',len(proof),'const roots',len(d.const))
    bad=None
    for c in clauses:
        vals=[d.getconst_lit(l) for l in c]
        if vals and all(v==0 for v in vals): bad=c; break
    print('bad',bad)
    if bad:
        with open(proofpath,'w') as f:
            f.write('pseudo-Boolean proof version 3.0\n')
            for p in proof: f.write(p)
            f.write('rup >= 1 ;\n')
            cid=len(clauses)+len(proof)+1
            f.write('output NONE ;\nconclusion UNSAT : %d ;\nend pseudo-Boolean proof ;\n'%cid)
        return True
    return False
if __name__=='__main__':
    solve(sys.argv[1],sys.argv[2])
