python-sat 1.8.dev25__cp310-cp310-macosx_11_0_arm64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pycard.cpython-310-darwin.so +0 -0
- pysat/__init__.py +24 -0
- pysat/_fileio.py +209 -0
- pysat/_utils.py +58 -0
- pysat/allies/__init__.py +0 -0
- pysat/allies/approxmc.py +385 -0
- pysat/allies/unigen.py +435 -0
- pysat/card.py +802 -0
- pysat/engines.py +1302 -0
- pysat/examples/__init__.py +0 -0
- pysat/examples/bbscan.py +663 -0
- pysat/examples/bica.py +691 -0
- pysat/examples/fm.py +527 -0
- pysat/examples/genhard.py +516 -0
- pysat/examples/hitman.py +653 -0
- pysat/examples/lbx.py +638 -0
- pysat/examples/lsu.py +496 -0
- pysat/examples/mcsls.py +610 -0
- pysat/examples/models.py +189 -0
- pysat/examples/musx.py +344 -0
- pysat/examples/optux.py +710 -0
- pysat/examples/primer.py +620 -0
- pysat/examples/rc2.py +1858 -0
- pysat/examples/usage.py +63 -0
- pysat/formula.py +5619 -0
- pysat/pb.py +463 -0
- pysat/process.py +363 -0
- pysat/solvers.py +7591 -0
- pysolvers.cpython-310-darwin.so +0 -0
- python_sat-1.8.dev25.data/scripts/approxmc.py +385 -0
- python_sat-1.8.dev25.data/scripts/bbscan.py +663 -0
- python_sat-1.8.dev25.data/scripts/bica.py +691 -0
- python_sat-1.8.dev25.data/scripts/fm.py +527 -0
- python_sat-1.8.dev25.data/scripts/genhard.py +516 -0
- python_sat-1.8.dev25.data/scripts/lbx.py +638 -0
- python_sat-1.8.dev25.data/scripts/lsu.py +496 -0
- python_sat-1.8.dev25.data/scripts/mcsls.py +610 -0
- python_sat-1.8.dev25.data/scripts/models.py +189 -0
- python_sat-1.8.dev25.data/scripts/musx.py +344 -0
- python_sat-1.8.dev25.data/scripts/optux.py +710 -0
- python_sat-1.8.dev25.data/scripts/primer.py +620 -0
- python_sat-1.8.dev25.data/scripts/rc2.py +1858 -0
- python_sat-1.8.dev25.data/scripts/unigen.py +435 -0
- python_sat-1.8.dev25.dist-info/METADATA +45 -0
- python_sat-1.8.dev25.dist-info/RECORD +48 -0
- python_sat-1.8.dev25.dist-info/WHEEL +6 -0
- python_sat-1.8.dev25.dist-info/licenses/LICENSE.txt +21 -0
- python_sat-1.8.dev25.dist-info/top_level.txt +3 -0
|
File without changes
|
pysat/examples/bbscan.py
ADDED
|
@@ -0,0 +1,663 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
#-*- coding:utf-8 -*-
|
|
3
|
+
##
|
|
4
|
+
## bbscan.py
|
|
5
|
+
##
|
|
6
|
+
## Created on: Sep 21, 2025
|
|
7
|
+
## Author: Alexey Ignatiev
|
|
8
|
+
## E-mail: alexey.ignatiev@monash.edu
|
|
9
|
+
##
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
===============
|
|
13
|
+
List of classes
|
|
14
|
+
===============
|
|
15
|
+
|
|
16
|
+
.. autosummary::
|
|
17
|
+
:nosignatures:
|
|
18
|
+
|
|
19
|
+
BBScan
|
|
20
|
+
|
|
21
|
+
==================
|
|
22
|
+
Module description
|
|
23
|
+
==================
|
|
24
|
+
|
|
25
|
+
A simple implementation of a few backbone extraction algorithms described
|
|
26
|
+
in [1]_. Given a Boolean formula :math:`\\varphi`, a backbone literal is a
|
|
27
|
+
literal that holds true in every satisfying assignment of
|
|
28
|
+
:math:`\\varphi`. The backbone of formula :math:`\\varphi` is the set of
|
|
29
|
+
all backbone literals. This implementation includes the following
|
|
30
|
+
algorithms from [1]_:
|
|
31
|
+
|
|
32
|
+
* implicant enumeration algorithm,
|
|
33
|
+
* iterative algorithm (with one test per variable),
|
|
34
|
+
* complement of backbone estimate algorithm,
|
|
35
|
+
* chunking algorithm,
|
|
36
|
+
* core-based algorithm,
|
|
37
|
+
* core-based algorithm with chunking.
|
|
38
|
+
|
|
39
|
+
.. [1] Mikolás Janota, Inês Lynce, Joao Marques-Silva. *Algorithms for
|
|
40
|
+
computing backbones of propositional formulae*. AI Commun. 28(2).
|
|
41
|
+
2015. pp. 161-177
|
|
42
|
+
|
|
43
|
+
The implementation can be used as an executable (the list of
|
|
44
|
+
available command-line options can be shown using ``bbscan.py -h``)
|
|
45
|
+
in the following way:
|
|
46
|
+
|
|
47
|
+
::
|
|
48
|
+
|
|
49
|
+
$ xzcat formula.cnf.xz
|
|
50
|
+
p cnf 3 4
|
|
51
|
+
1 2 0
|
|
52
|
+
1 -2 0
|
|
53
|
+
2 -3 0
|
|
54
|
+
-2 -3 0
|
|
55
|
+
|
|
56
|
+
$ bbscan.py -v formula.cnf.xz
|
|
57
|
+
c formula: 3 vars, 4 clauses
|
|
58
|
+
c using iterative algorithm
|
|
59
|
+
v +1 -3 0
|
|
60
|
+
c backbone size: 2 (66.67% of all variables)
|
|
61
|
+
c oracle time: 0.0000
|
|
62
|
+
c oracle calls: 4
|
|
63
|
+
|
|
64
|
+
Alternatively, the algorithms can be accessed and invoked through the
|
|
65
|
+
standard ``import`` interface of Python, e.g.
|
|
66
|
+
|
|
67
|
+
.. code-block:: python
|
|
68
|
+
|
|
69
|
+
>>> from pysat.examples.bbscan import BBScan
|
|
70
|
+
>>> from pysat.formula import CNF
|
|
71
|
+
>>>
|
|
72
|
+
>>> # reading a formula from file
|
|
73
|
+
>>> cnf = CNF(from_file='formula.wcnf.xz')
|
|
74
|
+
>>>
|
|
75
|
+
>>> # creating BBScan and running it
|
|
76
|
+
>>> with BBScan(cnf, solver='g3', rotate=True) as bbscan:
|
|
77
|
+
... bbone = bbscan.compute(algorithm='core')
|
|
78
|
+
...
|
|
79
|
+
... # outputting the results
|
|
80
|
+
... if bbone:
|
|
81
|
+
... print(bbone)
|
|
82
|
+
[1, -3]
|
|
83
|
+
|
|
84
|
+
Each of the available algorithms can be augmented with a simple heuristic
|
|
85
|
+
aiming at reducing satisfying assignments and, thus, filtering out
|
|
86
|
+
unnecessary literals: namely, filtering rotatable literals. The heuristic
|
|
87
|
+
is described in the aforementioned paper.
|
|
88
|
+
|
|
89
|
+
Note that most of the methods of the class :class:`BBScan` are made
|
|
90
|
+
private. Therefore, the tool provides a minimalistic interface via which a
|
|
91
|
+
user can specify all the necessary parameters.
|
|
92
|
+
|
|
93
|
+
==============
|
|
94
|
+
Module details
|
|
95
|
+
==============
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
#
|
|
99
|
+
#==============================================================================
|
|
100
|
+
import getopt
|
|
101
|
+
import itertools
|
|
102
|
+
import os
|
|
103
|
+
from pysat.formula import CNF, IDPool
|
|
104
|
+
from pysat.solvers import Solver, SolverNames
|
|
105
|
+
import sys
|
|
106
|
+
import re
|
|
107
|
+
import time
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
#
|
|
111
|
+
#==============================================================================
|
|
112
|
+
class BBScan:
|
|
113
|
+
"""
|
|
114
|
+
A solver for computing all backbone literals of a given Boolean
|
|
115
|
+
formula. It implements 6 algorithms for backbone computation described
|
|
116
|
+
in [1]_ augmented with a heuristic that can be speed up the process.
|
|
117
|
+
|
|
118
|
+
Note that the input formula can be a :class:`.CNF` object but also any
|
|
119
|
+
object of class :class:`.Formula`, thus the tool can used for
|
|
120
|
+
computing backbone literals of non-clausal formulas.
|
|
121
|
+
|
|
122
|
+
A user can select one of the SAT solvers available at hand
|
|
123
|
+
(``'glucose3'`` is used by default). The optional heuristic can be
|
|
124
|
+
also specified as a Boolean input arguments ``rotate`` (turned off by
|
|
125
|
+
default).
|
|
126
|
+
|
|
127
|
+
The list of initialiser's arguments is as follows:
|
|
128
|
+
|
|
129
|
+
:param formula: input formula whose backbone is sought
|
|
130
|
+
:param solver: SAT oracle name
|
|
131
|
+
:param rotate: apply rotatable literal filtering
|
|
132
|
+
:param verbose: verbosity level
|
|
133
|
+
|
|
134
|
+
:type formula: :class:`.Formula` or :class:`.CNF`
|
|
135
|
+
:type solver: str
|
|
136
|
+
:type rotate: bool
|
|
137
|
+
:type verbose: int
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
def __init__(self, formula, solver='g3', rotate=False, verbose=0):
|
|
141
|
+
"""
|
|
142
|
+
Constructor.
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
self.formula = formula
|
|
146
|
+
self.focuscl = list(range(len(formula.clauses))) if rotate else []
|
|
147
|
+
self.verbose = verbose
|
|
148
|
+
self.oracle = None
|
|
149
|
+
|
|
150
|
+
# implicant reduction heuristics
|
|
151
|
+
self.rotate = rotate
|
|
152
|
+
|
|
153
|
+
# basic stats
|
|
154
|
+
self.calls, self.filtered = 0, 0
|
|
155
|
+
|
|
156
|
+
# this is going to be our workhorse
|
|
157
|
+
self.oracle = Solver(name=solver, bootstrap_with=formula, use_timer=True)
|
|
158
|
+
|
|
159
|
+
def __del__(self):
|
|
160
|
+
"""
|
|
161
|
+
Destructor.
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
self.delete()
|
|
165
|
+
|
|
166
|
+
def __enter__(self):
|
|
167
|
+
"""
|
|
168
|
+
'with' constructor.
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
return self
|
|
172
|
+
|
|
173
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
174
|
+
"""
|
|
175
|
+
'with' destructor.
|
|
176
|
+
"""
|
|
177
|
+
|
|
178
|
+
self.delete()
|
|
179
|
+
|
|
180
|
+
def delete(self):
|
|
181
|
+
"""
|
|
182
|
+
Explicit destructor of the internal SAT oracle.
|
|
183
|
+
"""
|
|
184
|
+
|
|
185
|
+
if self.oracle:
|
|
186
|
+
self.oracle.delete()
|
|
187
|
+
self.oracle = None
|
|
188
|
+
|
|
189
|
+
def compute(self, algorithm='iter', chunk_size=100, focus_on=None):
|
|
190
|
+
"""
|
|
191
|
+
Main solving method. A user is supposed to specify which backbone
|
|
192
|
+
computation algorithm they want to use:
|
|
193
|
+
|
|
194
|
+
* ``'enum'`` (implicant enumeration algorithm),
|
|
195
|
+
* ``'iter'`` (iterative algorithm, with one test per variable),
|
|
196
|
+
* ``'compl'`` (complement of backbone estimate algorithm),
|
|
197
|
+
* ``'chunk'`` (chunking algorithm),
|
|
198
|
+
* ``'core'`` (core-based algorithm),
|
|
199
|
+
* ``'corechunk'`` (core-based algorithm with chunking).
|
|
200
|
+
|
|
201
|
+
By default, :class:`.BBScan` opts for ``'iter'``.
|
|
202
|
+
|
|
203
|
+
If the chunking algorithm is selected (either ``'chunk'`` or
|
|
204
|
+
``'corechunk'``), the user may specify the size of the chunk,
|
|
205
|
+
which is set to 100 by default. Note that the chunk_size can be a
|
|
206
|
+
floating-point number in the interval (0, 1], which will set the
|
|
207
|
+
actual chunk_size relative to the total number of literals of
|
|
208
|
+
interest.
|
|
209
|
+
|
|
210
|
+
Finally, one may opt for computing backbone literals out of a
|
|
211
|
+
particular subset of literals, which may run faster than computing
|
|
212
|
+
the entire formula's backbone. To focus on a particular set of
|
|
213
|
+
literals, the user should use the parameter ``focus_on``, which is
|
|
214
|
+
set to ``None`` by default.
|
|
215
|
+
|
|
216
|
+
.. note::
|
|
217
|
+
|
|
218
|
+
The method raises ``ValueError`` if the input formula is
|
|
219
|
+
unsatisfiable.
|
|
220
|
+
|
|
221
|
+
:param algorithm: backbone computation algorithm
|
|
222
|
+
:param chunk_size: number of literals in the chunk (for chunking algorithms)
|
|
223
|
+
:param focus_on: a list of literals to focus on
|
|
224
|
+
|
|
225
|
+
:type algorithm: str
|
|
226
|
+
:type chunk_size: int or float
|
|
227
|
+
:type focus_on: iterable(int)
|
|
228
|
+
"""
|
|
229
|
+
|
|
230
|
+
self.calls += 1
|
|
231
|
+
trivial = []
|
|
232
|
+
|
|
233
|
+
if self.oracle.solve():
|
|
234
|
+
self.model = set(self.oracle.get_model())
|
|
235
|
+
if focus_on is not None:
|
|
236
|
+
self.model &= set(focus_on)
|
|
237
|
+
|
|
238
|
+
# if filtering rotatable literals is requested then
|
|
239
|
+
# we should be clever about the clauses to traverse
|
|
240
|
+
if self.rotate:
|
|
241
|
+
self.focuscl = []
|
|
242
|
+
for i, cl in enumerate(self.formula):
|
|
243
|
+
for l in cl:
|
|
244
|
+
if l in self.model:
|
|
245
|
+
if len(cl) == 1:
|
|
246
|
+
trivial.append(l)
|
|
247
|
+
self.model.remove(l)
|
|
248
|
+
else:
|
|
249
|
+
self.focuscl.append(i)
|
|
250
|
+
break
|
|
251
|
+
else:
|
|
252
|
+
raise ValueError('Unsatisfiable formula')
|
|
253
|
+
|
|
254
|
+
if isinstance(chunk_size, float):
|
|
255
|
+
assert 0 < chunk_size <= 1, f'Wrong chunk proportion {chunk_size}'
|
|
256
|
+
chunk_size = int(min(self.formula.nv, len(self.model)) * chunk_size)
|
|
257
|
+
|
|
258
|
+
if algorithm == 'enum':
|
|
259
|
+
result = self._compute_enum()
|
|
260
|
+
elif algorithm == 'iter':
|
|
261
|
+
result = self._compute_iter()
|
|
262
|
+
elif algorithm == 'compl':
|
|
263
|
+
result = self._compute_compl()
|
|
264
|
+
elif algorithm == 'chunk':
|
|
265
|
+
result = self._compute_chunking(chunk_size)
|
|
266
|
+
elif algorithm == 'core':
|
|
267
|
+
result = self._compute_core_based()
|
|
268
|
+
elif algorithm == 'corechunk':
|
|
269
|
+
result = self._compute_core_chunking(chunk_size)
|
|
270
|
+
else:
|
|
271
|
+
assert 0, f'Unknown algorithm: {algorithm}'
|
|
272
|
+
|
|
273
|
+
return sorted(trivial + result, key=lambda l: abs(l))
|
|
274
|
+
|
|
275
|
+
def _filter_rotatable(self, model):
|
|
276
|
+
"""
|
|
277
|
+
Filter out rotatable literals.
|
|
278
|
+
"""
|
|
279
|
+
|
|
280
|
+
def get_unit(cl):
|
|
281
|
+
# this is supposed to be a faster alternative to the
|
|
282
|
+
# complete clause traversal with a conditional inside
|
|
283
|
+
unit = None
|
|
284
|
+
for l in cl:
|
|
285
|
+
if l in model:
|
|
286
|
+
if unit:
|
|
287
|
+
return
|
|
288
|
+
else:
|
|
289
|
+
unit = l
|
|
290
|
+
return unit
|
|
291
|
+
|
|
292
|
+
# result of this procedure
|
|
293
|
+
units = set([])
|
|
294
|
+
|
|
295
|
+
# determining unit literals
|
|
296
|
+
for i in self.focuscl:
|
|
297
|
+
unit = get_unit(self.formula.clauses[i])
|
|
298
|
+
if unit:
|
|
299
|
+
units.add(unit)
|
|
300
|
+
|
|
301
|
+
self.filtered += len(model) - len(units)
|
|
302
|
+
return units
|
|
303
|
+
|
|
304
|
+
def _compute_enum(self, focus_on=None):
|
|
305
|
+
"""
|
|
306
|
+
Enumeration-based backbone computation.
|
|
307
|
+
"""
|
|
308
|
+
|
|
309
|
+
if self.verbose:
|
|
310
|
+
print('c using enumeration-based algorithm')
|
|
311
|
+
|
|
312
|
+
# initial backbone estimate contains all literals
|
|
313
|
+
bbone = self.model if focus_on is None else focus_on
|
|
314
|
+
|
|
315
|
+
while bbone:
|
|
316
|
+
# counting the number of calls
|
|
317
|
+
self.calls += 1
|
|
318
|
+
|
|
319
|
+
# stop if there are no more models
|
|
320
|
+
if not self.oracle.solve():
|
|
321
|
+
break
|
|
322
|
+
|
|
323
|
+
# updating backbone estimate - intersection
|
|
324
|
+
bbone &= set(self.oracle.get_model())
|
|
325
|
+
|
|
326
|
+
if self.rotate:
|
|
327
|
+
bbone = self._filter_rotatable(bbone)
|
|
328
|
+
|
|
329
|
+
# blocking the previously found implicant
|
|
330
|
+
self.oracle.add_clause([-l for l in bbone])
|
|
331
|
+
|
|
332
|
+
return list(bbone)
|
|
333
|
+
|
|
334
|
+
def _compute_iter(self, focus_on=None):
|
|
335
|
+
"""
|
|
336
|
+
Iterative algorithm with one test per variable.
|
|
337
|
+
"""
|
|
338
|
+
|
|
339
|
+
if self.verbose:
|
|
340
|
+
print('c using iterative algorithm')
|
|
341
|
+
|
|
342
|
+
# initial estimate
|
|
343
|
+
# using sets for model and assumps to save filtering time
|
|
344
|
+
bbone, model = [], self.model if focus_on is None else focus_on
|
|
345
|
+
|
|
346
|
+
while model:
|
|
347
|
+
# counting the number of oracle calls
|
|
348
|
+
self.calls += 1
|
|
349
|
+
|
|
350
|
+
# checking this literal
|
|
351
|
+
lit = model.pop()
|
|
352
|
+
|
|
353
|
+
if self.oracle.solve(assumptions=[-lit]) == False:
|
|
354
|
+
# it is a backbone literal
|
|
355
|
+
bbone.append(lit)
|
|
356
|
+
else:
|
|
357
|
+
# it isn't and we've got a counterexample
|
|
358
|
+
# => using it to filter out more literals
|
|
359
|
+
model &= set(self.oracle.get_model())
|
|
360
|
+
|
|
361
|
+
if self.rotate:
|
|
362
|
+
model = self._filter_rotatable(model)
|
|
363
|
+
|
|
364
|
+
return bbone
|
|
365
|
+
|
|
366
|
+
def _compute_compl(self, focus_on=None):
|
|
367
|
+
"""
|
|
368
|
+
Iterative algorithm with complement of backbone estimate.
|
|
369
|
+
"""
|
|
370
|
+
|
|
371
|
+
if self.verbose:
|
|
372
|
+
print('c using complement of backbone estimate algorithm')
|
|
373
|
+
|
|
374
|
+
# initial estimate
|
|
375
|
+
bbone = self.model if focus_on is None else focus_on
|
|
376
|
+
|
|
377
|
+
# iterating until we find the backbone or determine there is none
|
|
378
|
+
while bbone:
|
|
379
|
+
self.calls += 1
|
|
380
|
+
|
|
381
|
+
# first, adding a new D-clause
|
|
382
|
+
self.oracle.add_clause([-l for l in bbone])
|
|
383
|
+
|
|
384
|
+
# testing for unsatisfiability
|
|
385
|
+
if self.oracle.solve() == False:
|
|
386
|
+
break
|
|
387
|
+
else:
|
|
388
|
+
bbone &= set(self.oracle.get_model())
|
|
389
|
+
|
|
390
|
+
if self.rotate:
|
|
391
|
+
bbone = self._filter_rotatable(bbone)
|
|
392
|
+
|
|
393
|
+
return list(bbone)
|
|
394
|
+
|
|
395
|
+
def _compute_chunking(self, chunk_size=100, focus_on=None):
|
|
396
|
+
"""
|
|
397
|
+
Chunking algorithm.
|
|
398
|
+
"""
|
|
399
|
+
|
|
400
|
+
if self.verbose:
|
|
401
|
+
print('c using chunking algorithm, with chunk size:', chunk_size)
|
|
402
|
+
|
|
403
|
+
# initial estimate
|
|
404
|
+
bbone, model = [], list(self.model if focus_on is None else focus_on)
|
|
405
|
+
|
|
406
|
+
# we are going to use clause selectors
|
|
407
|
+
vpool = IDPool(start_from=self.formula.nv + 1)
|
|
408
|
+
|
|
409
|
+
# iterating until we find the backbone or determine there is none
|
|
410
|
+
while model:
|
|
411
|
+
self.calls += 1
|
|
412
|
+
|
|
413
|
+
# preparing the call
|
|
414
|
+
size = min(chunk_size, len(model))
|
|
415
|
+
selv = vpool.id()
|
|
416
|
+
|
|
417
|
+
# first, adding a new D-clause for the selected chunk
|
|
418
|
+
self.oracle.add_clause([-model[-i] for i in range(1, size + 1)] + [-selv])
|
|
419
|
+
|
|
420
|
+
# testing for unsatisfiability
|
|
421
|
+
if self.oracle.solve(assumptions=[selv]) == False:
|
|
422
|
+
# all literals in the chunk are in the backbone
|
|
423
|
+
for _ in range(size):
|
|
424
|
+
lit = model.pop()
|
|
425
|
+
bbone.append(lit)
|
|
426
|
+
self.oracle.add_clause([lit])
|
|
427
|
+
else:
|
|
428
|
+
coex = set(self.oracle.get_model())
|
|
429
|
+
model = [l for l in model if l in coex]
|
|
430
|
+
|
|
431
|
+
if self.rotate:
|
|
432
|
+
model = list(self._filter_rotatable(set(model)))
|
|
433
|
+
|
|
434
|
+
return bbone
|
|
435
|
+
|
|
436
|
+
def _compute_core_based(self, focus_on=None):
|
|
437
|
+
"""
|
|
438
|
+
Core-based algorithm.
|
|
439
|
+
"""
|
|
440
|
+
|
|
441
|
+
if self.verbose:
|
|
442
|
+
print('c using core-based algorithm')
|
|
443
|
+
|
|
444
|
+
# initial estimate
|
|
445
|
+
# using sets for model and assumps to save filtering time
|
|
446
|
+
bbone, model = [], self.model if focus_on is None else focus_on
|
|
447
|
+
|
|
448
|
+
# iterating until we find the backbone or determine there is none
|
|
449
|
+
while model:
|
|
450
|
+
# flipping all the literals
|
|
451
|
+
assumps = {-l for l in model}
|
|
452
|
+
|
|
453
|
+
# getting unsatisfiable cores with them
|
|
454
|
+
while True:
|
|
455
|
+
self.calls += 1
|
|
456
|
+
|
|
457
|
+
if self.oracle.solve(assumptions=assumps):
|
|
458
|
+
model &= set(self.oracle.get_model())
|
|
459
|
+
|
|
460
|
+
if self.rotate:
|
|
461
|
+
model = self._filter_rotatable(model)
|
|
462
|
+
|
|
463
|
+
break
|
|
464
|
+
|
|
465
|
+
else:
|
|
466
|
+
core = self.oracle.get_core()
|
|
467
|
+
if len(core) == 1:
|
|
468
|
+
bbone.append(-core[0])
|
|
469
|
+
self.oracle.add_clause([-core[0]])
|
|
470
|
+
|
|
471
|
+
# remove from the working model
|
|
472
|
+
model.remove(-core[0])
|
|
473
|
+
|
|
474
|
+
# filtering out unnecessary flipped literals
|
|
475
|
+
assumps -= set(core)
|
|
476
|
+
|
|
477
|
+
if not assumps:
|
|
478
|
+
self.model = model
|
|
479
|
+
return bbone + self._compute_iter()
|
|
480
|
+
|
|
481
|
+
return bbone
|
|
482
|
+
|
|
483
|
+
def _compute_core_chunking(self, chunk_size=100, focus_on=None):
|
|
484
|
+
"""
|
|
485
|
+
Core-based algorithm with chunking.
|
|
486
|
+
"""
|
|
487
|
+
|
|
488
|
+
if self.verbose:
|
|
489
|
+
print('c using core-based chunking, with chunk size:', chunk_size)
|
|
490
|
+
|
|
491
|
+
# initial estimate
|
|
492
|
+
bbone, model = [], list(self.model if focus_on is None else focus_on)
|
|
493
|
+
|
|
494
|
+
# we are going to use clause selectors
|
|
495
|
+
vpool = IDPool(start_from=self.formula.nv + 1)
|
|
496
|
+
|
|
497
|
+
# iterating until we find the backbone or determine there is none
|
|
498
|
+
while model:
|
|
499
|
+
# preparing the chunking
|
|
500
|
+
size = min(chunk_size, len(model))
|
|
501
|
+
|
|
502
|
+
# flipping all the literals
|
|
503
|
+
assumps, skipped = {-model.pop() for i in range(size)}, set()
|
|
504
|
+
|
|
505
|
+
# getting unsatisfiable cores with them
|
|
506
|
+
while True:
|
|
507
|
+
self.calls += 1
|
|
508
|
+
|
|
509
|
+
if self.oracle.solve(assumptions=assumps):
|
|
510
|
+
coex = set(self.oracle.get_model())
|
|
511
|
+
model = [l for l in model if l in coex]
|
|
512
|
+
|
|
513
|
+
if self.rotate:
|
|
514
|
+
model = list(self._filter_rotatable(set(model)))
|
|
515
|
+
|
|
516
|
+
if skipped:
|
|
517
|
+
bbone += self._compute_iter(focus_on=skipped)
|
|
518
|
+
|
|
519
|
+
break
|
|
520
|
+
|
|
521
|
+
else:
|
|
522
|
+
core = self.oracle.get_core()
|
|
523
|
+
|
|
524
|
+
if len(core) == 1:
|
|
525
|
+
# a unit-size core must contain a backbone literal
|
|
526
|
+
bbone.append(-core[0])
|
|
527
|
+
self.oracle.add_clause([-core[0]])
|
|
528
|
+
else:
|
|
529
|
+
# all removed literals are going to be tested later
|
|
530
|
+
skipped |= {-l for l in core}
|
|
531
|
+
|
|
532
|
+
# filtering out unnecessary flipped literals
|
|
533
|
+
assumps -= set(core)
|
|
534
|
+
|
|
535
|
+
if not assumps:
|
|
536
|
+
# resorting to the iterative traversal algorithm
|
|
537
|
+
# in order to test all the removed literals
|
|
538
|
+
bbone += self._compute_iter(focus_on=skipped)
|
|
539
|
+
break
|
|
540
|
+
|
|
541
|
+
return bbone
|
|
542
|
+
|
|
543
|
+
def oracle_time(self):
|
|
544
|
+
"""
|
|
545
|
+
This method computes and returns the total SAT solving time
|
|
546
|
+
involved, including the time spent by the hitting set enumerator
|
|
547
|
+
and the two SAT oracles.
|
|
548
|
+
|
|
549
|
+
:rtype: float
|
|
550
|
+
"""
|
|
551
|
+
|
|
552
|
+
return self.oracle.time_accum()
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
#
|
|
556
|
+
#==============================================================================
|
|
557
|
+
def parse_options():
|
|
558
|
+
"""
|
|
559
|
+
Parses command-line options.
|
|
560
|
+
"""
|
|
561
|
+
|
|
562
|
+
try:
|
|
563
|
+
opts, args = getopt.getopt(sys.argv[1:],
|
|
564
|
+
'a:c:hrs:v',
|
|
565
|
+
['algo=',
|
|
566
|
+
'chunk=',
|
|
567
|
+
'help',
|
|
568
|
+
'rotate',
|
|
569
|
+
'solver=',
|
|
570
|
+
'verbose'])
|
|
571
|
+
except getopt.GetoptError as err:
|
|
572
|
+
sys.stderr.write(str(err).capitalize() + '\n')
|
|
573
|
+
usage()
|
|
574
|
+
sys.exit(1)
|
|
575
|
+
|
|
576
|
+
algo = 'iter'
|
|
577
|
+
chunk = 100
|
|
578
|
+
rotate = False
|
|
579
|
+
solver = 'g3'
|
|
580
|
+
verbose = 0
|
|
581
|
+
|
|
582
|
+
for opt, arg in opts:
|
|
583
|
+
if opt in ('-a', '--algo'):
|
|
584
|
+
algo = str(arg)
|
|
585
|
+
assert algo in ('enum', 'iter', 'compl', 'chunk', 'core', 'corechunk'), 'Unknown algorithm'
|
|
586
|
+
elif opt in ('-c', '--chunk'):
|
|
587
|
+
chunk = float(arg)
|
|
588
|
+
if chunk.is_integer():
|
|
589
|
+
chunk = int(chunk)
|
|
590
|
+
else:
|
|
591
|
+
assert 0 < chunk <= 1, f'Wrong chunk proportion {chunk_size}'
|
|
592
|
+
elif opt in ('-h', '--help'):
|
|
593
|
+
usage()
|
|
594
|
+
sys.exit(0)
|
|
595
|
+
elif opt in ('-r', '--rotate'):
|
|
596
|
+
rotate = True
|
|
597
|
+
elif opt in ('-s', '--solver'):
|
|
598
|
+
solver = str(arg)
|
|
599
|
+
elif opt in ('-v', '--verbose'):
|
|
600
|
+
verbose += 1
|
|
601
|
+
else:
|
|
602
|
+
assert False, 'Unhandled option: {0} {1}'.format(opt, arg)
|
|
603
|
+
|
|
604
|
+
return algo, chunk, rotate, solver, verbose, args
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
#
|
|
608
|
+
#==============================================================================
|
|
609
|
+
def usage():
|
|
610
|
+
"""
|
|
611
|
+
Prints usage message.
|
|
612
|
+
"""
|
|
613
|
+
|
|
614
|
+
print('Usage:', os.path.basename(sys.argv[0]), '[options] file')
|
|
615
|
+
print('Options:')
|
|
616
|
+
print(' -a, --algo=<string> Algorithm to use')
|
|
617
|
+
print(' Available values: enum, iter, compl, chunk, core, corechunk (default: iter)')
|
|
618
|
+
print(' -c, --chunk=<int,float> Chunk size for chunking algorithms')
|
|
619
|
+
print(' Available values: [1 .. INT_MAX] or (0 .. 1] (default: 100)')
|
|
620
|
+
print(' -h, --help Show this message')
|
|
621
|
+
print(' -r, --rotate Heuristically filter out rotatable literals')
|
|
622
|
+
print(' -s, --solver=<string> SAT solver to use')
|
|
623
|
+
print(' Available values: cd15, cd19, g3, g4, lgl, mcb, mcm, mpl, m22, mc, mgh (default: g3)')
|
|
624
|
+
print(' -v, --verbose Be verbose (can be used multiple times)')
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
#
|
|
628
|
+
#==============================================================================
|
|
629
|
+
if __name__ == '__main__':
|
|
630
|
+
algo, chunk, rotate, solver, verbose, files = parse_options()
|
|
631
|
+
|
|
632
|
+
if files:
|
|
633
|
+
# read CNF from file
|
|
634
|
+
assert re.search(r'cnf(\.(gz|bz2|lzma|xz|zst))?$', files[0]), \
|
|
635
|
+
'Unknown input file extension'
|
|
636
|
+
formula = CNF(from_file=files[0])
|
|
637
|
+
|
|
638
|
+
if verbose:
|
|
639
|
+
print('c formula: {0} vars, {1} clauses'.format(formula.nv,
|
|
640
|
+
len(formula.clauses)))
|
|
641
|
+
|
|
642
|
+
# computing the backbone
|
|
643
|
+
with BBScan(formula, solver=solver, rotate=rotate, verbose=verbose) as bbscan:
|
|
644
|
+
try:
|
|
645
|
+
bbone = bbscan.compute(algorithm=algo, chunk_size=chunk)
|
|
646
|
+
|
|
647
|
+
except ValueError as e:
|
|
648
|
+
print('s UNSATISFIABLE')
|
|
649
|
+
print('c', str(e))
|
|
650
|
+
sys.exit(1)
|
|
651
|
+
|
|
652
|
+
# outputting the results
|
|
653
|
+
if bbone:
|
|
654
|
+
print('v {0} 0'.format(' '.join(['{0}{1}'.format('+' if v > 0 else '', v) for v in bbone])))
|
|
655
|
+
print('c backbone size: {0} ({1:.2f}% of all variables)'.format(len(bbone), 100. * len(bbone) / formula.nv))
|
|
656
|
+
else:
|
|
657
|
+
print('v 0')
|
|
658
|
+
|
|
659
|
+
if verbose > 1:
|
|
660
|
+
print('c filtered: {0} ({1:.2f})'.format(bbscan.filtered, 100. * bbscan.filtered / formula.nv))
|
|
661
|
+
|
|
662
|
+
print('c oracle time: {0:.4f}'.format(bbscan.oracle_time()))
|
|
663
|
+
print('c oracle calls: {0}'.format(bbscan.calls))
|