Path: blob/master/src/sage/sat/solvers/cryptominisat/cryptominisat.pyx
8823 views
"""1CryptoMiniSat.23"CryptoMiniSat is an LGPL-licenced SAT solver that aims to become a4premier SAT solver with all the features and speed of successful SAT5solvers, such as MiniSat and PrecoSat. The long-term goals of6CryptoMiniSat are to be an efficient sequential, parallel and7distributed solver. There are solvers that are good at one or the8other, e.g. ManySat (parallel) or PSolver (distributed), but we wish9to excel at all." -- http://www.msoos.org/cryptominisat2/1011.. note::1213Our SAT solver interfaces are 1-based, i.e., literals start at141. This is consistent with the popular DIMACS format for SAT15solving but not with Pythion's 0-based convention. However, this16also allows to construct clauses using simple integers.1718AUTHORS:1920- Martin Albrecht (2012): first version21"""22##############################################################################23# Copyright (C) 2012 Martin Albrecht <[email protected]>24# Distributed under the terms of the GNU General Public License (GPL)25# The full text of the GPL is available at:26# http://www.gnu.org/licenses/27##############################################################################2829include "sage/ext/stdsage.pxi"30include "sage/ext/interrupt.pxi"3132from libc.stdint cimport uint32_t33from decl cimport lbool, Var, Lit, Clause, l_Undef, l_False, RetClause34from decl cimport vec, vector35from decl cimport GaussConf36from solverconf cimport SolverConf3738from sage.misc.misc import get_verbose3940cdef extern from "cryptominisat_helper.h":41# Cython doesn't handle cdef vec[Lit] foo = solver.get_unitary_learnts() propertly. It will42# declare foo first and then assign the answer of get_unitary_learnts() to foo. This requires43# that operator= is available which isn't necessarily the case.44cdef uint32_t* get_unitary_learnts_helper(Solver* solver, uint32_t* num)45cdef uint32_t** get_sorted_learnts_helper(Solver* solver, uint32_t* num)4647cdef class CryptoMiniSat(SatSolver):48"""49The CryptoMiniSat solver.5051EXAMPLE::5253sage: from sage.sat.solvers import CryptoMiniSat # optional - cryptominisat54sage: cms = CryptoMiniSat() # optional - cryptominisat55sage: cms.add_clause((1,2,-3)) # optional - cryptominisat56sage: cms() # optional - cryptominisat57(None, True, True, False)5859.. note::6061Do not import 'sage.sat.solvers.cryptominisat.cryptominisat'62directly, but use 'sage.sat.solvers.cryptominisat' which63throws a friendlier error message if the CryptoMiniSat SPKG is64not installed. Also, 'CryptoMiniSat' will be available in65'sage.sat.solvers' if the CryptoMiniSat SPKG is installed.66"""67def __cinit__(self, SolverConf sc=None, **kwds):68"""69Construct a new CryptoMiniSat instance.7071INPUT:7273- ``SolverConf`` - a :cls:`sage.sat.solvers.cryptominisat.SolverConf` instance74- ``**kwds`` - passed to :cls:`sage.sat.solvers.cryptominisat.SolverConf`7576EXAMPLE::7778sage: from sage.sat.solvers import CryptoMiniSat # optional - cryptominisat79sage: cms = CryptoMiniSat() # optional - cryptominisat8081CryptoMiniSat accepts a :cls:`sage.sat.solvers.cryptominisat.SolverConf` instance as parameter::8283sage: from sage.sat.solvers.cryptominisat import SolverConf # optional - cryptominisat84sage: sc = SolverConf(verbosity=2) # optional - cryptominisat85sage: cms = CryptoMiniSat(sc=sc) # optional - cryptominisat8687However, parameters passed directly tot he solver overwrite88any options passed to the :cls:`sage.sat.solvers.cryptominisat.SolverConf` instance::8990sage: from sage.sat.solvers.cryptominisat import SolverConf # optional - cryptominisat91sage: sc = SolverConf(verbosity=2) # optional - cryptominisat92sage: cms = CryptoMiniSat(sc=sc, verbosity=3) # optional - cryptominisat93"""94cdef SolverConf _sc95if sc is not None:96_sc = sc.__copy__()97else:98_sc = SolverConf()99cdef GaussConf gc100101for k,v in kwds.iteritems():102_sc[k] = v103104self._solver = new Solver(_sc._conf[0], gc)105106def __dealloc__(self):107"""108TESTS::109110sage: from sage.sat.solvers import CryptoMiniSat # optional - cryptominisat111sage: cms = CryptoMiniSat() # optional - cryptominisat112sage: del cms # optional - cryptominisat113"""114sig_on()115del self116sig_off()117118def __repr__(self):119"""120TESTS::121122sage: from sage.sat.solvers import CryptoMiniSat # optional - cryptominisat123sage: cms = CryptoMiniSat() # optional - cryptominisat124sage: cms.add_clause((1,2,-3)) # optional - cryptominisat125sage: cms # optional - cryptominisat126CryptoMiniSat127#vars: 3, #lits: 3, #clauses: 1, #learnt: 0, #assigns: 0128"""129s = """CryptoMiniSat130#vars: %7d, #lits: %7d, #clauses: %7d, #learnt: %7d, #assigns: %7d131"""%(self._solver.nVars(), self._solver.nLiterals(), self._solver.nClauses(), self._solver.nLearnts(), self._solver.nAssigns())132return s133134def var(self, decision=None):135"""136Return a *new* generator.137138INPUT:139140- ``decision`` - if ``True`` this variable will be used for141decisions (default: ``None``, let the solver decide.)142143EXAMPLE::144145sage: from sage.sat.solvers import CryptoMiniSat # optional - cryptominisat146sage: cms = CryptoMiniSat() # optional - cryptominisat147sage: cms.var() # optional - cryptominisat1481149sage: cms.var(decision=True) # optional - cryptominisat1502151152"""153assert(self._solver.okay())154155cdef Var var156if decision is None:157var = self._solver.newVar()158else:159var = self._solver.newVar(bool(decision))160return int(var+1)161162def nvars(self):163"""164Return the number of variables.165166EXAMPLE::167168sage: from sage.sat.solvers import CryptoMiniSat # optional - cryptominisat169sage: cms = CryptoMiniSat() # optional - cryptominisat170sage: cms.var() # optional - cryptominisat1711172sage: cms.var(decision=True) # optional - cryptominisat1732174sage: cms.nvars() # optional - cryptominisat1752176"""177return int(self._solver.nVars())178179def add_clause(self, lits):180"""181Add a new clause to set of clauses.182183INPUT:184185- ``lits`` - a tuple of integers != 0186187.. note::188189If any element ``e`` in ``lits`` has ``abs(e)`` greater190than the number of variables generated so far, then new191variables are created automatically.192193EXAMPLE::194195sage: from sage.sat.solvers import CryptoMiniSat # optional - cryptominisat196sage: cms = CryptoMiniSat() # optional - cryptominisat197sage: cms.var() # optional - cryptominisat1981199sage: cms.var(decision=True) # optional - cryptominisat2002201sage: cms.add_clause( (1, -2 , 3) ) # optional - cryptominisat202sage: cms # optional - cryptominisat203CryptoMiniSat204#vars: 3, #lits: 3, #clauses: 1, #learnt: 0, #assigns: 0205"""206assert(self._solver.okay())207208cdef vec[Lit] l209for lit in lits:210lit = int(lit)211while abs(lit) > self._solver.nVars():212self._solver.newVar()213l.push(Lit(abs(lit)-1,lit<0))214self._solver.addClause(l)215216def add_xor_clause(self, lits, isfalse):217"""218Add a new XOR clause to set of clauses.219220INPUT:221222- ``lits`` - a tuple of integers != 0223- ``isfalse`` - set to ``True`` if the XOR chain should evaluate to ``False``224225.. note::226227If any element ``e`` in ``lits`` has ``abs(e)`` greater228than the number of variables generated so far, then new229variables are created automatically.230231EXAMPLE::232233sage: from sage.sat.solvers import CryptoMiniSat # optional - cryptominisat234sage: cms = CryptoMiniSat() # optional - cryptominisat235sage: cms.var() # optional - cryptominisat2361237sage: cms.var(decision=True) # optional - cryptominisat2382239sage: cms.add_xor_clause( (1, -2 , 3), True ) # optional - cryptominisat240sage: cms # optional - cryptominisat241CryptoMiniSat242#vars: 3, #lits: 3, #clauses: 1, #learnt: 0, #assigns: 0243"""244assert(self._solver.okay())245246cdef vec[Lit] l247for lit in lits:248while abs(lit) > self._solver.nVars():249self._solver.newVar()250l.push(Lit(abs(lit)-1,lit<0))251self._solver.addXorClause(l, bool(isfalse))252253def __call__(self, assumptions=None):254"""255Solve this instance.256257INPUT:258259- ``assumptions`` - assumed variable assignments (default: ``None``)260261OUTPUT:262263- If this instance is SAT: A tuple of length ``nvars()+1``264where the ``i``-th entry holds an assignment for the265``i``-th variables (the ``0``-th entry is always ``None``).266267- If this instance is UNSAT: ``False``268269- If the solver was interrupted before deciding satisfiability270``None``.271272EXAMPLE:273274We construct a simple example::275276sage: from sage.sat.solvers import CryptoMiniSat # optional - cryptominisat277sage: cms = CryptoMiniSat() # optional - cryptominisat278sage: cms.add_clause( (1, 2) ) # optional - cryptominisat279280281First, we do not assume anything, note that the first entry is282``None`` because there is not ``0``-th variable::283284sage: cms() # optional - cryptominisat285(None, True, False)286287Now, we make assumptions which make this instance UNSAT::288289sage: cms( (-1, -2) ) # optional - cryptominisat290False291sage: cms.conflict_clause() # optional - cryptominisat292(2, 1)293294Finally, we use assumptions to decide on a solution::295296sage: cms( (-1,) ) # optional - cryptominisat297(None, False, True)298"""299cdef vec[Lit] l300cdef lbool r301if assumptions is None:302sig_on()303r = self._solver.solve()304sig_off()305else:306for lit in assumptions:307while abs(lit) > self._solver.nVars():308self._solver.newVar()309l.push(Lit(abs(lit)-1,lit<0))310sig_on()311r = self._solver.solve(l)312sig_off()313314if r == l_False:315return False316if r == l_Undef:317return None318319return (None, ) + tuple([self._solver.model[i].getBool() for i in range(self._solver.model.size())])320321def conflict_clause(self):322"""323Return conflict clause if this instance is UNSAT and the last324call used assumptions.325326EXAMPLE::327328sage: from sage.sat.solvers import CryptoMiniSat # optional - cryptominisat329sage: cms = CryptoMiniSat() # optional - cryptominisat330sage: cms.add_clause( (1,2) ) # optional - cryptominisat331sage: cms.add_clause( (1,-2) ) # optional - cryptominisat332sage: cms.add_clause( (-1,) ) # optional - cryptominisat333sage: cms.conflict_clause() # optional - cryptominisat334()335336We solve again, but this time with an explicit assumption::337338sage: from sage.sat.solvers import CryptoMiniSat # optional - cryptominisat339sage: cms = CryptoMiniSat() # optional - cryptominisat340sage: cms.add_clause( (1,2) ) # optional - cryptominisat341sage: cms.add_clause( (1,-2) ) # optional - cryptominisat342sage: cms( (-1,) ) # optional - cryptominisat343False344sage: cms.conflict_clause() # optional - cryptominisat345(1,)346347A more elaborate example using small-scale AES where we set 80 variables to ``1``::348349sage: from sage.sat.solvers import CryptoMiniSat # optional - cryptominisat350sage: from sage.sat.converters.polybori import CNFEncoder # optional - cryptominisat351sage: set_random_seed( 22 ) # optional - cryptominisat352sage: sr = mq.SR(1,4,4,4,gf2=True,polybori=True) # optional - cryptominisat353sage: F,s = sr.polynomial_system() # optional - cryptominisat354sage: cms = CryptoMiniSat() # optional - cryptominisat355sage: phi = CNFEncoder(cms, F.ring())(F) # optional - cryptominisat356sage: cms( range(1,120) ) # optional - cryptominisat357False358359This guess was wrong and we need to flip one of the following variables::360361sage: cms.conflict_clause() # optional - cryptominisat362(-119, -118, -117, -116, -114, -113, -112, -110, -109, -100, -98, -97, -96, -94, -93, -92, -91, -76, -75, -71, -70, -69)363"""364cdef Lit l365r = []366for i in range(self._solver.conflict.size()):367l = self._solver.conflict[i]368r.append( (-1)**l.sign() * (l.var()+1) )369return tuple(r)370371def learnt_clauses(self, unitary_only=False):372"""373Return learnt clauses.374375INPUT:376377- ``unitary_only`` - return only unitary learnt clauses (default: ``False``)378379EXAMPLE::380381sage: from sage.sat.solvers import CryptoMiniSat # optional - cryptominisat382sage: from sage.sat.converters.polybori import CNFEncoder # optional - cryptominisat383sage: set_random_seed( 22 ) # optional - cryptominisat384sage: sr = mq.SR(1,4,4,4,gf2=True,polybori=True) # optional - cryptominisat385sage: F,s = sr.polynomial_system() # optional - cryptominisat386sage: cms = CryptoMiniSat(maxrestarts=10,verbosity=0) # optional - cryptominisat387sage: phi = CNFEncoder(cms, F.ring())(F) # optional - cryptominisat388sage: cms() # optional - cryptominisat389sage: sorted(cms.learnt_clauses())[0] # optional - cryptominisat, output random390(-592, -578, -68, 588, 94, 579, 584, 583)391392393An example for unitary clauses::394395sage: from sage.sat.solvers import CryptoMiniSat # optional - cryptominisat396sage: from sage.sat.converters.polybori import CNFEncoder # optional - cryptominisat397sage: set_random_seed( 22 ) # optional - cryptominisat398sage: sr = mq.SR(1,4,4,4,gf2=True,polybori=True) # optional - cryptominisat399sage: F,s = sr.polynomial_system() # optional - cryptominisat400sage: cms = CryptoMiniSat(maxrestarts=10,verbosity=0) # optional - cryptominisat401sage: phi = CNFEncoder(cms, F.ring())(F) # optional - cryptominisat402sage: cms() # optional - cryptominisat403sage: cms.learnt_clauses(unitary_only=True) # optional - cryptominisat404()405406.. todo::407408Find a more useful example for unitary learnt clauses.409"""410cdef uint32_t num = 0411cdef uint32_t *learnt1 = get_unitary_learnts_helper(self._solver,&num)412413r = []414for i in range(num):415r.append( (-1)**int(learnt1[i]&1) * (int(learnt1[i]>>1)+1) )416sage_free(learnt1)417418if unitary_only:419return tuple(r)420421cdef uint32_t **learnt = get_sorted_learnts_helper(self._solver,&num)422cdef uint32_t *clause = NULL423424r = []425for i in range(num):426clause = learnt[i]427C = [(-1)**int(clause[j]&1) * (int(clause[j]>>1)+1) for j in range(1,clause[0]+1)]428sage_free(clause)429r.append(tuple(C))430sage_free(learnt)431return tuple(r)432433def clauses(self, filename=None):434"""435Return (possibly simplified) original clauses.436437INPUT:438439- ``filename'' - if not ``None`` clauses are written to ``filename`` in440CryptoMinisat's extended DIMACS format (default: ``None``)441442OUTPUT:443444If ``filename`` is ``None`` then a list of ``lits, is_xor, rhs``445tuples is returned, where ``lits`` is a tuple of literals,446``is_xor`` indicates whether the clause is an xor clause and ``rhs``447is either ``True`` or ``False`` for xor clauses and ``None``448otherwise.449450If ``filename`` points to a writable file, then the list of original451clauses is written to that file in CryptoMiniSat's extended DIMACS452format.453454EXAMPLES::455456sage: from sage.sat.solvers import CryptoMiniSat # optional - cryptominisat457sage: cms = CryptoMiniSat() # optional - cryptominisat458sage: cms.add_xor_clause((1,2,3,4,5,6,7,8,9), isfalse=False) # optional - cryptominisat459sage: cms.add_clause((1,2,3,4,5,6,7,8,-9)) # optional - cryptominisat460sage: cms.clauses() # optional - cryptominisat461[((1, 2, 3, 4, 5, 6, 7, 8, -9), False, None),462((1, 2, 3, 4, 5, 6, 7, 8, 9), True, True)]463464Clauses may have been simplified already::465466sage: from sage.sat.solvers import CryptoMiniSat # optional - cryptominisat467sage: cms = CryptoMiniSat() # optional - cryptominisat468sage: cms.add_xor_clause((1,2), isfalse=False) # optional - cryptominisat469sage: cms.add_clause((1,2)) # optional - cryptominisat470sage: cms.clauses() # optional - cryptominisat471[((2, 1), True, True),472((-1, -2), False, None),473((1, 2), False, None),474((1, 2), False, None)]475476DIMACS format output::477478sage: from sage.sat.solvers import CryptoMiniSat # optional - cryptominisat479sage: cms = CryptoMiniSat() # optional - cryptominisat480sage: cms.add_xor_clause((1,2), isfalse=False) # optional - cryptominisat481sage: cms.add_clause((1,2)) # optional - cryptominisat482sage: fn = tmp_filename() # optional - cryptominisat483sage: cms.clauses(fn) # optional - cryptominisat484sage: print open(fn).read() # optional - cryptominisat485p cnf 2 4486x2 1 0487-1 -2 04881 2 04891 2 0490<BLANKLINE>491"""492cdef vector[RetClause] v = self._solver.dumpOrigClauses()493cdef vector[Lit] l494cdef list original = []495496for i in range(v.size()):497l = v[i].lits498L = tuple([(-1)**l[j].sign() * (l[j].var()+1) for j in range(l.size())])499original.append( (L, v[i].is_xor, v[i].right_hand_side if v[i].is_xor else None ) )500501if filename is None:502return original503else:504from sage.sat.solvers.dimacs import DIMACS505DIMACS.render_dimacs(original, filename, self._solver.nVars())506507508