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
pysat/card.py
ADDED
|
@@ -0,0 +1,802 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
#-*- coding:utf-8 -*-
|
|
3
|
+
##
|
|
4
|
+
## card.py
|
|
5
|
+
##
|
|
6
|
+
## Created on: Sep 26, 2017
|
|
7
|
+
## Author: Alexey S. Ignatiev
|
|
8
|
+
## E-mail: aignatiev@ciencias.ulisboa.pt
|
|
9
|
+
##
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
===============
|
|
13
|
+
List of classes
|
|
14
|
+
===============
|
|
15
|
+
|
|
16
|
+
.. autosummary::
|
|
17
|
+
:nosignatures:
|
|
18
|
+
|
|
19
|
+
EncType
|
|
20
|
+
CardEnc
|
|
21
|
+
ITotalizer
|
|
22
|
+
|
|
23
|
+
==================
|
|
24
|
+
Module description
|
|
25
|
+
==================
|
|
26
|
+
|
|
27
|
+
This module provides access to various *cardinality constraint* [1]_
|
|
28
|
+
encodings to formulas in conjunctive normal form (CNF). These include
|
|
29
|
+
pairwise [2]_, bitwise [2]_, ladder/regular [3]_ [4]_, sequential counters
|
|
30
|
+
[5]_, sorting [6]_ and cardinality networks [7]_, totalizer [8]_, modulo
|
|
31
|
+
totalizer [9]_, and modulo totalizer for :math:`k`-cardinality [10]_, as
|
|
32
|
+
well as a *native* cardinality constraint representation supported by the
|
|
33
|
+
`MiniCard solver <https://github.com/liffiton/minicard>`__.
|
|
34
|
+
|
|
35
|
+
.. [1] Olivier Roussel, Vasco M. Manquinho. *Pseudo-Boolean and Cardinality
|
|
36
|
+
Constraints*. Handbook of Satisfiability. 2009. pp. 695-733
|
|
37
|
+
|
|
38
|
+
.. [2] Steven David Prestwich. *CNF Encodings*. Handbook of Satisfiability.
|
|
39
|
+
2009. pp. 75-97
|
|
40
|
+
|
|
41
|
+
.. [3] Carlos Ansótegui, Felip Manyà. *Mapping Problems with Finite-Domain
|
|
42
|
+
Variables to Problems with Boolean Variables*. SAT (Selected Papers)
|
|
43
|
+
2004. pp. 1-15
|
|
44
|
+
|
|
45
|
+
.. [4] Ian P. Gent, Peter Nightingale. *A New Encoding of Alldifferent Into
|
|
46
|
+
SAT*. In International workshop on modelling and reformulating
|
|
47
|
+
constraint satisfaction problems 2004. pp. 95-110
|
|
48
|
+
|
|
49
|
+
.. [5] Carsten Sinz. *Towards an Optimal CNF Encoding of Boolean
|
|
50
|
+
Cardinality Constraints*. CP 2005. pp. 827-831
|
|
51
|
+
|
|
52
|
+
.. [6] Kenneth E. Batcher. *Sorting Networks and Their Applications*.
|
|
53
|
+
AFIPS Spring Joint Computing Conference 1968. pp. 307-314
|
|
54
|
+
|
|
55
|
+
.. [7] Roberto Asin, Robert Nieuwenhuis, Albert Oliveras,
|
|
56
|
+
Enric Rodriguez-Carbonell. *Cardinality Networks and Their
|
|
57
|
+
Applications*. SAT 2009. pp. 167-180
|
|
58
|
+
|
|
59
|
+
.. [8] Olivier Bailleux, Yacine Boufkhad. *Efficient CNF Encoding of
|
|
60
|
+
Boolean Cardinality Constraints*. CP 2003. pp. 108-122
|
|
61
|
+
|
|
62
|
+
.. [9] Toru Ogawa, Yangyang Liu, Ryuzo Hasegawa, Miyuki Koshimura,
|
|
63
|
+
Hiroshi Fujita. *Modulo Based CNF Encoding of Cardinality Constraints
|
|
64
|
+
and Its Application to MaxSAT Solvers*. ICTAI 2013. pp. 9-17
|
|
65
|
+
|
|
66
|
+
.. [10] António Morgado, Alexey Ignatiev, Joao Marques-Silva. *MSCG: Robust
|
|
67
|
+
Core-Guided MaxSAT Solving*. System Description. JSAT 2015. vol. 9,
|
|
68
|
+
pp. 129-134
|
|
69
|
+
|
|
70
|
+
A cardinality constraint is a constraint of the form:
|
|
71
|
+
:math:`\\sum_{i=1}^n{x_i}\\leq k`. Cardinality constraints are ubiquitous
|
|
72
|
+
in practical problem formulations. Note that the implementation of the
|
|
73
|
+
pairwise, bitwise, and ladder encodings can only deal with AtMost1
|
|
74
|
+
constraints, e.g. :math:`\\sum_{i=1}^n{x_i}\\leq 1`.
|
|
75
|
+
|
|
76
|
+
Access to all cardinality encodings can be made through the main class of
|
|
77
|
+
this module, which is :class:`.CardEnc`.
|
|
78
|
+
|
|
79
|
+
Additionally, to the standard cardinality encodings that are basically
|
|
80
|
+
"static" CNF formulas, the module is designed to able to construct
|
|
81
|
+
*incremental* cardinality encodings, i.e. those that can be incrementally
|
|
82
|
+
extended at a later stage. At this point only the *iterative totalizer*
|
|
83
|
+
[11]_ encoding is supported. Iterative totalizer can be accessed with the
|
|
84
|
+
use of the :class:`.ITotalizer` class.
|
|
85
|
+
|
|
86
|
+
.. [11] Ruben Martins, Saurabh Joshi, Vasco M. Manquinho, Inês Lynce.
|
|
87
|
+
*Incremental Cardinality Constraints for MaxSAT*. CP 2014. pp. 531-548
|
|
88
|
+
|
|
89
|
+
==============
|
|
90
|
+
Module details
|
|
91
|
+
==============
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
#
|
|
95
|
+
#==============================================================================
|
|
96
|
+
import math
|
|
97
|
+
from pysat.formula import CNF, CNFPlus, IDPool
|
|
98
|
+
from pysat._utils import MainThread
|
|
99
|
+
import pycard
|
|
100
|
+
import signal
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
#
|
|
104
|
+
#==============================================================================
|
|
105
|
+
class NoSuchEncodingError(Exception):
|
|
106
|
+
"""
|
|
107
|
+
This exception is raised when creating an unknown an AtMostK, AtLeastK,
|
|
108
|
+
or EqualK constraint encoding.
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
pass
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
#
|
|
115
|
+
#==============================================================================
|
|
116
|
+
class UnsupportedBound(Exception):
|
|
117
|
+
"""
|
|
118
|
+
This exception is raised when creating a pairwise, or bitwise, or
|
|
119
|
+
ladder encoding of AtMostK, AtLeastK, or EqualsK with the bound
|
|
120
|
+
different from ``1`` and ``N - 1``.
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
pass
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
#
|
|
127
|
+
#==============================================================================
|
|
128
|
+
class EncType(object):
|
|
129
|
+
"""
|
|
130
|
+
This class represents a C-like ``enum`` type for choosing the
|
|
131
|
+
cardinality encoding to use. The values denoting the encodings are:
|
|
132
|
+
|
|
133
|
+
::
|
|
134
|
+
|
|
135
|
+
pairwise = 0
|
|
136
|
+
seqcounter = 1
|
|
137
|
+
sortnetwrk = 2
|
|
138
|
+
cardnetwrk = 3
|
|
139
|
+
bitwise = 4
|
|
140
|
+
ladder = 5
|
|
141
|
+
totalizer = 6
|
|
142
|
+
mtotalizer = 7
|
|
143
|
+
kmtotalizer = 8
|
|
144
|
+
native = 9
|
|
145
|
+
|
|
146
|
+
The desired encoding can be selected either directly by its integer
|
|
147
|
+
identifier, e.g. ``2``, or by its alphabetical name, e.g.
|
|
148
|
+
``EncType.sortnetwrk``.
|
|
149
|
+
|
|
150
|
+
Note that while most of the encodings are produced as a list of
|
|
151
|
+
clauses, the "native" encoding of `MiniCard
|
|
152
|
+
<https://github.com/liffiton/minicard>`__ is managed as one clause.
|
|
153
|
+
Given an AtMostK constraint :math:`\\sum_{i=1}^n{x_i\\leq k}`, the
|
|
154
|
+
native encoding represents it as a pair ``[lits, k]``, where ``lits``
|
|
155
|
+
is a list of size ``n`` containing literals in the sum.
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
pairwise = 0
|
|
159
|
+
seqcounter = 1
|
|
160
|
+
sortnetwrk = 2
|
|
161
|
+
cardnetwrk = 3
|
|
162
|
+
bitwise = 4
|
|
163
|
+
ladder = 5
|
|
164
|
+
totalizer = 6
|
|
165
|
+
mtotalizer = 7
|
|
166
|
+
kmtotalizer = 8
|
|
167
|
+
native = 9 # native representation used by Minicard
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
#
|
|
171
|
+
#==============================================================================
|
|
172
|
+
class CardEnc(object):
|
|
173
|
+
"""
|
|
174
|
+
This abstract class is responsible for the creation of cardinality
|
|
175
|
+
constraints encoded to a CNF formula. The class has three *class
|
|
176
|
+
methods* for creating AtMostK, AtLeastK, and EqualsK constraints. Given
|
|
177
|
+
a list of literals, an integer bound and an encoding type, each of
|
|
178
|
+
these methods returns an object of class :class:`pysat.formula.CNFPlus`
|
|
179
|
+
representing the resulting CNF formula.
|
|
180
|
+
|
|
181
|
+
Since the class is abstract, there is no need to create an object of
|
|
182
|
+
it. Instead, the methods should be called directly as class methods,
|
|
183
|
+
e.g. ``CardEnc.atmost(lits, bound)`` or ``CardEnc.equals(lits,
|
|
184
|
+
bound)``. An example usage is the following:
|
|
185
|
+
|
|
186
|
+
.. code-block:: python
|
|
187
|
+
|
|
188
|
+
>>> from pysat.card import *
|
|
189
|
+
>>> cnf = CardEnc.atmost(lits=[1, 2, 3], encoding=EncType.pairwise)
|
|
190
|
+
>>> print(cnf.clauses)
|
|
191
|
+
[[-1, -2], [-1, -3], [-2, -3]]
|
|
192
|
+
>>> cnf = CardEnc.equals(lits=[1, 2, 3], encoding=EncType.pairwise)
|
|
193
|
+
>>> print(cnf.clauses)
|
|
194
|
+
[[1, 2, 3], [-1, -2], [-1, -3], [-2, -3]]
|
|
195
|
+
"""
|
|
196
|
+
|
|
197
|
+
@classmethod
|
|
198
|
+
def _update_vids(cls, cnf, inp, vpool):
|
|
199
|
+
"""
|
|
200
|
+
Update variable ids in the given formula and id pool. The literals
|
|
201
|
+
serving as the input to the cardinality constraint are not
|
|
202
|
+
updated.
|
|
203
|
+
|
|
204
|
+
:param cnf: a list of literals in the sum.
|
|
205
|
+
:param inp: the list of input literals
|
|
206
|
+
:param vpool: the value of bound :math:`k`.
|
|
207
|
+
|
|
208
|
+
:type cnf: :class:`.formula.CNFPlus`
|
|
209
|
+
:type inp: list(int)
|
|
210
|
+
:type vpool: :class:`.formula.IDPool`
|
|
211
|
+
"""
|
|
212
|
+
|
|
213
|
+
top, vmap = max(inp + [vpool.top]), {} # current top and variable mapping
|
|
214
|
+
|
|
215
|
+
# we are going to use this to check if literals are input
|
|
216
|
+
inp = set([abs(l) for l in inp])
|
|
217
|
+
|
|
218
|
+
# creating a new variable mapping, taking into
|
|
219
|
+
# account variables marked as "occupied"
|
|
220
|
+
while top < cnf.nv:
|
|
221
|
+
top += 1
|
|
222
|
+
|
|
223
|
+
# skipping the input literals
|
|
224
|
+
if top in inp:
|
|
225
|
+
vmap[top] = top
|
|
226
|
+
continue
|
|
227
|
+
|
|
228
|
+
vpool.top += 1
|
|
229
|
+
|
|
230
|
+
while vpool._occupied and vpool.top >= vpool._occupied[0][0]:
|
|
231
|
+
if vpool.top <= vpool._occupied[0][1] + 1:
|
|
232
|
+
vpool.top = vpool._occupied[0][1] + 1
|
|
233
|
+
|
|
234
|
+
vpool._occupied.pop(0)
|
|
235
|
+
|
|
236
|
+
# mapping this literal to a free one
|
|
237
|
+
vmap[top] = vpool.top
|
|
238
|
+
|
|
239
|
+
# updating the clauses
|
|
240
|
+
for cl in cnf.clauses:
|
|
241
|
+
cl[:] = map(lambda l: int(math.copysign(vmap[abs(l)], l)) if abs(l) in vmap else l, cl)
|
|
242
|
+
|
|
243
|
+
# updating the number of variables
|
|
244
|
+
cnf.nv = vpool.top
|
|
245
|
+
|
|
246
|
+
@classmethod
|
|
247
|
+
def atmost(cls, lits, bound=1, top_id=None, vpool=None,
|
|
248
|
+
encoding=EncType.seqcounter):
|
|
249
|
+
"""
|
|
250
|
+
This method can be used for creating a CNF encoding of an AtMostK
|
|
251
|
+
constraint, i.e. of :math:`\\sum_{i=1}^{n}{x_i}\\leq k`. The
|
|
252
|
+
method shares the arguments and the return type with method
|
|
253
|
+
:meth:`CardEnc.atleast`. Please, see it for details.
|
|
254
|
+
"""
|
|
255
|
+
|
|
256
|
+
if encoding < 0 or encoding > 9:
|
|
257
|
+
raise(NoSuchEncodingError(encoding))
|
|
258
|
+
|
|
259
|
+
# checking if the bound is meaningless for any encoding
|
|
260
|
+
if bound < 0:
|
|
261
|
+
raise ValueError('Wrong bound: {0}'.format(bound))
|
|
262
|
+
|
|
263
|
+
if encoding in (0, 4, 5) and 1 < bound < len(lits) - 1:
|
|
264
|
+
raise(UnsupportedBound(encoding, bound))
|
|
265
|
+
|
|
266
|
+
assert not top_id or not vpool, \
|
|
267
|
+
'Use either a top id or a pool of variables but not both.'
|
|
268
|
+
|
|
269
|
+
# we are going to return this formula
|
|
270
|
+
ret = CNFPlus()
|
|
271
|
+
|
|
272
|
+
# if the list of literals is empty, return empty formula
|
|
273
|
+
if not lits:
|
|
274
|
+
return ret
|
|
275
|
+
|
|
276
|
+
# obtaining the top id from the variable pool
|
|
277
|
+
if vpool:
|
|
278
|
+
top_id = vpool.top
|
|
279
|
+
|
|
280
|
+
# making sure we are dealing with a list of literals
|
|
281
|
+
lits = list(lits)
|
|
282
|
+
|
|
283
|
+
# choosing the maximum id among the current top and the list of literals
|
|
284
|
+
top_id = max(map(lambda x: abs(x), lits + [top_id if top_id != None else 0]))
|
|
285
|
+
|
|
286
|
+
# MiniCard's native representation is handled separately
|
|
287
|
+
if encoding == 9:
|
|
288
|
+
ret.atmosts, ret.nv = [(lits, bound)], top_id
|
|
289
|
+
return ret
|
|
290
|
+
|
|
291
|
+
res = pycard.encode_atmost(lits, bound, top_id, encoding,
|
|
292
|
+
int(MainThread.check()))
|
|
293
|
+
|
|
294
|
+
if res:
|
|
295
|
+
ret.clauses, ret.nv = res
|
|
296
|
+
|
|
297
|
+
# updating vpool if necessary
|
|
298
|
+
if vpool:
|
|
299
|
+
if vpool._occupied and vpool.top <= vpool._occupied[0][0] <= ret.nv:
|
|
300
|
+
cls._update_vids(ret, lits, vpool)
|
|
301
|
+
else:
|
|
302
|
+
# here, ret.nv id is assumed to be larger than the top id
|
|
303
|
+
vpool.top = ret.nv - 1
|
|
304
|
+
vpool._next()
|
|
305
|
+
|
|
306
|
+
return ret
|
|
307
|
+
|
|
308
|
+
@classmethod
|
|
309
|
+
def atleast(cls, lits, bound=1, top_id=None, vpool=None,
|
|
310
|
+
encoding=EncType.seqcounter):
|
|
311
|
+
"""
|
|
312
|
+
This method can be used for creating a CNF encoding of an AtLeastK
|
|
313
|
+
constraint, i.e. of :math:`\\sum_{i=1}^{n}{x_i}\\geq k`. The
|
|
314
|
+
method takes 1 mandatory argument ``lits`` and 3 default arguments
|
|
315
|
+
can be specified: ``bound``, ``top_id``, ``vpool``, and
|
|
316
|
+
``encoding``.
|
|
317
|
+
|
|
318
|
+
:param lits: a list of literals in the sum.
|
|
319
|
+
:param bound: the value of bound :math:`k`.
|
|
320
|
+
:param top_id: top variable identifier used so far.
|
|
321
|
+
:param vpool: variable pool for counting the number of variables.
|
|
322
|
+
:param encoding: identifier of the encoding to use.
|
|
323
|
+
|
|
324
|
+
:type lits: iterable(int)
|
|
325
|
+
:type bound: int
|
|
326
|
+
:type top_id: integer or None
|
|
327
|
+
:type vpool: :class:`.IDPool`
|
|
328
|
+
:type encoding: integer
|
|
329
|
+
|
|
330
|
+
Parameter ``top_id`` serves to increase integer identifiers of
|
|
331
|
+
auxiliary variables introduced during the encoding process. This
|
|
332
|
+
is helpful when augmenting an existing CNF formula with the new
|
|
333
|
+
cardinality encoding to make sure there is no collision between
|
|
334
|
+
identifiers of the variables. If specified, the identifiers of the
|
|
335
|
+
first auxiliary variable will be ``top_id+1``.
|
|
336
|
+
|
|
337
|
+
Instead of ``top_id``, one may want to use a pool of variable
|
|
338
|
+
identifiers ``vpool``, which is automatically updated during the
|
|
339
|
+
method call. In many circumstances, this is more convenient than
|
|
340
|
+
using ``top_id``. Also note that parameters ``top_id`` and
|
|
341
|
+
``vpool`` **cannot** be specified *simultaneously*.
|
|
342
|
+
|
|
343
|
+
The default value of ``encoding`` is :attr:`Enctype.seqcounter`.
|
|
344
|
+
|
|
345
|
+
The method *translates* the AtLeast constraint into an AtMost
|
|
346
|
+
constraint by *negating* the literals of ``lits``, creating a new
|
|
347
|
+
bound :math:`n-k` and invoking :meth:`CardEnc.atmost` with the
|
|
348
|
+
modified list of literals and the new bound.
|
|
349
|
+
|
|
350
|
+
:raises CardEnc.NoSuchEncodingError: if encoding does not exist.
|
|
351
|
+
:raises ValueError: if bound is meaningless for encoding.
|
|
352
|
+
|
|
353
|
+
:rtype: a :class:`.CNFPlus` object where the new \
|
|
354
|
+
clauses (or the new native atmost constraint) are stored.
|
|
355
|
+
"""
|
|
356
|
+
|
|
357
|
+
if encoding < 0 or encoding > 9:
|
|
358
|
+
raise(NoSuchEncodingError(encoding))
|
|
359
|
+
|
|
360
|
+
# checking if the bound is meaningless for any encoding
|
|
361
|
+
if bound > len(lits):
|
|
362
|
+
raise ValueError('Wrong bound: {0}'.format(bound))
|
|
363
|
+
|
|
364
|
+
if encoding in (0, 4, 5) and 1 < bound < len(lits) - 1:
|
|
365
|
+
raise(UnsupportedBound(encoding, bound))
|
|
366
|
+
|
|
367
|
+
assert not top_id or not vpool, \
|
|
368
|
+
'Use either a top id or a pool of variables but not both.'
|
|
369
|
+
|
|
370
|
+
# we are going to return this formula
|
|
371
|
+
ret = CNFPlus()
|
|
372
|
+
|
|
373
|
+
# if the list of literals is empty, return empty formula
|
|
374
|
+
if not lits:
|
|
375
|
+
return ret
|
|
376
|
+
|
|
377
|
+
# obtaining the top id from the variable pool
|
|
378
|
+
if vpool:
|
|
379
|
+
top_id = vpool.top
|
|
380
|
+
|
|
381
|
+
# making sure we are dealing with a list of literals
|
|
382
|
+
lits = list(lits)
|
|
383
|
+
|
|
384
|
+
# choosing the maximum id among the current top and the list of literals
|
|
385
|
+
top_id = max(map(lambda x: abs(x), lits + [top_id if top_id != None else 0]))
|
|
386
|
+
|
|
387
|
+
# Minicard's native representation is handled separately
|
|
388
|
+
if encoding == 9:
|
|
389
|
+
ret.atmosts, ret.nv = [([-l for l in lits], len(lits) - bound)], top_id
|
|
390
|
+
return ret
|
|
391
|
+
|
|
392
|
+
res = pycard.encode_atleast(lits, bound, top_id, encoding,
|
|
393
|
+
int(MainThread.check()))
|
|
394
|
+
|
|
395
|
+
if res:
|
|
396
|
+
ret.clauses, ret.nv = res
|
|
397
|
+
|
|
398
|
+
# updating vpool if necessary
|
|
399
|
+
if vpool:
|
|
400
|
+
if vpool._occupied and vpool.top <= vpool._occupied[0][0] <= ret.nv:
|
|
401
|
+
cls._update_vids(ret, lits, vpool)
|
|
402
|
+
else:
|
|
403
|
+
# here, ret.nv id is assumed to be larger than the top id
|
|
404
|
+
vpool.top = ret.nv - 1
|
|
405
|
+
vpool._next()
|
|
406
|
+
|
|
407
|
+
return ret
|
|
408
|
+
|
|
409
|
+
@classmethod
|
|
410
|
+
def equals(cls, lits, bound=1, top_id=None, vpool=None,
|
|
411
|
+
encoding=EncType.seqcounter):
|
|
412
|
+
"""
|
|
413
|
+
This method can be used for creating a CNF encoding of an EqualsK
|
|
414
|
+
constraint, i.e. of :math:`\\sum_{i=1}^{n}{x_i}= k`. The method
|
|
415
|
+
makes consecutive calls of both :meth:`CardEnc.atleast` and
|
|
416
|
+
:meth:`CardEnc.atmost`. It shares the arguments and the return type
|
|
417
|
+
with method :meth:`CardEnc.atleast`. Please, see it for details.
|
|
418
|
+
"""
|
|
419
|
+
|
|
420
|
+
if vpool:
|
|
421
|
+
res1 = cls.atleast(lits, bound=bound, vpool=vpool, encoding=encoding)
|
|
422
|
+
res2 = cls.atmost(lits, bound=bound, vpool=vpool, encoding=encoding)
|
|
423
|
+
else:
|
|
424
|
+
res1 = cls.atleast(lits, bound=bound, top_id=top_id, encoding=encoding)
|
|
425
|
+
res2 = cls.atmost(lits, bound=bound, top_id=res1.nv, encoding=encoding)
|
|
426
|
+
|
|
427
|
+
# merging together AtLeast and AtMost constraints
|
|
428
|
+
res1.nv = max(res1.nv, res2.nv)
|
|
429
|
+
res1.clauses.extend(res2.clauses)
|
|
430
|
+
res1.atmosts.extend(res2.atmosts)
|
|
431
|
+
|
|
432
|
+
return res1
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
#
|
|
436
|
+
#==============================================================================
|
|
437
|
+
class ITotalizer(object):
|
|
438
|
+
"""
|
|
439
|
+
This class implements the iterative totalizer encoding [11]_. Note that
|
|
440
|
+
:class:`ITotalizer` can be used only for creating AtMostK constraints.
|
|
441
|
+
In contrast to class :class:`EncType`, this class is not abstract and
|
|
442
|
+
its objects once created can be reused several times. The idea is that
|
|
443
|
+
a *totalizer tree* can be extended, or the bound can be increased, as
|
|
444
|
+
well as two totalizer trees can be merged into one.
|
|
445
|
+
|
|
446
|
+
The constructor of the class object takes 3 default arguments.
|
|
447
|
+
|
|
448
|
+
:param lits: a list of literals to sum.
|
|
449
|
+
:param ubound: the largest potential bound to use.
|
|
450
|
+
:param top_id: top variable identifier used so far.
|
|
451
|
+
|
|
452
|
+
:type lits: iterable(int)
|
|
453
|
+
:type ubound: int
|
|
454
|
+
:type top_id: integer or None
|
|
455
|
+
|
|
456
|
+
The encoding of the current tree can be accessed with the use of
|
|
457
|
+
:class:`.CNF` variable stored as ``self.cnf``. Potential bounds **are
|
|
458
|
+
not** imposed by default but can be added as unit clauses in the final
|
|
459
|
+
CNF formula. The bounds are stored in the list of Boolean variables as
|
|
460
|
+
``self.rhs``. A concrete bound :math:`k` can be enforced by considering
|
|
461
|
+
a unit clause ``-self.rhs[k]``. **Note** that ``-self.rhs[0]`` enforces
|
|
462
|
+
all literals of the sum to be *false*.
|
|
463
|
+
|
|
464
|
+
An :class:`ITotalizer` object should be deleted if it is not needed
|
|
465
|
+
anymore.
|
|
466
|
+
|
|
467
|
+
Possible usage of the class is shown below:
|
|
468
|
+
|
|
469
|
+
.. code-block:: python
|
|
470
|
+
|
|
471
|
+
>>> from pysat.card import ITotalizer
|
|
472
|
+
>>> t = ITotalizer(lits=[1, 2, 3], ubound=1)
|
|
473
|
+
>>> print(t.cnf.clauses)
|
|
474
|
+
[[-2, 4], [-1, 4], [-1, -2, 5], [-4, 6], [-5, 7], [-3, 6], [-3, -4, 7]]
|
|
475
|
+
>>> print(t.rhs)
|
|
476
|
+
[6, 7]
|
|
477
|
+
>>> t.delete()
|
|
478
|
+
|
|
479
|
+
Alternatively, an object can be created using the ``with`` keyword. In
|
|
480
|
+
this case, the object is deleted automatically:
|
|
481
|
+
|
|
482
|
+
.. code-block:: python
|
|
483
|
+
|
|
484
|
+
>>> from pysat.card import ITotalizer
|
|
485
|
+
>>> with ITotalizer(lits=[1, 2, 3], ubound=1) as t:
|
|
486
|
+
... print(t.cnf.clauses)
|
|
487
|
+
[[-2, 4], [-1, 4], [-1, -2, 5], [-4, 6], [-5, 7], [-3, 6], [-3, -4, 7]]
|
|
488
|
+
... print(t.rhs)
|
|
489
|
+
[6, 7]
|
|
490
|
+
"""
|
|
491
|
+
|
|
492
|
+
def __init__(self, lits=[], ubound=1, top_id=None):
|
|
493
|
+
"""
|
|
494
|
+
Constructor.
|
|
495
|
+
"""
|
|
496
|
+
|
|
497
|
+
# internal totalizer object
|
|
498
|
+
self.tobj = None
|
|
499
|
+
|
|
500
|
+
# its characteristics
|
|
501
|
+
self.lits = []
|
|
502
|
+
self.ubound = 0
|
|
503
|
+
self.top_id = 0
|
|
504
|
+
|
|
505
|
+
# encoding result
|
|
506
|
+
self.cnf = CNF() # CNF formula encoding the totalizer object
|
|
507
|
+
self.rhs = [] # upper bounds on the number of literals (rhs)
|
|
508
|
+
|
|
509
|
+
# number of new clauses
|
|
510
|
+
self.nof_new = 0
|
|
511
|
+
|
|
512
|
+
# this newly created totalizer object is not yet merged in any other
|
|
513
|
+
self._merged = False
|
|
514
|
+
|
|
515
|
+
if lits:
|
|
516
|
+
self.new(lits=lits, ubound=ubound, top_id=top_id)
|
|
517
|
+
|
|
518
|
+
def new(self, lits=[], ubound=1, top_id=None):
|
|
519
|
+
"""
|
|
520
|
+
The actual constructor of :class:`ITotalizer`. Invoked from
|
|
521
|
+
``self.__init__()``. Creates an object of :class:`ITotalizer` given
|
|
522
|
+
a list of literals in the sum, the largest potential bound to
|
|
523
|
+
consider, as well as the top variable identifier used so far. See
|
|
524
|
+
the description of :class:`ITotalizer` for details.
|
|
525
|
+
"""
|
|
526
|
+
|
|
527
|
+
self.lits = list(lits)
|
|
528
|
+
self.ubound = ubound
|
|
529
|
+
self.top_id = max(map(lambda x: abs(x), self.lits + [top_id if top_id != None else 0]))
|
|
530
|
+
|
|
531
|
+
# creating the object
|
|
532
|
+
self.tobj, clauses, self.rhs, self.top_id = pycard.itot_new(self.lits,
|
|
533
|
+
self.ubound, self.top_id, int(MainThread.check()))
|
|
534
|
+
|
|
535
|
+
# saving the result
|
|
536
|
+
self.cnf.clauses = clauses
|
|
537
|
+
self.cnf.nv = self.top_id
|
|
538
|
+
|
|
539
|
+
# for convenience, keeping the number of clauses
|
|
540
|
+
self.nof_new = len(clauses)
|
|
541
|
+
|
|
542
|
+
def delete(self):
|
|
543
|
+
"""
|
|
544
|
+
Destroys a previously constructed :class:`ITotalizer` object.
|
|
545
|
+
Internal variables ``self.cnf`` and ``self.rhs`` get cleaned.
|
|
546
|
+
"""
|
|
547
|
+
|
|
548
|
+
if self.tobj:
|
|
549
|
+
if not self._merged:
|
|
550
|
+
pycard.itot_del(self.tobj)
|
|
551
|
+
|
|
552
|
+
# otherwise, this totalizer object is merged into a larger one
|
|
553
|
+
# therefore, this memory should be freed in its destructor
|
|
554
|
+
|
|
555
|
+
self.tobj = None
|
|
556
|
+
|
|
557
|
+
self.lits = []
|
|
558
|
+
self.ubound = 0
|
|
559
|
+
self.top_id = 0
|
|
560
|
+
|
|
561
|
+
self.cnf = CNF()
|
|
562
|
+
self.rhs = []
|
|
563
|
+
|
|
564
|
+
self.nof_new = 0
|
|
565
|
+
|
|
566
|
+
def __enter__(self):
|
|
567
|
+
"""
|
|
568
|
+
'with' constructor.
|
|
569
|
+
"""
|
|
570
|
+
|
|
571
|
+
return self
|
|
572
|
+
|
|
573
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
574
|
+
"""
|
|
575
|
+
'with' destructor.
|
|
576
|
+
"""
|
|
577
|
+
|
|
578
|
+
self.delete()
|
|
579
|
+
|
|
580
|
+
def __del__(self):
|
|
581
|
+
"""
|
|
582
|
+
Destructor.
|
|
583
|
+
"""
|
|
584
|
+
|
|
585
|
+
self.delete()
|
|
586
|
+
|
|
587
|
+
def increase(self, ubound=1, top_id=None):
|
|
588
|
+
"""
|
|
589
|
+
Increases a potential upper bound that can be imposed on the
|
|
590
|
+
literals in the sum of an existing :class:`ITotalizer` object to a
|
|
591
|
+
new value.
|
|
592
|
+
|
|
593
|
+
:param ubound: a new upper bound.
|
|
594
|
+
:param top_id: a new top variable identifier.
|
|
595
|
+
|
|
596
|
+
:type ubound: int
|
|
597
|
+
:type top_id: integer or None
|
|
598
|
+
|
|
599
|
+
The top identifier ``top_id`` applied only if it is greater than
|
|
600
|
+
the one used in ``self``.
|
|
601
|
+
|
|
602
|
+
This method creates additional clauses encoding the existing
|
|
603
|
+
totalizer tree up to the new upper bound given and appends them to
|
|
604
|
+
the list of clauses of :class:`.CNF` ``self.cnf``. The number of
|
|
605
|
+
newly created clauses is stored in variable ``self.nof_new``.
|
|
606
|
+
|
|
607
|
+
Also, a list of bounds ``self.rhs`` gets increased and its length
|
|
608
|
+
becomes ``ubound+1``.
|
|
609
|
+
|
|
610
|
+
The method can be used in the following way:
|
|
611
|
+
|
|
612
|
+
.. code-block:: python
|
|
613
|
+
|
|
614
|
+
>>> from pysat.card import ITotalizer
|
|
615
|
+
>>> t = ITotalizer(lits=[1, 2, 3], ubound=1)
|
|
616
|
+
>>> print(t.cnf.clauses)
|
|
617
|
+
[[-2, 4], [-1, 4], [-1, -2, 5], [-4, 6], [-5, 7], [-3, 6], [-3, -4, 7]]
|
|
618
|
+
>>> print(t.rhs)
|
|
619
|
+
[6, 7]
|
|
620
|
+
>>>
|
|
621
|
+
>>> t.increase(ubound=2)
|
|
622
|
+
>>> print(t.cnf.clauses)
|
|
623
|
+
[[-2, 4], [-1, 4], [-1, -2, 5], [-4, 6], [-5, 7], [-3, 6], [-3, -4, 7], [-3, -5, 8]]
|
|
624
|
+
>>> print(t.cnf.clauses[-t.nof_new:])
|
|
625
|
+
[[-3, -5, 8]]
|
|
626
|
+
>>> print(t.rhs)
|
|
627
|
+
[6, 7, 8]
|
|
628
|
+
>>> t.delete()
|
|
629
|
+
"""
|
|
630
|
+
|
|
631
|
+
self.top_id = max(self.top_id, top_id if top_id != None else 0)
|
|
632
|
+
|
|
633
|
+
# do nothing if the bound is set incorrectly
|
|
634
|
+
if ubound <= self.ubound or self.ubound >= len(self.lits):
|
|
635
|
+
self.nof_new = 0
|
|
636
|
+
return
|
|
637
|
+
else:
|
|
638
|
+
self.ubound = ubound
|
|
639
|
+
|
|
640
|
+
# updating the object and adding more variables and clauses
|
|
641
|
+
clauses, self.rhs, self.top_id = pycard.itot_inc(self.tobj,
|
|
642
|
+
self.ubound, self.top_id, int(MainThread.check()))
|
|
643
|
+
|
|
644
|
+
# saving the result
|
|
645
|
+
self.cnf.clauses.extend(clauses)
|
|
646
|
+
self.cnf.nv = self.top_id
|
|
647
|
+
|
|
648
|
+
# keeping the number of newly added clauses
|
|
649
|
+
self.nof_new = len(clauses)
|
|
650
|
+
|
|
651
|
+
def extend(self, lits=[], ubound=None, top_id=None):
|
|
652
|
+
"""
|
|
653
|
+
Extends the list of literals in the sum and (if needed) increases a
|
|
654
|
+
potential upper bound that can be imposed on the complete list of
|
|
655
|
+
literals in the sum of an existing :class:`ITotalizer` object to a
|
|
656
|
+
new value.
|
|
657
|
+
|
|
658
|
+
:param lits: additional literals to be included in the sum.
|
|
659
|
+
:param ubound: a new upper bound.
|
|
660
|
+
:param top_id: a new top variable identifier.
|
|
661
|
+
|
|
662
|
+
:type lits: iterable(int)
|
|
663
|
+
:type ubound: int
|
|
664
|
+
:type top_id: integer or None
|
|
665
|
+
|
|
666
|
+
The top identifier ``top_id`` applied only if it is greater than
|
|
667
|
+
the one used in ``self``.
|
|
668
|
+
|
|
669
|
+
This method creates additional clauses encoding the existing
|
|
670
|
+
totalizer tree augmented with new literals in the sum and updating
|
|
671
|
+
the upper bound. As a result, it appends the new clauses to the
|
|
672
|
+
list of clauses of :class:`.CNF` ``self.cnf``. The number of newly
|
|
673
|
+
created clauses is stored in variable ``self.nof_new``.
|
|
674
|
+
|
|
675
|
+
Also, if the upper bound is updated, a list of bounds ``self.rhs``
|
|
676
|
+
gets increased and its length becomes ``ubound+1``. Otherwise, it
|
|
677
|
+
is updated with new values.
|
|
678
|
+
|
|
679
|
+
The method can be used in the following way:
|
|
680
|
+
|
|
681
|
+
.. code-block:: python
|
|
682
|
+
|
|
683
|
+
>>> from pysat.card import ITotalizer
|
|
684
|
+
>>> t = ITotalizer(lits=[1, 2], ubound=1)
|
|
685
|
+
>>> print(t.cnf.clauses)
|
|
686
|
+
[[-2, 3], [-1, 3], [-1, -2, 4]]
|
|
687
|
+
>>> print(t.rhs)
|
|
688
|
+
[3, 4]
|
|
689
|
+
>>>
|
|
690
|
+
>>> t.extend(lits=[5], ubound=2)
|
|
691
|
+
>>> print(t.cnf.clauses)
|
|
692
|
+
[[-2, 3], [-1, 3], [-1, -2, 4], [-5, 6], [-3, 6], [-4, 7], [-3, -5, 7], [-4, -5, 8]]
|
|
693
|
+
>>> print(t.cnf.clauses[-t.nof_new:])
|
|
694
|
+
[[-5, 6], [-3, 6], [-4, 7], [-3, -5, 7], [-4, -5, 8]]
|
|
695
|
+
>>> print(t.rhs)
|
|
696
|
+
[6, 7, 8]
|
|
697
|
+
>>> t.delete()
|
|
698
|
+
"""
|
|
699
|
+
|
|
700
|
+
# preparing a new list of distinct input literals
|
|
701
|
+
lits = sorted(set(lits).difference(set(self.lits)))
|
|
702
|
+
|
|
703
|
+
if not lits:
|
|
704
|
+
# nothing to merge with -> just increase the bound
|
|
705
|
+
if ubound:
|
|
706
|
+
self.increase(ubound=ubound, top_id=top_id)
|
|
707
|
+
|
|
708
|
+
return
|
|
709
|
+
|
|
710
|
+
self.top_id = max(map(lambda x: abs(x), self.lits + [self.top_id, top_id if top_id != None else 0]))
|
|
711
|
+
self.ubound = max(self.ubound, ubound if ubound != None else 0)
|
|
712
|
+
|
|
713
|
+
# updating the object and adding more variables and clauses
|
|
714
|
+
self.tobj, clauses, self.rhs, self.top_id = pycard.itot_ext(self.tobj,
|
|
715
|
+
lits, self.ubound, self.top_id, int(MainThread.check()))
|
|
716
|
+
|
|
717
|
+
# saving the result
|
|
718
|
+
self.cnf.clauses.extend(clauses)
|
|
719
|
+
self.cnf.nv = self.top_id
|
|
720
|
+
self.lits.extend(lits)
|
|
721
|
+
|
|
722
|
+
# for convenience, keeping the number of new clauses
|
|
723
|
+
self.nof_new = len(clauses)
|
|
724
|
+
|
|
725
|
+
def merge_with(self, another, ubound=None, top_id=None):
|
|
726
|
+
"""
|
|
727
|
+
This method merges a tree of the current :class:`ITotalizer`
|
|
728
|
+
object, with a tree of another object and (if needed) increases a
|
|
729
|
+
potential upper bound that can be imposed on the complete list of
|
|
730
|
+
literals in the sum of an existing :class:`ITotalizer` object to a
|
|
731
|
+
new value.
|
|
732
|
+
|
|
733
|
+
:param another: another totalizer to merge with.
|
|
734
|
+
:param ubound: a new upper bound.
|
|
735
|
+
:param top_id: a new top variable identifier.
|
|
736
|
+
|
|
737
|
+
:type another: :class:`ITotalizer`
|
|
738
|
+
:type ubound: int
|
|
739
|
+
:type top_id: integer or None
|
|
740
|
+
|
|
741
|
+
The top identifier ``top_id`` applied only if it is greater than
|
|
742
|
+
the one used in ``self``.
|
|
743
|
+
|
|
744
|
+
This method creates additional clauses encoding the existing
|
|
745
|
+
totalizer tree merged with another totalizer tree into *one* sum
|
|
746
|
+
and updating the upper bound. As a result, it appends the new
|
|
747
|
+
clauses to the list of clauses of :class:`.CNF` ``self.cnf``. The
|
|
748
|
+
number of newly created clauses is stored in variable
|
|
749
|
+
``self.nof_new``.
|
|
750
|
+
|
|
751
|
+
Also, if the upper bound is updated, a list of bounds ``self.rhs``
|
|
752
|
+
gets increased and its length becomes ``ubound+1``. Otherwise, it
|
|
753
|
+
is updated with new values.
|
|
754
|
+
|
|
755
|
+
The method can be used in the following way:
|
|
756
|
+
|
|
757
|
+
.. code-block:: python
|
|
758
|
+
|
|
759
|
+
>>> from pysat.card import ITotalizer
|
|
760
|
+
>>> with ITotalizer(lits=[1, 2], ubound=1) as t1:
|
|
761
|
+
... print(t1.cnf.clauses)
|
|
762
|
+
[[-2, 3], [-1, 3], [-1, -2, 4]]
|
|
763
|
+
... print(t1.rhs)
|
|
764
|
+
[3, 4]
|
|
765
|
+
...
|
|
766
|
+
... t2 = ITotalizer(lits=[5, 6], ubound=1)
|
|
767
|
+
... print(t1.cnf.clauses)
|
|
768
|
+
[[-6, 7], [-5, 7], [-5, -6, 8]]
|
|
769
|
+
... print(t1.rhs)
|
|
770
|
+
[7, 8]
|
|
771
|
+
...
|
|
772
|
+
... t1.merge_with(t2)
|
|
773
|
+
... print(t1.cnf.clauses)
|
|
774
|
+
[[-2, 3], [-1, 3], [-1, -2, 4], [-6, 7], [-5, 7], [-5, -6, 8], [-7, 9], [-8, 10], [-3, 9], [-4, 10], [-3, -7, 10]]
|
|
775
|
+
... print(t1.cnf.clauses[-t1.nof_new:])
|
|
776
|
+
[[-6, 7], [-5, 7], [-5, -6, 8], [-7, 9], [-8, 10], [-3, 9], [-4, 10], [-3, -7, 10]]
|
|
777
|
+
... print(t1.rhs)
|
|
778
|
+
[9, 10]
|
|
779
|
+
...
|
|
780
|
+
... t2.delete()
|
|
781
|
+
"""
|
|
782
|
+
|
|
783
|
+
self.top_id = max(self.top_id, top_id if top_id != None else 0, another.top_id)
|
|
784
|
+
self.ubound = max(self.ubound, ubound if ubound != None else 0, another.ubound)
|
|
785
|
+
|
|
786
|
+
# extending the list of input literals
|
|
787
|
+
self.lits.extend(another.lits)
|
|
788
|
+
|
|
789
|
+
# updating the object and adding more variables and clauses
|
|
790
|
+
self.tobj, clauses, self.rhs, self.top_id = pycard.itot_mrg(self.tobj,
|
|
791
|
+
another.tobj, self.ubound, self.top_id, int(MainThread.check()))
|
|
792
|
+
|
|
793
|
+
# saving the result
|
|
794
|
+
self.cnf.clauses.extend(another.cnf.clauses)
|
|
795
|
+
self.cnf.clauses.extend(clauses)
|
|
796
|
+
self.cnf.nv = self.top_id
|
|
797
|
+
|
|
798
|
+
# for convenience, keeping the number of new clauses
|
|
799
|
+
self.nof_new = len(another.cnf.clauses) + len(clauses)
|
|
800
|
+
|
|
801
|
+
# memory deallocation should not be done for the merged tree
|
|
802
|
+
another._merged = True
|