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/rc2.py
CHANGED
|
@@ -118,6 +118,11 @@
|
|
|
118
118
|
either to compute one MaxSAT solution of an input formula, or to
|
|
119
119
|
enumerate a given number (or *all*) of its top MaxSAT solutions.
|
|
120
120
|
|
|
121
|
+
Importantly, the value of the cost computed during the solving process is
|
|
122
|
+
*not* the optimal value of the objective function. Instead, it is a
|
|
123
|
+
*complement* to the optimal value, i.e. the smallest price one has to pay
|
|
124
|
+
with the optimal solution.
|
|
125
|
+
|
|
121
126
|
==============
|
|
122
127
|
Module details
|
|
123
128
|
==============
|
|
@@ -133,7 +138,9 @@ from math import copysign
|
|
|
133
138
|
import os
|
|
134
139
|
from pysat.formula import CNFPlus, WCNFPlus, IDPool
|
|
135
140
|
from pysat.card import ITotalizer
|
|
141
|
+
from pysat.process import Processor
|
|
136
142
|
from pysat.solvers import Solver, SolverNames
|
|
143
|
+
from threading import Timer
|
|
137
144
|
import re
|
|
138
145
|
import six
|
|
139
146
|
from six.moves import range
|
|
@@ -160,17 +167,17 @@ class RC2(object):
|
|
|
160
167
|
- *unsatisfiable core reduction* (see method :func:`minimize_core`),
|
|
161
168
|
- *intrinsic AtMost1 constraints* (see method :func:`adapt_am1`).
|
|
162
169
|
|
|
163
|
-
:class:`RC2` can use any SAT solver available in PySAT. The
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
the
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
170
|
+
:class:`RC2` can use any SAT solver available in PySAT. The default
|
|
171
|
+
SAT solver to use is ``g3`` (see :class:`.SolverNames`). Additionally,
|
|
172
|
+
if Glucose is chosen, the ``incr`` parameter controls whether to use
|
|
173
|
+
the incremental mode of Glucose [7]_ (turned off by default). Boolean
|
|
174
|
+
parameters ``adapt``, ``exhaust``, and ``minz`` control whether or to
|
|
175
|
+
apply detection and adaptation of intrinsic AtMost1 constraints, core
|
|
176
|
+
exhaustion, and core reduction. Unsatisfiable cores can be trimmed if
|
|
177
|
+
the ``trim`` parameter is set to a non-zero integer. Formula
|
|
178
|
+
preprocessing can be applied a given number of rounds specified as the
|
|
179
|
+
value of parameter ``process``. Finally, verbosity level can be set
|
|
180
|
+
using the ``verbose`` parameter.
|
|
174
181
|
|
|
175
182
|
.. [7] Gilles Audemard, Jean-Marie Lagniez, Laurent Simon.
|
|
176
183
|
*Improving Glucose for Incremental SAT Solving with
|
|
@@ -183,6 +190,7 @@ class RC2(object):
|
|
|
183
190
|
:param exhaust: do core exhaustion
|
|
184
191
|
:param incr: use incremental mode of Glucose
|
|
185
192
|
:param minz: do heuristic core reduction
|
|
193
|
+
:param process: apply formula preprocessing this many times
|
|
186
194
|
:param trim: do core trimming at most this number of times
|
|
187
195
|
:param verbose: verbosity level
|
|
188
196
|
|
|
@@ -192,12 +200,13 @@ class RC2(object):
|
|
|
192
200
|
:type exhaust: bool
|
|
193
201
|
:type incr: bool
|
|
194
202
|
:type minz: bool
|
|
203
|
+
:type process: int
|
|
195
204
|
:type trim: int
|
|
196
205
|
:type verbose: int
|
|
197
206
|
"""
|
|
198
207
|
|
|
199
208
|
def __init__(self, formula, solver='g3', adapt=False, exhaust=False,
|
|
200
|
-
incr=False, minz=False, trim=0, verbose=0):
|
|
209
|
+
incr=False, minz=False, process=0, trim=0, verbose=0):
|
|
201
210
|
"""
|
|
202
211
|
Constructor.
|
|
203
212
|
"""
|
|
@@ -205,12 +214,20 @@ class RC2(object):
|
|
|
205
214
|
# saving verbosity level and other options
|
|
206
215
|
self.verbose = verbose
|
|
207
216
|
self.exhaust = exhaust
|
|
217
|
+
self.process = process
|
|
208
218
|
self.solver = solver
|
|
209
219
|
self.adapt = adapt
|
|
210
220
|
self.minz = minz
|
|
211
221
|
self.trim = trim
|
|
212
222
|
|
|
223
|
+
# oracles are initialised to be None
|
|
224
|
+
self.oracle, self.processor = None, None
|
|
225
|
+
|
|
226
|
+
# parameters related to asynchronous interruption
|
|
227
|
+
self.expect_interrupt, self.interrupted = False, False
|
|
228
|
+
|
|
213
229
|
# clause selectors and mapping from selectors to clause ids
|
|
230
|
+
# .sall, .s2cl, and .sneg are required only for model enumeration
|
|
214
231
|
self.sels, self.smap, self.sall, self.s2cl, self.sneg = [], {}, [], {}, set([])
|
|
215
232
|
|
|
216
233
|
# other MaxSAT related stuff
|
|
@@ -235,6 +252,23 @@ class RC2(object):
|
|
|
235
252
|
if not formula.hard and len(self.sels) > 100000 and min(wght) == max(wght):
|
|
236
253
|
self.minz = False
|
|
237
254
|
|
|
255
|
+
def _call_oracle(self, assumptions=[], expect_interrupt=False):
|
|
256
|
+
"""
|
|
257
|
+
Makes a call to the internal SAT solver by means of invoking
|
|
258
|
+
`oracle.solve_limited()`. The two arguments are the list of
|
|
259
|
+
assumption literals and the Boolean flag indicating whether the
|
|
260
|
+
call can be interrupted.
|
|
261
|
+
|
|
262
|
+
:param assumptions: a list of assumption literals.
|
|
263
|
+
:param expect_interrupt: whether :meth:`interrupt` may be called
|
|
264
|
+
|
|
265
|
+
:type assumptions: iterable(int)
|
|
266
|
+
:type expect_interrupt: bool
|
|
267
|
+
"""
|
|
268
|
+
|
|
269
|
+
return self.oracle.solve_limited(assumptions=assumptions,
|
|
270
|
+
expect_interrupt=expect_interrupt)
|
|
271
|
+
|
|
238
272
|
def __del__(self):
|
|
239
273
|
"""
|
|
240
274
|
Destructor.
|
|
@@ -278,13 +312,18 @@ class RC2(object):
|
|
|
278
312
|
"""
|
|
279
313
|
|
|
280
314
|
# creating a solver object
|
|
281
|
-
self.oracle = Solver(name=self.solver,
|
|
282
|
-
|
|
315
|
+
self.oracle = Solver(name=self.solver,
|
|
316
|
+
bootstrap_with=formula.hard if self.process == 0 else [],
|
|
317
|
+
incr=incr, use_timer=True)
|
|
283
318
|
|
|
284
319
|
# adding native cardinality constraints (if any) as hard clauses
|
|
285
320
|
# this can be done only if the Minicard solver is in use
|
|
286
321
|
# this cannot be done if RC2 is run from the command line
|
|
287
322
|
if isinstance(formula, WCNFPlus) and formula.atms:
|
|
323
|
+
# we are using CaDiCaL195 and it can use external linear engine
|
|
324
|
+
if self.solver in SolverNames.cadical195:
|
|
325
|
+
self.oracle.activate_atmost()
|
|
326
|
+
|
|
288
327
|
assert self.oracle.supports_atmost(), \
|
|
289
328
|
'{0} does not support native cardinality constraints. Make sure you use the right type of formula.'.format(self.solver)
|
|
290
329
|
|
|
@@ -300,7 +339,14 @@ class RC2(object):
|
|
|
300
339
|
|
|
301
340
|
self.s2cl[selv] = cl[:]
|
|
302
341
|
cl.append(-selv)
|
|
303
|
-
|
|
342
|
+
|
|
343
|
+
if self.process == 0:
|
|
344
|
+
self.oracle.add_clause(cl)
|
|
345
|
+
else:
|
|
346
|
+
# adding to formula's hard clauses
|
|
347
|
+
# if any preprocessing is required
|
|
348
|
+
formula.hard.append(cl + [-sel])
|
|
349
|
+
formula.soft[i] = [sel]
|
|
304
350
|
|
|
305
351
|
if selv not in self.wght:
|
|
306
352
|
# record selector and its weight
|
|
@@ -315,14 +361,26 @@ class RC2(object):
|
|
|
315
361
|
self.sels_set = set(self.sels)
|
|
316
362
|
self.sall = self.sels[:]
|
|
317
363
|
|
|
364
|
+
# we may end up having zero-weighed soft clauses
|
|
365
|
+
self.garbage = set([l for l in self.sels if self.wght[l] == 0])
|
|
366
|
+
if self.garbage:
|
|
367
|
+
self.filter_assumps()
|
|
368
|
+
|
|
369
|
+
# hard clauses are added last if formula processing has to be done
|
|
370
|
+
if self.process > 0:
|
|
371
|
+
self.processor = Processor(bootstrap_with=formula.hard)
|
|
372
|
+
hard = self.processor.process(rounds=self.process, freeze=self.sels)
|
|
373
|
+
self.oracle.append_formula(hard)
|
|
374
|
+
|
|
318
375
|
# at this point internal and external variables are the same
|
|
319
376
|
for v in range(1, formula.nv + 1):
|
|
320
377
|
self.vmap.e2i[v] = v
|
|
321
378
|
self.vmap.i2e[v] = v
|
|
322
379
|
|
|
323
380
|
if self.verbose > 1:
|
|
381
|
+
nofh = len(hard.clauses) if self.processor else len(formula.hard)
|
|
324
382
|
print('c formula: {0} vars, {1} hard, {2} soft'.format(formula.nv,
|
|
325
|
-
|
|
383
|
+
nofh, len(formula.soft)))
|
|
326
384
|
|
|
327
385
|
def add_clause(self, clause, weight=None):
|
|
328
386
|
"""
|
|
@@ -389,7 +447,7 @@ class RC2(object):
|
|
|
389
447
|
assert self.oracle.supports_atmost(), \
|
|
390
448
|
'{0} does not support native cardinality constraints. Make sure you use the right type of formula.'.format(self.solver)
|
|
391
449
|
|
|
392
|
-
self.oracle.add_atmost(cl, clause[1])
|
|
450
|
+
self.oracle.add_atmost(cl, clause[1], weights=clause[2] if len(clause) == 3 else [])
|
|
393
451
|
else:
|
|
394
452
|
# soft clauses should be augmented with a selector
|
|
395
453
|
selv = cl[0] # for a unit clause, no selector is needed
|
|
@@ -416,18 +474,23 @@ class RC2(object):
|
|
|
416
474
|
def delete(self):
|
|
417
475
|
"""
|
|
418
476
|
Explicit destructor of the internal SAT oracle and all the
|
|
419
|
-
totalizer objects creating during the solving process.
|
|
477
|
+
totalizer objects creating during the solving process. This also
|
|
478
|
+
destroys the processor (if any).
|
|
420
479
|
"""
|
|
421
480
|
|
|
422
481
|
if self.oracle:
|
|
423
|
-
if not self.oracle.supports_atmost(): # for minicard, there is nothing to free
|
|
482
|
+
if not self.oracle.supports_atmost(): # for minicard-like, there is nothing to free
|
|
424
483
|
for t in six.itervalues(self.tobj):
|
|
425
484
|
t.delete()
|
|
426
485
|
|
|
427
486
|
self.oracle.delete()
|
|
428
487
|
self.oracle = None
|
|
429
488
|
|
|
430
|
-
|
|
489
|
+
if self.processor:
|
|
490
|
+
self.processor.delete()
|
|
491
|
+
self.processor = None
|
|
492
|
+
|
|
493
|
+
def compute(self, expect_interrupt=False):
|
|
431
494
|
"""
|
|
432
495
|
This method can be used for computing one MaxSAT solution,
|
|
433
496
|
i.e. for computing an assignment satisfying all hard
|
|
@@ -442,6 +505,12 @@ class RC2(object):
|
|
|
442
505
|
can enumerate top-:math:`k` MaxSAT solutions (this can
|
|
443
506
|
also be done by calling :meth:`enumerate()`).
|
|
444
507
|
|
|
508
|
+
This MaxSAT call can be asynchronously interrupted, in which case
|
|
509
|
+
the value of ``expect_interrupt`` must be set to ``True``.
|
|
510
|
+
|
|
511
|
+
:param expect_interrupt: whether :meth:`interrupt` may be called
|
|
512
|
+
:type expect_interrupt: bool
|
|
513
|
+
|
|
445
514
|
:return: a MaxSAT model
|
|
446
515
|
:rtype: list(int)
|
|
447
516
|
|
|
@@ -467,6 +536,9 @@ class RC2(object):
|
|
|
467
536
|
>>> rc2.delete()
|
|
468
537
|
"""
|
|
469
538
|
|
|
539
|
+
# keeping the current interruption preference
|
|
540
|
+
self.expect_interrupt = expect_interrupt
|
|
541
|
+
|
|
470
542
|
# simply apply MaxSAT only once
|
|
471
543
|
res = self.compute_()
|
|
472
544
|
|
|
@@ -483,9 +555,14 @@ class RC2(object):
|
|
|
483
555
|
self.model = map(lambda l: int(copysign(self.vmap.i2e[abs(l)], l)), self.model)
|
|
484
556
|
self.model = sorted(self.model, key=lambda l: abs(l))
|
|
485
557
|
|
|
558
|
+
# if formula processing was used, we should
|
|
559
|
+
# restore the model for the original formula
|
|
560
|
+
if self.processor:
|
|
561
|
+
self.model = self.processor.restore(self.model)
|
|
562
|
+
|
|
486
563
|
return self.model
|
|
487
564
|
|
|
488
|
-
def enumerate(self, block=0):
|
|
565
|
+
def enumerate(self, block=0, expect_interrupt=False):
|
|
489
566
|
"""
|
|
490
567
|
Enumerate top MaxSAT solutions (from best to worst). The
|
|
491
568
|
method works as a generator, which iteratively calls
|
|
@@ -499,8 +576,15 @@ class RC2(object):
|
|
|
499
576
|
it to ``-1``. By the default (for blocking MaxSAT models),
|
|
500
577
|
``block`` is set to ``0``.
|
|
501
578
|
|
|
579
|
+
This MaxSAT enumeration call can be asynchronously interrupted, in
|
|
580
|
+
which case the value of ``expect_interrupt`` must be set to
|
|
581
|
+
``True``.
|
|
582
|
+
|
|
502
583
|
:param block: preferred way to block solutions when enumerating
|
|
584
|
+
:param expect_interrupt: whether :meth:`interrupt` may be called
|
|
585
|
+
|
|
503
586
|
:type block: int
|
|
587
|
+
:type expect_interrupt: bool
|
|
504
588
|
|
|
505
589
|
:return: a MaxSAT model
|
|
506
590
|
:rtype: list(int)
|
|
@@ -530,7 +614,7 @@ class RC2(object):
|
|
|
530
614
|
|
|
531
615
|
done = False
|
|
532
616
|
while not done:
|
|
533
|
-
model = self.compute()
|
|
617
|
+
model = self.compute(expect_interrupt=expect_interrupt)
|
|
534
618
|
|
|
535
619
|
if model != None:
|
|
536
620
|
if block == 1:
|
|
@@ -584,8 +668,21 @@ class RC2(object):
|
|
|
584
668
|
if self.adapt:
|
|
585
669
|
self.adapt_am1()
|
|
586
670
|
|
|
671
|
+
# at the beginning, the solver is not interrupted
|
|
672
|
+
self.interrupted = False
|
|
673
|
+
|
|
587
674
|
# main solving loop
|
|
588
|
-
while not self.
|
|
675
|
+
while not self._call_oracle(assumptions=self.sels + self.sums,
|
|
676
|
+
expect_interrupt=self.expect_interrupt):
|
|
677
|
+
|
|
678
|
+
# even if the call has been interrupted, we
|
|
679
|
+
# still need to finish the current iteration
|
|
680
|
+
if self.oracle.get_status() is None:
|
|
681
|
+
self.clear_interrupt()
|
|
682
|
+
|
|
683
|
+
if self.verbose > 1:
|
|
684
|
+
print('c interrupted; processing the last found core')
|
|
685
|
+
|
|
589
686
|
self.get_core()
|
|
590
687
|
|
|
591
688
|
if not self.core:
|
|
@@ -598,6 +695,10 @@ class RC2(object):
|
|
|
598
695
|
print('c cost: {0}; core sz: {1}; soft sz: {2}'.format(self.cost,
|
|
599
696
|
len(self.core), len(self.sels) + len(self.sums)))
|
|
600
697
|
|
|
698
|
+
# the solver got interrupted => returning None
|
|
699
|
+
if self.interrupted:
|
|
700
|
+
return # None
|
|
701
|
+
|
|
601
702
|
return True
|
|
602
703
|
|
|
603
704
|
def get_core(self):
|
|
@@ -652,9 +753,6 @@ class RC2(object):
|
|
|
652
753
|
# updating the cost
|
|
653
754
|
self.cost += self.minw
|
|
654
755
|
|
|
655
|
-
# assumptions to remove
|
|
656
|
-
self.garbage = set()
|
|
657
|
-
|
|
658
756
|
if len(self.core_sels) != 1 or len(self.core_sums) > 0:
|
|
659
757
|
# process selectors in the core
|
|
660
758
|
self.process_sels()
|
|
@@ -691,8 +789,8 @@ class RC2(object):
|
|
|
691
789
|
"""
|
|
692
790
|
Detect and adapt intrinsic AtMost1 constraints. Assume
|
|
693
791
|
there is a subset of soft clauses
|
|
694
|
-
:math:`\\mathcal{S}'
|
|
695
|
-
:math
|
|
792
|
+
:math:`\\mathcal{S}'\\subseteq \\mathcal{S}` s.t.
|
|
793
|
+
:math:`\\sum_{c\\in\\mathcal{S}'}{c\\leq 1}`, i.e. at most
|
|
696
794
|
one of the clauses of :math:`\\mathcal{S}'` can be
|
|
697
795
|
satisfied.
|
|
698
796
|
|
|
@@ -705,7 +803,7 @@ class RC2(object):
|
|
|
705
803
|
unit-propagated to be false. Note that this method for
|
|
706
804
|
detection of AtMost1 constraints is *incomplete*, because
|
|
707
805
|
in general unit propagation does not suffice to test
|
|
708
|
-
whether or not :math:`\\mathcal{F}
|
|
806
|
+
whether or not :math:`\\mathcal{F}\\wedge l_i\\models
|
|
709
807
|
\\neg{l_j}`.
|
|
710
808
|
|
|
711
809
|
Each intrinsic AtMost1 constraint detected this way is
|
|
@@ -792,7 +890,7 @@ class RC2(object):
|
|
|
792
890
|
has cost at least :math:`|\\mathcal{S}'|-1` (assuming
|
|
793
891
|
*unweighted* MaxSAT). Furthermore, it is safe to replace
|
|
794
892
|
all clauses of :math:`\\mathcal{S}'` with a single soft
|
|
795
|
-
clause :math
|
|
893
|
+
clause :math:`\\sum_{c\\in\\mathcal{S}'}{c}`.
|
|
796
894
|
|
|
797
895
|
Here, input parameter ``am1`` plays the role of subset
|
|
798
896
|
:math:`\\mathcal{S}'` mentioned above. The procedure bumps
|
|
@@ -808,9 +906,6 @@ class RC2(object):
|
|
|
808
906
|
:type am1: list(int)
|
|
809
907
|
"""
|
|
810
908
|
|
|
811
|
-
# assumptions to remove
|
|
812
|
-
self.garbage = set()
|
|
813
|
-
|
|
814
909
|
while len(am1) > 1:
|
|
815
910
|
# computing am1's weight
|
|
816
911
|
self.minw = min(map(lambda l: self.wght[l], am1))
|
|
@@ -825,7 +920,7 @@ class RC2(object):
|
|
|
825
920
|
self.process_sels()
|
|
826
921
|
|
|
827
922
|
# updating the list of literals in am1 after splitting the weights
|
|
828
|
-
am1 =
|
|
923
|
+
am1 = [l for l in am1 if l not in self.garbage]
|
|
829
924
|
|
|
830
925
|
# new selector
|
|
831
926
|
selv = self.pool.id()
|
|
@@ -851,7 +946,7 @@ class RC2(object):
|
|
|
851
946
|
for i in range(self.trim):
|
|
852
947
|
# call solver with core assumption only
|
|
853
948
|
# it must return 'unsatisfiable'
|
|
854
|
-
self.
|
|
949
|
+
self._call_oracle(assumptions=self.core)
|
|
855
950
|
|
|
856
951
|
# extract a new core
|
|
857
952
|
new_core = self.oracle.get_core()
|
|
@@ -889,13 +984,16 @@ class RC2(object):
|
|
|
889
984
|
while i < len(self.core):
|
|
890
985
|
to_test = self.core[:i] + self.core[(i + 1):]
|
|
891
986
|
|
|
892
|
-
if self.
|
|
987
|
+
if self._call_oracle(assumptions=to_test) == False:
|
|
893
988
|
self.core = to_test
|
|
894
989
|
elif self.oracle.get_status() == True:
|
|
895
990
|
i += 1
|
|
896
991
|
else:
|
|
897
992
|
break
|
|
898
993
|
|
|
994
|
+
# disabling the budget
|
|
995
|
+
self.oracle.conf_budget(budget=-1)
|
|
996
|
+
|
|
899
997
|
def exhaust_core(self, tobj):
|
|
900
998
|
"""
|
|
901
999
|
Exhaust core by increasing its bound as much as possible.
|
|
@@ -903,13 +1001,13 @@ class RC2(object):
|
|
|
903
1001
|
optimization* in [6]_.
|
|
904
1002
|
|
|
905
1003
|
Given a totalizer object ``tobj`` representing a sum of
|
|
906
|
-
some *relaxation* variables :math:`r
|
|
1004
|
+
some *relaxation* variables :math:`r\\in R` that augment
|
|
907
1005
|
soft clauses :math:`\\mathcal{C}_r`, the idea is to
|
|
908
1006
|
increase the right-hand side of the sum (which is equal to
|
|
909
1007
|
1 by default) as much as possible, reaching a value
|
|
910
1008
|
:math:`k` s.t. formula
|
|
911
|
-
:math:`\\mathcal{H}
|
|
912
|
-
R}{r
|
|
1009
|
+
:math:`\\mathcal{H}\\wedge\\mathcal{C}_r\\wedge(\\sum_{r\\in
|
|
1010
|
+
R}{r\\leq k})` is still unsatisfiable while increasing it
|
|
913
1011
|
further makes the formula satisfiable (here
|
|
914
1012
|
:math:`\\mathcal{H}` denotes the hard part of the
|
|
915
1013
|
formula).
|
|
@@ -921,7 +1019,7 @@ class RC2(object):
|
|
|
921
1019
|
"""
|
|
922
1020
|
|
|
923
1021
|
# the first case is simpler
|
|
924
|
-
if self.
|
|
1022
|
+
if self._call_oracle(assumptions=[-tobj.rhs[1]]):
|
|
925
1023
|
return 1
|
|
926
1024
|
else:
|
|
927
1025
|
self.cost += self.minw
|
|
@@ -934,7 +1032,7 @@ class RC2(object):
|
|
|
934
1032
|
# increasing the bound
|
|
935
1033
|
self.update_sum(-tobj.rhs[i - 1])
|
|
936
1034
|
|
|
937
|
-
if self.
|
|
1035
|
+
if self._call_oracle(assumptions=[-tobj.rhs[i]]):
|
|
938
1036
|
# the bound should be equal to i
|
|
939
1037
|
return i
|
|
940
1038
|
|
|
@@ -1162,12 +1260,18 @@ class RC2(object):
|
|
|
1162
1260
|
:func:`process_sels`, and :func:`process_sums`.
|
|
1163
1261
|
"""
|
|
1164
1262
|
|
|
1165
|
-
|
|
1166
|
-
self.
|
|
1263
|
+
# updating the list of selectors and sums
|
|
1264
|
+
self.sels = [l for l in self.sels if l not in self.garbage]
|
|
1265
|
+
self.sums = [l for l in self.sums if l not in self.garbage]
|
|
1167
1266
|
|
|
1168
|
-
|
|
1169
|
-
|
|
1267
|
+
# cleaning the dictionaries
|
|
1268
|
+
for l in list(self.garbage):
|
|
1269
|
+
if l in self.bnds:
|
|
1270
|
+
del self.bnds[l]
|
|
1271
|
+
if l in self.wght:
|
|
1272
|
+
del self.wght[l]
|
|
1170
1273
|
|
|
1274
|
+
# removing garbage from the set of selectors
|
|
1171
1275
|
self.sels_set.difference_update(set(self.garbage))
|
|
1172
1276
|
|
|
1173
1277
|
self.garbage.clear()
|
|
@@ -1212,6 +1316,65 @@ class RC2(object):
|
|
|
1212
1316
|
|
|
1213
1317
|
return int(copysign(i, l))
|
|
1214
1318
|
|
|
1319
|
+
def interrupt(self):
|
|
1320
|
+
"""
|
|
1321
|
+
Interrupt the execution of the current *limited* SAT call in the
|
|
1322
|
+
RC2 algorithm. Can be used to enforce time limits using timer
|
|
1323
|
+
objects. The interrupt must be cleared before performing another
|
|
1324
|
+
`compute` call (see :meth:`clear_interrupt`).
|
|
1325
|
+
|
|
1326
|
+
Importantly, interruption is implemented such that it can work
|
|
1327
|
+
incrementally with multiple MaxSAT calls, i.e. upon an interrupted
|
|
1328
|
+
invocation a user may extend the resources / time and call the
|
|
1329
|
+
solver again. To make this work, none of the SAT calls used by the
|
|
1330
|
+
heuristics are interrupted. For this reason, the solver may take
|
|
1331
|
+
slightly more time than assumed (spent to properly finish the
|
|
1332
|
+
processing of the last unsatisfiable core).
|
|
1333
|
+
|
|
1334
|
+
**Note** that this method can be called if the `compute` call was
|
|
1335
|
+
made with the option ``expect_interrupt`` set to ``True``.
|
|
1336
|
+
Behaviour is **undefined** if used to ``expect_interrupt`` was set
|
|
1337
|
+
to ``False``.
|
|
1338
|
+
|
|
1339
|
+
Example:
|
|
1340
|
+
|
|
1341
|
+
.. code-block:: python
|
|
1342
|
+
|
|
1343
|
+
>>> from pysat.examples.rc2 import RC2
|
|
1344
|
+
>>> from pysat.formula import WCNF
|
|
1345
|
+
>>> from threading import Timer
|
|
1346
|
+
>>>
|
|
1347
|
+
>>> wcnf = WCNF(from_file='somefile.wcnf')
|
|
1348
|
+
>>>
|
|
1349
|
+
>>> with RC2(wcnf) as rc2:
|
|
1350
|
+
>>>
|
|
1351
|
+
>>> def interrupt(s):
|
|
1352
|
+
>>> print('interrupted!')
|
|
1353
|
+
>>> s.interrupt()
|
|
1354
|
+
>>>
|
|
1355
|
+
>>> timer = Timer(1, interrupt, [rc2])
|
|
1356
|
+
>>> timer.start()
|
|
1357
|
+
>>> print('computing...')
|
|
1358
|
+
>>> rc2.compute(expect_interrupt=True)
|
|
1359
|
+
>>> print('done')
|
|
1360
|
+
"""
|
|
1361
|
+
|
|
1362
|
+
if self.oracle:
|
|
1363
|
+
self.oracle.interrupt()
|
|
1364
|
+
|
|
1365
|
+
# recording the interruption request
|
|
1366
|
+
self.interrupted = True
|
|
1367
|
+
|
|
1368
|
+
def clear_interrupt(self):
|
|
1369
|
+
"""
|
|
1370
|
+
Clears a previous interrupt. Technically, this method should be
|
|
1371
|
+
used every time after the previous MaxSAT call gets interrupted.
|
|
1372
|
+
**However**, the current implementation handles this on its own.
|
|
1373
|
+
"""
|
|
1374
|
+
|
|
1375
|
+
if self.oracle:
|
|
1376
|
+
self.oracle.clear_interrupt()
|
|
1377
|
+
|
|
1215
1378
|
|
|
1216
1379
|
#
|
|
1217
1380
|
#==============================================================================
|
|
@@ -1243,16 +1406,16 @@ class RC2Stratified(RC2, object):
|
|
|
1243
1406
|
"""
|
|
1244
1407
|
|
|
1245
1408
|
def __init__(self, formula, solver='g3', adapt=False, blo='div',
|
|
1246
|
-
exhaust=False, incr=False, minz=False, nohard=False,
|
|
1247
|
-
verbose=0):
|
|
1409
|
+
exhaust=False, incr=False, minz=False, nohard=False, process=0,
|
|
1410
|
+
trim=0, verbose=0):
|
|
1248
1411
|
"""
|
|
1249
1412
|
Constructor.
|
|
1250
1413
|
"""
|
|
1251
1414
|
|
|
1252
1415
|
# calling the constructor for the basic version
|
|
1253
1416
|
super(RC2Stratified, self).__init__(formula, solver=solver,
|
|
1254
|
-
adapt=adapt, exhaust=exhaust, incr=incr, minz=minz,
|
|
1255
|
-
verbose=verbose)
|
|
1417
|
+
adapt=adapt, exhaust=exhaust, incr=incr, minz=minz,
|
|
1418
|
+
process=process, trim=trim, verbose=verbose)
|
|
1256
1419
|
|
|
1257
1420
|
self.levl = 0 # initial optimization level
|
|
1258
1421
|
self.blop = [] # a list of blo levels
|
|
@@ -1296,7 +1459,7 @@ class RC2Stratified(RC2, object):
|
|
|
1296
1459
|
# number of finished levels
|
|
1297
1460
|
self.done = 0
|
|
1298
1461
|
|
|
1299
|
-
def compute(self):
|
|
1462
|
+
def compute(self, expect_interrupt=False):
|
|
1300
1463
|
"""
|
|
1301
1464
|
This method solves the MaxSAT problem iteratively. Each
|
|
1302
1465
|
optimization level is tackled the standard way, i.e. by
|
|
@@ -1307,6 +1470,9 @@ class RC2Stratified(RC2, object):
|
|
|
1307
1470
|
:func:`activate_clauses`.
|
|
1308
1471
|
"""
|
|
1309
1472
|
|
|
1473
|
+
# keeping the current interruption preference
|
|
1474
|
+
self.expect_interrupt = expect_interrupt
|
|
1475
|
+
|
|
1310
1476
|
if self.done == 0 and self.levl != None:
|
|
1311
1477
|
# it is a fresh start of the solver
|
|
1312
1478
|
# i.e. no optimization level is finished yet
|
|
@@ -1322,7 +1488,7 @@ class RC2Stratified(RC2, object):
|
|
|
1322
1488
|
print('c wght str:', self.blop[self.levl])
|
|
1323
1489
|
|
|
1324
1490
|
# call RC2
|
|
1325
|
-
if self.compute_()
|
|
1491
|
+
if self.compute_() != True: # can be either False or None
|
|
1326
1492
|
return
|
|
1327
1493
|
|
|
1328
1494
|
# updating the list of distinct weight levels
|
|
@@ -1350,7 +1516,7 @@ class RC2Stratified(RC2, object):
|
|
|
1350
1516
|
# i.e. all levels are finished and so all clauses are present
|
|
1351
1517
|
# thus, we need to simply call RC2 for the next model
|
|
1352
1518
|
self.done = -1 # we are done with stratification, disabling it
|
|
1353
|
-
if self.compute_()
|
|
1519
|
+
if self.compute_() != True:
|
|
1354
1520
|
return
|
|
1355
1521
|
|
|
1356
1522
|
# extracting a model
|
|
@@ -1365,6 +1531,11 @@ class RC2Stratified(RC2, object):
|
|
|
1365
1531
|
self.model = map(lambda l: int(copysign(self.vmap.i2e[abs(l)], l)), self.model)
|
|
1366
1532
|
self.model = sorted(self.model, key=lambda l: abs(l))
|
|
1367
1533
|
|
|
1534
|
+
# if formula processing was used, we should
|
|
1535
|
+
# restore the model for the original formula
|
|
1536
|
+
if self.processor:
|
|
1537
|
+
self.model = self.processor.restore(self.model)
|
|
1538
|
+
|
|
1368
1539
|
return self.model
|
|
1369
1540
|
|
|
1370
1541
|
def next_level(self):
|
|
@@ -1506,7 +1677,7 @@ class RC2Stratified(RC2, object):
|
|
|
1506
1677
|
super(RC2Stratified, self).process_sels()
|
|
1507
1678
|
|
|
1508
1679
|
# updating the list of literals in am1 after splitting the weights
|
|
1509
|
-
am1 =
|
|
1680
|
+
am1 = [l for l in am1 if l not in self.garbage]
|
|
1510
1681
|
|
|
1511
1682
|
# new selector
|
|
1512
1683
|
selv = self.pool.id()
|
|
@@ -1533,7 +1704,7 @@ class RC2Stratified(RC2, object):
|
|
|
1533
1704
|
to_deactivate.add(l)
|
|
1534
1705
|
|
|
1535
1706
|
# deactivating unnecessary selectors
|
|
1536
|
-
self.sels =
|
|
1707
|
+
self.sels = [l for l in self.sels if l not in to_deactivate]
|
|
1537
1708
|
|
|
1538
1709
|
# removing unnecessary assumptions
|
|
1539
1710
|
self.filter_assumps()
|
|
@@ -1573,7 +1744,7 @@ class RC2Stratified(RC2, object):
|
|
|
1573
1744
|
self.rels.append(-l)
|
|
1574
1745
|
|
|
1575
1746
|
# deactivating unnecessary selectors
|
|
1576
|
-
self.sels =
|
|
1747
|
+
self.sels = [l for l in self.sels if l not in to_deactivate]
|
|
1577
1748
|
|
|
1578
1749
|
def process_sums(self):
|
|
1579
1750
|
"""
|
|
@@ -1616,7 +1787,7 @@ class RC2Stratified(RC2, object):
|
|
|
1616
1787
|
self.rels.append(-l)
|
|
1617
1788
|
|
|
1618
1789
|
# deactivating unnecessary sums
|
|
1619
|
-
self.sums =
|
|
1790
|
+
self.sums = [l for l in self.sums if l not in to_deactivate]
|
|
1620
1791
|
|
|
1621
1792
|
|
|
1622
1793
|
#
|
|
@@ -1627,10 +1798,10 @@ def parse_options():
|
|
|
1627
1798
|
"""
|
|
1628
1799
|
|
|
1629
1800
|
try:
|
|
1630
|
-
opts, args = getopt.getopt(sys.argv[1:], 'ab:c:e:hil:
|
|
1801
|
+
opts, args = getopt.getopt(sys.argv[1:], 'ab:c:e:hil:mp:s:t:T:vx',
|
|
1631
1802
|
['adapt', 'block=', 'comp=', 'enum=', 'exhaust', 'help',
|
|
1632
|
-
'incr', 'blo=', 'minimize', '
|
|
1633
|
-
'vnew'])
|
|
1803
|
+
'incr', 'blo=', 'minimize', 'process=', 'solver=',
|
|
1804
|
+
'trim=', 'timeout=', 'verbose', 'vnew'])
|
|
1634
1805
|
except getopt.GetoptError as err:
|
|
1635
1806
|
sys.stderr.write(str(err).capitalize())
|
|
1636
1807
|
usage()
|
|
@@ -1644,8 +1815,10 @@ def parse_options():
|
|
|
1644
1815
|
incr = False
|
|
1645
1816
|
blo = 'none'
|
|
1646
1817
|
minz = False
|
|
1818
|
+
process = 0
|
|
1647
1819
|
solver = 'g3'
|
|
1648
1820
|
trim = 0
|
|
1821
|
+
timeout = None
|
|
1649
1822
|
verbose = 1
|
|
1650
1823
|
vnew = False
|
|
1651
1824
|
|
|
@@ -1671,10 +1844,15 @@ def parse_options():
|
|
|
1671
1844
|
blo = str(arg)
|
|
1672
1845
|
elif opt in ('-m', '--minimize'):
|
|
1673
1846
|
minz = True
|
|
1847
|
+
elif opt in ('-p', '--process'):
|
|
1848
|
+
process = int(arg)
|
|
1674
1849
|
elif opt in ('-s', '--solver'):
|
|
1675
1850
|
solver = str(arg)
|
|
1676
1851
|
elif opt in ('-t', '--trim'):
|
|
1677
1852
|
trim = int(arg)
|
|
1853
|
+
elif opt in ('-T', '--timeout'):
|
|
1854
|
+
if str(arg) != 'none':
|
|
1855
|
+
timeout = float(arg)
|
|
1678
1856
|
elif opt in ('-v', '--verbose'):
|
|
1679
1857
|
verbose += 1
|
|
1680
1858
|
elif opt == '--vnew':
|
|
@@ -1690,7 +1868,7 @@ def parse_options():
|
|
|
1690
1868
|
block = bmap[block]
|
|
1691
1869
|
|
|
1692
1870
|
return adapt, blo, block, cmode, to_enum, exhaust, incr, minz, \
|
|
1693
|
-
solver, trim, verbose, vnew, args
|
|
1871
|
+
process, solver, trim, timeout, verbose, vnew, args
|
|
1694
1872
|
|
|
1695
1873
|
|
|
1696
1874
|
#
|
|
@@ -1714,10 +1892,14 @@ def usage():
|
|
|
1714
1892
|
print(' -l, --blo=<string> Use BLO and stratification')
|
|
1715
1893
|
print(' Available values: basic, div, cluster, none, full (default = none)')
|
|
1716
1894
|
print(' -m, --minimize Use a heuristic unsatisfiable core minimizer')
|
|
1895
|
+
print(' -p, --process=<int> Number of processing rounds')
|
|
1896
|
+
print(' Available values: [0 .. INT_MAX] (default = 0)')
|
|
1717
1897
|
print(' -s, --solver=<string> SAT solver to use')
|
|
1718
|
-
print(' Available values: g3, g4, lgl, mcb, mcm, mpl, m22, mc, mgh (default = g3)')
|
|
1898
|
+
print(' Available values: cd15, cd19, g3, g4, lgl, mcb, mcm, mpl, m22, mc, mgh (default = g3)')
|
|
1719
1899
|
print(' -t, --trim=<int> How many times to trim unsatisfiable cores')
|
|
1720
1900
|
print(' Available values: [0 .. INT_MAX] (default = 0)')
|
|
1901
|
+
print(' -T, --timeout=<float> Set time limit for MaxSAT solver')
|
|
1902
|
+
print(' Available values: [0 .. FLOAT_MAX], none (default: none)')
|
|
1721
1903
|
print(' -v, --verbose Be verbose')
|
|
1722
1904
|
print(' --vnew Print v-line in the new format')
|
|
1723
1905
|
print(' -x, --exhaust Exhaust new unsatisfiable cores')
|
|
@@ -1726,12 +1908,12 @@ def usage():
|
|
|
1726
1908
|
#
|
|
1727
1909
|
#==============================================================================
|
|
1728
1910
|
if __name__ == '__main__':
|
|
1729
|
-
adapt, blo, block, cmode, to_enum, exhaust, incr, minz,
|
|
1730
|
-
verbose, vnew, files = parse_options()
|
|
1911
|
+
adapt, blo, block, cmode, to_enum, exhaust, incr, minz, process, solver, \
|
|
1912
|
+
trim, timeout, verbose, vnew, files = parse_options()
|
|
1731
1913
|
|
|
1732
1914
|
if files:
|
|
1733
1915
|
# parsing the input formula
|
|
1734
|
-
if re.search('\.wcnf[p|+]?(\.(gz|bz2|lzma|xz))?$', files[0]):
|
|
1916
|
+
if re.search(r'\.wcnf[p|+]?(\.(gz|bz2|lzma|xz|zst))?$', files[0]):
|
|
1735
1917
|
formula = WCNFPlus(from_file=files[0])
|
|
1736
1918
|
else: # expecting '*.cnf[,p,+].*'
|
|
1737
1919
|
formula = CNFPlus(from_file=files[0]).weighted()
|
|
@@ -1759,7 +1941,8 @@ if __name__ == '__main__':
|
|
|
1759
1941
|
|
|
1760
1942
|
# starting the solver
|
|
1761
1943
|
with MXS(formula, solver=solver, adapt=adapt, exhaust=exhaust,
|
|
1762
|
-
incr=incr, minz=minz, trim=trim,
|
|
1944
|
+
incr=incr, minz=minz, process=process, trim=trim,
|
|
1945
|
+
verbose=verbose) as rc2:
|
|
1763
1946
|
|
|
1764
1947
|
if isinstance(rc2, RC2Stratified):
|
|
1765
1948
|
rc2.bstr = blomap[blo] # select blo strategy
|
|
@@ -1768,14 +1951,26 @@ if __name__ == '__main__':
|
|
|
1768
1951
|
print('c hardening is disabled for model enumeration')
|
|
1769
1952
|
rc2.hard = False
|
|
1770
1953
|
|
|
1954
|
+
# setting a timer if necessary
|
|
1955
|
+
if timeout is not None:
|
|
1956
|
+
if verbose > 1:
|
|
1957
|
+
print('c timeout: {0}'.format(timeout))
|
|
1958
|
+
|
|
1959
|
+
timer = Timer(timeout, lambda s: s.interrupt(), [rc2])
|
|
1960
|
+
timer.start()
|
|
1961
|
+
else:
|
|
1962
|
+
timer = None
|
|
1963
|
+
|
|
1771
1964
|
optimum_found = False
|
|
1772
|
-
for i, model in enumerate(rc2.enumerate(block=block
|
|
1965
|
+
for i, model in enumerate(rc2.enumerate(block=block,
|
|
1966
|
+
expect_interrupt=timeout is not None), 1):
|
|
1773
1967
|
optimum_found = True
|
|
1774
1968
|
|
|
1775
1969
|
if verbose:
|
|
1776
1970
|
if i == 1:
|
|
1777
|
-
|
|
1778
|
-
|
|
1971
|
+
if not rc2.interrupted:
|
|
1972
|
+
print('s OPTIMUM FOUND')
|
|
1973
|
+
print('o {0}'.format(rc2.cost))
|
|
1779
1974
|
|
|
1780
1975
|
if verbose > 2:
|
|
1781
1976
|
if vnew: # new format of the v-line
|
|
@@ -1791,10 +1986,14 @@ if __name__ == '__main__':
|
|
|
1791
1986
|
print('v')
|
|
1792
1987
|
|
|
1793
1988
|
if verbose:
|
|
1794
|
-
if not optimum_found:
|
|
1989
|
+
if not optimum_found and not rc2.interrupted:
|
|
1795
1990
|
print('s UNSATISFIABLE')
|
|
1796
1991
|
elif to_enum != 1:
|
|
1797
1992
|
print('c models found:', i)
|
|
1798
1993
|
|
|
1799
1994
|
if verbose > 1:
|
|
1800
1995
|
print('c oracle time: {0:.4f}'.format(rc2.oracle_time()))
|
|
1996
|
+
|
|
1997
|
+
# cancelling the timer (if any) because we are done
|
|
1998
|
+
if timer:
|
|
1999
|
+
timer.cancel()
|