python-sat 1.8.dev25__cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.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.cpython-314-x86_64-linux-gnu.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-314-x86_64-linux-gnu.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
pysat/examples/fm.py
ADDED
|
@@ -0,0 +1,527 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
#-*- coding:utf-8 -*-
|
|
3
|
+
##
|
|
4
|
+
## fm.py
|
|
5
|
+
##
|
|
6
|
+
## Created on: Feb 5, 2018
|
|
7
|
+
## Author: Antonio Morgado, Alexey Ignatiev
|
|
8
|
+
## E-mail: {ajmorgado, aignatiev}@ciencias.ulisboa.pt
|
|
9
|
+
##
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
===============
|
|
13
|
+
List of classes
|
|
14
|
+
===============
|
|
15
|
+
|
|
16
|
+
.. autosummary::
|
|
17
|
+
:nosignatures:
|
|
18
|
+
|
|
19
|
+
FM
|
|
20
|
+
|
|
21
|
+
==================
|
|
22
|
+
Module description
|
|
23
|
+
==================
|
|
24
|
+
|
|
25
|
+
This module implements a variant of the seminal core-guided MaxSAT
|
|
26
|
+
algorithm originally proposed by [1]_ and then improved and modified
|
|
27
|
+
further in [2]_ [3]_ [4]_ [5]_. Namely, the implementation follows the
|
|
28
|
+
WMSU1 variant [5]_ of the algorithm extended to the case of *weighted
|
|
29
|
+
partial* formulas.
|
|
30
|
+
|
|
31
|
+
.. [1] Zhaohui Fu, Sharad Malik. *On Solving the Partial MAX-SAT Problem*.
|
|
32
|
+
SAT 2006. pp. 252-265
|
|
33
|
+
|
|
34
|
+
.. [2] Joao Marques-Silva, Jordi Planes. *On Using Unsatisfiability for
|
|
35
|
+
Solving Maximum Satisfiability*. CoRR abs/0712.1097. 2007
|
|
36
|
+
|
|
37
|
+
.. [3] Joao Marques-Silva, Vasco M. Manquinho. *Towards More Effective
|
|
38
|
+
Unsatisfiability-Based Maximum Satisfiability Algorithms*. SAT 2008.
|
|
39
|
+
pp. 225-230
|
|
40
|
+
|
|
41
|
+
.. [4] Carlos Ansótegui, Maria Luisa Bonet, Jordi Levy. *Solving
|
|
42
|
+
(Weighted) Partial MaxSAT through Satisfiability Testing*. SAT 2009.
|
|
43
|
+
pp. 427-440
|
|
44
|
+
|
|
45
|
+
.. [5] Vasco M. Manquinho, Joao Marques Silva, Jordi Planes. *Algorithms
|
|
46
|
+
for Weighted Boolean Optimization*. SAT 2009. pp. 495-508
|
|
47
|
+
|
|
48
|
+
The implementation can be used as an executable (the list of available
|
|
49
|
+
command-line options can be shown using ``fm.py -h``) in the following way:
|
|
50
|
+
|
|
51
|
+
::
|
|
52
|
+
|
|
53
|
+
$ xzcat formula.wcnf.xz
|
|
54
|
+
p wcnf 3 6 4
|
|
55
|
+
1 1 0
|
|
56
|
+
1 2 0
|
|
57
|
+
1 3 0
|
|
58
|
+
4 -1 -2 0
|
|
59
|
+
4 -1 -3 0
|
|
60
|
+
4 -2 -3 0
|
|
61
|
+
|
|
62
|
+
$ fm.py -c cardn -s glucose3 -vv formula.wcnf.xz
|
|
63
|
+
c cost: 1; core sz: 2
|
|
64
|
+
c cost: 2; core sz: 3
|
|
65
|
+
s OPTIMUM FOUND
|
|
66
|
+
o 2
|
|
67
|
+
v -1 -2 3 0
|
|
68
|
+
c oracle time: 0.0001
|
|
69
|
+
|
|
70
|
+
Alternatively, the algorithm can be accessed and invoked through the
|
|
71
|
+
standard ``import`` interface of Python, e.g.
|
|
72
|
+
|
|
73
|
+
.. code-block:: python
|
|
74
|
+
|
|
75
|
+
>>> from pysat.examples.fm import FM
|
|
76
|
+
>>> from pysat.formula import WCNF
|
|
77
|
+
>>>
|
|
78
|
+
>>> wcnf = WCNF(from_file='formula.wcnf.xz')
|
|
79
|
+
>>>
|
|
80
|
+
>>> fm = FM(wcnf, verbose=0)
|
|
81
|
+
>>> fm.compute() # set of hard clauses should be satisfiable
|
|
82
|
+
True
|
|
83
|
+
>>> print(fm.cost) # cost of MaxSAT solution should be 2
|
|
84
|
+
>>> 2
|
|
85
|
+
>>> print(fm.model)
|
|
86
|
+
[-1, -2, 3]
|
|
87
|
+
|
|
88
|
+
==============
|
|
89
|
+
Module details
|
|
90
|
+
==============
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
#
|
|
94
|
+
#==============================================================================
|
|
95
|
+
from __future__ import print_function
|
|
96
|
+
import copy
|
|
97
|
+
import getopt
|
|
98
|
+
import gzip
|
|
99
|
+
import os
|
|
100
|
+
from pysat.formula import CNFPlus, WCNFPlus
|
|
101
|
+
from pysat.card import CardEnc, EncType
|
|
102
|
+
from pysat.solvers import Solver, SolverNames
|
|
103
|
+
import re
|
|
104
|
+
from six.moves import range
|
|
105
|
+
import sys
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# cardinality encodings
|
|
109
|
+
#==============================================================================
|
|
110
|
+
encmap = {
|
|
111
|
+
'pw': EncType.pairwise,
|
|
112
|
+
'bw': EncType.bitwise,
|
|
113
|
+
'seqc': EncType.seqcounter,
|
|
114
|
+
'cardn': EncType.cardnetwrk,
|
|
115
|
+
'sortn': EncType.sortnetwrk,
|
|
116
|
+
'ladder': EncType.ladder,
|
|
117
|
+
'tot': EncType.totalizer,
|
|
118
|
+
'mtot': EncType.mtotalizer,
|
|
119
|
+
'kmtot': EncType.kmtotalizer,
|
|
120
|
+
'native': EncType.native
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
#
|
|
125
|
+
#==============================================================================
|
|
126
|
+
class FM(object):
|
|
127
|
+
"""
|
|
128
|
+
A non-incremental implementation of the FM (Fu&Malik, or WMSU1)
|
|
129
|
+
algorithm. The algorithm (see details in [5]_) is *core-guided*, i.e.
|
|
130
|
+
it solves maximum satisfiability with a series of unsatisfiability
|
|
131
|
+
oracle calls, each producing an unsatisfiable core. The clauses
|
|
132
|
+
involved in an unsatisfiable core are *relaxed* and a new
|
|
133
|
+
:math:`\\textsf{AtMost1}` constraint on the corresponding *relaxation
|
|
134
|
+
variables* is added to the formula. The process gets a bit more
|
|
135
|
+
sophisticated in the case of weighted formulas because of the *clause
|
|
136
|
+
weight splitting* technique.
|
|
137
|
+
|
|
138
|
+
The constructor of :class:`FM` objects receives a target :class:`.WCNF`
|
|
139
|
+
MaxSAT formula, an identifier of the cardinality encoding to use, a SAT
|
|
140
|
+
solver name, and a verbosity level. Note that the algorithm uses the
|
|
141
|
+
``pairwise`` (see :class:`.card.EncType`) cardinality encoding by
|
|
142
|
+
default, while the default SAT solver is MiniSat22 (referred to as
|
|
143
|
+
``'m22'``, see :class:`.SolverNames` for details). The default
|
|
144
|
+
verbosity level is ``1``.
|
|
145
|
+
|
|
146
|
+
:param formula: input MaxSAT formula
|
|
147
|
+
:param enc: cardinality encoding to use
|
|
148
|
+
:param solver: name of SAT solver
|
|
149
|
+
:param verbose: verbosity level
|
|
150
|
+
|
|
151
|
+
:type formula: :class:`.WCNF`
|
|
152
|
+
:type enc: int
|
|
153
|
+
:type solver: str
|
|
154
|
+
:type verbose: int
|
|
155
|
+
"""
|
|
156
|
+
|
|
157
|
+
def __init__(self, formula, enc=EncType.pairwise, solver='m22', verbose=1):
|
|
158
|
+
"""
|
|
159
|
+
Constructor.
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
# saving verbosity level
|
|
163
|
+
self.verbose = verbose
|
|
164
|
+
self.solver = solver
|
|
165
|
+
self.time = 0.0
|
|
166
|
+
|
|
167
|
+
# MaxSAT related stuff
|
|
168
|
+
self.topv = self.orig_nv = formula.nv
|
|
169
|
+
self.hard = copy.deepcopy(formula.hard)
|
|
170
|
+
self.soft = copy.deepcopy(formula.soft)
|
|
171
|
+
self.wght = formula.wght[:]
|
|
172
|
+
self.cenc = enc
|
|
173
|
+
self.cost = 0
|
|
174
|
+
|
|
175
|
+
if isinstance(formula, WCNFPlus) and formula.atms:
|
|
176
|
+
self.atm1 = copy.deepcopy(formula.atms)
|
|
177
|
+
else:
|
|
178
|
+
self.atm1 = None
|
|
179
|
+
|
|
180
|
+
# initialize SAT oracle with hard clauses only
|
|
181
|
+
self.init(with_soft=False)
|
|
182
|
+
|
|
183
|
+
def __enter__(self):
|
|
184
|
+
"""
|
|
185
|
+
'with' constructor.
|
|
186
|
+
"""
|
|
187
|
+
|
|
188
|
+
return self
|
|
189
|
+
|
|
190
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
191
|
+
"""
|
|
192
|
+
'with' destructor.
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
self.delete()
|
|
196
|
+
|
|
197
|
+
def init(self, with_soft=True):
|
|
198
|
+
"""
|
|
199
|
+
The method for the SAT oracle initialization. Since the oracle is
|
|
200
|
+
is used non-incrementally, it is reinitialized at every iteration
|
|
201
|
+
of the MaxSAT algorithm (see :func:`reinit`). An input parameter
|
|
202
|
+
``with_soft`` (``False`` by default) regulates whether or not the
|
|
203
|
+
formula's soft clauses are copied to the oracle.
|
|
204
|
+
|
|
205
|
+
:param with_soft: copy formula's soft clauses to the oracle or not
|
|
206
|
+
:type with_soft: bool
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
self.oracle = Solver(name=self.solver, bootstrap_with=self.hard, use_timer=True)
|
|
210
|
+
|
|
211
|
+
if self.atm1: # this check is needed at the beggining (before iteration 1)
|
|
212
|
+
# we are using CaDiCaL195 and it can use external linear engine
|
|
213
|
+
if self.solver in SolverNames.cadical195:
|
|
214
|
+
self.oracle.activate_atmost()
|
|
215
|
+
|
|
216
|
+
assert self.oracle.supports_atmost(), \
|
|
217
|
+
'{0} does not support native cardinality constraints. Make sure you use the right type of formula.'.format(solver_name)
|
|
218
|
+
|
|
219
|
+
# self.atm1 is not empty only in case of minicard
|
|
220
|
+
for am in self.atm1:
|
|
221
|
+
self.oracle.add_atmost(*am)
|
|
222
|
+
|
|
223
|
+
if with_soft:
|
|
224
|
+
for cl, cpy in zip(self.soft, self.scpy):
|
|
225
|
+
if cpy:
|
|
226
|
+
self.oracle.add_clause(cl)
|
|
227
|
+
|
|
228
|
+
def delete(self):
|
|
229
|
+
"""
|
|
230
|
+
Explicit destructor of the internal SAT oracle.
|
|
231
|
+
"""
|
|
232
|
+
|
|
233
|
+
if self.oracle:
|
|
234
|
+
self.time += self.oracle.time_accum() # keep SAT solving time
|
|
235
|
+
|
|
236
|
+
self.oracle.delete()
|
|
237
|
+
self.oracle = None
|
|
238
|
+
|
|
239
|
+
def reinit(self):
|
|
240
|
+
"""
|
|
241
|
+
This method calls :func:`delete` and :func:`init` to reinitialize
|
|
242
|
+
the internal SAT oracle. This is done at every iteration of the
|
|
243
|
+
MaxSAT algorithm.
|
|
244
|
+
"""
|
|
245
|
+
|
|
246
|
+
self.delete()
|
|
247
|
+
self.init();
|
|
248
|
+
|
|
249
|
+
def compute(self):
|
|
250
|
+
"""
|
|
251
|
+
Compute a MaxSAT solution. First, the method checks whether or
|
|
252
|
+
not the set of hard clauses is satisfiable. If not, the method
|
|
253
|
+
returns ``False``. Otherwise, add soft clauses to the oracle and
|
|
254
|
+
call the MaxSAT algorithm (see :func:`_compute`).
|
|
255
|
+
|
|
256
|
+
Note that the soft clauses are added to the oracles after being
|
|
257
|
+
augmented with additional *selector* literals. The selectors
|
|
258
|
+
literals are then used as *assumptions* when calling the SAT oracle
|
|
259
|
+
and are needed for extracting unsatisfiable cores.
|
|
260
|
+
"""
|
|
261
|
+
|
|
262
|
+
if self.oracle.solve():
|
|
263
|
+
# hard part is satisfiable
|
|
264
|
+
# create selectors and a mapping from selectors to clause ids
|
|
265
|
+
self.sels, self.vmap = [], {}
|
|
266
|
+
self.scpy = [True for cl in self.soft]
|
|
267
|
+
|
|
268
|
+
# adding soft clauses to oracle
|
|
269
|
+
for i in range(len(self.soft)):
|
|
270
|
+
self.topv += 1
|
|
271
|
+
|
|
272
|
+
self.soft[i].append(-self.topv)
|
|
273
|
+
self.sels.append(self.topv)
|
|
274
|
+
self.oracle.add_clause(self.soft[i])
|
|
275
|
+
|
|
276
|
+
self.vmap[self.topv] = i
|
|
277
|
+
|
|
278
|
+
self._compute()
|
|
279
|
+
return True
|
|
280
|
+
else:
|
|
281
|
+
return False
|
|
282
|
+
|
|
283
|
+
def _compute(self):
|
|
284
|
+
"""
|
|
285
|
+
This method implements WMSU1 algorithm. The method is essentially a
|
|
286
|
+
loop, which at each iteration calls the SAT oracle to decide
|
|
287
|
+
whether the working formula is satisfiable. If it is, the method
|
|
288
|
+
derives a model (stored in variable ``self.model``) and returns.
|
|
289
|
+
Otherwise, a new unsatisfiable core of the formula is extracted
|
|
290
|
+
and processed (see :func:`treat_core`), and the algorithm proceeds.
|
|
291
|
+
"""
|
|
292
|
+
|
|
293
|
+
while True:
|
|
294
|
+
if self.oracle.solve(assumptions=self.sels):
|
|
295
|
+
self.model = [l for l in self.oracle.get_model() if abs(l) <= self.orig_nv]
|
|
296
|
+
return
|
|
297
|
+
else:
|
|
298
|
+
self.treat_core()
|
|
299
|
+
|
|
300
|
+
if self.verbose > 1:
|
|
301
|
+
print('c cost: {0}; core sz: {1}'.format(self.cost, len(self.core)))
|
|
302
|
+
|
|
303
|
+
self.reinit()
|
|
304
|
+
|
|
305
|
+
def treat_core(self):
|
|
306
|
+
"""
|
|
307
|
+
Now that the previous SAT call returned UNSAT, a new unsatisfiable
|
|
308
|
+
core should be extracted and relaxed. Core extraction is done
|
|
309
|
+
through a call to the :func:`pysat.solvers.Solver.get_core` method,
|
|
310
|
+
which returns a subset of the selector literals deemed responsible
|
|
311
|
+
for unsatisfiability.
|
|
312
|
+
|
|
313
|
+
After the core is extracted, its *minimum weight* ``minw`` is
|
|
314
|
+
computed, i.e. it is the minimum weight among the weights of all
|
|
315
|
+
soft clauses involved in the core (see [5]_). Note that the cost of
|
|
316
|
+
the MaxSAT solution is incremented by ``minw``.
|
|
317
|
+
|
|
318
|
+
Clauses that have weight larger than ``minw`` are split (see
|
|
319
|
+
:func:`split_core`). Afterwards, all clauses of the unsatisfiable
|
|
320
|
+
core are relaxed (see :func:`relax_core`).
|
|
321
|
+
"""
|
|
322
|
+
|
|
323
|
+
# extracting the core
|
|
324
|
+
self.core = [self.vmap[sel] for sel in self.oracle.get_core()]
|
|
325
|
+
minw = min(map(lambda i: self.wght[i], self.core))
|
|
326
|
+
|
|
327
|
+
# updating the cost
|
|
328
|
+
self.cost += minw
|
|
329
|
+
|
|
330
|
+
# splitting clauses in the core if necessary
|
|
331
|
+
self.split_core(minw)
|
|
332
|
+
|
|
333
|
+
# relaxing clauses in the core and adding a new atmost1 constraint
|
|
334
|
+
self.relax_core()
|
|
335
|
+
|
|
336
|
+
def split_core(self, minw):
|
|
337
|
+
"""
|
|
338
|
+
Split clauses in the core whenever necessary.
|
|
339
|
+
|
|
340
|
+
Given a list of soft clauses in an unsatisfiable core, the method
|
|
341
|
+
is used for splitting clauses whose weights are greater than the
|
|
342
|
+
minimum weight of the core, i.e. the ``minw`` value computed in
|
|
343
|
+
:func:`treat_core`. Each clause :math:`(c\\vee\\neg{s},w)`, s.t.
|
|
344
|
+
:math:`w>minw` and :math:`s` is its selector literal, is split into
|
|
345
|
+
clauses (1) clause :math:`(c\\vee\\neg{s}, minw)` and (2) a
|
|
346
|
+
residual clause :math:`(c\\vee\\neg{s}',w-minw)`. Note that the
|
|
347
|
+
residual clause has a fresh selector literal :math:`s'` different
|
|
348
|
+
from :math:`s`.
|
|
349
|
+
|
|
350
|
+
:param minw: minimum weight of the core
|
|
351
|
+
:type minw: int
|
|
352
|
+
"""
|
|
353
|
+
|
|
354
|
+
for clid in self.core:
|
|
355
|
+
sel = self.sels[clid]
|
|
356
|
+
|
|
357
|
+
if self.wght[clid] > minw:
|
|
358
|
+
self.topv += 1
|
|
359
|
+
|
|
360
|
+
cl_new = []
|
|
361
|
+
for l in self.soft[clid]:
|
|
362
|
+
if l != -sel:
|
|
363
|
+
cl_new.append(l)
|
|
364
|
+
else:
|
|
365
|
+
cl_new.append(-self.topv)
|
|
366
|
+
|
|
367
|
+
self.sels.append(self.topv)
|
|
368
|
+
self.vmap[self.topv] = len(self.soft)
|
|
369
|
+
|
|
370
|
+
self.soft.append(cl_new)
|
|
371
|
+
self.wght.append(self.wght[clid] - minw)
|
|
372
|
+
self.wght[clid] = minw
|
|
373
|
+
|
|
374
|
+
self.scpy.append(True)
|
|
375
|
+
|
|
376
|
+
def relax_core(self):
|
|
377
|
+
"""
|
|
378
|
+
Relax and bound the core.
|
|
379
|
+
|
|
380
|
+
After unsatisfiable core splitting, this method is called. If the
|
|
381
|
+
core contains only one clause, i.e. this clause cannot be satisfied
|
|
382
|
+
together with the hard clauses of the formula, the formula gets
|
|
383
|
+
augmented with the negation of the clause (see
|
|
384
|
+
:func:`remove_unit_core`).
|
|
385
|
+
|
|
386
|
+
Otherwise (if the core contains more than one clause), every clause
|
|
387
|
+
:math:`c` of the core is *relaxed*. This means a new *relaxation
|
|
388
|
+
literal* is added to the clause, i.e. :math:`c\\gets c\\vee r`,
|
|
389
|
+
where :math:`r` is a fresh (unused) relaxation variable. After the
|
|
390
|
+
clauses get relaxed, a new cardinality encoding is added to the
|
|
391
|
+
formula enforcing the sum of the new relaxation variables to be not
|
|
392
|
+
greater than 1, :math:`\\sum_{c\\in\\phi}{r\\leq 1}`, where
|
|
393
|
+
:math:`\\phi` denotes the unsatisfiable core.
|
|
394
|
+
"""
|
|
395
|
+
|
|
396
|
+
if len(self.core) > 1:
|
|
397
|
+
# relaxing
|
|
398
|
+
rels = []
|
|
399
|
+
|
|
400
|
+
for clid in self.core:
|
|
401
|
+
self.topv += 1
|
|
402
|
+
rels.append(self.topv)
|
|
403
|
+
self.soft[clid].append(self.topv)
|
|
404
|
+
|
|
405
|
+
# creating a new cardinality constraint
|
|
406
|
+
am1 = CardEnc.atmost(lits=rels, top_id=self.topv, encoding=self.cenc)
|
|
407
|
+
|
|
408
|
+
for cl in am1.clauses:
|
|
409
|
+
self.hard.append(cl)
|
|
410
|
+
|
|
411
|
+
# only if minicard
|
|
412
|
+
# (for other solvers am1.atmosts should be empty)
|
|
413
|
+
for am in am1.atmosts:
|
|
414
|
+
self.atm1.append(am)
|
|
415
|
+
|
|
416
|
+
self.topv = am1.nv
|
|
417
|
+
|
|
418
|
+
elif len(self.core) == 1: # unit core => simply negate the clause
|
|
419
|
+
self.remove_unit_core()
|
|
420
|
+
|
|
421
|
+
def remove_unit_core(self):
|
|
422
|
+
"""
|
|
423
|
+
If an unsatisfiable core contains only one clause :math:`c`, this
|
|
424
|
+
method is invoked to add a bunch of new unit size hard clauses. As
|
|
425
|
+
a result, the SAT oracle gets unit clauses :math:`(\\neg{l})` for
|
|
426
|
+
all literals :math:`l` in clause :math:`c`.
|
|
427
|
+
"""
|
|
428
|
+
|
|
429
|
+
self.scpy[self.core[0]] = False
|
|
430
|
+
|
|
431
|
+
for l in self.soft[self.core[0]]:
|
|
432
|
+
self.hard.append([-l])
|
|
433
|
+
|
|
434
|
+
def oracle_time(self):
|
|
435
|
+
"""
|
|
436
|
+
Method for calculating and reporting the total SAT solving time.
|
|
437
|
+
"""
|
|
438
|
+
|
|
439
|
+
self.time += self.oracle.time_accum() # include time of the last SAT call
|
|
440
|
+
return self.time
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
#
|
|
444
|
+
#==============================================================================
|
|
445
|
+
def parse_options():
|
|
446
|
+
"""
|
|
447
|
+
Parses command-line option
|
|
448
|
+
"""
|
|
449
|
+
|
|
450
|
+
try:
|
|
451
|
+
opts, args = getopt.getopt(sys.argv[1:], 'hs:c:v', ['help','solver=','cardenc=','verbose'])
|
|
452
|
+
except getopt.GetoptError as err:
|
|
453
|
+
sys.stderr.write(str(err).capitalize())
|
|
454
|
+
usage()
|
|
455
|
+
sys.exit(1)
|
|
456
|
+
|
|
457
|
+
solver = 'm22'
|
|
458
|
+
cardenc = 'seqc'
|
|
459
|
+
verbose = 1
|
|
460
|
+
|
|
461
|
+
for opt, arg in opts:
|
|
462
|
+
if opt in ('-c', '--cardenc'):
|
|
463
|
+
cardenc = str(arg)
|
|
464
|
+
elif opt in ('-h', '--help'):
|
|
465
|
+
usage()
|
|
466
|
+
sys.exit(0)
|
|
467
|
+
elif opt in ('-s', '--solver'):
|
|
468
|
+
solver = str(arg)
|
|
469
|
+
elif opt in ('-v', '--verbose'):
|
|
470
|
+
verbose += 1
|
|
471
|
+
else:
|
|
472
|
+
assert False, 'Unhandled option: {0} {1}'.format(opt, arg)
|
|
473
|
+
|
|
474
|
+
cardenc = encmap[cardenc]
|
|
475
|
+
|
|
476
|
+
# using minicard's native implementation of AtMost1 constraints
|
|
477
|
+
if solver in SolverNames.minicard + SolverNames.gluecard3 + \
|
|
478
|
+
SolverNames.gluecard4 + SolverNames.cadical195:
|
|
479
|
+
cardenc = encmap['native']
|
|
480
|
+
else:
|
|
481
|
+
assert cardenc != encmap['native'], 'Only Minicard can handle cardinality constraints natively'
|
|
482
|
+
|
|
483
|
+
return solver, cardenc, verbose, args
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
#==============================================================================
|
|
487
|
+
def usage():
|
|
488
|
+
"""
|
|
489
|
+
Prints usage message.
|
|
490
|
+
"""
|
|
491
|
+
|
|
492
|
+
print('Usage:', os.path.basename(sys.argv[0]), '[options] dimacs-file')
|
|
493
|
+
print('Options:')
|
|
494
|
+
print(' -c, --cardenc Cardinality encoding to use:')
|
|
495
|
+
print(' Available values: bw, cardn, kmtot, ladder, mtot, pw, seqc, sortn, tot (default = seqc)')
|
|
496
|
+
print(' -h, --help')
|
|
497
|
+
print(' -s, --solver SAT solver to use')
|
|
498
|
+
print(' Available values: cd15, cd19, g3, g4, lgl, mcb, mcm, mpl, m22, mc, mgh (default = m22)')
|
|
499
|
+
print(' -v, --verbose Be verbose')
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
#
|
|
503
|
+
#==============================================================================
|
|
504
|
+
if __name__ == '__main__':
|
|
505
|
+
solver, cardenc, verbose, files = parse_options()
|
|
506
|
+
|
|
507
|
+
if files:
|
|
508
|
+
# parsing the input formula
|
|
509
|
+
if re.search(r'\.wcnf[p|+]?(\.(gz|bz2|lzma|xz|zst))?$', files[0]):
|
|
510
|
+
formula = WCNFPlus(from_file=files[0])
|
|
511
|
+
else: # expecting '*.cnf[,p,+].*'
|
|
512
|
+
formula = CNFPlus(from_file=files[0]).weighted()
|
|
513
|
+
|
|
514
|
+
with FM(formula, solver=solver, enc=cardenc, verbose=verbose) as fm:
|
|
515
|
+
res = fm.compute()
|
|
516
|
+
|
|
517
|
+
if res:
|
|
518
|
+
print('s OPTIMUM FOUND')
|
|
519
|
+
print('o {0}'.format(fm.cost))
|
|
520
|
+
|
|
521
|
+
if verbose > 2:
|
|
522
|
+
print('v', ' '.join([str(l) for l in fm.model]), '0')
|
|
523
|
+
else:
|
|
524
|
+
print('s UNSATISFIABLE')
|
|
525
|
+
|
|
526
|
+
if verbose > 1:
|
|
527
|
+
print('c oracle time: {0:.4f}'.format(fm.oracle_time()))
|