python-sat 1.8.dev25__cp310-cp310-musllinux_1_2_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.
- pycard.cpython-310-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-310-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 +50 -0
- python_sat-1.8.dev25.dist-info/WHEEL +5 -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
- python_sat.libs/libgcc_s-0cd532bd.so.1 +0 -0
- python_sat.libs/libstdc++-5d72f927.so.6.0.33 +0 -0
pysat/examples/hitman.py
ADDED
|
@@ -0,0 +1,653 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
#-*- coding:utf-8 -*-
|
|
3
|
+
##
|
|
4
|
+
## hitman.py
|
|
5
|
+
##
|
|
6
|
+
## A minimum/minimal hitting set enumerator based on MaxSAT solving
|
|
7
|
+
## and also MCS enumeration (LBX- or CLD-like). MaxSAT-based hitting
|
|
8
|
+
## set enumeration computes hitting sets in a sorted manner, e.g. from
|
|
9
|
+
## smallest size to largest size. MCS-based hitting set solver computes
|
|
10
|
+
## arbitrary hitting sets, with no respect to their size.
|
|
11
|
+
##
|
|
12
|
+
## Created on: Aug 23, 2018
|
|
13
|
+
## Author: Alexey S. Ignatiev
|
|
14
|
+
## E-mail: aignatiev@ciencias.ulisboa.pt
|
|
15
|
+
##
|
|
16
|
+
|
|
17
|
+
"""
|
|
18
|
+
===============
|
|
19
|
+
List of classes
|
|
20
|
+
===============
|
|
21
|
+
|
|
22
|
+
.. autosummary::
|
|
23
|
+
:nosignatures:
|
|
24
|
+
|
|
25
|
+
Hitman
|
|
26
|
+
|
|
27
|
+
==================
|
|
28
|
+
Module description
|
|
29
|
+
==================
|
|
30
|
+
|
|
31
|
+
A SAT-based implementation of an implicit minimal hitting set [1]_
|
|
32
|
+
enumerator. The implementation is capable of computing/enumerating
|
|
33
|
+
cardinality- and subset-minimal hitting sets of a given set of sets.
|
|
34
|
+
Cardinality-minimal hitting set enumeration can be seen as ordered (sorted
|
|
35
|
+
by size) subset-minimal hitting enumeration.
|
|
36
|
+
|
|
37
|
+
The minimal hitting set problem is trivially formulated as a MaxSAT formula
|
|
38
|
+
in WCNF, as follows. Assume :math:`E=\\{e_1,\\ldots,e_n\\}` to be a universe
|
|
39
|
+
of elements. Also assume there are :math:`k` sets to hit:
|
|
40
|
+
:math:`s_i=\\{e_{i,1},\\ldots,e_{i,j_i}\\}` s.t. :math:`e_{i,l}\\in E`. Every
|
|
41
|
+
set :math:`s_i=\\{e_{i,1},\\ldots,e_{i,j_i}\\}` is translated into a hard
|
|
42
|
+
clause :math:`(e_{i,1} \\vee \\ldots \\vee e_{i,j_i})`. This results in the
|
|
43
|
+
set of hard clauses having size :math:`k`. The set of soft clauses
|
|
44
|
+
comprises unit clauses of the form :math:`(\\neg{e_{j}})` s.t.
|
|
45
|
+
:math:`e_{j}\\in E`, each having weight 1.
|
|
46
|
+
|
|
47
|
+
Taking into account this problem formulation as MaxSAT, ordered hitting
|
|
48
|
+
enumeration is done with the use of the state-of-the-art MaxSAT solver
|
|
49
|
+
called :class:`.RC2` [2]_ [3]_ [4]_ while unordered hitting set enumeration
|
|
50
|
+
is done through the *minimal correction subset* (MCS) enumeration, e.g.
|
|
51
|
+
using the :class:`.LBX`- [5]_ or :class:`.MCSls`-like [6]_ MCS enumerators.
|
|
52
|
+
|
|
53
|
+
Note that this implementation additionally supports *pure* SAT-based
|
|
54
|
+
minimal hitting set enumeration with the use of preferred variable
|
|
55
|
+
polarity setting following the approach of [7]_.
|
|
56
|
+
|
|
57
|
+
.. [1] Erick Moreno-Centeno, Richard M. Karp. *The Implicit Hitting Set
|
|
58
|
+
Approach to Solve Combinatorial Optimization Problems with an
|
|
59
|
+
Application to Multigenome Alignment*. Operations Research 61(2). 2013.
|
|
60
|
+
pp. 453-468
|
|
61
|
+
|
|
62
|
+
.. [2] António Morgado, Carmine Dodaro, Joao Marques-Silva. *Core-Guided
|
|
63
|
+
MaxSAT with Soft Cardinality Constraints*. CP 2014. pp. 564-573
|
|
64
|
+
|
|
65
|
+
.. [3] António Morgado, Alexey Ignatiev, Joao Marques-Silva. *MSCG: Robust
|
|
66
|
+
Core-Guided MaxSAT Solving*. JSAT 9. 2014. pp. 129-134
|
|
67
|
+
|
|
68
|
+
.. [4] Alexey Ignatiev, António Morgado, Joao Marques-Silva. *RC2: a
|
|
69
|
+
Python-based MaxSAT Solver*. MaxSAT Evaluation 2018. p. 22
|
|
70
|
+
|
|
71
|
+
.. [5] Carlos Mencía, Alessandro Previti, Joao Marques-Silva.
|
|
72
|
+
*Literal-Based MCS Extraction*. IJCAI. 2015. pp. 1973-1979
|
|
73
|
+
|
|
74
|
+
.. [6] Joao Marques-Silva, Federico Heras, Mikolás Janota,
|
|
75
|
+
Alessandro Previti, Anton Belov. *On Computing Minimal Correction
|
|
76
|
+
Subsets*. IJCAI. 2013. pp. 615-622
|
|
77
|
+
|
|
78
|
+
.. [7] Enrico Giunchiglia, Marco Maratea. *Solving Optimization Problems
|
|
79
|
+
with DLL*. ECAI 2006. pp. 377-381
|
|
80
|
+
|
|
81
|
+
:class:`Hitman` supports hitting set enumeration in the *implicit* manner,
|
|
82
|
+
i.e. when sets to hit can be added on the fly as well as hitting sets can
|
|
83
|
+
be blocked on demand.
|
|
84
|
+
|
|
85
|
+
An example usage of :class:`Hitman` through the Python ``import`` interface
|
|
86
|
+
is shown below. Here we target unordered subset-minimal hitting set
|
|
87
|
+
enumeration.
|
|
88
|
+
|
|
89
|
+
.. code-block:: python
|
|
90
|
+
|
|
91
|
+
>>> from pysat.examples.hitman import Hitman
|
|
92
|
+
>>>
|
|
93
|
+
>>> h = Hitman(solver='m22', htype='lbx')
|
|
94
|
+
>>> # adding sets to hit
|
|
95
|
+
>>> h.hit([1, 2, 3])
|
|
96
|
+
>>> h.hit([1, 4])
|
|
97
|
+
>>> h.hit([5, 6, 7])
|
|
98
|
+
>>>
|
|
99
|
+
>>> h.get()
|
|
100
|
+
[1, 5]
|
|
101
|
+
>>>
|
|
102
|
+
>>> h.block([1, 5])
|
|
103
|
+
>>>
|
|
104
|
+
>>> h.get()
|
|
105
|
+
[2, 4, 5]
|
|
106
|
+
>>>
|
|
107
|
+
>>> h.delete()
|
|
108
|
+
|
|
109
|
+
Enumerating cardinality-minimal hitting sets can be done as follows:
|
|
110
|
+
|
|
111
|
+
.. code-block:: python
|
|
112
|
+
|
|
113
|
+
>>> from pysat.examples.hitman import Hitman
|
|
114
|
+
>>>
|
|
115
|
+
>>> sets = [[1, 2, 3], [1, 4], [5, 6, 7]]
|
|
116
|
+
>>> with Hitman(bootstrap_with=sets, htype='sorted') as hitman:
|
|
117
|
+
... for hs in hitman.enumerate():
|
|
118
|
+
... print(hs)
|
|
119
|
+
...
|
|
120
|
+
[1, 5]
|
|
121
|
+
[1, 6]
|
|
122
|
+
[1, 7]
|
|
123
|
+
[3, 4, 7]
|
|
124
|
+
[2, 4, 7]
|
|
125
|
+
[3, 4, 6]
|
|
126
|
+
[3, 4, 5]
|
|
127
|
+
[2, 4, 6]
|
|
128
|
+
[2, 4, 5]
|
|
129
|
+
|
|
130
|
+
Finally, implicit hitting set enumeration can be used in practical problem
|
|
131
|
+
solving. As an example, let us show the basic flow of a MaxHS-like [8]_
|
|
132
|
+
algorithm for MaxSAT:
|
|
133
|
+
|
|
134
|
+
.. code-block:: python
|
|
135
|
+
|
|
136
|
+
>>> from pysat.examples.hitman import Hitman
|
|
137
|
+
>>> from pysat.solvers import Solver
|
|
138
|
+
>>>
|
|
139
|
+
>>> hitman = Hitman(htype='sorted')
|
|
140
|
+
>>> oracle = Solver()
|
|
141
|
+
>>>
|
|
142
|
+
>>> # here we assume that the SAT oracle
|
|
143
|
+
>>> # is initialized with a MaxSAT formula,
|
|
144
|
+
>>> # whose soft clauses are extended with
|
|
145
|
+
>>> # selector literals stored in "sels"
|
|
146
|
+
>>> while True:
|
|
147
|
+
... hs = hitman.get() # hitting the set of unsatisfiable cores
|
|
148
|
+
... ts = set(sels).difference(set(hs)) # soft clauses to try
|
|
149
|
+
...
|
|
150
|
+
... if oracle.solve(assumptions=ts):
|
|
151
|
+
... print('s OPTIMUM FOUND')
|
|
152
|
+
... print('o', len(hs))
|
|
153
|
+
... break
|
|
154
|
+
... else:
|
|
155
|
+
... core = oracle.get_core()
|
|
156
|
+
... hitman.hit(core)
|
|
157
|
+
|
|
158
|
+
.. [8] Jessica Davies, Fahiem Bacchus. *Solving MAXSAT by Solving a
|
|
159
|
+
Sequence of Simpler SAT Instances*. CP 2011. pp. 225-239
|
|
160
|
+
|
|
161
|
+
==============
|
|
162
|
+
Module details
|
|
163
|
+
==============
|
|
164
|
+
"""
|
|
165
|
+
|
|
166
|
+
#
|
|
167
|
+
#==============================================================================
|
|
168
|
+
import collections
|
|
169
|
+
from pysat.examples.rc2 import RC2, RC2Stratified
|
|
170
|
+
from pysat.examples.lbx import LBX
|
|
171
|
+
from pysat.examples.mcsls import MCSls
|
|
172
|
+
from pysat.formula import IDPool, WCNFPlus
|
|
173
|
+
from pysat.solvers import Solver, SolverNames
|
|
174
|
+
import six
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
#
|
|
178
|
+
#==============================================================================
|
|
179
|
+
class Atom(object):
|
|
180
|
+
"""
|
|
181
|
+
Atoms are elementary (signed) objects necessary when dealing with
|
|
182
|
+
hitting sets subject to hard constraints.
|
|
183
|
+
"""
|
|
184
|
+
|
|
185
|
+
def __init__(self, obj, sign=True):
|
|
186
|
+
"""
|
|
187
|
+
Simple atom initialiser.
|
|
188
|
+
"""
|
|
189
|
+
|
|
190
|
+
self.obj = obj
|
|
191
|
+
self.sign = sign
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
#
|
|
195
|
+
#==============================================================================
|
|
196
|
+
class Hitman(object):
|
|
197
|
+
"""
|
|
198
|
+
|
|
199
|
+
A cardinality-/subset-minimal hitting set enumerator. The enumerator
|
|
200
|
+
can be set up to use either a MaxSAT solver :class:`.RC2` or an MCS
|
|
201
|
+
enumerator (either :class:`.LBX` or :class:`.MCSls`). In the former
|
|
202
|
+
case, the hitting sets enumerated are ordered by size (smallest size
|
|
203
|
+
hitting sets are computed first), i.e. *sorted*. In the latter case,
|
|
204
|
+
subset-minimal hitting are enumerated in an arbitrary order, i.e.
|
|
205
|
+
*unsorted*. Additionally, Hitman supports pure SAT-based minimal
|
|
206
|
+
hitting set enumeration with the use of polarity preferences.
|
|
207
|
+
|
|
208
|
+
This is handled with the use of parameter ``htype``, which is set to
|
|
209
|
+
be ``'sorted'`` by default. The MaxSAT-based enumerator can be chosen
|
|
210
|
+
by setting ``htype`` to one of the following values: ``'maxsat'``,
|
|
211
|
+
``'mxsat'``, or ``'rc2'``. Alternatively, by setting it to ``'mcs'``
|
|
212
|
+
or ``'lbx'``, a user can enforce using the :class:`.LBX` MCS
|
|
213
|
+
enumerator. If ``htype`` is set to ``'mcsls'``, the :class:`.MCSls`
|
|
214
|
+
enumerator is used. Finally, value ``'sat'`` can be given, in which
|
|
215
|
+
case minimal hitting set enumeration will performed by means of a SAT
|
|
216
|
+
solver (can be either MiniSat-GH, or Lingeling, or CaDiCaL 153) with
|
|
217
|
+
polarity setting.
|
|
218
|
+
|
|
219
|
+
In either case, unless pure SAT-based hitting set enumeration is
|
|
220
|
+
selected, an underlying problem solver can use a SAT oracle specified
|
|
221
|
+
as an input parameter ``solver``. The default SAT solver is Glucose3
|
|
222
|
+
(specified as ``g3``, see :class:`.SolverNames` for details). For
|
|
223
|
+
SAT-based enumeration, MinisatGH is used as an underlying SAT solver.
|
|
224
|
+
|
|
225
|
+
Objects of class :class:`Hitman` can be bootstrapped with an iterable
|
|
226
|
+
of iterables, e.g. a list of lists. This is handled using the
|
|
227
|
+
``bootstrap_with`` parameter. Each set to hit can comprise elements of
|
|
228
|
+
any type, e.g. integers, strings or objects of any Python class, as
|
|
229
|
+
well as their combinations. The bootstrapping phase is done in
|
|
230
|
+
:func:`init`.
|
|
231
|
+
|
|
232
|
+
Another optional parameter ``subject_to`` can be used to specify
|
|
233
|
+
arbitrary hard constraints that must be respected when computing
|
|
234
|
+
hitting sets of the given sets. Note that ``subject_to`` should be an
|
|
235
|
+
iterable containing pure clauses and/or native AtMostK constraints.
|
|
236
|
+
Note that native cardinality constraints supported only by
|
|
237
|
+
MiniCard-like solvers. Finally, note that these hard constraints must
|
|
238
|
+
be defined over the set of signed atomic objects, i.e. instances of
|
|
239
|
+
class :class:`.Atom`.
|
|
240
|
+
|
|
241
|
+
A few other optional parameters include the possible options for RC2
|
|
242
|
+
as well as for LBX- and MCSls-like MCS enumerators that control the
|
|
243
|
+
behaviour of the underlying solvers.
|
|
244
|
+
|
|
245
|
+
:param bootstrap_with: input set of sets to hit
|
|
246
|
+
:param weights: a mapping from objects to their weights (if weighted)
|
|
247
|
+
:param subject_to: hard constraints (either clauses or native AtMostK constraints)
|
|
248
|
+
:param solver: name of SAT solver
|
|
249
|
+
:param htype: enumerator type
|
|
250
|
+
:param mxs_adapt: detect and process AtMost1 constraints in RC2
|
|
251
|
+
:param mxs_exhaust: apply unsatisfiable core exhaustion in RC2
|
|
252
|
+
:param mxs_minz: apply heuristic core minimization in RC2
|
|
253
|
+
:param mxs_trim: trim unsatisfiable cores at most this number of times
|
|
254
|
+
:param mcs_usecld: use clause-D heuristic in the MCS enumerator
|
|
255
|
+
|
|
256
|
+
:type bootstrap_with: iterable(iterable(obj))
|
|
257
|
+
:type weights: dict(obj)
|
|
258
|
+
:type subject_to: iterable(iterable(Atom))
|
|
259
|
+
:type solver: str
|
|
260
|
+
:type htype: str
|
|
261
|
+
:type mxs_adapt: bool
|
|
262
|
+
:type mxs_exhaust: bool
|
|
263
|
+
:type mxs_minz: bool
|
|
264
|
+
:type mxs_trim: int
|
|
265
|
+
:type mcs_usecld: bool
|
|
266
|
+
"""
|
|
267
|
+
|
|
268
|
+
def __init__(self, bootstrap_with=[], weights=None, subject_to=[],
|
|
269
|
+
solver='g3', htype='sorted', mxs_adapt=False, mxs_exhaust=False,
|
|
270
|
+
mxs_minz=False, mxs_trim=0, mcs_usecld=False):
|
|
271
|
+
"""
|
|
272
|
+
Constructor.
|
|
273
|
+
"""
|
|
274
|
+
|
|
275
|
+
# hitting set solver
|
|
276
|
+
self.oracle = None
|
|
277
|
+
|
|
278
|
+
# name of SAT solver
|
|
279
|
+
self.solver = solver
|
|
280
|
+
|
|
281
|
+
# various oracle options
|
|
282
|
+
self.adapt = mxs_adapt
|
|
283
|
+
self.exhaust = mxs_exhaust
|
|
284
|
+
self.minz = mxs_minz
|
|
285
|
+
self.trim = mxs_trim
|
|
286
|
+
self.usecld = mcs_usecld
|
|
287
|
+
|
|
288
|
+
# enumeration phase, for SAT-based oracles only
|
|
289
|
+
# (can be equal either to 1 or to -1)
|
|
290
|
+
self.phase = 1
|
|
291
|
+
|
|
292
|
+
# hitman type: either a MaxSAT solver or an MCS enumerator
|
|
293
|
+
if htype in ('maxsat', 'mxsat', 'rc2', 'sorted'):
|
|
294
|
+
self.htype = 'rc2'
|
|
295
|
+
elif htype in ('mcs', 'lbx'):
|
|
296
|
+
self.htype = 'lbx'
|
|
297
|
+
elif htype == 'mcsls':
|
|
298
|
+
self.htype = 'mcsls'
|
|
299
|
+
else: # 'sat'
|
|
300
|
+
self.htype = 'sat'
|
|
301
|
+
|
|
302
|
+
# pool of variable identifiers (for objects to hit)
|
|
303
|
+
self.idpool = IDPool()
|
|
304
|
+
|
|
305
|
+
# initialize hitting set solver
|
|
306
|
+
self.init(bootstrap_with, weights=weights, subject_to=subject_to)
|
|
307
|
+
|
|
308
|
+
def __del__(self):
|
|
309
|
+
"""
|
|
310
|
+
Destructor.
|
|
311
|
+
"""
|
|
312
|
+
|
|
313
|
+
self.delete()
|
|
314
|
+
|
|
315
|
+
def __enter__(self):
|
|
316
|
+
"""
|
|
317
|
+
'with' constructor.
|
|
318
|
+
"""
|
|
319
|
+
|
|
320
|
+
return self
|
|
321
|
+
|
|
322
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
323
|
+
"""
|
|
324
|
+
'with' destructor.
|
|
325
|
+
"""
|
|
326
|
+
|
|
327
|
+
self.delete()
|
|
328
|
+
|
|
329
|
+
def init(self, bootstrap_with, weights=None, subject_to=[]):
|
|
330
|
+
"""
|
|
331
|
+
This method serves for initializing the hitting set solver with a
|
|
332
|
+
given list of sets to hit. Concretely, the hitting set problem is
|
|
333
|
+
encoded into partial MaxSAT as outlined above, which is then fed
|
|
334
|
+
either to a MaxSAT solver or an MCS enumerator.
|
|
335
|
+
|
|
336
|
+
An additional optional parameter is ``weights``, which can be used
|
|
337
|
+
to specify non-unit weights for the target objects in the sets to
|
|
338
|
+
hit. This only works if ``'sorted'`` enumeration of hitting sets
|
|
339
|
+
is applied.
|
|
340
|
+
|
|
341
|
+
Another optional parameter is available, namely, ``subject_to``.
|
|
342
|
+
It can be used to specify arbitrary hard constraints that must be
|
|
343
|
+
respected when computing hitting sets of the given sets. Note that
|
|
344
|
+
``subject_to`` should be an iterable containing pure clauses
|
|
345
|
+
and/or native AtMostK constraints. Finally, note that these hard
|
|
346
|
+
constraints must be defined over the set of signed atomic objects,
|
|
347
|
+
i.e. instances of class :class:`.Atom`.
|
|
348
|
+
|
|
349
|
+
:param bootstrap_with: input set of sets to hit
|
|
350
|
+
:param weights: weights of the objects in case the problem is weighted
|
|
351
|
+
:param subject_to: hard constraints (either clauses or native AtMostK constraints)
|
|
352
|
+
:type bootstrap_with: iterable(iterable(obj))
|
|
353
|
+
:type weights: dict(obj)
|
|
354
|
+
:type subject_to: iterable(iterable(Atom))
|
|
355
|
+
"""
|
|
356
|
+
|
|
357
|
+
# formula encoding the sets to hit
|
|
358
|
+
formula = WCNFPlus()
|
|
359
|
+
|
|
360
|
+
# hard clauses
|
|
361
|
+
for to_hit in bootstrap_with:
|
|
362
|
+
to_hit = map(lambda obj: self.idpool.id(obj), to_hit)
|
|
363
|
+
|
|
364
|
+
formula.append([self.phase * vid for vid in to_hit])
|
|
365
|
+
|
|
366
|
+
# additional hard constraints
|
|
367
|
+
for cl in subject_to:
|
|
368
|
+
if not len(cl) == 2 or not type(cl[0]) in (list, tuple, set):
|
|
369
|
+
# this is a pure clause
|
|
370
|
+
formula.append(list(map(lambda a: self.idpool.id(a.obj) * (2 * a.sign - 1), cl)))
|
|
371
|
+
else:
|
|
372
|
+
# this is a native AtMostK constraint
|
|
373
|
+
formula.append([list(map(lambda a: self.idpool.id(a.obj) * (2 * a.sign - 1), cl[0])), cl[1]], is_atmost=True)
|
|
374
|
+
|
|
375
|
+
# soft clauses
|
|
376
|
+
for obj_id in six.iterkeys(self.idpool.id2obj):
|
|
377
|
+
formula.append([-obj_id],
|
|
378
|
+
weight=1 if not weights else weights[self.idpool.obj(obj_id)])
|
|
379
|
+
|
|
380
|
+
if self.htype == 'rc2':
|
|
381
|
+
if not weights or min(weights.values()) == max(weights.values()):
|
|
382
|
+
self.oracle = RC2(formula, solver=self.solver, adapt=self.adapt,
|
|
383
|
+
exhaust=self.exhaust, minz=self.minz, trim=self.trim)
|
|
384
|
+
else:
|
|
385
|
+
self.oracle = RC2Stratified(formula, solver=self.solver,
|
|
386
|
+
adapt=self.adapt, exhaust=self.exhaust, minz=self.minz,
|
|
387
|
+
nohard=True, trim=self.trim)
|
|
388
|
+
elif self.htype == 'lbx':
|
|
389
|
+
self.oracle = LBX(formula, solver_name=self.solver,
|
|
390
|
+
use_cld=self.usecld, use_timer=True)
|
|
391
|
+
elif self.htype == 'mcsls':
|
|
392
|
+
self.oracle = MCSls(formula, solver_name=self.solver,
|
|
393
|
+
use_cld=self.usecld, use_timer=True)
|
|
394
|
+
else: # 'sat'
|
|
395
|
+
assert self.solver in SolverNames.minisatgh + \
|
|
396
|
+
SolverNames.lingeling + SolverNames.cadical153 + \
|
|
397
|
+
SolverNames.cadical195, 'Hard polarity setting is unsupported by {0}'.format(self.solver)
|
|
398
|
+
|
|
399
|
+
# setting up a SAT solver, so that it supports the same interface
|
|
400
|
+
if formula.atms == []:
|
|
401
|
+
self.oracle = Solver(name=self.solver, bootstrap_with=formula.hard,
|
|
402
|
+
use_timer=True)
|
|
403
|
+
elif self.solver in SolverNames.cadical195:
|
|
404
|
+
self.oracle = Solver(name=self.solver, bootstrap_with=formula.hard,
|
|
405
|
+
use_timer=True, native_card=True)
|
|
406
|
+
|
|
407
|
+
for atm in formula.atms:
|
|
408
|
+
self.oracle.add_atmost(*atm)
|
|
409
|
+
else:
|
|
410
|
+
assert 0, 'Native AtMostK constraints aren\'t supported by MinisatGH, Lingeling, or CaDiCaL 153'
|
|
411
|
+
|
|
412
|
+
# MinisatGH supports warm start mode
|
|
413
|
+
if self.solver in SolverNames.minisatgh:
|
|
414
|
+
self.oracle.start_mode(warm=True)
|
|
415
|
+
|
|
416
|
+
# soft clauses are enforced by means of setting polarities
|
|
417
|
+
self.oracle.set_phases(literals=[self.phase * cl[0] for cl in formula.soft])
|
|
418
|
+
|
|
419
|
+
# "adding" the missing compute() and oracle_time() methods
|
|
420
|
+
self.oracle.compute = lambda: [self.oracle.solve(), self.oracle.get_model()][-1]
|
|
421
|
+
self.oracle.oracle_time = self.oracle.time_accum
|
|
422
|
+
|
|
423
|
+
# adding a dummy VariableMap, as is in RC2 and LBX/MCSls
|
|
424
|
+
VariableMap = collections.namedtuple('VariableMap', ['e2i', 'i2e'])
|
|
425
|
+
self.oracle.vmap = VariableMap(e2i={}, i2e={})
|
|
426
|
+
for vid in self.idpool.id2obj.keys():
|
|
427
|
+
self.oracle.vmap.e2i[vid] = vid
|
|
428
|
+
self.oracle.vmap.i2e[vid] = vid
|
|
429
|
+
|
|
430
|
+
def switch_phase(self):
|
|
431
|
+
"""
|
|
432
|
+
If a pure SAT-based hitting set enumeration is used, it is
|
|
433
|
+
possible to instruct it to switch from enumerating target sets to
|
|
434
|
+
enumerating dual sets, by polarity switching. This is what this
|
|
435
|
+
method enables a user to do.
|
|
436
|
+
"""
|
|
437
|
+
|
|
438
|
+
if self.htype == 'sat':
|
|
439
|
+
if self.solver in SolverNames.minisatgh:
|
|
440
|
+
# resetting the mode forces the solver to backtrack to level 0
|
|
441
|
+
self.oracle.start_mode(warm=True)
|
|
442
|
+
|
|
443
|
+
# switching the phase value
|
|
444
|
+
self.phase *= -1
|
|
445
|
+
|
|
446
|
+
# updating the preferences
|
|
447
|
+
self.oracle.set_phases(literals=[self.phase * (-v) for v in self.idpool.id2obj])
|
|
448
|
+
|
|
449
|
+
def add_hard(self, clause, weights=None, no_obj=False):
|
|
450
|
+
"""
|
|
451
|
+
Add a hard constraint, which can be either a pure clause or an
|
|
452
|
+
AtMostK constraint.
|
|
453
|
+
|
|
454
|
+
Note that an optional parameter that can be passed to this method
|
|
455
|
+
is ``weights``, which contains a mapping the objects under
|
|
456
|
+
question into weights. Also note that the weight of an object must
|
|
457
|
+
not change from one call of :meth:`hit` to another.
|
|
458
|
+
|
|
459
|
+
Finally, note that if ``no_obj`` is set to True, no new objects
|
|
460
|
+
will be added to the hitting set enumerator. This is useful if the
|
|
461
|
+
variables in the clause are either already present or are
|
|
462
|
+
auxiliary. This is turned off by default.
|
|
463
|
+
|
|
464
|
+
:param clause: hard constraint (either a clause or a native AtMostK constraint)
|
|
465
|
+
:param weights: a mapping from objects to weights
|
|
466
|
+
:param no_obj: if True, no new objects are added to the oracle
|
|
467
|
+
|
|
468
|
+
:type clause: iterable(obj)
|
|
469
|
+
:type weights: dict(obj)
|
|
470
|
+
:type no_obj: bool
|
|
471
|
+
"""
|
|
472
|
+
|
|
473
|
+
if not len(clause) == 2 or not type(clause[0]) in (list, tuple, set):
|
|
474
|
+
# this is a pure clause
|
|
475
|
+
clause = list(map(lambda a: self.idpool.id(a.obj) * (2 * a.sign - 1), clause))
|
|
476
|
+
|
|
477
|
+
if no_obj is False:
|
|
478
|
+
# a soft clause should be added for each new object
|
|
479
|
+
new_obj = filter(lambda vid: abs(vid) not in self.oracle.vmap.e2i, clause)
|
|
480
|
+
else:
|
|
481
|
+
# this is a native AtMostK constraint
|
|
482
|
+
clause = [list(map(lambda a: self.idpool.id(a.obj) * (2 * a.sign - 1), clause[0])), clause[1]]
|
|
483
|
+
|
|
484
|
+
if no_obj is False:
|
|
485
|
+
# a soft clause should be added for each new object
|
|
486
|
+
new_obj = filter(lambda vid: abs(vid) not in self.oracle.vmap.e2i, clause[0])
|
|
487
|
+
|
|
488
|
+
# there may be duplicate literals if the constraint is weighted
|
|
489
|
+
new_obj = list(set(new_obj))
|
|
490
|
+
|
|
491
|
+
# adding the hard clause
|
|
492
|
+
self.oracle.add_clause(clause)
|
|
493
|
+
|
|
494
|
+
if no_obj is False:
|
|
495
|
+
# some of the literals may also have the opposite polarity
|
|
496
|
+
new_obj = [l if l in self.idpool.obj2id else -l for l in new_obj]
|
|
497
|
+
|
|
498
|
+
if self.htype != 'sat':
|
|
499
|
+
# new soft clauses
|
|
500
|
+
for vid in new_obj:
|
|
501
|
+
self.oracle.add_clause([-vid], 1 if not weights else weights[self.idpool.obj(vid)])
|
|
502
|
+
else:
|
|
503
|
+
# dummy variable id mapping
|
|
504
|
+
for vid in new_obj:
|
|
505
|
+
self.oracle.vmap.e2i[vid] = vid
|
|
506
|
+
self.oracle.vmap.i2e[vid] = vid
|
|
507
|
+
|
|
508
|
+
# setting variable polarities
|
|
509
|
+
self.oracle.set_phases(literals=[self.phase * (-vid) for vid in new_obj])
|
|
510
|
+
|
|
511
|
+
def delete(self):
|
|
512
|
+
"""
|
|
513
|
+
Explicit destructor of the internal hitting set oracle.
|
|
514
|
+
"""
|
|
515
|
+
|
|
516
|
+
if self.oracle:
|
|
517
|
+
self.oracle.delete()
|
|
518
|
+
self.oracle = None
|
|
519
|
+
|
|
520
|
+
def get(self):
|
|
521
|
+
"""
|
|
522
|
+
This method computes and returns a hitting set. The hitting set is
|
|
523
|
+
obtained using the underlying oracle operating the MaxSAT problem
|
|
524
|
+
formulation. The computed solution is mapped back to objects of the
|
|
525
|
+
problem domain.
|
|
526
|
+
|
|
527
|
+
:rtype: list(obj)
|
|
528
|
+
"""
|
|
529
|
+
|
|
530
|
+
model = self.oracle.compute()
|
|
531
|
+
|
|
532
|
+
if model is not None:
|
|
533
|
+
if self.htype in ('rc2', 'sat'):
|
|
534
|
+
# extracting a hitting set; the use of map may look
|
|
535
|
+
# silly but this is to support negative phases too
|
|
536
|
+
self.hset = map(lambda v: abs(v), filter(lambda v: v * self.phase > 0, model))
|
|
537
|
+
else:
|
|
538
|
+
self.hset = model
|
|
539
|
+
|
|
540
|
+
return list(map(lambda vid: self.idpool.id2obj[vid], self.hset))
|
|
541
|
+
|
|
542
|
+
def hit(self, to_hit, weights=None):
|
|
543
|
+
"""
|
|
544
|
+
This method adds a new set to hit to the hitting set solver. This
|
|
545
|
+
is done by translating the input iterable of objects into a list of
|
|
546
|
+
Boolean variables in the MaxSAT problem formulation.
|
|
547
|
+
|
|
548
|
+
Note that an optional parameter that can be passed to this method
|
|
549
|
+
is ``weights``, which contains a mapping the objects under
|
|
550
|
+
question into weights. Also note that the weight of an object must
|
|
551
|
+
not change from one call of :meth:`hit` to another.
|
|
552
|
+
|
|
553
|
+
:param to_hit: a new set to hit
|
|
554
|
+
:param weights: a mapping from objects to weights
|
|
555
|
+
|
|
556
|
+
:type to_hit: iterable(obj)
|
|
557
|
+
:type weights: dict(obj)
|
|
558
|
+
"""
|
|
559
|
+
|
|
560
|
+
# translating objects to variables
|
|
561
|
+
to_hit = list(map(lambda obj: self.idpool.id(obj), to_hit))
|
|
562
|
+
|
|
563
|
+
# a soft clause should be added for each new object
|
|
564
|
+
new_obj = [vid for vid in to_hit if vid not in self.oracle.vmap.e2i]
|
|
565
|
+
|
|
566
|
+
# new hard clause; phase multiplication is needed
|
|
567
|
+
# for making phase switching possible (pure SAT only)
|
|
568
|
+
self.oracle.add_clause([self.phase * vid for vid in to_hit])
|
|
569
|
+
|
|
570
|
+
# new soft clauses
|
|
571
|
+
if self.htype != 'sat':
|
|
572
|
+
# new soft clauses
|
|
573
|
+
for vid in new_obj:
|
|
574
|
+
self.oracle.add_clause([-vid], 1 if not weights else weights[self.idpool.obj(vid)])
|
|
575
|
+
else:
|
|
576
|
+
# dummy variable id mapping
|
|
577
|
+
for vid in new_obj:
|
|
578
|
+
self.oracle.vmap.e2i[vid] = vid
|
|
579
|
+
self.oracle.vmap.i2e[vid] = vid
|
|
580
|
+
|
|
581
|
+
# setting variable polarities
|
|
582
|
+
self.oracle.set_phases(literals=[self.phase * (-vid) for vid in new_obj])
|
|
583
|
+
|
|
584
|
+
def block(self, to_block, weights=None):
|
|
585
|
+
"""
|
|
586
|
+
The method serves for imposing a constraint forbidding the hitting
|
|
587
|
+
set solver to compute a given hitting set. Each set to block is
|
|
588
|
+
encoded as a hard clause in the MaxSAT problem formulation, which
|
|
589
|
+
is then added to the underlying oracle.
|
|
590
|
+
|
|
591
|
+
Note that an optional parameter that can be passed to this method
|
|
592
|
+
is ``weights``, which contains a mapping the objects under
|
|
593
|
+
question into weights. Also note that the weight of an object must
|
|
594
|
+
not change from one call of :meth:`hit` to another.
|
|
595
|
+
|
|
596
|
+
:param to_block: a set to block
|
|
597
|
+
:param weights: a mapping from objects to weights
|
|
598
|
+
|
|
599
|
+
:type to_block: iterable(obj)
|
|
600
|
+
:type weights: dict(obj)
|
|
601
|
+
"""
|
|
602
|
+
|
|
603
|
+
# translating objects to variables
|
|
604
|
+
to_block = list(map(lambda obj: self.idpool.id(obj), to_block))
|
|
605
|
+
|
|
606
|
+
# a soft clause should be added for each new object
|
|
607
|
+
new_obj = [vid for vid in to_block if vid not in self.oracle.vmap.e2i]
|
|
608
|
+
|
|
609
|
+
# new hard clause; phase multiplication is needed
|
|
610
|
+
# for making phase switching possible (pure SAT only)
|
|
611
|
+
self.oracle.add_clause([self.phase * (-vid) for vid in to_block])
|
|
612
|
+
|
|
613
|
+
# new soft clauses
|
|
614
|
+
if self.htype != 'sat':
|
|
615
|
+
for vid in new_obj:
|
|
616
|
+
self.oracle.add_clause([-vid], 1 if not weights else weights[self.idpool.obj(vid)])
|
|
617
|
+
else:
|
|
618
|
+
# dummy variable id mapping
|
|
619
|
+
for vid in new_obj:
|
|
620
|
+
self.oracle.vmap.e2i[vid] = vid
|
|
621
|
+
self.oracle.vmap.i2e[vid] = vid
|
|
622
|
+
|
|
623
|
+
# setting variable polarities
|
|
624
|
+
self.oracle.set_phases(literals=[self.phase * (-vid) for vid in new_obj])
|
|
625
|
+
|
|
626
|
+
def enumerate(self):
|
|
627
|
+
"""
|
|
628
|
+
The method can be used as a simple iterator computing and blocking
|
|
629
|
+
the hitting sets on the fly. It essentially calls :func:`get`
|
|
630
|
+
followed by :func:`block`. Each hitting set is reported as a list
|
|
631
|
+
of objects in the original problem domain, i.e. it is mapped back
|
|
632
|
+
from the solutions over Boolean variables computed by the
|
|
633
|
+
underlying oracle.
|
|
634
|
+
|
|
635
|
+
:rtype: list(obj)
|
|
636
|
+
"""
|
|
637
|
+
|
|
638
|
+
done = False
|
|
639
|
+
while not done:
|
|
640
|
+
hset = self.get()
|
|
641
|
+
|
|
642
|
+
if hset != None:
|
|
643
|
+
self.block(hset)
|
|
644
|
+
yield hset
|
|
645
|
+
else:
|
|
646
|
+
done = True
|
|
647
|
+
|
|
648
|
+
def oracle_time(self):
|
|
649
|
+
"""
|
|
650
|
+
Report the total SAT solving time.
|
|
651
|
+
"""
|
|
652
|
+
|
|
653
|
+
return self.oracle.oracle_time()
|