Sat

Sage supports solving clauses in Conjunctive Normal Form (see Wikipedia article Conjunctive_normal_form), i.e., SAT solving, via an interface inspired by the usual DIMACS format used in SAT solving [SG09]. For example, to express that:

x1 OR x2 OR (NOT x3)

should be true, we write:

(1, 2, -3)

Warning

Variable indices must start at one.

Solvers

By default, Sage solves SAT instances as an Integer Linear Program (see sage.numerical.mip), but any SAT solver supporting the DIMACS input format is easily interfaced using the sage.sat.solvers.dimacs.DIMACS blueprint. Sage ships with pre-written interfaces for RSat [RS] and Glucose [GL]. Furthermore, Sage provides an interface to the CryptoMiniSat [CMS] SAT solver which can be used interchangeably with DIMACS-based solvers. For this last solver, the optional CryptoMiniSat package must be installed, this can be accomplished by typing the following in the shell:

sage -i cryptominisat sagelib

We now show how to solve a simple SAT problem.

(x1 OR x2 OR x3) AND (x1 OR x2 OR (NOT x3))

In Sage’s notation:

sage: solver = SAT()
sage: solver.add_clause( ( 1,  2,  3) )
sage: solver.add_clause( ( 1,  2, -3) )
sage: solver()       # random
(None, True, True, False)
>>> from sage.all import *
>>> solver = SAT()
>>> solver.add_clause( ( Integer(1),  Integer(2),  Integer(3)) )
>>> solver.add_clause( ( Integer(1),  Integer(2), -Integer(3)) )
>>> solver()       # random
(None, True, True, False)
solver = SAT()
solver.add_clause( ( 1,  2,  3) )
solver.add_clause( ( 1,  2, -3) )
solver()       # random

Note

add_clause() creates new variables when necessary. When using CryptoMiniSat, it creates all variables up to the given index. Hence, adding a literal involving the variable 1000 creates up to 1000 internal variables.

DIMACS-base solvers can also be used to write DIMACS files:

sage: from sage.sat.solvers.dimacs import DIMACS
sage: fn = tmp_filename()
sage: solver = DIMACS(filename=fn)
sage: solver.add_clause( ( 1,  2,  3) )
sage: solver.add_clause( ( 1,  2, -3) )
sage: _ = solver.write()
sage: for line in open(fn).readlines():
....:    print(line)
p cnf 3 2
1 2 3 0
1 2 -3 0
>>> from sage.all import *
>>> from sage.sat.solvers.dimacs import DIMACS
>>> fn = tmp_filename()
>>> solver = DIMACS(filename=fn)
>>> solver.add_clause( ( Integer(1),  Integer(2),  Integer(3)) )
>>> solver.add_clause( ( Integer(1),  Integer(2), -Integer(3)) )
>>> _ = solver.write()
>>> for line in open(fn).readlines():
...    print(line)
p cnf 3 2
1 2 3 0
1 2 -3 0
from sage.sat.solvers.dimacs import DIMACS
fn = tmp_filename()
solver = DIMACS(filename=fn)
solver.add_clause( ( 1,  2,  3) )
solver.add_clause( ( 1,  2, -3) )
_ = solver.write()
for line in open(fn).readlines():
   print(line)

Alternatively, there is sage.sat.solvers.dimacs.DIMACS.clauses():

sage: from sage.sat.solvers.dimacs import DIMACS
sage: fn = tmp_filename()
sage: solver = DIMACS()
sage: solver.add_clause( ( 1,  2,  3) )
sage: solver.add_clause( ( 1,  2, -3) )
sage: solver.clauses(fn)
sage: for line in open(fn).readlines():
....:    print(line)
p cnf 3 2
1 2 3 0
1 2 -3 0
>>> from sage.all import *
>>> from sage.sat.solvers.dimacs import DIMACS
>>> fn = tmp_filename()
>>> solver = DIMACS()
>>> solver.add_clause( ( Integer(1),  Integer(2),  Integer(3)) )
>>> solver.add_clause( ( Integer(1),  Integer(2), -Integer(3)) )
>>> solver.clauses(fn)
>>> for line in open(fn).readlines():
...    print(line)
p cnf 3 2
1 2 3 0
1 2 -3 0
from sage.sat.solvers.dimacs import DIMACS
fn = tmp_filename()
solver = DIMACS()
solver.add_clause( ( 1,  2,  3) )
solver.add_clause( ( 1,  2, -3) )
solver.clauses(fn)
for line in open(fn).readlines():
   print(line)

These files can then be passed external SAT solvers.

Details on Specific Solvers

Converters

Sage supports conversion from Boolean polynomials (also known as Algebraic Normal Form) to Conjunctive Normal Form:

sage: B.<a,b,c> = BooleanPolynomialRing()
sage: from sage.sat.converters.polybori import CNFEncoder
sage: from sage.sat.solvers.dimacs import DIMACS
sage: fn = tmp_filename()
sage: solver = DIMACS(filename=fn)
sage: e = CNFEncoder(solver, B)
sage: e.clauses_sparse(a*b + a + 1)
sage: _ = solver.write()
sage: print(open(fn).read())
p cnf 3 2
-2 0
1 0
>>> from sage.all import *
>>> B = BooleanPolynomialRing(names=('a', 'b', 'c',)); (a, b, c,) = B._first_ngens(3)
>>> from sage.sat.converters.polybori import CNFEncoder
>>> from sage.sat.solvers.dimacs import DIMACS
>>> fn = tmp_filename()
>>> solver = DIMACS(filename=fn)
>>> e = CNFEncoder(solver, B)
>>> e.clauses_sparse(a*b + a + Integer(1))
>>> _ = solver.write()
>>> print(open(fn).read())
p cnf 3 2
-2 0
1 0
<BLANKLINE>
B.<a,b,c> = BooleanPolynomialRing()
from sage.sat.converters.polybori import CNFEncoder
from sage.sat.solvers.dimacs import DIMACS
fn = tmp_filename()
solver = DIMACS(filename=fn)
e = CNFEncoder(solver, B)
e.clauses_sparse(a*b + a + 1)
_ = solver.write()
print(open(fn).read())

Details on Specific Converterts

Highlevel Interfaces

Sage provides various highlevel functions which make working with Boolean polynomials easier. We construct a very small-scale AES system of equations and pass it to a SAT solver:

sage: sr = mq.SR(1,1,1,4,gf2=True,polybori=True)
sage: while True:
....:     try:
....:         F,s = sr.polynomial_system()
....:         break
....:     except ZeroDivisionError:
....:         pass
sage: from sage.sat.boolean_polynomials import solve as solve_sat # optional - pycryptosat
sage: s = solve_sat(F)                                            # optional - pycryptosat
sage: F.subs(s[0])                                                # optional - pycryptosat
Polynomial Sequence with 36 Polynomials in 0 Variables
>>> from sage.all import *
>>> sr = mq.SR(Integer(1),Integer(1),Integer(1),Integer(4),gf2=True,polybori=True)
>>> while True:
...     try:
...         F,s = sr.polynomial_system()
...         break
...     except ZeroDivisionError:
...         pass
>>> from sage.sat.boolean_polynomials import solve as solve_sat # optional - pycryptosat
>>> s = solve_sat(F)                                            # optional - pycryptosat
>>> F.subs(s[Integer(0)])                                                # optional - pycryptosat
Polynomial Sequence with 36 Polynomials in 0 Variables
sr = mq.SR(1,1,1,4,gf2=True,polybori=True)
while True:
    try:
        F,s = sr.polynomial_system()
        break
    except ZeroDivisionError:
        pass
from sage.sat.boolean_polynomials import solve as solve_sat # optional - pycryptosat
s = solve_sat(F)                                            # optional - pycryptosat
F.subs(s[0])                                                # optional - pycryptosat

Details on Specific Highlevel Interfaces

REFERENCES:

Indices and Tables