python-sat 0.1.8.dev10__cp310-cp310-win_amd64.whl → 1.8.dev26__cp310-cp310-win_amd64.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.
Potentially problematic release.
This version of python-sat might be problematic. Click here for more details.
- pycard.cp310-win_amd64.pyd +0 -0
- pysat/__init__.py +4 -4
- pysat/_fileio.py +30 -14
- pysat/allies/approxmc.py +22 -22
- pysat/allies/unigen.py +435 -0
- pysat/card.py +13 -12
- pysat/engines.py +1302 -0
- pysat/examples/bbscan.py +663 -0
- pysat/examples/bica.py +691 -0
- pysat/examples/fm.py +12 -8
- pysat/examples/genhard.py +24 -23
- pysat/examples/hitman.py +53 -37
- pysat/examples/lbx.py +56 -15
- pysat/examples/lsu.py +28 -14
- pysat/examples/mcsls.py +53 -15
- pysat/examples/models.py +6 -4
- pysat/examples/musx.py +15 -7
- pysat/examples/optux.py +71 -32
- pysat/examples/primer.py +620 -0
- pysat/examples/rc2.py +268 -69
- pysat/formula.py +3241 -229
- pysat/pb.py +85 -37
- pysat/process.py +16 -2
- pysat/solvers.py +2119 -724
- pysolvers.cp310-win_amd64.pyd +0 -0
- {python_sat-0.1.8.dev10.data → python_sat-1.8.dev26.data}/scripts/approxmc.py +22 -22
- python_sat-1.8.dev26.data/scripts/bbscan.py +663 -0
- python_sat-1.8.dev26.data/scripts/bica.py +691 -0
- {python_sat-0.1.8.dev10.data → python_sat-1.8.dev26.data}/scripts/fm.py +12 -8
- {python_sat-0.1.8.dev10.data → python_sat-1.8.dev26.data}/scripts/genhard.py +24 -23
- {python_sat-0.1.8.dev10.data → python_sat-1.8.dev26.data}/scripts/lbx.py +56 -15
- {python_sat-0.1.8.dev10.data → python_sat-1.8.dev26.data}/scripts/lsu.py +28 -14
- {python_sat-0.1.8.dev10.data → python_sat-1.8.dev26.data}/scripts/mcsls.py +53 -15
- {python_sat-0.1.8.dev10.data → python_sat-1.8.dev26.data}/scripts/models.py +6 -4
- {python_sat-0.1.8.dev10.data → python_sat-1.8.dev26.data}/scripts/musx.py +15 -7
- {python_sat-0.1.8.dev10.data → python_sat-1.8.dev26.data}/scripts/optux.py +71 -32
- python_sat-1.8.dev26.data/scripts/primer.py +620 -0
- {python_sat-0.1.8.dev10.data → python_sat-1.8.dev26.data}/scripts/rc2.py +268 -69
- python_sat-1.8.dev26.data/scripts/unigen.py +435 -0
- {python_sat-0.1.8.dev10.dist-info → python_sat-1.8.dev26.dist-info}/METADATA +19 -5
- python_sat-1.8.dev26.dist-info/RECORD +48 -0
- {python_sat-0.1.8.dev10.dist-info → python_sat-1.8.dev26.dist-info}/WHEEL +1 -1
- python_sat-0.1.8.dev10.dist-info/RECORD +0 -39
- {python_sat-0.1.8.dev10.dist-info → python_sat-1.8.dev26.dist-info/licenses}/LICENSE.txt +0 -0
- {python_sat-0.1.8.dev10.dist-info → python_sat-1.8.dev26.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,620 @@
|
|
|
1
|
+
#!python
|
|
2
|
+
#-*- coding:utf-8 -*-
|
|
3
|
+
##
|
|
4
|
+
## primer.py
|
|
5
|
+
##
|
|
6
|
+
## Created on: Jul 23, 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
|
+
Primer
|
|
20
|
+
|
|
21
|
+
==================
|
|
22
|
+
Module description
|
|
23
|
+
==================
|
|
24
|
+
|
|
25
|
+
A reimplementation of the prime implicant (and implicate) enumeration
|
|
26
|
+
algorithm originally called Primer-B [1]_. The algorithm exploits the
|
|
27
|
+
minimal hitting duality between prime implicants and implicates of an
|
|
28
|
+
input formula and hence makes extensive use of a hitting set enumerator.
|
|
29
|
+
The input formula can be in a clausal or a non-clausal form, i.e. the
|
|
30
|
+
input can be given as an object of either :class:`.CNF` or
|
|
31
|
+
:class:`.Formula`.
|
|
32
|
+
|
|
33
|
+
The implementation relies on :class:`.Hitman` and supports both sorted and
|
|
34
|
+
unsorted hitting set enumeration. In the former case, hitting sets are
|
|
35
|
+
computed from smallest to largest, which is achieved with MaxSAT-based
|
|
36
|
+
hitting set enumeration, using :class:`.RC2` [2]_ [3]_ [4]_. In the latter
|
|
37
|
+
case, either an LBX-like MCS enumerator :class:`.LBX` [5]_ is used or
|
|
38
|
+
*pure* SAT-based minimal model enumeration [6]_.
|
|
39
|
+
|
|
40
|
+
.. [1] Alessandro Previti, Alexey Ignatiev, António Morgado,
|
|
41
|
+
Joao Marques-Silva. *Prime Compilation of Non-Clausal Formulae.*
|
|
42
|
+
IJCAI 2015. pp. 1980-1988
|
|
43
|
+
|
|
44
|
+
.. [2] António Morgado, Carmine Dodaro, Joao Marques-Silva. *Core-Guided
|
|
45
|
+
MaxSAT with Soft Cardinality Constraints*. CP 2014. pp. 564-573
|
|
46
|
+
|
|
47
|
+
.. [3] António Morgado, Alexey Ignatiev, Joao Marques-Silva. *MSCG: Robust
|
|
48
|
+
Core-Guided MaxSAT Solving*. JSAT 9. 2014. pp. 129-134
|
|
49
|
+
|
|
50
|
+
.. [4] Alexey Ignatiev, António Morgado, Joao Marques-Silva. *RC2: a
|
|
51
|
+
Python-based MaxSAT Solver*. MaxSAT Evaluation 2018. p. 22
|
|
52
|
+
|
|
53
|
+
.. [5] Carlos Mencía, Alessandro Previti, Joao Marques-Silva.
|
|
54
|
+
*Literal-Based MCS Extraction*. IJCAI. 2015. pp. 1973-1979
|
|
55
|
+
|
|
56
|
+
.. [6] Enrico Giunchiglia, Marco Maratea. *Solving Optimization Problems
|
|
57
|
+
with DLL*. ECAI 2006. pp. 377-381
|
|
58
|
+
|
|
59
|
+
The implementation can be used as an executable (the list of available
|
|
60
|
+
command-line options can be shown using ``primer.py -h``) in the following
|
|
61
|
+
way:
|
|
62
|
+
|
|
63
|
+
::
|
|
64
|
+
|
|
65
|
+
$ xzcat formula.cnf.xz
|
|
66
|
+
p cnf 4 3
|
|
67
|
+
1 2 4 0
|
|
68
|
+
1 -2 3 0
|
|
69
|
+
-1 2 -4 0
|
|
70
|
+
|
|
71
|
+
$ primer.py -i -e all formula.cnf.xz
|
|
72
|
+
v +1 +2 0
|
|
73
|
+
v +2 +3 0
|
|
74
|
+
v +1 -4 0
|
|
75
|
+
v -1 +3 +4 0
|
|
76
|
+
v -1 -2 +4 0
|
|
77
|
+
c primes: 5
|
|
78
|
+
c oracle time: 0.0001
|
|
79
|
+
c oracle calls: 41
|
|
80
|
+
|
|
81
|
+
Alternatively, the algorithm can be accessed and invoked through the
|
|
82
|
+
standard ``import`` interface of Python, e.g.
|
|
83
|
+
|
|
84
|
+
.. code-block:: python
|
|
85
|
+
|
|
86
|
+
>>> from pysat.examples.primer import Primer
|
|
87
|
+
>>> from pysat.formula import CNF
|
|
88
|
+
>>>
|
|
89
|
+
>>> cnf = CNF(from_file='test.cnf.xz')
|
|
90
|
+
>>>
|
|
91
|
+
>>> with Primer(cnf, implicates=False) as primer:
|
|
92
|
+
... for p in primer.enumerate():
|
|
93
|
+
... print(f'prime: {p}')
|
|
94
|
+
...
|
|
95
|
+
prime: [2, 3]
|
|
96
|
+
prime: [1, 2]
|
|
97
|
+
prime: [1, -4]
|
|
98
|
+
prime: [-1, 3, 4]
|
|
99
|
+
prime: [-1, -2, 4]
|
|
100
|
+
|
|
101
|
+
The tool can be instructed to enumerate either prime implicates or prime
|
|
102
|
+
implicants (a set of the dual primes covering the formula is computed as a
|
|
103
|
+
by-product of Primer's implicit hitting set algorithm). Namely, it targets
|
|
104
|
+
implicate enumeration *by default*; setting the Boolean parameter
|
|
105
|
+
``implicates=False`` will force it to focus on prime implicants instead.
|
|
106
|
+
(In the command line, the same effect can be achieved if using the option
|
|
107
|
+
'-i'.)
|
|
108
|
+
|
|
109
|
+
A user may also want to compute :math:`k` primes instead of enumerating
|
|
110
|
+
them exhaustively. This may come in handy, for example, if the input
|
|
111
|
+
formula has an exponential number of primes. Command-line option `-e NUM`
|
|
112
|
+
is responsible for this. In the case of complete prime implicant
|
|
113
|
+
enumeration the algorithm will essentially end up computing the input
|
|
114
|
+
formula's *Blake Canonical Form* (BCF) [7]_.
|
|
115
|
+
|
|
116
|
+
.. [7] Archie Blake. *Canonical Expressions in Boolean Algebra.*
|
|
117
|
+
Dissertation. University of Chicago. 1937.
|
|
118
|
+
|
|
119
|
+
==============
|
|
120
|
+
Module details
|
|
121
|
+
==============
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
#
|
|
125
|
+
#==============================================================================
|
|
126
|
+
import getopt
|
|
127
|
+
import os
|
|
128
|
+
from pysat.examples.hitman import Hitman
|
|
129
|
+
from pysat.formula import IDPool, CNF, Neg
|
|
130
|
+
from pysat.solvers import Solver
|
|
131
|
+
import re
|
|
132
|
+
import sys
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
#
|
|
136
|
+
#==============================================================================
|
|
137
|
+
class Primer:
|
|
138
|
+
"""
|
|
139
|
+
A simple Python-based reimplementation of the Primer-B algorithm. It
|
|
140
|
+
can be used for computing either :math:`k` prime implicates or
|
|
141
|
+
implicants of an input formula, or enumerating them all exhaustively
|
|
142
|
+
As the algorithm is based on implicit hitting set enumeration, a set
|
|
143
|
+
of dual primes (either prime implicants or implicates) covering the
|
|
144
|
+
input formula is computed as a by-product.
|
|
145
|
+
|
|
146
|
+
The input formula can be specified either in :class:`.CNF` or be a
|
|
147
|
+
generic Boolean :class:`.Formula`. In the latter case, the
|
|
148
|
+
implementation will clausify the formula and, importantly, report all
|
|
149
|
+
the primes using the integer variable IDs introduced by the
|
|
150
|
+
clausification process. As a result, a user is assumed responsible for
|
|
151
|
+
translating these back to the original :class:`Atom` objects, should
|
|
152
|
+
the need arise.
|
|
153
|
+
|
|
154
|
+
Additionally, the user may additionally specify the negation of the
|
|
155
|
+
formula (if not, Primer will create one in the process) and a few
|
|
156
|
+
input parameters controlling the run of the algorithm. All of these in
|
|
157
|
+
fact relate to the parameters of the hitting set enumerator, which is
|
|
158
|
+
the key component of the tool.
|
|
159
|
+
|
|
160
|
+
Namely, a user may specify the SAT solver of their choice to be used
|
|
161
|
+
by :class:`.Hitman` and the two extra SAT oracles as well as set the
|
|
162
|
+
parameters of MaxSAT/MCS/SAT-based hitting set enumeration. For
|
|
163
|
+
details on these parameters and what exactly they control, please
|
|
164
|
+
refer to to the description of :class:`.Hitman`.
|
|
165
|
+
|
|
166
|
+
Finally, when the algorithm determines a hitting set *not* to be a
|
|
167
|
+
target prime, a model is extracted evidencing this fact, which is then
|
|
168
|
+
reduced into a *dual prime*. The reduction process is based on MUS
|
|
169
|
+
extraction and can involve a linear literal traversal or dichotomic
|
|
170
|
+
similar to the ideas of QuickXPlain [8]_. The choice of the type of
|
|
171
|
+
literal traversal can be made using the ``search`` parameter, which
|
|
172
|
+
can be set either to ``'lin'`` or ``'bin'``.
|
|
173
|
+
|
|
174
|
+
.. [8] Ulrich Junker. *QUICKXPLAIN: Preferred Explanations and
|
|
175
|
+
Relaxations for Over-Constrained Problems.* AAAI 2004.
|
|
176
|
+
pp. 167-172
|
|
177
|
+
|
|
178
|
+
The complete list of input parameters is as follows:
|
|
179
|
+
|
|
180
|
+
:param formula: input formula whose prime representation is sought
|
|
181
|
+
:param negated: input's formula negation (if any)
|
|
182
|
+
:param solver: SAT oracle name
|
|
183
|
+
:param implicates: whether or not prime implicates to target
|
|
184
|
+
:param adapt: detect and adapt intrinsic AtMost1 constraints
|
|
185
|
+
:param dcalls: apply clause D oracle calls (for unsorted enumeration only)
|
|
186
|
+
:param exhaust: do core exhaustion
|
|
187
|
+
:param minz: do heuristic core reduction
|
|
188
|
+
:param puresat: use pure SAT-based hitting set enumeration
|
|
189
|
+
:param search: dual prime reduction strategy
|
|
190
|
+
:param unsorted: apply unsorted MUS enumeration
|
|
191
|
+
:param trim: do core trimming at most this number of times
|
|
192
|
+
:param verbose: verbosity level
|
|
193
|
+
|
|
194
|
+
:type formula: :class:`.Formula` or :class:`.CNF`
|
|
195
|
+
:type negated: :class:`.Formula` or :class:`.CNF`
|
|
196
|
+
:type solver: str
|
|
197
|
+
:type implicates: bool
|
|
198
|
+
:type adapt: bool
|
|
199
|
+
:type dcalls: bool
|
|
200
|
+
:type exhaust: bool
|
|
201
|
+
:type minz: bool
|
|
202
|
+
:type puresat: str
|
|
203
|
+
:type search: str
|
|
204
|
+
:type unsorted: bool
|
|
205
|
+
:type trim: int
|
|
206
|
+
:type verbose: int
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
def __init__(self, formula, negated=None, solver='cd19', implicates=True,
|
|
210
|
+
adapt=False, dcalls=False, exhaust=False, minz=False,
|
|
211
|
+
puresat=False, search='lin', unsorted=False, trim=False,
|
|
212
|
+
verbose=0):
|
|
213
|
+
"""
|
|
214
|
+
Initialiser.
|
|
215
|
+
"""
|
|
216
|
+
|
|
217
|
+
# copying some of the arguments
|
|
218
|
+
self.form = formula
|
|
219
|
+
self.fneg = negated
|
|
220
|
+
self.mode = bool(implicates)
|
|
221
|
+
|
|
222
|
+
# dual prime minimisation strategy
|
|
223
|
+
self.search = search
|
|
224
|
+
|
|
225
|
+
# verbosity level
|
|
226
|
+
self.verbose = verbose
|
|
227
|
+
|
|
228
|
+
# number of SAT oracle calls
|
|
229
|
+
self.calls = 0
|
|
230
|
+
|
|
231
|
+
# creating hitman
|
|
232
|
+
if not unsorted:
|
|
233
|
+
# MaxSAT-based hitting set enumerator
|
|
234
|
+
self.hitman = Hitman(bootstrap_with=[], solver=solver,
|
|
235
|
+
htype='sorted', mxs_adapt=adapt,
|
|
236
|
+
mxs_exhaust=exhaust, mxs_minz=minz,
|
|
237
|
+
mxs_trim=trim)
|
|
238
|
+
elif not puresat:
|
|
239
|
+
# MCS-based hitting set enumerator
|
|
240
|
+
self.hitman = Hitman(bootstrap_with=[], solver=solver,
|
|
241
|
+
htype='lbx', mcs_usecld=dcalls)
|
|
242
|
+
else:
|
|
243
|
+
# pure SAT-based hitting set enumerator with preferred phases
|
|
244
|
+
self.hitman = Hitman(bootstrap_with=[], solver=puresat,
|
|
245
|
+
htype='sat')
|
|
246
|
+
|
|
247
|
+
# creating the checker and reducer oracles
|
|
248
|
+
self.checker = Solver(name=solver, use_timer=True)
|
|
249
|
+
self.reducer = Solver(name=solver, use_timer=True)
|
|
250
|
+
|
|
251
|
+
# populate all the oracles with all the relevant clauses
|
|
252
|
+
self.init_oracles()
|
|
253
|
+
|
|
254
|
+
def __del__(self):
|
|
255
|
+
"""
|
|
256
|
+
Destructor.
|
|
257
|
+
"""
|
|
258
|
+
|
|
259
|
+
self.delete()
|
|
260
|
+
|
|
261
|
+
def __enter__(self):
|
|
262
|
+
"""
|
|
263
|
+
'with' constructor.
|
|
264
|
+
"""
|
|
265
|
+
|
|
266
|
+
return self
|
|
267
|
+
|
|
268
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
269
|
+
"""
|
|
270
|
+
'with' destructor.
|
|
271
|
+
"""
|
|
272
|
+
|
|
273
|
+
self.delete()
|
|
274
|
+
|
|
275
|
+
def delete(self):
|
|
276
|
+
"""
|
|
277
|
+
Explicit destructor of the internal hitting set enumerator and
|
|
278
|
+
the SAT oracles.
|
|
279
|
+
"""
|
|
280
|
+
|
|
281
|
+
self.calls = 0
|
|
282
|
+
|
|
283
|
+
if self.hitman:
|
|
284
|
+
self.hitman.delete()
|
|
285
|
+
self.hitman = None
|
|
286
|
+
|
|
287
|
+
if self.checker:
|
|
288
|
+
self.checker.delete()
|
|
289
|
+
self.checker = None
|
|
290
|
+
|
|
291
|
+
if self.reducer:
|
|
292
|
+
self.reducer.delete()
|
|
293
|
+
self.reducer = None
|
|
294
|
+
|
|
295
|
+
def init_oracles(self):
|
|
296
|
+
"""
|
|
297
|
+
Encodes the formula in dual-rail representation and initialises
|
|
298
|
+
the hitting set enumerator as well as the two additional SAT
|
|
299
|
+
oracles.
|
|
300
|
+
|
|
301
|
+
In particular, this method initialises the hitting set enumerator
|
|
302
|
+
with the dual-rail clauses :math:`(\\neg{p_i} \\vee \\neg{n_i})`
|
|
303
|
+
for each variable :math:`v_i` of the formula. Additionally, the
|
|
304
|
+
two SAT oracles (*prime checker* and *dual reducer*) are fed the
|
|
305
|
+
input formula and its negation, or the other way around (depending
|
|
306
|
+
on whether the user aims at enumerating implicates of implicants).
|
|
307
|
+
|
|
308
|
+
Given the above, the method necessarily creates a clausal
|
|
309
|
+
representation of both the original formula and its negation,
|
|
310
|
+
"""
|
|
311
|
+
|
|
312
|
+
# creating the negated formula
|
|
313
|
+
if self.fneg is None:
|
|
314
|
+
self.fneg = Neg(self.form)
|
|
315
|
+
|
|
316
|
+
# initialising the checker and reducer oracles
|
|
317
|
+
if self.mode is True:
|
|
318
|
+
self.checker.append_formula(self.form)
|
|
319
|
+
self.reducer.append_formula(self.fneg)
|
|
320
|
+
else:
|
|
321
|
+
self.checker.append_formula(self.fneg)
|
|
322
|
+
self.reducer.append_formula(self.form)
|
|
323
|
+
|
|
324
|
+
# list of original literals
|
|
325
|
+
self.lits = []
|
|
326
|
+
|
|
327
|
+
# initialising hitman, with all the dual-rail literals
|
|
328
|
+
self.dpool = IDPool()
|
|
329
|
+
for a in self.form.atoms():
|
|
330
|
+
if type(a) is int:
|
|
331
|
+
self.lits.append(+a)
|
|
332
|
+
self.lits.append(-a)
|
|
333
|
+
self.hitman.block([self.dpool.id(+a), self.dpool.id(-a)])
|
|
334
|
+
else:
|
|
335
|
+
self.lits.append(+a.name)
|
|
336
|
+
self.lits.append(-a.name)
|
|
337
|
+
self.hitman.block([self.dpool.id(a.name), self.dpool.id(-a.name)])
|
|
338
|
+
|
|
339
|
+
# converting the list of original literals into a set; it is
|
|
340
|
+
# to be used to filter out auxiliary variables from the model
|
|
341
|
+
self.lits = set(self.lits)
|
|
342
|
+
|
|
343
|
+
def compute(self):
|
|
344
|
+
"""
|
|
345
|
+
Computes a single prime. Performs as many iterations of the
|
|
346
|
+
Primer-B algorithm as required to get the next prime. This often
|
|
347
|
+
involves the computation of the dual cover of the formula.
|
|
348
|
+
|
|
349
|
+
Returns a list of literals included in the result prime.
|
|
350
|
+
|
|
351
|
+
:rtype: list(int)
|
|
352
|
+
"""
|
|
353
|
+
|
|
354
|
+
while True:
|
|
355
|
+
# computing a new candidate prime
|
|
356
|
+
hs = self.hitman.get()
|
|
357
|
+
|
|
358
|
+
if hs is None:
|
|
359
|
+
# no more hitting sets exist
|
|
360
|
+
break
|
|
361
|
+
|
|
362
|
+
# mapping dual-rail variables to original variables
|
|
363
|
+
prime = [self.dpool.obj(l) for l in hs]
|
|
364
|
+
|
|
365
|
+
self.calls += 1
|
|
366
|
+
if not self.checker.solve(assumptions=[(1 - 2 * self.mode) * l for l in prime]):
|
|
367
|
+
if self.verbose > 2:
|
|
368
|
+
print('c trgt: {0} 0 ✓'.format(' '.join(['{0}{1}'.format('+' if v > 0 else '', v) for v in prime])))
|
|
369
|
+
|
|
370
|
+
# this is a target prime: we block it and return
|
|
371
|
+
self.hitman.block(hs)
|
|
372
|
+
return prime
|
|
373
|
+
else:
|
|
374
|
+
if self.verbose > 2:
|
|
375
|
+
print('c trgt: {0} 0 ✗'.format(' '.join(['{0}{1}'.format('+' if v > 0 else '', v) for v in prime])))
|
|
376
|
+
|
|
377
|
+
# candidate is not a target prime, so we
|
|
378
|
+
# need to extract and hit a dual prime
|
|
379
|
+
|
|
380
|
+
# filtering the model so that it contains no auxiliary variables
|
|
381
|
+
model = [l for l in self.checker.get_model() if l in self.lits]
|
|
382
|
+
|
|
383
|
+
# model minimisation
|
|
384
|
+
dual = [(2 * self.mode - 1) * l for l in self.minimise_dual(model)]
|
|
385
|
+
self.hitman.hit([self.dpool.id(lit) for lit in dual])
|
|
386
|
+
|
|
387
|
+
if self.verbose > 2:
|
|
388
|
+
print('c dual: {0} 0'.format(' '.join(['{0}{1}'.format('+' if v > 0 else '', v) for v in dual])))
|
|
389
|
+
|
|
390
|
+
def minimise_dual(self, core):
|
|
391
|
+
"""
|
|
392
|
+
Reduces a *dual* prime from the model of the checker oracle. The
|
|
393
|
+
procedure is initialised with the over-approximation of a prime
|
|
394
|
+
and builds on simple MUS extraction. Hence the name of the input
|
|
395
|
+
parameter to start from: `core`. The result of this method is a
|
|
396
|
+
dual prime.
|
|
397
|
+
|
|
398
|
+
The method traverses the dual to reduce either in the linear
|
|
399
|
+
fashion or runs dichotomic QuickXPlain-like literal traversal.
|
|
400
|
+
This is controlled by the input parameter ``search`` passed to the
|
|
401
|
+
constructor of :class:`Primer`.
|
|
402
|
+
|
|
403
|
+
:rtype: list(int)
|
|
404
|
+
|
|
405
|
+
"""
|
|
406
|
+
|
|
407
|
+
def _do_linear(core):
|
|
408
|
+
"""
|
|
409
|
+
Do linear search.
|
|
410
|
+
"""
|
|
411
|
+
|
|
412
|
+
def _assump_needed(a):
|
|
413
|
+
if len(to_test) > 1:
|
|
414
|
+
to_test.remove(a)
|
|
415
|
+
self.calls += 1
|
|
416
|
+
if not self.reducer.solve(assumptions=to_test):
|
|
417
|
+
return False
|
|
418
|
+
to_test.add(a)
|
|
419
|
+
return True
|
|
420
|
+
else:
|
|
421
|
+
return True
|
|
422
|
+
|
|
423
|
+
to_test = set(core)
|
|
424
|
+
return list(filter(lambda a: _assump_needed(a), core))
|
|
425
|
+
|
|
426
|
+
def _do_binary(core):
|
|
427
|
+
"""
|
|
428
|
+
Do dichotomic search similar to QuickXPlain.
|
|
429
|
+
"""
|
|
430
|
+
|
|
431
|
+
wset = core[:]
|
|
432
|
+
filt_sz = len(wset) / 2.0
|
|
433
|
+
while filt_sz >= 1:
|
|
434
|
+
i = 0
|
|
435
|
+
while i < len(wset):
|
|
436
|
+
to_test = wset[:i] + wset[(i + int(filt_sz)):]
|
|
437
|
+
# actual binary hypotheses to test
|
|
438
|
+
self.calls += 1
|
|
439
|
+
if to_test and not self.reducer.solve(assumptions=to_test):
|
|
440
|
+
# assumps are not needed
|
|
441
|
+
wset = to_test
|
|
442
|
+
else:
|
|
443
|
+
# assumps are needed => check the next chunk
|
|
444
|
+
i += int(filt_sz)
|
|
445
|
+
# decreasing size of the set to filter
|
|
446
|
+
filt_sz /= 2.0
|
|
447
|
+
if filt_sz > len(wset) / 2.0:
|
|
448
|
+
# next size is too large => make it smaller
|
|
449
|
+
filt_sz = len(wset) / 2.0
|
|
450
|
+
return wset
|
|
451
|
+
|
|
452
|
+
if self.search == 'bin':
|
|
453
|
+
dual = _do_binary(core)
|
|
454
|
+
else: # by default, linear MUS extraction is used
|
|
455
|
+
dual = _do_linear(core)
|
|
456
|
+
|
|
457
|
+
return dual
|
|
458
|
+
|
|
459
|
+
def enumerate(self):
|
|
460
|
+
"""
|
|
461
|
+
This is generator method iterating through primes and enumerating
|
|
462
|
+
them until the formula has no more primes, or a user decides to
|
|
463
|
+
stop the process (this is controlled from the outside).
|
|
464
|
+
|
|
465
|
+
:rtype: list(int)
|
|
466
|
+
"""
|
|
467
|
+
|
|
468
|
+
done = False
|
|
469
|
+
|
|
470
|
+
while not done:
|
|
471
|
+
prime = self.compute()
|
|
472
|
+
|
|
473
|
+
if prime is not None:
|
|
474
|
+
yield prime
|
|
475
|
+
else:
|
|
476
|
+
done = True
|
|
477
|
+
|
|
478
|
+
def oracle_time(self):
|
|
479
|
+
"""
|
|
480
|
+
This method computes and returns the total SAT solving time
|
|
481
|
+
involved, including the time spent by the hitting set enumerator
|
|
482
|
+
and the two SAT oracles.
|
|
483
|
+
|
|
484
|
+
:rtype: float
|
|
485
|
+
"""
|
|
486
|
+
|
|
487
|
+
return self.hitman.oracle_time() + self.checker.time_accum() + self.reducer.time_accum()
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
#
|
|
491
|
+
#==============================================================================
|
|
492
|
+
def parse_options():
|
|
493
|
+
"""
|
|
494
|
+
Parses command-line options:
|
|
495
|
+
"""
|
|
496
|
+
|
|
497
|
+
try:
|
|
498
|
+
opts, args = getopt.getopt(sys.argv[1:], 'ade:himp:r:s:t:uvx',
|
|
499
|
+
['adapt', 'dcalls', 'enum=', 'help',
|
|
500
|
+
'implicants', 'minimize', 'puresat=',
|
|
501
|
+
'reduce=', 'solver=', 'trim=', 'unsorted',
|
|
502
|
+
'verbose', 'exhaust'])
|
|
503
|
+
except getopt.GetoptError as err:
|
|
504
|
+
sys.stderr.write(str(err).capitalize())
|
|
505
|
+
usage()
|
|
506
|
+
sys.exit(1)
|
|
507
|
+
|
|
508
|
+
to_enum = 1
|
|
509
|
+
mode = 1 # 1 = implicates, 0 = implicants
|
|
510
|
+
adapt = False
|
|
511
|
+
dcalls = False
|
|
512
|
+
exhaust = False
|
|
513
|
+
minz = False
|
|
514
|
+
search = 'lin'
|
|
515
|
+
solver = 'cd19'
|
|
516
|
+
puresat = False
|
|
517
|
+
unsorted = False
|
|
518
|
+
trim = 0
|
|
519
|
+
verbose = 1
|
|
520
|
+
|
|
521
|
+
for opt, arg in opts:
|
|
522
|
+
if opt in ('-a', '--adapt'):
|
|
523
|
+
adapt = True
|
|
524
|
+
elif opt in ('-d', '--dcalls'):
|
|
525
|
+
dcalls = True
|
|
526
|
+
elif opt in ('-e', '--enum'):
|
|
527
|
+
to_enum = str(arg)
|
|
528
|
+
if to_enum != 'all':
|
|
529
|
+
to_enum = int(to_enum)
|
|
530
|
+
elif opt in ('-h', '--help'):
|
|
531
|
+
usage()
|
|
532
|
+
sys.exit(0)
|
|
533
|
+
elif opt in ('-i', '--implicants'):
|
|
534
|
+
mode = 0
|
|
535
|
+
elif opt in ('-m', '--minimize'):
|
|
536
|
+
minz = True
|
|
537
|
+
elif opt in ('-p', '--puresat'):
|
|
538
|
+
puresat = str(arg)
|
|
539
|
+
elif opt in ('-r', '--reduce'):
|
|
540
|
+
search = str(arg)
|
|
541
|
+
assert search in ('lin', 'bin'), 'Wrong minimisation method: {0}'.format(search)
|
|
542
|
+
elif opt in ('-s', '--solver'):
|
|
543
|
+
solver = str(arg)
|
|
544
|
+
elif opt in ('-u', '--unsorted'):
|
|
545
|
+
unsorted = True
|
|
546
|
+
elif opt in ('-t', '--trim'):
|
|
547
|
+
trim = int(arg)
|
|
548
|
+
elif opt in ('-v', '--verbose'):
|
|
549
|
+
verbose += 1
|
|
550
|
+
elif opt in ('-x', '--exhaust'):
|
|
551
|
+
exhaust = True
|
|
552
|
+
else:
|
|
553
|
+
assert False, 'Unhandled option: {0} {1}'.format(opt, arg)
|
|
554
|
+
|
|
555
|
+
return to_enum, mode, adapt, dcalls, exhaust, minz, trim, \
|
|
556
|
+
search, solver, puresat, unsorted, verbose, args
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
#
|
|
560
|
+
#==============================================================================
|
|
561
|
+
def usage():
|
|
562
|
+
"""
|
|
563
|
+
Prints usage message.
|
|
564
|
+
"""
|
|
565
|
+
|
|
566
|
+
print('Usage:', os.path.basename(sys.argv[0]), '[options]')
|
|
567
|
+
print('Options:')
|
|
568
|
+
print(' -a, --adapt Try to adapt (simplify) input formula')
|
|
569
|
+
print(' -d, --dcalls Apply clause D calls (in unsorted enumeration only)')
|
|
570
|
+
print(' -e, --enum=<int> Enumerate this many primes')
|
|
571
|
+
print(' Available values: [1 .. INT_MAX], all (default = 1)')
|
|
572
|
+
print(' -h, --help Print this help message')
|
|
573
|
+
print(' -i, --implicants Target prime implicants instead of implicates')
|
|
574
|
+
print(' -m, --minimize Use a heuristic unsatisfiable core minimizer')
|
|
575
|
+
print(' -p, --puresat=<string> Use a pure SAT-based hitting set enumerator')
|
|
576
|
+
print(' Available values: cd15, cd19, lgl, mgh (default = mgh)')
|
|
577
|
+
print(' Requires: unsorted mode, i.e. \'-u\'')
|
|
578
|
+
print(' -r, --reduce Dual prime minimiser')
|
|
579
|
+
print(' Available values: lin, bin (default = lin)')
|
|
580
|
+
print(' -s, --solver SAT solver to use')
|
|
581
|
+
print(' Available values: cd, cd15, cd19, g3, g41, g42, lgl, mcb, mcm, mpl, m22, mc, mg3, mgh (default = cd19)')
|
|
582
|
+
print(' -t, --trim=<int> How many times to trim unsatisfiable cores')
|
|
583
|
+
print(' Available values: [0 .. INT_MAX] (default = 0)')
|
|
584
|
+
print(' -u, --unsorted Enumerate MUSes in an unsorted way using LBX')
|
|
585
|
+
print(' -v, --verbose Be verbose')
|
|
586
|
+
print(' -x, --exhaust Exhaust new unsatisfiable cores')
|
|
587
|
+
|
|
588
|
+
#
|
|
589
|
+
#==============================================================================
|
|
590
|
+
if __name__ == '__main__':
|
|
591
|
+
# parse command-line options
|
|
592
|
+
to_enum, mode, adapt, dcalls, exhaust, minz, trim, search, solver, \
|
|
593
|
+
puresat, unsorted, verbose, files = parse_options()
|
|
594
|
+
|
|
595
|
+
if files:
|
|
596
|
+
# read CNF from file
|
|
597
|
+
assert re.search(r'cnf(\.(gz|bz2|lzma|xz|zst))?$', files[0]), 'Unknown input file extension'
|
|
598
|
+
formula = CNF(from_file=files[0])
|
|
599
|
+
|
|
600
|
+
# creating an object of Primer
|
|
601
|
+
with Primer(formula, negated=None, solver=solver, implicates=mode,
|
|
602
|
+
adapt=adapt, dcalls=dcalls, exhaust=exhaust, minz=minz,
|
|
603
|
+
puresat=puresat, search=search, unsorted=unsorted,
|
|
604
|
+
trim=trim, verbose=verbose) as primer:
|
|
605
|
+
|
|
606
|
+
# iterating over the necessary number of primes
|
|
607
|
+
for i, prime in enumerate(primer.enumerate()):
|
|
608
|
+
# reporting the current solution
|
|
609
|
+
if verbose:
|
|
610
|
+
print('v {0} 0'.format(' '.join(['{0}{1}'.format('+' if v > 0 else '', v) for v in prime])))
|
|
611
|
+
|
|
612
|
+
# checking if we are done
|
|
613
|
+
if to_enum and i + 1 == to_enum:
|
|
614
|
+
break
|
|
615
|
+
|
|
616
|
+
# reporting the total oracle time
|
|
617
|
+
if verbose > 1:
|
|
618
|
+
print('c primes: {0}'.format(i + 1))
|
|
619
|
+
print('c oracle time: {0:.4f}'.format(primer.oracle_time()))
|
|
620
|
+
print('c oracle calls: {0}'.format(primer.calls))
|