Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/sat/boolean_polynomials.py
8817 views
1
"""
2
SAT Functions for Boolean Polynomials
3
4
These highlevel functions support solving and learning from Boolean polynomial systems. In this
5
context, "learning" means the construction of new polynomials in the ideal spanned by the original
6
polynomials.
7
8
AUTHOR:
9
10
- Martin Albrecht (2012): initial version
11
12
Functions
13
^^^^^^^^^
14
"""
15
##############################################################################
16
# Copyright (C) 2012 Martin Albrecht <[email protected]>
17
# Distributed under the terms of the GNU General Public License (GPL)
18
# The full text of the GPL is available at:
19
# http://www.gnu.org/licenses/
20
##############################################################################
21
22
from sage.structure.sequence import Sequence
23
from sage.rings.infinity import PlusInfinity
24
25
from sage.sat.solvers import SatSolver
26
from sage.sat.converters import ANF2CNFConverter
27
28
def solve(F, converter=None, solver=None, n=1, target_variables=None, **kwds):
29
"""
30
Solve system of Boolean polynomials ``F`` by solving the
31
SAT-problem -- produced by ``converter`` -- using ``solver``.
32
33
INPUT:
34
35
- ``F`` - a sequence of Boolean polynomials
36
37
- ``n`` - number of solutions to return. If ``n`` is +infinity
38
then all solutions are returned. If ``n <infinity`` then ``n``
39
solutions are returned if ``F`` has at least ``n``
40
solutions. Otherwise, all solutions of ``F`` are
41
returned. (default: ``1``)
42
43
- ``converter`` - an ANF to CNF converter class or object. If
44
``converter`` is ``None`` then
45
:class:`sage.sat.converters.polybori.CNFEncoder` is used to
46
construct a new converter. (default: ``None``)
47
48
- ``solver`` - a SAT-solver class or object. If ``solver`` is
49
``None`` then :class:`sage.sat.solvers.cryptominisat.CryptoMiniSat`
50
is used to construct a new converter. (default: ``None``)
51
52
- ``target_variables`` - a list of variables. The elements of the list are
53
used to exclude a particular combination of variable assignments of a
54
solution from any further solution. Furthermore ``target_variables``
55
denotes which variable-value pairs appear in the solutions. If
56
``target_variables`` is ``None`` all variables appearing in the
57
polynomials of ``F`` are used to construct exclusion clauses.
58
(default: ``None``)
59
60
- ``**kwds`` - parameters can be passed to the converter and the
61
solver by prefixing them with ``c_`` and ``s_`` respectively. For
62
example, to increase CryptoMiniSat's verbosity level, pass
63
``s_verbosity=1``.
64
65
OUTPUT:
66
67
A list of dictionaries, each of which contains a variable
68
assignment solving ``F``.
69
70
EXAMPLE:
71
72
We construct a very small-scale AES system of equations::
73
74
sage: sr = mq.SR(1,1,1,4,gf2=True,polybori=True)
75
sage: F,s = sr.polynomial_system()
76
77
and pass it to a SAT solver::
78
79
sage: from sage.sat.boolean_polynomials import solve as solve_sat # optional - cryptominisat
80
sage: s = solve_sat(F) # optional - cryptominisat
81
sage: F.subs(s[0]) # optional - cryptominisat
82
Polynomial Sequence with 36 Polynomials in 0 Variables
83
84
This time we pass a few options through to the converter and the solver::
85
86
sage: s = solve_sat(F, s_verbosity=1, c_max_vars_sparse=4, c_cutting_number=8) # optional - cryptominisat
87
sage: F.subs(s[0]) # optional - cryptominisat
88
Polynomial Sequence with 36 Polynomials in 0 Variables
89
90
We construct a very simple system with three solutions and ask for a specific number of solutions::
91
92
sage: B.<a,b> = BooleanPolynomialRing() # optional - cryptominisat
93
sage: f = a*b # optional - cryptominisat
94
sage: l = solve_sat([f],n=1) # optional - cryptominisat
95
sage: len(l) == 1, f.subs(l[0]) # optional - cryptominisat
96
(True, 0)
97
98
sage: l = sorted(solve_sat([a*b],n=2)) # optional - cryptominisat
99
sage: len(l) == 2, f.subs(l[0]), f.subs(l[1]) # optional - cryptominisat
100
(True, 0, 0)
101
102
sage: sorted(solve_sat([a*b],n=3)) # optional - cryptominisat
103
[{b: 0, a: 0}, {b: 0, a: 1}, {b: 1, a: 0}]
104
sage: sorted(solve_sat([a*b],n=4)) # optional - cryptominisat
105
[{b: 0, a: 0}, {b: 0, a: 1}, {b: 1, a: 0}]
106
sage: sorted(solve_sat([a*b],n=infinity)) # optional - cryptominisat
107
[{b: 0, a: 0}, {b: 0, a: 1}, {b: 1, a: 0}]
108
109
In the next example we see how the ``target_variables`` parameter works::
110
111
sage: from sage.sat.boolean_polynomials import solve as solve_sat # optional - cryptominisat
112
sage: R.<a,b,c,d> = BooleanPolynomialRing() # optional - cryptominisat
113
sage: F = [a+b,a+c+d] # optional - cryptominisat
114
115
First the normal use case::
116
117
sage: solve_sat(F,n=infinity) # optional - cryptominisat
118
[{d: 0, b: 0, c: 0, a: 0}, {d: 1, b: 1, c: 0, a: 1},
119
{d: 1, b: 0, c: 1, a: 0}, {d: 0, b: 1, c: 1, a: 1}]
120
121
Now we are only interested in the solutions of the variables a and b::
122
123
sage: solve_sat(F,n=infinity,target_variables=[a,b]) # optional - cryptominisat
124
[{b: 0, a: 0}, {b: 1, a: 1}]
125
126
.. NOTE::
127
128
Although supported, passing converter and solver objects
129
instead of classes is discouraged because these objects are
130
stateful.
131
"""
132
assert(n>0)
133
134
try:
135
m = len(F)
136
except AttributeError:
137
F = F.gens()
138
m = len(F)
139
140
P = iter(F).next().parent()
141
K = P.base_ring()
142
143
if target_variables is None:
144
target_variables = Sequence(F).variables()
145
else:
146
target_variables = Sequence(target_variables).variables()
147
assert(set(target_variables).issubset(set(P.gens())))
148
149
# instantiate the SAT solver
150
151
if solver is None:
152
from sage.sat.solvers.cryptominisat import CryptoMiniSat as solver
153
154
if not isinstance(solver, SatSolver):
155
solver_kwds = {}
156
for k,v in kwds.iteritems():
157
if k.startswith("s_"):
158
solver_kwds[k[2:]] = v
159
160
solver = solver(**solver_kwds)
161
162
# instantiate the ANF to CNF converter
163
164
if converter is None:
165
from sage.sat.converters.polybori import CNFEncoder as converter
166
167
if not isinstance(converter, ANF2CNFConverter):
168
converter_kwds = {}
169
for k,v in kwds.iteritems():
170
if k.startswith("c_"):
171
converter_kwds[k[2:]] = v
172
173
converter = converter(solver, P, **converter_kwds)
174
175
phi = converter(F)
176
rho = dict( (phi[i],i) for i in range(len(phi)) )
177
178
S = []
179
180
while True:
181
s = solver()
182
183
if s:
184
S.append( dict( (x, K(s[rho[x]])) for x in target_variables ) )
185
186
if n is not None and len(S) == n:
187
break
188
189
exclude_solution = tuple(-rho[x] if s[rho[x]] else rho[x] for x in target_variables)
190
solver.add_clause(exclude_solution)
191
192
else:
193
try:
194
learnt = solver.learnt_clauses(unitary_only=True)
195
if learnt:
196
S.append( dict((phi[abs(i)-1],K(i<0)) for i in learnt) )
197
else:
198
S.append( s )
199
break
200
except (AttributeError, NotImplementedError):
201
# solver does not support recovering learnt clauses
202
S.append( s )
203
break
204
205
if len(S) == 1:
206
if S[0] is False:
207
return False
208
if S[0] is None:
209
return None
210
elif S[-1] is False:
211
return S[0:-1]
212
return S
213
214
def learn(F, converter=None, solver=None, max_learnt_length=3, interreduction=False, **kwds):
215
"""
216
Learn new polynomials by running SAT-solver ``solver`` on
217
SAT-instance produced by ``converter`` from ``F``.
218
219
INPUT:
220
221
- ``F`` - a sequence of Boolean polynomials
222
223
- ``converter`` - an ANF to CNF converter class or object. If ``converter`` is ``None`` then
224
:class:`sage.sat.converters.polybori.CNFEncoder` is used to construct a new
225
converter. (default: ``None``)
226
227
- ``solver`` - a SAT-solver class or object. If ``solver`` is ``None`` then
228
:class:`sage.sat.solvers.cryptominisat.CryptoMiniSat` is used to construct a new converter.
229
(default: ``None``)
230
231
- ``max_learnt_length`` - only clauses of length <= ``max_length_learnt`` are considered and
232
converted to polynomials. (default: ``3``)
233
234
- ``interreduction`` - inter-reduce the resulting polynomials (default: ``False``)
235
236
.. NOTE::
237
238
More parameters can be passed to the converter and the solver by prefixing them with ``c_`` and
239
``s_`` respectively. For example, to increase CryptoMiniSat's verbosity level, pass
240
``s_verbosity=1``.
241
242
OUTPUT:
243
244
A sequence of Boolean polynomials.
245
246
EXAMPLE::
247
248
sage: from sage.sat.boolean_polynomials import learn as learn_sat # optional - cryptominisat
249
250
We construct a simple system and solve it::
251
252
sage: set_random_seed(2300) # optional - cryptominisat
253
sage: sr = mq.SR(1,2,2,4,gf2=True,polybori=True) # optional - cryptominisat
254
sage: F,s = sr.polynomial_system() # optional - cryptominisat
255
sage: H = learn_sat(F) # optional - cryptominisat
256
sage: H[-1] # optional - cryptominisat
257
k033 + 1
258
259
We construct a slightly larger equation system and recover some
260
equations after 20 restarts::
261
262
sage: set_random_seed(2300) # optional - cryptominisat
263
sage: sr = mq.SR(1,4,4,4,gf2=True,polybori=True) # optional - cryptominisat
264
sage: F,s = sr.polynomial_system() # optional - cryptominisat
265
sage: from sage.sat.boolean_polynomials import learn as learn_sat # optional - cryptominisat
266
sage: H = learn_sat(F, s_maxrestarts=20, interreduction=True) # optional - cryptominisat
267
sage: H[-1] # optional - cryptominisat, output random
268
k001200*s031*x011201 + k001200*x011201
269
270
.. NOTE::
271
272
This function is meant to be called with some parameter such
273
that the SAT-solver is interrupted. For CryptoMiniSat this is
274
max_restarts, so pass 'c_max_restarts' to limit the number of
275
restarts CryptoMiniSat will attempt. If no such parameter is
276
passed, then this function behaves essentially like
277
:func:`solve` except that this function does not support
278
``n>1``.
279
"""
280
try:
281
m = len(F)
282
except AttributeError:
283
F = F.gens()
284
m = len(F)
285
286
P = iter(F).next().parent()
287
K = P.base_ring()
288
289
# instantiate the SAT solver
290
291
if solver is None:
292
from sage.sat.solvers.cryptominisat import CryptoMiniSat as solver
293
294
solver_kwds = {}
295
for k,v in kwds.iteritems():
296
if k.startswith("s_"):
297
solver_kwds[k[2:]] = v
298
299
solver = solver(**solver_kwds)
300
301
# instantiate the ANF to CNF converter
302
303
if converter is None:
304
from sage.sat.converters.polybori import CNFEncoder as converter
305
306
converter_kwds = {}
307
for k,v in kwds.iteritems():
308
if k.startswith("c_"):
309
converter_kwds[k[2:]] = v
310
311
converter = converter(solver, P, **converter_kwds)
312
313
314
phi = converter(F)
315
rho = dict( (phi[i],i) for i in range(len(phi)) )
316
317
s = solver()
318
319
if s:
320
learnt = [x + K(s[rho[x]]) for x in P.gens()]
321
else:
322
learnt = []
323
for c in solver.learnt_clauses():
324
if len(c) <= max_learnt_length:
325
try:
326
learnt.append( converter.to_polynomial(c) )
327
except (ValueError, NotImplementedError, AttributeError):
328
# the solver might have learnt clauses that contain CNF
329
# variables which have no correspondence to variables in our
330
# polynomial ring (XOR chaining variables for example)
331
pass
332
learnt = Sequence(learnt)
333
334
if interreduction:
335
learnt = learnt.ideal().interreduced_basis()
336
return learnt
337
338
339