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
pysat/examples/fm.py
CHANGED
|
@@ -209,6 +209,10 @@ class FM(object):
|
|
|
209
209
|
self.oracle = Solver(name=self.solver, bootstrap_with=self.hard, use_timer=True)
|
|
210
210
|
|
|
211
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
|
+
|
|
212
216
|
assert self.oracle.supports_atmost(), \
|
|
213
217
|
'{0} does not support native cardinality constraints. Make sure you use the right type of formula.'.format(solver_name)
|
|
214
218
|
|
|
@@ -288,8 +292,7 @@ class FM(object):
|
|
|
288
292
|
|
|
289
293
|
while True:
|
|
290
294
|
if self.oracle.solve(assumptions=self.sels):
|
|
291
|
-
self.model = self.oracle.get_model()
|
|
292
|
-
self.model = list(filter(lambda l: abs(l) <= self.orig_nv, self.model))
|
|
295
|
+
self.model = [l for l in self.oracle.get_model() if abs(l) <= self.orig_nv]
|
|
293
296
|
return
|
|
294
297
|
else:
|
|
295
298
|
self.treat_core()
|
|
@@ -382,12 +385,12 @@ class FM(object):
|
|
|
382
385
|
|
|
383
386
|
Otherwise (if the core contains more than one clause), every clause
|
|
384
387
|
:math:`c` of the core is *relaxed*. This means a new *relaxation
|
|
385
|
-
literal* is added to the clause, i.e. :math:`c
|
|
388
|
+
literal* is added to the clause, i.e. :math:`c\\gets c\\vee r`,
|
|
386
389
|
where :math:`r` is a fresh (unused) relaxation variable. After the
|
|
387
390
|
clauses get relaxed, a new cardinality encoding is added to the
|
|
388
391
|
formula enforcing the sum of the new relaxation variables to be not
|
|
389
|
-
greater than 1, :math
|
|
390
|
-
:math
|
|
392
|
+
greater than 1, :math:`\\sum_{c\\in\\phi}{r\\leq 1}`, where
|
|
393
|
+
:math:`\\phi` denotes the unsatisfiable core.
|
|
391
394
|
"""
|
|
392
395
|
|
|
393
396
|
if len(self.core) > 1:
|
|
@@ -471,7 +474,8 @@ def parse_options():
|
|
|
471
474
|
cardenc = encmap[cardenc]
|
|
472
475
|
|
|
473
476
|
# using minicard's native implementation of AtMost1 constraints
|
|
474
|
-
if solver in SolverNames.minicard
|
|
477
|
+
if solver in SolverNames.minicard + SolverNames.gluecard3 + \
|
|
478
|
+
SolverNames.gluecard4 + SolverNames.cadical195:
|
|
475
479
|
cardenc = encmap['native']
|
|
476
480
|
else:
|
|
477
481
|
assert cardenc != encmap['native'], 'Only Minicard can handle cardinality constraints natively'
|
|
@@ -491,7 +495,7 @@ def usage():
|
|
|
491
495
|
print(' Available values: bw, cardn, kmtot, ladder, mtot, pw, seqc, sortn, tot (default = seqc)')
|
|
492
496
|
print(' -h, --help')
|
|
493
497
|
print(' -s, --solver SAT solver to use')
|
|
494
|
-
print(' Available values: g3, g4, lgl, mcb, mcm, mpl, m22, mc, mgh (default = m22)')
|
|
498
|
+
print(' Available values: cd15, cd19, g3, g4, lgl, mcb, mcm, mpl, m22, mc, mgh (default = m22)')
|
|
495
499
|
print(' -v, --verbose Be verbose')
|
|
496
500
|
|
|
497
501
|
|
|
@@ -502,7 +506,7 @@ if __name__ == '__main__':
|
|
|
502
506
|
|
|
503
507
|
if files:
|
|
504
508
|
# parsing the input formula
|
|
505
|
-
if re.search('\.wcnf[p|+]?(\.(gz|bz2|lzma|xz))?$', files[0]):
|
|
509
|
+
if re.search(r'\.wcnf[p|+]?(\.(gz|bz2|lzma|xz|zst))?$', files[0]):
|
|
506
510
|
formula = WCNFPlus(from_file=files[0])
|
|
507
511
|
else: # expecting '*.cnf[,p,+].*'
|
|
508
512
|
formula = CNFPlus(from_file=files[0]).weighted()
|
pysat/examples/genhard.py
CHANGED
|
@@ -129,7 +129,7 @@ class PHP(CNF, object):
|
|
|
129
129
|
"""
|
|
130
130
|
Generator of :math:`k` pigeonhole principle (:math:`k`-PHP) formulas.
|
|
131
131
|
Given integer parameters :math:`m` and :math:`k`, the :math:`k`
|
|
132
|
-
pigeonhole principle states that if :math:`k
|
|
132
|
+
pigeonhole principle states that if :math:`k\\cdot m+1` pigeons are
|
|
133
133
|
distributes by :math:`m` holes, then at least one hole contains more
|
|
134
134
|
than :math:`k` pigeons.
|
|
135
135
|
|
|
@@ -139,9 +139,9 @@ class PHP(CNF, object):
|
|
|
139
139
|
|
|
140
140
|
Assume that a Boolean variable :math:`x_{ij}` encodes that pigeon
|
|
141
141
|
:math:`i` resides in hole :math:`j`. Then a PHP formula can be seen as
|
|
142
|
-
a conjunction: :math:`\\bigwedge_{i=1}^{k
|
|
143
|
-
m+1}{\\textsf{AtLeast1}(x_{i1}
|
|
144
|
-
\\bigwedge_{j=1}^{m}{\\textsf{AtMost}k(x_{1j}
|
|
142
|
+
a conjunction: :math:`\\bigwedge_{i=1}^{k\\cdot
|
|
143
|
+
m+1}{\\textsf{AtLeast1}(x_{i1},\\ldots,x_{im})}\\wedge
|
|
144
|
+
\\bigwedge_{j=1}^{m}{\\textsf{AtMost}k(x_{1j},\\ldots,x_{k\\cdot
|
|
145
145
|
m+1,j})}`. Here each :math:`\\textsf{AtLeast1}` constraint forces every
|
|
146
146
|
pigeon to be placed into at least one hole while each
|
|
147
147
|
:math:`\\textsf{AtMost}k` constraint allows the corresponding hole to
|
|
@@ -204,16 +204,17 @@ class GT(CNF, object):
|
|
|
204
204
|
"""
|
|
205
205
|
Generator of ordering (or *greater than*, GT) principle formulas. Given
|
|
206
206
|
an integer parameter :math:`n`, the principle states that any partial
|
|
207
|
-
order on the set :math
|
|
208
|
-
|
|
209
|
-
Assume variable :math:`x_{ij}`, for :math:`i,j
|
|
210
|
-
the fact that :math:`i
|
|
211
|
-
\\neg{x_{ji}})` and :math:`(\\neg{x_{ij}}
|
|
212
|
-
x_{ik})` ensure that the relation
|
|
213
|
-
|
|
214
|
-
:math
|
|
215
|
-
|
|
216
|
-
:math:`
|
|
207
|
+
order on the set :math:`\\{1,2,\\ldots,n\\}` must have a maximal element.
|
|
208
|
+
|
|
209
|
+
Assume variable :math:`x_{ij}`, for :math:`i,j\\in[n],i\\neq j`,
|
|
210
|
+
denotes the fact that :math:`i \\succ j`. Clauses
|
|
211
|
+
:math:`(\\neg{x_{ij}} \\vee \\neg{x_{ji}})` and :math:`(\\neg{x_{ij}}
|
|
212
|
+
\\vee \\neg{x_{jk}} \\vee x_{ik})` ensure that the relation
|
|
213
|
+
:math:`\\succ` is anti-symmetric and transitive. As a result,
|
|
214
|
+
:math:`\\succ` is a partial order on :math:`[n]`. The additional
|
|
215
|
+
requirement that each element :math:`i` has a successor in
|
|
216
|
+
:math:`[n]\\setminus\\{i\\}` represented a clause :math:`(\\vee_{j
|
|
217
|
+
\\neq i}{x_{ji}})` makes the formula unsatisfiable.
|
|
217
218
|
|
|
218
219
|
GT formulas were originally conjectured [2]_ to be hard for resolution.
|
|
219
220
|
However, [5]_ proved the existence of a polynomial size resolution
|
|
@@ -276,18 +277,18 @@ class CB(CNF, object):
|
|
|
276
277
|
"""
|
|
277
278
|
Mutilated chessboard principle (CB). Given an integer :math:`n`, the
|
|
278
279
|
principle states that it is impossible to cover a chessboard of size
|
|
279
|
-
:math:`2n
|
|
280
|
+
:math:`2n\\cdot 2n` by domino tiles if two diagonally opposite corners
|
|
280
281
|
of the chessboard are removed.
|
|
281
282
|
|
|
282
283
|
Note that the chessboard has :math:`4n^2-2` cells. Introduce a Boolean
|
|
283
|
-
variable :math:`x_{ij}` for :math:`i,j
|
|
284
|
-
and :math:`j` are adjacent (no variables are introduced for
|
|
285
|
-
non-adjacent cells). CB formulas comprise clauses (1)
|
|
284
|
+
variable :math:`x_{ij}` for :math:`i,j\\in[4n^2-2]` s.t. cells
|
|
285
|
+
:math:`i` and :math:`j` are adjacent (no variables are introduced for
|
|
286
|
+
pairs of non-adjacent cells). CB formulas comprise clauses (1)
|
|
286
287
|
:math:`(\\neg{x_{ji} \\vee \\neg{x_{ki}}})` for every :math:`i,j \\neq
|
|
287
288
|
k` meaning that no more than one adjacent cell can be paired with the
|
|
288
|
-
current one; and (2) :math:`(\\vee_{j
|
|
289
|
-
\\forall i` enforcing that every cell :math:`i` should
|
|
290
|
-
at least one adjacent cell.
|
|
289
|
+
current one; and (2) :math:`(\\vee_{j \\in \\text{Adj}(i)}
|
|
290
|
+
{x_{ij}})\\,\\, \\forall i` enforcing that every cell :math:`i` should
|
|
291
|
+
be paired with at least one adjacent cell.
|
|
291
292
|
|
|
292
293
|
Clearly, since the two diagonal corners are removed, the formula is
|
|
293
294
|
unsatisfiable. Also note the following. Assuming that the number of
|
|
@@ -389,8 +390,8 @@ class PAR(CNF, object):
|
|
|
389
390
|
variables :math:`x_{ij},i \\neq j`. If variable :math:`x_{ij}` is
|
|
390
391
|
*true*, then there is an edge between nodes :math:`i` and :math:`j`.
|
|
391
392
|
The formula consists of the following clauses: :math:`(\\vee_{j \\neq
|
|
392
|
-
i}{x_{ij}})` for every :math:`i
|
|
393
|
-
\\vee \\neg{x_{kj}})` for all distinct :math:`i,j,k
|
|
393
|
+
i}{x_{ij}})` for every :math:`i\\in[2n+1]`, and :math:`(\\neg{x_{ij}}
|
|
394
|
+
\\vee \\neg{x_{kj}})` for all distinct :math:`i,j,k \\in [2n+1]`.
|
|
394
395
|
|
|
395
396
|
The parity principle is known to be hard for resolution [4]_.
|
|
396
397
|
|
pysat/examples/hitman.py
CHANGED
|
@@ -35,14 +35,14 @@
|
|
|
35
35
|
by size) subset-minimal hitting enumeration.
|
|
36
36
|
|
|
37
37
|
The minimal hitting set problem is trivially formulated as a MaxSAT formula
|
|
38
|
-
in WCNF, as follows. Assume :math:`E
|
|
38
|
+
in WCNF, as follows. Assume :math:`E=\\{e_1,\\ldots,e_n\\}` to be a universe
|
|
39
39
|
of elements. Also assume there are :math:`k` sets to hit:
|
|
40
|
-
:math:`s_i
|
|
41
|
-
set :math:`s_i
|
|
42
|
-
clause :math:`(e_{i,1} \\vee
|
|
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
43
|
set of hard clauses having size :math:`k`. The set of soft clauses
|
|
44
44
|
comprises unit clauses of the form :math:`(\\neg{e_{j}})` s.t.
|
|
45
|
-
:math:`e_{j}
|
|
45
|
+
:math:`e_{j}\\in E`, each having weight 1.
|
|
46
46
|
|
|
47
47
|
Taking into account this problem formulation as MaxSAT, ordered hitting
|
|
48
48
|
enumeration is done with the use of the state-of-the-art MaxSAT solver
|
|
@@ -387,21 +387,27 @@ class Hitman(object):
|
|
|
387
387
|
nohard=True, trim=self.trim)
|
|
388
388
|
elif self.htype == 'lbx':
|
|
389
389
|
self.oracle = LBX(formula, solver_name=self.solver,
|
|
390
|
-
use_cld=self.usecld)
|
|
390
|
+
use_cld=self.usecld, use_timer=True)
|
|
391
391
|
elif self.htype == 'mcsls':
|
|
392
392
|
self.oracle = MCSls(formula, solver_name=self.solver,
|
|
393
|
-
use_cld=self.usecld)
|
|
393
|
+
use_cld=self.usecld, use_timer=True)
|
|
394
394
|
else: # 'sat'
|
|
395
395
|
assert self.solver in SolverNames.minisatgh + \
|
|
396
|
-
SolverNames.lingeling + SolverNames.cadical153
|
|
397
|
-
'Hard polarity setting is unsupported by {0}'.format(self.solver)
|
|
398
|
-
|
|
399
|
-
assert formula.atms == [], 'Native AtMostK constraints aren\'t' \
|
|
400
|
-
'supported by MinisatGH, Lingeling, or CaDiCaL 153'
|
|
396
|
+
SolverNames.lingeling + SolverNames.cadical153 + \
|
|
397
|
+
SolverNames.cadical195, 'Hard polarity setting is unsupported by {0}'.format(self.solver)
|
|
401
398
|
|
|
402
399
|
# setting up a SAT solver, so that it supports the same interface
|
|
403
|
-
|
|
404
|
-
|
|
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'
|
|
405
411
|
|
|
406
412
|
# MinisatGH supports warm start mode
|
|
407
413
|
if self.solver in SolverNames.minisatgh:
|
|
@@ -440,7 +446,7 @@ class Hitman(object):
|
|
|
440
446
|
# updating the preferences
|
|
441
447
|
self.oracle.set_phases(literals=[self.phase * (-v) for v in self.idpool.id2obj])
|
|
442
448
|
|
|
443
|
-
def add_hard(self, clause, weights=None):
|
|
449
|
+
def add_hard(self, clause, weights=None, no_obj=False):
|
|
444
450
|
"""
|
|
445
451
|
Add a hard constraint, which can be either a pure clause or an
|
|
446
452
|
AtMostK constraint.
|
|
@@ -450,47 +456,57 @@ class Hitman(object):
|
|
|
450
456
|
question into weights. Also note that the weight of an object must
|
|
451
457
|
not change from one call of :meth:`hit` to another.
|
|
452
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
|
+
|
|
453
464
|
:param clause: hard constraint (either a clause or a native AtMostK constraint)
|
|
454
465
|
:param weights: a mapping from objects to weights
|
|
466
|
+
:param no_obj: if True, no new objects are added to the oracle
|
|
455
467
|
|
|
456
468
|
:type clause: iterable(obj)
|
|
457
469
|
:type weights: dict(obj)
|
|
470
|
+
:type no_obj: bool
|
|
458
471
|
"""
|
|
459
472
|
|
|
460
473
|
if not len(clause) == 2 or not type(clause[0]) in (list, tuple, set):
|
|
461
474
|
# this is a pure clause
|
|
462
475
|
clause = list(map(lambda a: self.idpool.id(a.obj) * (2 * a.sign - 1), clause))
|
|
463
476
|
|
|
464
|
-
|
|
465
|
-
|
|
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)
|
|
466
480
|
else:
|
|
467
481
|
# this is a native AtMostK constraint
|
|
468
482
|
clause = [list(map(lambda a: self.idpool.id(a.obj) * (2 * a.sign - 1), clause[0])), clause[1]]
|
|
469
483
|
|
|
470
|
-
|
|
471
|
-
|
|
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])
|
|
472
487
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
# some of the literals may also have the opposite polarity
|
|
477
|
-
new_obj = [l if l in self.idpool.obj2id else -l for l in new_obj]
|
|
488
|
+
# there may be duplicate literals if the constraint is weighted
|
|
489
|
+
new_obj = list(set(new_obj))
|
|
478
490
|
|
|
479
491
|
# adding the hard clause
|
|
480
492
|
self.oracle.add_clause(clause)
|
|
481
493
|
|
|
482
|
-
if
|
|
483
|
-
#
|
|
484
|
-
for
|
|
485
|
-
self.oracle.add_clause([-vid], 1 if not weights else weights[self.idpool.obj(vid)])
|
|
486
|
-
else:
|
|
487
|
-
# dummy variable id mapping
|
|
488
|
-
for vid in new_obj:
|
|
489
|
-
self.oracle.vmap.e2i[vid] = vid
|
|
490
|
-
self.oracle.vmap.i2e[vid] = vid
|
|
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]
|
|
491
497
|
|
|
492
|
-
|
|
493
|
-
|
|
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])
|
|
494
510
|
|
|
495
511
|
def delete(self):
|
|
496
512
|
"""
|
|
@@ -545,7 +561,7 @@ class Hitman(object):
|
|
|
545
561
|
to_hit = list(map(lambda obj: self.idpool.id(obj), to_hit))
|
|
546
562
|
|
|
547
563
|
# a soft clause should be added for each new object
|
|
548
|
-
new_obj =
|
|
564
|
+
new_obj = [vid for vid in to_hit if vid not in self.oracle.vmap.e2i]
|
|
549
565
|
|
|
550
566
|
# new hard clause; phase multiplication is needed
|
|
551
567
|
# for making phase switching possible (pure SAT only)
|
|
@@ -588,7 +604,7 @@ class Hitman(object):
|
|
|
588
604
|
to_block = list(map(lambda obj: self.idpool.id(obj), to_block))
|
|
589
605
|
|
|
590
606
|
# a soft clause should be added for each new object
|
|
591
|
-
new_obj =
|
|
607
|
+
new_obj = [vid for vid in to_block if vid not in self.oracle.vmap.e2i]
|
|
592
608
|
|
|
593
609
|
# new hard clause; phase multiplication is needed
|
|
594
610
|
# for making phase switching possible (pure SAT only)
|
pysat/examples/lbx.py
CHANGED
|
@@ -90,6 +90,7 @@ import getopt
|
|
|
90
90
|
from math import copysign
|
|
91
91
|
import os
|
|
92
92
|
from pysat.formula import CNFPlus, WCNFPlus
|
|
93
|
+
from pysat.process import Processor
|
|
93
94
|
from pysat.solvers import Solver, SolverNames
|
|
94
95
|
import re
|
|
95
96
|
from six.moves import range
|
|
@@ -112,30 +113,46 @@ class LBX(object):
|
|
|
112
113
|
``use_cld`` is set to ``False``. Internal SAT solver's timer is also
|
|
113
114
|
disabled by default, i.e. ``use_timer`` is ``False``.
|
|
114
115
|
|
|
116
|
+
Additionally, the input formula can be preprocessed before running MCS
|
|
117
|
+
enumeration. This is controlled by the input parameter ``process``
|
|
118
|
+
whose integer value signifies the number of processing rounds to be
|
|
119
|
+
applied. The number of rounds is set to 0 by default.
|
|
120
|
+
|
|
115
121
|
:param formula: unsatisfiable partial CNF formula
|
|
116
122
|
:param use_cld: whether or not to use "clause :math:`D`"
|
|
117
123
|
:param solver_name: SAT oracle name
|
|
124
|
+
:param process: apply formula preprocessing this many times
|
|
118
125
|
:param use_timer: whether or not to use SAT solver's timer
|
|
119
126
|
|
|
120
127
|
:type formula: :class:`.WCNF`
|
|
121
128
|
:type use_cld: bool
|
|
122
129
|
:type solver_name: str
|
|
130
|
+
:type process: int
|
|
123
131
|
:type use_timer: bool
|
|
124
132
|
"""
|
|
125
133
|
|
|
126
|
-
def __init__(self, formula, use_cld=False, solver_name='m22',
|
|
134
|
+
def __init__(self, formula, use_cld=False, solver_name='m22', process=0,
|
|
135
|
+
use_timer=False):
|
|
127
136
|
"""
|
|
128
137
|
Constructor.
|
|
129
138
|
"""
|
|
130
139
|
|
|
140
|
+
# dealing with preprocessing, if required
|
|
141
|
+
hard = formula.hard if process > 0 else []
|
|
142
|
+
|
|
131
143
|
# bootstrapping the solver with hard clauses
|
|
132
|
-
self.oracle = Solver(name=solver_name,
|
|
133
|
-
|
|
144
|
+
self.oracle = Solver(name=solver_name,
|
|
145
|
+
bootstrap_with=formula.hard if process == 0 else [],
|
|
146
|
+
use_timer=use_timer)
|
|
134
147
|
self.solver = solver_name
|
|
135
148
|
|
|
136
149
|
# adding native cardinality constraints (if any) as hard clauses
|
|
137
150
|
# this can be done only if the Minicard solver is in use
|
|
138
151
|
if isinstance(formula, WCNFPlus) and formula.atms:
|
|
152
|
+
# we are using CaDiCaL195 and it can use external linear engine
|
|
153
|
+
if solver_name in SolverNames.cadical195:
|
|
154
|
+
self.oracle.activate_atmost()
|
|
155
|
+
|
|
139
156
|
assert self.oracle.supports_atmost(), \
|
|
140
157
|
'{0} does not support native cardinality constraints. Make sure you use the right type of formula.'.format(solver_name)
|
|
141
158
|
|
|
@@ -162,10 +179,27 @@ class LBX(object):
|
|
|
162
179
|
self.topv += 1
|
|
163
180
|
sel = self.topv
|
|
164
181
|
|
|
165
|
-
|
|
182
|
+
if process == 0:
|
|
183
|
+
self.oracle.add_clause(cl + [-sel])
|
|
184
|
+
else:
|
|
185
|
+
# adding to formula's hard clauses
|
|
186
|
+
# if any preprocessing is required
|
|
187
|
+
hard.append(cl + [-sel])
|
|
166
188
|
|
|
167
189
|
self.sels.append(sel)
|
|
168
190
|
|
|
191
|
+
# finally, applying formula processing, if any
|
|
192
|
+
if process:
|
|
193
|
+
# the processor is immediately destroyed,
|
|
194
|
+
# as we do not need to restore the models
|
|
195
|
+
with Processor(bootstrap_with=hard) as processor:
|
|
196
|
+
proc = processor.process(rounds=process, freeze=self.sels)
|
|
197
|
+
self.oracle.append_formula(proc)
|
|
198
|
+
|
|
199
|
+
# we won't have access to the original soft
|
|
200
|
+
# clauses in the case of formula preprocessing
|
|
201
|
+
self.soft = [[l] for l in self.sels]
|
|
202
|
+
|
|
169
203
|
def __del__(self):
|
|
170
204
|
"""
|
|
171
205
|
Destructor.
|
|
@@ -510,10 +544,11 @@ def parse_options():
|
|
|
510
544
|
|
|
511
545
|
try:
|
|
512
546
|
opts, args = getopt.getopt(sys.argv[1:],
|
|
513
|
-
'de:
|
|
547
|
+
'de:hp:p:s:v',
|
|
514
548
|
['dcalls',
|
|
515
549
|
'enum=',
|
|
516
550
|
'help',
|
|
551
|
+
'process=',
|
|
517
552
|
'solver=',
|
|
518
553
|
'verbose'])
|
|
519
554
|
except getopt.GetoptError as err:
|
|
@@ -523,6 +558,7 @@ def parse_options():
|
|
|
523
558
|
|
|
524
559
|
dcalls = False
|
|
525
560
|
to_enum = 1
|
|
561
|
+
process = 0
|
|
526
562
|
solver = 'm22'
|
|
527
563
|
verbose = 0
|
|
528
564
|
|
|
@@ -536,6 +572,8 @@ def parse_options():
|
|
|
536
572
|
elif opt in ('-h', '--help'):
|
|
537
573
|
usage()
|
|
538
574
|
sys.exit(0)
|
|
575
|
+
elif opt in ('-p', '--process'):
|
|
576
|
+
process = int(arg)
|
|
539
577
|
elif opt in ('-s', '--solver'):
|
|
540
578
|
solver = str(arg)
|
|
541
579
|
elif opt in ('-v', '--verbose'):
|
|
@@ -543,7 +581,7 @@ def parse_options():
|
|
|
543
581
|
else:
|
|
544
582
|
assert False, 'Unhandled option: {0} {1}'.format(opt, arg)
|
|
545
583
|
|
|
546
|
-
return dcalls, to_enum, solver, verbose, args
|
|
584
|
+
return dcalls, to_enum, solver, process, verbose, args
|
|
547
585
|
|
|
548
586
|
|
|
549
587
|
#
|
|
@@ -559,28 +597,31 @@ def usage():
|
|
|
559
597
|
print(' -e, --enum=<string> How many solutions to compute')
|
|
560
598
|
print(' Available values: [1 .. all] (default: 1)')
|
|
561
599
|
print(' -h, --help')
|
|
600
|
+
print(' -p, --process=<int> Number of processing rounds')
|
|
601
|
+
print(' Available values: [0 .. INT_MAX] (default = 0)')
|
|
562
602
|
print(' -s, --solver SAT solver to use')
|
|
563
|
-
print(' Available values: g3, g4, lgl, mcb, mcm, mpl, m22, mc, mgh (default = m22)')
|
|
603
|
+
print(' Available values: cd15, cd19, g3, g4, lgl, mcb, mcm, mpl, m22, mc, mgh (default = m22)')
|
|
564
604
|
print(' -v, --verbose Be verbose')
|
|
565
605
|
|
|
566
606
|
|
|
567
607
|
#
|
|
568
608
|
#==============================================================================
|
|
569
609
|
if __name__ == '__main__':
|
|
570
|
-
dcalls, to_enum, solver, verbose, files = parse_options()
|
|
610
|
+
dcalls, to_enum, solver, process, verbose, files = parse_options()
|
|
571
611
|
|
|
572
612
|
if type(to_enum) == str:
|
|
573
613
|
to_enum = 0
|
|
574
614
|
|
|
575
615
|
if files:
|
|
576
616
|
# reading standard CNF, WCNF, or (W)CNF+
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
with LBX(formula, use_cld=dcalls, solver_name=solver,
|
|
617
|
+
assert re.search(r'cnf[p|+]?(\.(gz|bz2|lzma|xz|zst))?$', files[0]), 'Unknown input file extension'
|
|
618
|
+
if re.search(r'\.wcnf[p|+]?(\.(gz|bz2|lzma|xz|zst))?$', files[0]):
|
|
619
|
+
formula = WCNFPlus(from_file=files[0])
|
|
620
|
+
else: # expecting '*.cnf[,p,+].*'
|
|
621
|
+
formula = CNFPlus(from_file=files[0]).weighted()
|
|
622
|
+
|
|
623
|
+
with LBX(formula, use_cld=dcalls, solver_name=solver, process=process,
|
|
624
|
+
use_timer=True) as mcsls:
|
|
584
625
|
for i, mcs in enumerate(mcsls.enumerate()):
|
|
585
626
|
if verbose:
|
|
586
627
|
print('c MCS:', ' '.join([str(cl_id) for cl_id in mcs]), '0')
|
pysat/examples/lsu.py
CHANGED
|
@@ -118,22 +118,25 @@ class LSU:
|
|
|
118
118
|
|
|
119
119
|
:param formula: input MaxSAT formula
|
|
120
120
|
:param solver: name of SAT solver
|
|
121
|
+
:param incr: enable incremental mode of Glucose
|
|
121
122
|
:param expect_interrupt: whether or not an :meth:`interrupt` call is expected
|
|
122
123
|
:param verbose: verbosity level
|
|
123
124
|
|
|
124
125
|
:type formula: :class:`.WCNF`
|
|
125
126
|
:type solver: str
|
|
127
|
+
:type incr: bool
|
|
126
128
|
:type expect_interrupt: bool
|
|
127
129
|
:type verbose: int
|
|
128
130
|
"""
|
|
129
131
|
|
|
130
|
-
def __init__(self, formula, solver='g4', expect_interrupt=False, verbose=0):
|
|
132
|
+
def __init__(self, formula, solver='g4', incr=False, expect_interrupt=False, verbose=0):
|
|
131
133
|
"""
|
|
132
134
|
Constructor.
|
|
133
135
|
"""
|
|
134
136
|
|
|
135
137
|
self.verbose = verbose
|
|
136
138
|
self.solver = solver
|
|
139
|
+
self.incr = incr
|
|
137
140
|
self.expect_interrupt = expect_interrupt
|
|
138
141
|
self.formula = formula
|
|
139
142
|
self.topv = formula.nv # largest variable index
|
|
@@ -154,7 +157,7 @@ class LSU:
|
|
|
154
157
|
"""
|
|
155
158
|
|
|
156
159
|
self.oracle = Solver(name=self.solver, bootstrap_with=formula.hard,
|
|
157
|
-
incr=
|
|
160
|
+
incr=self.incr, use_timer=True)
|
|
158
161
|
|
|
159
162
|
for i, cl in enumerate(formula.soft):
|
|
160
163
|
# TODO: if clause is unit, use its literal as selector
|
|
@@ -353,18 +356,23 @@ class LSUPlus(LSU, object):
|
|
|
353
356
|
:type verbose: int
|
|
354
357
|
"""
|
|
355
358
|
|
|
356
|
-
def __init__(self, formula, solver, expect_interrupt=False, verbose=0):
|
|
359
|
+
def __init__(self, formula, solver='g4', incr=False, expect_interrupt=False, verbose=0):
|
|
357
360
|
"""
|
|
358
361
|
Constructor.
|
|
359
362
|
"""
|
|
360
363
|
|
|
361
364
|
assert solver in SolverNames.gluecard3 or \
|
|
362
365
|
solver in SolverNames.gluecard4 or \
|
|
363
|
-
solver in SolverNames.minicard
|
|
366
|
+
solver in SolverNames.minicard or \
|
|
367
|
+
solver in SolverNames.cadical195, '{0} does not support native cardinality constraints'.format(solver)
|
|
364
368
|
|
|
365
|
-
super(LSUPlus, self).__init__(formula, solver=solver,
|
|
369
|
+
super(LSUPlus, self).__init__(formula, solver=solver, incr=incr,
|
|
366
370
|
expect_interrupt=expect_interrupt, verbose=verbose)
|
|
367
371
|
|
|
372
|
+
# we are using CaDiCaL195 and it can use external linear engine
|
|
373
|
+
if solver in SolverNames.cadical195:
|
|
374
|
+
self.oracle.activate_atmost()
|
|
375
|
+
|
|
368
376
|
# adding atmost constraints
|
|
369
377
|
for am in formula.atms:
|
|
370
378
|
self.oracle.add_atmost(*am)
|
|
@@ -391,13 +399,14 @@ def parse_options():
|
|
|
391
399
|
"""
|
|
392
400
|
|
|
393
401
|
try:
|
|
394
|
-
opts, args = getopt.getopt(sys.argv[1:], '
|
|
402
|
+
opts, args = getopt.getopt(sys.argv[1:], 'hims:t:v', ['help', 'incr', 'model', 'solver=', 'timeout=', 'verbose'])
|
|
395
403
|
except getopt.GetoptError as err:
|
|
396
404
|
sys.stderr.write(str(err).capitalize())
|
|
397
405
|
print_usage()
|
|
398
406
|
sys.exit(1)
|
|
399
407
|
|
|
400
408
|
solver = 'g4'
|
|
409
|
+
incr = False
|
|
401
410
|
verbose = 1
|
|
402
411
|
print_model = False
|
|
403
412
|
timeout = None
|
|
@@ -406,6 +415,8 @@ def parse_options():
|
|
|
406
415
|
if opt in ('-h', '--help'):
|
|
407
416
|
print_usage()
|
|
408
417
|
sys.exit(0)
|
|
418
|
+
elif opt in ('-i', '--incr'):
|
|
419
|
+
incr = True
|
|
409
420
|
elif opt in ('-m', '--model'):
|
|
410
421
|
print_model = True
|
|
411
422
|
elif opt in ('-s', '--solver'):
|
|
@@ -418,7 +429,7 @@ def parse_options():
|
|
|
418
429
|
else:
|
|
419
430
|
assert False, 'Unhandled option: {0} {1}'.format(opt, arg)
|
|
420
431
|
|
|
421
|
-
return print_model, solver, timeout, verbose, args
|
|
432
|
+
return incr, print_model, solver, timeout, verbose, args
|
|
422
433
|
|
|
423
434
|
|
|
424
435
|
#
|
|
@@ -431,9 +442,10 @@ def print_usage():
|
|
|
431
442
|
print('Usage: ' + os.path.basename(sys.argv[0]) + ' [options] dimacs-file')
|
|
432
443
|
print('Options:')
|
|
433
444
|
print(' -h, --help Show this message')
|
|
445
|
+
print(' -i, --incr Enable incremental model (for Glucose only)')
|
|
434
446
|
print(' -m, --model Print model')
|
|
435
447
|
print(' -s, --solver=<string> SAT solver to use')
|
|
436
|
-
print(' Available values: g3, g4, mc, m22, mgh (default = g4)')
|
|
448
|
+
print(' Available values: cd15, cd19, g3, g4, mc, m22, mgh (default = g4)')
|
|
437
449
|
print(' -t, --timeout=<float> Set time limit for MaxSAT solver')
|
|
438
450
|
print(' Available values: [0 .. FLOAT_MAX], none (default: none)')
|
|
439
451
|
print(' -v, --verbose Be verbose')
|
|
@@ -442,24 +454,26 @@ def print_usage():
|
|
|
442
454
|
#
|
|
443
455
|
#==============================================================================
|
|
444
456
|
if __name__ == '__main__':
|
|
445
|
-
print_model, solver, timeout, verbose, files = parse_options()
|
|
457
|
+
incr, print_model, solver, timeout, verbose, files = parse_options()
|
|
446
458
|
|
|
447
459
|
if files:
|
|
448
460
|
# reading standard CNF or WCNF
|
|
449
|
-
if re.search('cnf(\.(gz|bz2|lzma|xz))?$', files[0]):
|
|
450
|
-
if re.search('\.wcnf(\.(gz|bz2|lzma|xz))?$', files[0]):
|
|
461
|
+
if re.search(r'cnf(\.(gz|bz2|lzma|xz|zst))?$', files[0]):
|
|
462
|
+
if re.search(r'\.wcnf(\.(gz|bz2|lzma|xz|zst))?$', files[0]):
|
|
451
463
|
formula = WCNF(from_file=files[0])
|
|
452
464
|
else: # expecting '*.cnf'
|
|
453
465
|
formula = CNF(from_file=files[0]).weighted()
|
|
454
466
|
|
|
455
|
-
lsu = LSU(formula, solver=solver,
|
|
467
|
+
lsu = LSU(formula, solver=solver, incr=incr,
|
|
456
468
|
expect_interrupt=(timeout != None), verbose=verbose)
|
|
457
469
|
|
|
458
470
|
# reading WCNF+
|
|
459
|
-
elif re.search('\.wcnf[p,+](\.(gz|bz2|lzma|xz))?$', files[0]):
|
|
471
|
+
elif re.search(r'\.wcnf[p,+](\.(gz|bz2|lzma|xz|zst))?$', files[0]):
|
|
460
472
|
formula = WCNFPlus(from_file=files[0])
|
|
461
|
-
lsu = LSUPlus(formula, solver=solver,
|
|
473
|
+
lsu = LSUPlus(formula, solver=solver, incr=incr,
|
|
462
474
|
expect_interrupt=(timeout != None), verbose=verbose)
|
|
475
|
+
else:
|
|
476
|
+
assert False, 'Unknown input file extension'
|
|
463
477
|
|
|
464
478
|
# setting a timer if necessary
|
|
465
479
|
if timeout is not None:
|