python-sat 1.9.dev6__cp314-cp314-pyemscripten_2026_0_wasm32.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.

Files changed (50) hide show
  1. pycard.cpython-314-wasm32-emscripten.so +0 -0
  2. pyformula.cpython-314-wasm32-emscripten.so +0 -0
  3. pysat/__init__.py +24 -0
  4. pysat/_fileio.py +224 -0
  5. pysat/_utils.py +58 -0
  6. pysat/allies/__init__.py +0 -0
  7. pysat/allies/approxmc.py +385 -0
  8. pysat/allies/unigen.py +435 -0
  9. pysat/card.py +802 -0
  10. pysat/engines.py +1328 -0
  11. pysat/examples/__init__.py +0 -0
  12. pysat/examples/bbscan.py +663 -0
  13. pysat/examples/bica.py +691 -0
  14. pysat/examples/fm.py +527 -0
  15. pysat/examples/genhard.py +516 -0
  16. pysat/examples/hitman.py +653 -0
  17. pysat/examples/lbx.py +638 -0
  18. pysat/examples/lsu.py +496 -0
  19. pysat/examples/mcsls.py +610 -0
  20. pysat/examples/models.py +189 -0
  21. pysat/examples/musx.py +344 -0
  22. pysat/examples/optux.py +710 -0
  23. pysat/examples/primer.py +620 -0
  24. pysat/examples/rc2.py +2033 -0
  25. pysat/examples/usage.py +63 -0
  26. pysat/formula.py +5777 -0
  27. pysat/integer.py +2606 -0
  28. pysat/pb.py +463 -0
  29. pysat/process.py +363 -0
  30. pysat/solvers.py +9082 -0
  31. pysolvers.cpython-314-wasm32-emscripten.so +0 -0
  32. python_sat-1.9.dev6.data/scripts/approxmc.py +385 -0
  33. python_sat-1.9.dev6.data/scripts/bbscan.py +663 -0
  34. python_sat-1.9.dev6.data/scripts/bica.py +691 -0
  35. python_sat-1.9.dev6.data/scripts/fm.py +527 -0
  36. python_sat-1.9.dev6.data/scripts/genhard.py +516 -0
  37. python_sat-1.9.dev6.data/scripts/lbx.py +638 -0
  38. python_sat-1.9.dev6.data/scripts/lsu.py +496 -0
  39. python_sat-1.9.dev6.data/scripts/mcsls.py +610 -0
  40. python_sat-1.9.dev6.data/scripts/models.py +189 -0
  41. python_sat-1.9.dev6.data/scripts/musx.py +344 -0
  42. python_sat-1.9.dev6.data/scripts/optux.py +710 -0
  43. python_sat-1.9.dev6.data/scripts/primer.py +620 -0
  44. python_sat-1.9.dev6.data/scripts/rc2.py +2033 -0
  45. python_sat-1.9.dev6.data/scripts/unigen.py +435 -0
  46. python_sat-1.9.dev6.dist-info/METADATA +45 -0
  47. python_sat-1.9.dev6.dist-info/RECORD +50 -0
  48. python_sat-1.9.dev6.dist-info/WHEEL +5 -0
  49. python_sat-1.9.dev6.dist-info/licenses/LICENSE.txt +21 -0
  50. python_sat-1.9.dev6.dist-info/top_level.txt +4 -0
@@ -0,0 +1,2033 @@
1
+ #!python
2
+ #-*- coding:utf-8 -*-
3
+ ##
4
+ ## rc2.py
5
+ ##
6
+ ## Created on: Dec 2, 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
+ RC2
20
+ RC2Stratified
21
+
22
+ ==================
23
+ Module description
24
+ ==================
25
+
26
+ An implementation of the RC2 algorithm for solving maximum
27
+ satisfiability. RC2 stands for *relaxable cardinality constraints*
28
+ (alternatively, *soft cardinality constraints*) and represents an
29
+ improved version of the OLLITI algorithm, which was described in
30
+ [1]_ and [2]_ and originally implemented in the `MSCG MaxSAT
31
+ solver <https://reason.di.fc.ul.pt/wiki/doku.php?id=mscg>`_.
32
+
33
+ Initially, this solver was supposed to serve as an example of a possible
34
+ PySAT usage illustrating how a state-of-the-art MaxSAT algorithm could be
35
+ implemented in Python and still be efficient. It participated in the
36
+ `MaxSAT Evaluations 2018
37
+ <https://maxsat-evaluations.github.io/2018/rankings.html>`_ and `2019
38
+ <https://maxsat-evaluations.github.io/2019/rankings.html>`_ where,
39
+ surprisingly, it was ranked first in two complete categories: *unweighted*
40
+ and *weighted*. A brief solver description can be found in [3]_. A more
41
+ detailed solver description can be found in [4]_.
42
+
43
+ .. [1] António Morgado, Carmine Dodaro, Joao Marques-Silva.
44
+ *Core-Guided MaxSAT with Soft Cardinality Constraints*. CP
45
+ 2014. pp. 564-573
46
+
47
+ .. [2] António Morgado, Alexey Ignatiev, Joao Marques-Silva.
48
+ *MSCG: Robust Core-Guided MaxSAT Solving*. JSAT 9. 2014.
49
+ pp. 129-134
50
+
51
+ .. [3] Alexey Ignatiev, António Morgado, Joao Marques-Silva.
52
+ *RC2: A Python-based MaxSAT Solver*. MaxSAT Evaluation 2018.
53
+ p. 22
54
+
55
+ .. [4] Alexey Ignatiev, António Morgado, Joao Marques-Silva.
56
+ *RC2: An Efficient MaxSAT Solver*. MaxSAT Evaluation 2018.
57
+ JSAT 11. 2019. pp. 53-64
58
+
59
+ The file implements two classes: :class:`RC2` and
60
+ :class:`RC2Stratified`. The former class is the basic
61
+ implementation of the algorithm, which can be applied to a MaxSAT
62
+ formula in the :class:`.WCNFPlus` format. The latter class
63
+ additionally implements Boolean lexicographic optimization (BLO)
64
+ [5]_ and stratification [6]_ on top of :class:`RC2`.
65
+
66
+ .. [5] Joao Marques-Silva, Josep Argelich, Ana Graça, Inês Lynce.
67
+ *Boolean lexicographic optimization: algorithms &
68
+ applications*. Ann. Math. Artif. Intell. 62(3-4). 2011.
69
+ pp. 317-343
70
+
71
+ .. [6] Carlos Ansótegui, Maria Luisa Bonet, Joel Gabàs, Jordi
72
+ Levy. *Improving WPM2 for (Weighted) Partial MaxSAT*. CP
73
+ 2013. pp. 117-132
74
+
75
+ The implementation can be used as an executable (the list of
76
+ available command-line options can be shown using ``rc2.py -h``)
77
+ in the following way:
78
+
79
+ ::
80
+
81
+ $ xzcat formula.wcnf.xz
82
+ p wcnf 3 6 4
83
+ 1 1 0
84
+ 1 2 0
85
+ 1 3 0
86
+ 4 -1 -2 0
87
+ 4 -1 -3 0
88
+ 4 -2 -3 0
89
+
90
+ $ rc2.py -vv formula.wcnf.xz
91
+ c formula: 3 vars, 3 hard, 3 soft
92
+ c cost: 1; core sz: 2; soft sz: 2
93
+ c cost: 2; core sz: 2; soft sz: 1
94
+ s OPTIMUM FOUND
95
+ o 2
96
+ v -1 -2 3
97
+ c oracle time: 0.0001
98
+
99
+ Alternatively, the algorithm can be accessed and invoked through the
100
+ standard ``import`` interface of Python, e.g.
101
+
102
+ .. code-block:: python
103
+
104
+ >>> from pysat.examples.rc2 import RC2
105
+ >>> from pysat.formula import WCNF
106
+ >>>
107
+ >>> wcnf = WCNF(from_file='formula.wcnf.xz')
108
+ >>>
109
+ >>> with RC2(wcnf) as rc2:
110
+ ... for m in rc2.enumerate():
111
+ ... print('model {0} has cost {1}'.format(m, rc2.cost))
112
+ model [-1, -2, 3] has cost 2
113
+ model [1, -2, -3] has cost 2
114
+ model [-1, 2, -3] has cost 2
115
+ model [-1, -2, -3] has cost 3
116
+
117
+ As can be seen in the example above, the solver can be instructed
118
+ either to compute one MaxSAT solution of an input formula, or to
119
+ enumerate a given number (or *all*) of its top MaxSAT solutions.
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
+
126
+ ==============
127
+ Module details
128
+ ==============
129
+ """
130
+
131
+ #
132
+ #==============================================================================
133
+ from __future__ import print_function
134
+ import bisect
135
+ import collections
136
+ import getopt
137
+ import itertools
138
+ from math import copysign
139
+ import os
140
+ from pysat.formula import CNFPlus, WCNFPlus, IDPool
141
+ from pysat.card import ITotalizer
142
+ from pysat.process import Processor
143
+ from pysat.solvers import Solver, SolverNames
144
+ from threading import Timer
145
+ import re
146
+ import six
147
+ from six.moves import range
148
+ import sys
149
+
150
+
151
+ # names of BLO strategies
152
+ #==============================================================================
153
+ blomap = {'none': 0, 'basic': 1, 'div': 3, 'cluster': 5, 'full': 7}
154
+
155
+
156
+ #
157
+ #==============================================================================
158
+ class RC2(object):
159
+ """
160
+ Implementation of the basic RC2 algorithm. Given a (weighted)
161
+ (partial) CNF formula, i.e. formula in the :class:`.WCNFPlus`
162
+ format, this class can be used to compute a given number of
163
+ MaxSAT solutions for the input formula. :class:`RC2` roughly
164
+ follows the implementation of algorithm OLLITI [1]_ [2]_ of
165
+ MSCG and applies a few heuristics on top of it. These include
166
+
167
+ - *unsatisfiable core exhaustion* (see method :func:`exhaust_core`),
168
+ - *unsatisfiable core reduction* (see method :func:`minimize_core`),
169
+ - *intrinsic AtMost1 constraints* (see method :func:`adapt_am1`).
170
+
171
+ :class:`RC2` can use any SAT solver available in PySAT. The default
172
+ SAT solver to use is ``g3`` (see :class:`.SolverNames`). Additionally,
173
+ if Glucose is chosen, the ``incr`` parameter controls whether to use
174
+ the incremental mode of Glucose [7]_ (turned off by default). Boolean
175
+ parameters ``adapt``, ``exhaust``, and ``minz`` control whether or to
176
+ apply detection and adaptation of intrinsic AtMost1 constraints, core
177
+ exhaustion, and core reduction. Unsatisfiable cores can be trimmed if
178
+ the ``trim`` parameter is set to a non-zero integer. Formula
179
+ preprocessing can be applied a given number of rounds specified as the
180
+ value of parameter ``process``. Finally, verbosity level can be set
181
+ using the ``verbose`` parameter.
182
+
183
+ .. [7] Gilles Audemard, Jean-Marie Lagniez, Laurent Simon.
184
+ *Improving Glucose for Incremental SAT Solving with
185
+ Assumptions: Application to MUS Extraction*. SAT 2013.
186
+ pp. 309-317
187
+
188
+ :param formula: (weighted) (partial) CNFPlus formula
189
+ :param solver: SAT oracle name
190
+ :param adapt: detect and adapt intrinsic AtMost1 constraints
191
+ :param exhaust: do core exhaustion
192
+ :param incr: use incremental mode of Glucose
193
+ :param minz: do heuristic core reduction
194
+ :param process: apply formula preprocessing this many times
195
+ :param trim: do core trimming at most this number of times
196
+ :param verbose: verbosity level
197
+
198
+ :type formula: :class:`.WCNFPlus`
199
+ :type solver: str
200
+ :type adapt: bool
201
+ :type exhaust: bool
202
+ :type incr: bool
203
+ :type minz: bool
204
+ :type process: int
205
+ :type trim: int
206
+ :type verbose: int
207
+ """
208
+
209
+ def __init__(self, formula, solver='g3', adapt=False, exhaust=False,
210
+ incr=False, minz=False, process=0, trim=0, verbose=0):
211
+ """
212
+ Constructor.
213
+ """
214
+
215
+ # saving verbosity level and other options
216
+ self.verbose = verbose
217
+ self.exhaust = exhaust
218
+ self.process = process
219
+ self.solver = solver
220
+ self.adapt = adapt
221
+ self.minz = minz
222
+ self.trim = trim
223
+
224
+ # oracles are initialised to be None
225
+ self.oracle, self.processor = None, None
226
+
227
+ # parameters related to asynchronous interruption
228
+ self.expect_interrupt, self.interrupted = False, False
229
+
230
+ # clause selectors and mapping from selectors to clause ids
231
+ # .sall, .s2cl, and .sneg are required only for model enumeration
232
+ self.sels, self.smap, self.sall, self.s2cl, self.sneg = [], {}, [], {}, set([])
233
+
234
+ # other MaxSAT related stuff
235
+ self.pool = IDPool(start_from=formula.nv + 1)
236
+ self.wght = {} # weights of soft clauses
237
+ self.sums = [] # totalizer sum assumptions
238
+ self.bnds = {} # a mapping from sum assumptions to totalizer bounds
239
+ self.tobj = {} # a mapping from sum assumptions to totalizer objects
240
+ self.swgt = {} # a mapping from sum assumptions to their core weights
241
+ self.cost = 0
242
+
243
+ # mappings between internal and external variables
244
+ VariableMap = collections.namedtuple('VariableMap', ['e2i', 'i2e'])
245
+ self.vmap = VariableMap(e2i={}, i2e={})
246
+
247
+ # initialize SAT oracle with hard clauses only
248
+ self.init(formula, incr=incr)
249
+
250
+ # core minimization is going to be extremely expensive
251
+ # for large plain formulas, and so we turn it off here
252
+ wght = self.wght.values()
253
+ if not formula.hard and len(self.sels) > 100000 and min(wght) == max(wght):
254
+ self.minz = False
255
+
256
+ def _call_oracle(self, assumptions=[], expect_interrupt=False):
257
+ """
258
+ Makes a call to the internal SAT solver by means of invoking
259
+ `oracle.solve_limited()`. The two arguments are the list of
260
+ assumption literals and the Boolean flag indicating whether the
261
+ call can be interrupted.
262
+
263
+ :param assumptions: a list of assumption literals.
264
+ :param expect_interrupt: whether :meth:`interrupt` may be called
265
+
266
+ :type assumptions: iterable(int)
267
+ :type expect_interrupt: bool
268
+ """
269
+
270
+ return self.oracle.solve_limited(assumptions=assumptions,
271
+ expect_interrupt=expect_interrupt)
272
+
273
+ def __del__(self):
274
+ """
275
+ Destructor.
276
+ """
277
+
278
+ self.delete()
279
+
280
+ def __enter__(self):
281
+ """
282
+ 'with' constructor.
283
+ """
284
+
285
+ return self
286
+
287
+ def __exit__(self, exc_type, exc_value, traceback):
288
+ """
289
+ 'with' destructor.
290
+ """
291
+
292
+ self.delete()
293
+
294
+ def init(self, formula, incr=False):
295
+ """
296
+ Initialize the internal SAT oracle. The oracle is used
297
+ incrementally and so it is initialized only once when
298
+ constructing an object of class :class:`RC2`. Given an
299
+ input :class:`.WCNFPlus` formula, the method bootstraps the
300
+ oracle with its hard clauses. It also augments the soft
301
+ clauses with "fresh" selectors and adds them to the oracle
302
+ afterwards.
303
+
304
+ Optional input parameter ``incr`` (``False`` by default)
305
+ regulates whether or not Glucose's incremental mode [7]_
306
+ is turned on.
307
+
308
+ :param formula: input formula
309
+ :param incr: apply incremental mode of Glucose
310
+
311
+ :type formula: :class:`.WCNFPlus`
312
+ :type incr: bool
313
+ """
314
+
315
+ # creating a solver object
316
+ self.oracle = Solver(name=self.solver,
317
+ bootstrap_with=formula.hard if self.process == 0 else [],
318
+ incr=incr, use_timer=True)
319
+
320
+ # adding native cardinality constraints (if any) as hard clauses
321
+ # this can be done only if the Minicard solver is in use
322
+ # this cannot be done if RC2 is run from the command line
323
+ if isinstance(formula, WCNFPlus) and formula.atms:
324
+ # we are using CaDiCaL195 and it can use external linear engine
325
+ if self.solver in SolverNames.cadical195:
326
+ self.oracle.activate_atmost()
327
+
328
+ assert self.oracle.supports_atmost(), \
329
+ '{0} does not support native cardinality constraints. Make sure you use the right type of formula.'.format(self.solver)
330
+
331
+ for atm in formula.atms:
332
+ self.oracle.add_atmost(*atm)
333
+
334
+ # adding soft clauses to oracle
335
+ for i, cl in enumerate(formula.soft):
336
+ selv = cl[0] # if clause is unit, selector variable is its literal
337
+
338
+ if len(cl) > 1:
339
+ selv = self.pool.id()
340
+
341
+ self.s2cl[selv] = cl[:]
342
+ cl.append(-selv)
343
+
344
+ if self.process == 0:
345
+ self.oracle.add_clause(cl)
346
+ else:
347
+ # adding to formula's hard clauses
348
+ # if any preprocessing is required
349
+ formula.hard.append(cl)
350
+ formula.soft[i] = [selv]
351
+
352
+ if selv not in self.wght:
353
+ # record selector and its weight
354
+ self.sels.append(selv)
355
+ self.wght[selv] = formula.wght[i]
356
+ self.smap[selv] = i
357
+ else:
358
+ # selector is not new; increment its weight
359
+ self.wght[selv] += formula.wght[i]
360
+
361
+ # storing the set of selectors
362
+ self.sels_set = set(self.sels)
363
+ self.sall = self.sels[:]
364
+
365
+ # we may end up having zero-weighed soft clauses
366
+ self.garbage = set([l for l in self.sels if self.wght[l] == 0])
367
+ if self.garbage:
368
+ self.filter_assumps()
369
+
370
+ # hard clauses are added last if formula processing has to be done
371
+ if self.process > 0:
372
+ self.processor = Processor(bootstrap_with=formula.hard)
373
+ hard = self.processor.process(rounds=self.process, freeze=self.sels)
374
+ self.oracle.append_formula(hard)
375
+
376
+ # at this point internal and external variables are the same
377
+ for v in range(1, formula.nv + 1):
378
+ self.vmap.e2i[v] = v
379
+ self.vmap.i2e[v] = v
380
+
381
+ if self.verbose > 1:
382
+ nofh = len(hard.clauses) if self.processor else len(formula.hard)
383
+ print('c formula: {0} vars, {1} hard, {2} soft'.format(formula.nv,
384
+ nofh, len(formula.soft)))
385
+
386
+ def add_clause(self, clause, weight=None):
387
+ """
388
+ The method for adding a new hard of soft clause to the
389
+ problem formula. Although the input formula is to be
390
+ specified as an argument of the constructor of
391
+ :class:`RC2`, adding clauses may be helpful when
392
+ *enumerating* MaxSAT solutions of the formula. This way,
393
+ the clauses are added incrementally, i.e. *on the fly*.
394
+
395
+ The clause to add can be any iterable over integer
396
+ literals. The additional integer parameter ``weight`` can
397
+ be set to meaning the the clause being added is soft
398
+ having the corresponding weight (note that parameter
399
+ ``weight`` is set to ``None`` by default meaning that the
400
+ clause is hard).
401
+
402
+ Also note that besides pure clauses, the method can also expect
403
+ native cardinality constraints represented as a pair ``(lits,
404
+ bound)``. Only hard cardinality constraints can be added.
405
+
406
+ :param clause: a clause to add
407
+ :param weight: weight of the clause (if any)
408
+
409
+ :type clause: iterable(int)
410
+ :type weight: int
411
+
412
+ :return: selector literal for a soft clause and ``None`` for a hard one
413
+ :rtype: int or None
414
+
415
+ .. code-block:: python
416
+
417
+ >>> from pysat.examples.rc2 import RC2
418
+ >>> from pysat.formula import WCNF
419
+ >>>
420
+ >>> wcnf = WCNF()
421
+ >>> wcnf.append([-1, -2]) # adding hard clauses
422
+ >>> wcnf.append([-1, -3])
423
+ >>>
424
+ >>> wcnf.append([1], weight=1) # adding soft clauses
425
+ >>> wcnf.append([2], weight=1)
426
+ >>> wcnf.append([3], weight=1)
427
+ >>>
428
+ >>> with RC2(wcnf) as rc2:
429
+ ... rc2.compute() # solving the MaxSAT problem
430
+ [-1, 2, 3]
431
+ ... print(rc2.cost)
432
+ 1
433
+ ... rc2.add_clause([-2, -3]) # adding one more hard clause
434
+ ... rc2.compute() # computing another model
435
+ [-1, -2, 3]
436
+ ... print(rc2.cost)
437
+ 2
438
+ """
439
+
440
+ # first, map external literals to internal literals
441
+ # introduce new variables if necessary
442
+ cl = list(map(lambda l: self._map_extlit(l), clause if not len(clause) == 2 or not type(clause[0]) in (list, tuple, set) else clause[0]))
443
+
444
+ if not weight:
445
+ if not len(clause) == 2 or not type(clause[0]) in (list, tuple, set):
446
+ # the clause is hard, and so we simply add it to the SAT oracle
447
+ self.oracle.add_clause(cl)
448
+ else:
449
+ # this should be a native cardinality constraint,
450
+ # which can be used only together with Minicard
451
+ assert self.oracle.supports_atmost(), \
452
+ '{0} does not support native cardinality constraints. Make sure you use the right type of formula.'.format(self.solver)
453
+
454
+ self.oracle.add_atmost(cl, clause[1], weights=clause[2] if len(clause) == 3 else [])
455
+
456
+ return None
457
+ else:
458
+ # soft clauses should be augmented with a selector
459
+ selv = cl[0] # for a unit clause, no selector is needed
460
+
461
+ if len(cl) > 1:
462
+ selv = self.pool.id()
463
+
464
+ self.s2cl[selv] = cl[:]
465
+ cl.append(-selv)
466
+ self.oracle.add_clause(cl)
467
+
468
+ if selv not in self.wght:
469
+ # record selector and its weight
470
+ self.sels.append(selv)
471
+ self.wght[selv] = weight
472
+ self.smap[selv] = len(self.sels) - 1
473
+ else:
474
+ # selector is not new; increment its weight
475
+ self.wght[selv] += weight
476
+
477
+ self.sall.append(selv)
478
+ self.sels_set.add(selv)
479
+
480
+ return selv
481
+
482
+ def delete(self):
483
+ """
484
+ Explicit destructor of the internal SAT oracle and all the
485
+ totalizer objects creating during the solving process. This also
486
+ destroys the processor (if any).
487
+ """
488
+
489
+ if self.oracle:
490
+ if not self.oracle.supports_atmost(): # for minicard-like, there is nothing to free
491
+ for t in six.itervalues(self.tobj):
492
+ t.delete()
493
+
494
+ self.oracle.delete()
495
+ self.oracle = None
496
+
497
+ if self.processor:
498
+ self.processor.delete()
499
+ self.processor = None
500
+
501
+ def compute(self, expect_interrupt=False):
502
+ """
503
+ This method can be used for computing one MaxSAT solution,
504
+ i.e. for computing an assignment satisfying all hard
505
+ clauses of the input formula and maximizing the sum of
506
+ weights of satisfied soft clauses. It is a wrapper for the
507
+ internal :func:`compute_` method, which does the job,
508
+ followed by the model extraction.
509
+
510
+ Note that the method returns ``None`` if no MaxSAT model
511
+ exists. The method can be called multiple times, each
512
+ being followed by blocking the last model. This way one
513
+ can enumerate top-:math:`k` MaxSAT solutions (this can
514
+ also be done by calling :meth:`enumerate()`).
515
+
516
+ This MaxSAT call can be asynchronously interrupted, in which case
517
+ the value of ``expect_interrupt`` must be set to ``True``.
518
+
519
+ :param expect_interrupt: whether :meth:`interrupt` may be called
520
+ :type expect_interrupt: bool
521
+
522
+ :return: a MaxSAT model
523
+ :rtype: list(int)
524
+
525
+ .. code-block:: python
526
+
527
+ >>> from pysat.examples.rc2 import RC2
528
+ >>> from pysat.formula import WCNF
529
+ >>>
530
+ >>> rc2 = RC2(WCNF()) # passing an empty WCNF() formula
531
+ >>> rc2.add_clause([-1, -2])
532
+ >>> rc2.add_clause([-1, -3])
533
+ >>> rc2.add_clause([-2, -3])
534
+ >>>
535
+ >>> rc2.add_clause([1], weight=1)
536
+ >>> rc2.add_clause([2], weight=1)
537
+ >>> rc2.add_clause([3], weight=1)
538
+ >>>
539
+ >>> model = rc2.compute()
540
+ >>> print(model)
541
+ [-1, -2, 3]
542
+ >>> print(rc2.cost)
543
+ 2
544
+ >>> rc2.delete()
545
+ """
546
+
547
+ # keeping the current interruption preference
548
+ self.expect_interrupt = expect_interrupt
549
+
550
+ # simply apply MaxSAT only once
551
+ res = self.compute_()
552
+
553
+ if res:
554
+ # extracting a model
555
+ self.model = self.oracle.get_model()
556
+
557
+ if self.model is None and self.pool.top == 0:
558
+ # we seem to have been given an empty formula
559
+ # so let's transform the None model returned to []
560
+ self.model = []
561
+
562
+ self.model = filter(lambda l: abs(l) in self.vmap.i2e, self.model)
563
+ self.model = map(lambda l: int(copysign(self.vmap.i2e[abs(l)], l)), self.model)
564
+ self.model = sorted(self.model, key=lambda l: abs(l))
565
+
566
+ # if formula processing was used, we should
567
+ # restore the model for the original formula
568
+ if self.processor:
569
+ self.model = self.processor.restore(self.model)
570
+
571
+ return self.model
572
+
573
+ def enumerate(self, block=0, expect_interrupt=False):
574
+ """
575
+ Enumerate top MaxSAT solutions (from best to worst). The
576
+ method works as a generator, which iteratively calls
577
+ :meth:`compute` to compute a MaxSAT model, blocks it
578
+ internally and returns it.
579
+
580
+ An optional parameter can be used to enforce computation of MaxSAT
581
+ models corresponding to different maximal satisfiable subsets
582
+ (MSSes) or minimal correction subsets (MCSes). To block MSSes, one
583
+ should set the ``block`` parameter to ``1``. To block MCSes, set
584
+ it to ``-1``. By the default (for blocking MaxSAT models),
585
+ ``block`` is set to ``0``.
586
+
587
+ This MaxSAT enumeration call can be asynchronously interrupted, in
588
+ which case the value of ``expect_interrupt`` must be set to
589
+ ``True``.
590
+
591
+ :param block: preferred way to block solutions when enumerating
592
+ :param expect_interrupt: whether :meth:`interrupt` may be called
593
+
594
+ :type block: int
595
+ :type expect_interrupt: bool
596
+
597
+ :return: a MaxSAT model
598
+ :rtype: list(int)
599
+
600
+ .. code-block:: python
601
+
602
+ >>> from pysat.examples.rc2 import RC2
603
+ >>> from pysat.formula import WCNF
604
+ >>>
605
+ >>> rc2 = RC2(WCNF()) # passing an empty WCNF() formula
606
+ >>> rc2.add_clause([-1, -2]) # adding clauses "on the fly"
607
+ >>> rc2.add_clause([-1, -3])
608
+ >>> rc2.add_clause([-2, -3])
609
+ >>>
610
+ >>> rc2.add_clause([1], weight=1)
611
+ >>> rc2.add_clause([2], weight=1)
612
+ >>> rc2.add_clause([3], weight=1)
613
+ >>>
614
+ >>> for model in rc2.enumerate():
615
+ ... print(model, rc2.cost)
616
+ [-1, -2, 3] 2
617
+ [1, -2, -3] 2
618
+ [-1, 2, -3] 2
619
+ [-1, -2, -3] 3
620
+ >>> rc2.delete()
621
+ """
622
+
623
+ done = False
624
+ while not done:
625
+ model = self.compute(expect_interrupt=expect_interrupt)
626
+
627
+ if model != None:
628
+ if block == 1:
629
+ # to block an MSS corresponding to the model, we add
630
+ # a clause enforcing at least one of the MSS clauses
631
+ # to be falsified next time
632
+ m, cl = set(self.oracle.get_model()), []
633
+
634
+ for selv in self.sall:
635
+ if selv in m:
636
+ # clause is satisfied
637
+ cl.append(-selv)
638
+
639
+ # next time we want to falsify one of these
640
+ # clauses, i.e. we should encode the negation
641
+ # of each of these selectors
642
+ if selv in self.s2cl and not selv in self.sneg:
643
+ self.sneg.add(selv)
644
+ for il in self.s2cl[selv]:
645
+ self.oracle.add_clause([selv, -il])
646
+
647
+ self.oracle.add_clause(cl)
648
+ elif block == -1:
649
+ # a similar (but simpler) piece of code goes here,
650
+ # to block the MCS corresponding to the model
651
+ # (this blocking is stronger than MSS blocking above)
652
+ m = set(self.oracle.get_model())
653
+ self.oracle.add_clause([l for l in filter(lambda l: -l in m, self.sall)])
654
+ else:
655
+ # here, we simply block a previous MaxSAT model
656
+ self.add_clause([-l for l in model])
657
+
658
+ yield model
659
+ else:
660
+ done = True
661
+
662
+ def compute_(self):
663
+ """
664
+ Main core-guided loop, which iteratively calls a SAT
665
+ oracle, extracts a new unsatisfiable core and processes
666
+ it. The loop finishes as soon as a satisfiable formula is
667
+ obtained. If specified in the command line, the method
668
+ additionally calls :meth:`adapt_am1` to detect and adapt
669
+ intrinsic AtMost1 constraints before executing the loop.
670
+
671
+ :rtype: bool
672
+ """
673
+
674
+ # trying to adapt (simplify) the formula
675
+ # by detecting and using atmost1 constraints
676
+ if self.adapt:
677
+ self.adapt_am1()
678
+
679
+ # at the beginning, the solver is not interrupted
680
+ self.interrupted = False
681
+
682
+ # main solving loop
683
+ while not self._call_oracle(assumptions=self.sels + self.sums,
684
+ expect_interrupt=self.expect_interrupt):
685
+
686
+ # even if the call has been interrupted, we
687
+ # still need to finish the current iteration
688
+ if self.oracle.get_status() is None:
689
+ self.clear_interrupt()
690
+
691
+ if self.verbose > 1:
692
+ print('c interrupted; processing the last found core')
693
+
694
+ self.get_core()
695
+
696
+ if not self.core:
697
+ # core is empty, i.e. hard part is unsatisfiable
698
+ return False
699
+
700
+ self.process_core()
701
+
702
+ if self.verbose > 1:
703
+ print('c cost: {0}; core sz: {1}; soft sz: {2}'.format(self.cost,
704
+ len(self.core), len(self.sels) + len(self.sums)))
705
+
706
+ # the solver got interrupted => returning None
707
+ if self.interrupted:
708
+ return # None
709
+
710
+ return True
711
+
712
+ def get_core(self):
713
+ """
714
+ Extract unsatisfiable core. The result of the procedure is
715
+ stored in variable ``self.core``. If necessary, core
716
+ trimming and also heuristic core reduction is applied
717
+ depending on the command-line options. A *minimum weight*
718
+ of the core is computed and stored in ``self.minw``.
719
+ Finally, the core is divided into two parts:
720
+
721
+ 1. clause selectors (``self.core_sels``),
722
+ 2. sum assumptions (``self.core_sums``).
723
+ """
724
+
725
+ # extracting the core
726
+ self.core = self.oracle.get_core()
727
+
728
+ if self.core:
729
+ # try to reduce the core by trimming
730
+ self.trim_core()
731
+
732
+ # and by heuristic minimization
733
+ self.minimize_core()
734
+
735
+ # the core may be empty after core minimization
736
+ if not self.core:
737
+ return
738
+
739
+ # core weight
740
+ self.minw = min(map(lambda l: self.wght[l], self.core))
741
+
742
+ # dividing the core into two parts
743
+ iter1, iter2 = itertools.tee(self.core)
744
+ self.core_sels = list(l for l in iter1 if l in self.sels_set)
745
+ self.core_sums = list(l for l in iter2 if l not in self.sels_set)
746
+
747
+ def process_core(self):
748
+ """
749
+ The method deals with a core found previously in
750
+ :func:`get_core`. Clause selectors ``self.core_sels`` and
751
+ sum assumptions involved in the core are treated
752
+ separately of each other. This is handled by calling
753
+ methods :func:`process_sels` and :func:`process_sums`,
754
+ respectively. Whenever necessary, both methods relax the
755
+ core literals, which is followed by creating a new
756
+ totalizer object encoding the sum of the new relaxation
757
+ variables. The totalizer object can be "exhausted"
758
+ depending on the option.
759
+ """
760
+
761
+ # updating the cost
762
+ self.cost += self.minw
763
+
764
+ if len(self.core_sels) != 1 or len(self.core_sums) > 0:
765
+ # process selectors in the core
766
+ self.process_sels()
767
+
768
+ # process previously introducded sums in the core
769
+ self.process_sums()
770
+
771
+ if len(self.rels) > 1:
772
+ # create a new cardunality constraint
773
+ t = self.create_sum()
774
+
775
+ # apply core exhaustion if required
776
+ b = self.exhaust_core(t) if self.exhaust else 1
777
+
778
+ if b:
779
+ # save the info about this sum and
780
+ # add its assumption literal
781
+ self.set_bound(t, b, self.minw)
782
+ else:
783
+ # impossible to satisfy any of these clauses
784
+ # they must become hard
785
+ for relv in self.rels:
786
+ self.oracle.add_clause([relv])
787
+ else:
788
+ # unit cores are treated differently
789
+ # (their negation is added to the hard part)
790
+ self.oracle.add_clause([-self.core_sels[0]])
791
+ self.garbage.add(self.core_sels[0])
792
+
793
+ # remove unnecessary assumptions
794
+ self.filter_assumps()
795
+
796
+ def adapt_am1(self):
797
+ """
798
+ Detect and adapt intrinsic AtMost1 constraints. Assume
799
+ there is a subset of soft clauses
800
+ :math:`\\mathcal{S}'\\subseteq \\mathcal{S}` s.t.
801
+ :math:`\\sum_{c\\in\\mathcal{S}'}{c\\leq 1}`, i.e. at most
802
+ one of the clauses of :math:`\\mathcal{S}'` can be
803
+ satisfied.
804
+
805
+ Each AtMost1 relationship between the soft clauses can be
806
+ detected in the following way. The method traverses all
807
+ soft clauses of the formula one by one, sets one
808
+ respective selector literal to true and checks whether
809
+ some other soft clauses are forced to be false. This is
810
+ checked by testing if selectors for other soft clauses are
811
+ unit-propagated to be false. Note that this method for
812
+ detection of AtMost1 constraints is *incomplete*, because
813
+ in general unit propagation does not suffice to test
814
+ whether or not :math:`\\mathcal{F}\\wedge l_i\\models
815
+ \\neg{l_j}`.
816
+
817
+ Each intrinsic AtMost1 constraint detected this way is
818
+ handled by calling :func:`process_am1`.
819
+ """
820
+
821
+ # literal connections and selectors conflicting on their own
822
+ conns = collections.defaultdict(list)
823
+ confl = []
824
+
825
+ # prepare connections
826
+ for l1 in self.sels:
827
+ st, props = self.oracle.propagate(assumptions=[l1], phase_saving=2)
828
+ if st:
829
+ for l2 in props:
830
+ l2 = -l2
831
+ if l2 in self.sels_set:
832
+ conns[l1].append(l2)
833
+ conns[l2].append(l1)
834
+ else:
835
+ # propagating this literal results in a conflict
836
+ confl.append(l1)
837
+
838
+ # sort (in-place) and deduplicate adjacency lists
839
+ for l in list(conns):
840
+ neigh = conns[l]
841
+ if neigh:
842
+ neigh.sort()
843
+
844
+ j = 1
845
+ for i in range(1, len(neigh)):
846
+ if neigh[i] != neigh[j - 1]:
847
+ neigh[j] = neigh[i]
848
+ j += 1
849
+
850
+ del neigh[j:]
851
+
852
+ if confl:
853
+ # processing unit size cores
854
+ for l in confl:
855
+ self.core, self.minw = [l], self.wght[l]
856
+ self.core_sels, self.core_sums = [l], []
857
+ self.process_core()
858
+
859
+ if self.verbose > 1:
860
+ print('c unit cores found: {0}; cost: {1}'.format(len(confl),
861
+ self.cost))
862
+
863
+ # the following graph manipulation requires
864
+ # fast membership checks for confl; making it a set
865
+ confl = set(confl)
866
+
867
+ # current degrees in the remaining graph;
868
+ # literals disconnected after removing conflicts are ignored
869
+ degree, lits = {}, set()
870
+ for l in conns:
871
+ if l not in confl:
872
+ d = sum(1 for l2 in conns[l] if l2 not in confl)
873
+ if d:
874
+ degree[l] = d
875
+ lits.add(l)
876
+
877
+ # detect AM1s on the original formula with conflicting selectors
878
+ # filtered out, similarly to the previous implementation
879
+ nof_am1 = 0
880
+ len_am1 = []
881
+
882
+ while lits:
883
+ am1 = [min(lits, key=lambda l: degree[l])]
884
+
885
+ for l in sorted(conns[am1[0]], key=lambda l: degree.get(l, 0)):
886
+ if l in lits:
887
+ for l_added in am1[1:]:
888
+ # adjacency lists are sorted, so we use
889
+ # binary search for clique membership tests
890
+ i = bisect.bisect_left(conns[l], l_added)
891
+ if i == len(conns[l]) or conns[l][i] != l_added:
892
+ break
893
+ else:
894
+ am1.append(l)
895
+
896
+ # updating remaining literals and their degrees
897
+ am1_set = set(am1)
898
+ lits.difference_update(am1_set)
899
+
900
+ for l in am1:
901
+ for l2 in conns.get(l, []):
902
+ if l2 in lits:
903
+ degree[l2] -= 1
904
+
905
+ if len(am1) > 1:
906
+ # treat the new atmost1 relation
907
+ self.process_am1(am1)
908
+ nof_am1 += 1
909
+ len_am1.append(len(am1))
910
+
911
+ # updating the set of selectors
912
+ self.sels_set = set(self.sels)
913
+
914
+ if self.verbose > 1 and nof_am1:
915
+ print('c am1s found: {0}; avgsz: {1:.1f}; cost: {2}'.format(nof_am1,
916
+ sum(len_am1) / float(nof_am1), self.cost))
917
+
918
+ def process_am1(self, am1):
919
+ """
920
+ Process an AtMost1 relation detected by :func:`adapt_am1`.
921
+ Note that given a set of soft clauses
922
+ :math:`\\mathcal{S}'` at most one of which can be
923
+ satisfied, one can immediately conclude that the formula
924
+ has cost at least :math:`|\\mathcal{S}'|-1` (assuming
925
+ *unweighted* MaxSAT). Furthermore, it is safe to replace
926
+ all clauses of :math:`\\mathcal{S}'` with a single soft
927
+ clause :math:`\\sum_{c\\in\\mathcal{S}'}{c}`.
928
+
929
+ Here, input parameter ``am1`` plays the role of subset
930
+ :math:`\\mathcal{S}'` mentioned above. The procedure bumps
931
+ the MaxSAT cost by ``self.minw * (len(am1) - 1)``.
932
+
933
+ All soft clauses involved in ``am1`` are replaced by a
934
+ single soft clause, which is a disjunction of the
935
+ selectors of clauses in ``am1``. The weight of the new
936
+ soft clause is set to ``self.minw``.
937
+
938
+ :param am1: a list of selectors connected by an AtMost1 constraint
939
+
940
+ :type am1: list(int)
941
+ """
942
+
943
+ while len(am1) > 1:
944
+ # computing am1's weight
945
+ self.minw = min(map(lambda l: self.wght[l], am1))
946
+
947
+ # pretending am1 to be a core, and the bound is its size - 1
948
+ self.core_sels, b = am1, len(am1) - 1
949
+
950
+ # incrementing the cost
951
+ self.cost += b * self.minw
952
+
953
+ # splitting and relaxing if needed
954
+ self.process_sels()
955
+
956
+ # updating the list of literals in am1 after splitting the weights
957
+ am1 = [l for l in am1 if l not in self.garbage]
958
+
959
+ # new selector
960
+ selv = self.pool.id()
961
+
962
+ # adding a new clause
963
+ self.oracle.add_clause([-l for l in self.rels] + [-selv])
964
+
965
+ # integrating the new selector
966
+ self.sels.append(selv)
967
+ self.wght[selv] = self.minw
968
+ self.smap[selv] = len(self.wght) - 1
969
+
970
+ # removing unnecessary assumptions
971
+ self.filter_assumps()
972
+
973
+ def trim_core(self):
974
+ """
975
+ This method trims a previously extracted unsatisfiable
976
+ core at most a given number of times. If a fixed point is
977
+ reached before that, the method returns.
978
+ """
979
+
980
+ for i in range(self.trim):
981
+ # call solver with core assumption only
982
+ # it must return 'unsatisfiable'
983
+ self._call_oracle(assumptions=self.core)
984
+
985
+ # extract a new core
986
+ new_core = self.oracle.get_core()
987
+
988
+ if len(new_core) == len(self.core):
989
+ # stop if new core is not better than the previous one
990
+ break
991
+
992
+ # otherwise, update core
993
+ self.core = new_core
994
+
995
+ def minimize_core(self):
996
+ """
997
+ Reduce a previously extracted core and compute an
998
+ over-approximation of an MUS. This is done using the
999
+ simple deletion-based MUS extraction algorithm.
1000
+
1001
+ The idea is to try to deactivate soft clauses of the
1002
+ unsatisfiable core one by one while checking if the
1003
+ remaining soft clauses together with the hard part of the
1004
+ formula are unsatisfiable. Clauses that are necessary for
1005
+ preserving unsatisfiability comprise an MUS of the input
1006
+ formula (it is contained in the given unsatisfiable core)
1007
+ and are reported as a result of the procedure.
1008
+
1009
+ During this core minimization procedure, all SAT calls are
1010
+ dropped after obtaining 1000 conflicts.
1011
+ """
1012
+
1013
+ if self.minz and len(self.core) > 1:
1014
+ self.core = sorted(self.core, key=lambda l: self.wght[l])
1015
+ self.oracle.conf_budget(1000)
1016
+
1017
+ i = 0
1018
+ while i < len(self.core):
1019
+ to_test = self.core[:i] + self.core[(i + 1):]
1020
+
1021
+ if self._call_oracle(assumptions=to_test) == False:
1022
+ self.core = to_test
1023
+ elif self.oracle.get_status() == True:
1024
+ i += 1
1025
+ else:
1026
+ break
1027
+
1028
+ # disabling the budget
1029
+ self.oracle.conf_budget(budget=-1)
1030
+
1031
+ def exhaust_core(self, tobj):
1032
+ """
1033
+ Exhaust core by increasing its bound as much as possible.
1034
+ Core exhaustion was originally referred to as *cover
1035
+ optimization* in [6]_.
1036
+
1037
+ Given a totalizer object ``tobj`` representing a sum of
1038
+ some *relaxation* variables :math:`r\\in R` that augment
1039
+ soft clauses :math:`\\mathcal{C}_r`, the idea is to
1040
+ increase the right-hand side of the sum (which is equal to
1041
+ 1 by default) as much as possible, reaching a value
1042
+ :math:`k` s.t. formula
1043
+ :math:`\\mathcal{H}\\wedge\\mathcal{C}_r\\wedge(\\sum_{r\\in
1044
+ R}{r\\leq k})` is still unsatisfiable while increasing it
1045
+ further makes the formula satisfiable (here
1046
+ :math:`\\mathcal{H}` denotes the hard part of the
1047
+ formula).
1048
+
1049
+ The rationale is that calling an oracle incrementally on a
1050
+ series of slightly modified formulas focusing only on the
1051
+ recently computed unsatisfiable core and disregarding the
1052
+ rest of the formula may be practically effective.
1053
+ """
1054
+
1055
+ # the first case is simpler
1056
+ if self._call_oracle(assumptions=[-tobj.rhs[1]]):
1057
+ return 1
1058
+ else:
1059
+ self.cost += self.minw
1060
+
1061
+ for i in range(2, len(self.rels)):
1062
+ # saving the previous bound
1063
+ self.tobj[-tobj.rhs[i - 1]] = tobj
1064
+ self.bnds[-tobj.rhs[i - 1]] = i - 1
1065
+
1066
+ # increasing the bound
1067
+ self.update_sum(-tobj.rhs[i - 1])
1068
+
1069
+ if self._call_oracle(assumptions=[-tobj.rhs[i]]):
1070
+ # the bound should be equal to i
1071
+ return i
1072
+
1073
+ # the cost should increase further
1074
+ self.cost += self.minw
1075
+
1076
+ return None
1077
+
1078
+ def process_sels(self):
1079
+ """
1080
+ Process soft clause selectors participating in a new core.
1081
+ The negation :math:`\\neg{s}` of each selector literal
1082
+ :math:`s` participating in the unsatisfiable core is added
1083
+ to the list of relaxation literals, which will be later
1084
+ used to create a new totalizer object in
1085
+ :func:`create_sum`.
1086
+
1087
+ If the weight associated with a selector is equal to the
1088
+ minimal weight of the core, e.g. ``self.minw``, the
1089
+ selector is marked as garbage and will be removed in
1090
+ :func:`filter_assumps`. Otherwise, the clause is split as
1091
+ described in [1]_.
1092
+ """
1093
+
1094
+ # new relaxation variables
1095
+ self.rels = []
1096
+
1097
+ for l in self.core_sels:
1098
+ if self.wght[l] == self.minw:
1099
+ # marking variable as being a part of the core
1100
+ # so that next time it is not used as an assump
1101
+ self.garbage.add(l)
1102
+ else:
1103
+ # do not remove this variable from assumps
1104
+ # since it has a remaining non-zero weight
1105
+ self.wght[l] -= self.minw
1106
+
1107
+ # reuse assumption variable as relaxation
1108
+ self.rels.append(-l)
1109
+
1110
+ def process_sums(self):
1111
+ """
1112
+ Process cardinality sums participating in a new core.
1113
+ Whenever necessary, some of the sum assumptions are
1114
+ removed or split (depending on the value of
1115
+ ``self.minw``). Deleted sums are marked as garbage and are
1116
+ dealt with in :func:`filter_assumps`.
1117
+
1118
+ In some cases, the process involves updating the
1119
+ right-hand sides of the existing cardinality sums (see the
1120
+ call to :func:`update_sum`). The overall procedure is
1121
+ detailed in [1]_.
1122
+ """
1123
+
1124
+ for l in self.core_sums:
1125
+ if self.wght[l] == self.minw:
1126
+ # marking variable as being a part of the core
1127
+ # so that next time it is not used as an assump
1128
+ self.garbage.add(l)
1129
+ else:
1130
+ # do not remove this variable from assumps
1131
+ # since it has a remaining non-zero weight
1132
+ self.wght[l] -= self.minw
1133
+
1134
+ # increase bound for the sum
1135
+ t, b = self.update_sum(l)
1136
+
1137
+ # updating bounds and weights
1138
+ if b < len(t.rhs):
1139
+ lnew = -t.rhs[b]
1140
+ if lnew not in self.swgt:
1141
+ self.set_bound(t, b, self.swgt[l])
1142
+
1143
+ # put this assumption to relaxation vars
1144
+ self.rels.append(-l)
1145
+
1146
+ def create_sum(self, bound=1):
1147
+ """
1148
+ Create a totalizer object encoding a cardinality
1149
+ constraint on the new list of relaxation literals obtained
1150
+ in :func:`process_sels` and :func:`process_sums`. The
1151
+ clauses encoding the sum of the relaxation literals are
1152
+ added to the SAT oracle. The sum of the totalizer object
1153
+ is encoded up to the value of the input parameter
1154
+ ``bound``, which is set to ``1`` by default.
1155
+
1156
+ :param bound: right-hand side for the sum to be created
1157
+ :type bound: int
1158
+
1159
+ :rtype: :class:`.ITotalizer`
1160
+
1161
+ Note that if Minicard is used as a SAT oracle, native
1162
+ cardinality constraints are used instead of
1163
+ :class:`.ITotalizer`.
1164
+ """
1165
+
1166
+ if not self.oracle.supports_atmost(): # standard totalizer-based encoding
1167
+ # new totalizer sum
1168
+ t = ITotalizer(lits=self.rels, ubound=bound, top_id=self.pool.top)
1169
+
1170
+ # updating top variable id
1171
+ self.pool.top = t.top_id
1172
+
1173
+ # adding its clauses to oracle
1174
+ for cl in t.cnf.clauses:
1175
+ self.oracle.add_clause(cl)
1176
+ else:
1177
+ # for minicard, use native cardinality constraints instead of the
1178
+ # standard totalizer, i.e. create a new (empty) totalizer sum and
1179
+ # fill it with the necessary data supported by minicard
1180
+ t = ITotalizer()
1181
+ t.lits = self.rels
1182
+
1183
+ # a new variable will represent the bound
1184
+ bvar = self.pool.id()
1185
+
1186
+ # proper initial bound
1187
+ t.rhs = [None] * (len(t.lits))
1188
+ t.rhs[bound] = bvar
1189
+
1190
+ # new atmostb constraint instrumented with
1191
+ # an implication and represented natively
1192
+ rhs = len(t.lits)
1193
+ amb = [[-bvar] * (rhs - bound) + t.lits, rhs]
1194
+
1195
+ # add constraint to the solver
1196
+ self.oracle.add_atmost(*amb)
1197
+
1198
+ return t
1199
+
1200
+ def update_sum(self, assump):
1201
+ """
1202
+ The method is used to increase the bound for a given
1203
+ totalizer sum. The totalizer object is identified by the
1204
+ input parameter ``assump``, which is an assumption literal
1205
+ associated with the totalizer object.
1206
+
1207
+ The method increases the bound for the totalizer sum,
1208
+ which involves adding the corresponding new clauses to the
1209
+ internal SAT oracle.
1210
+
1211
+ The method returns the totalizer object followed by the
1212
+ new bound obtained.
1213
+
1214
+ :param assump: assumption literal associated with the sum
1215
+ :type assump: int
1216
+
1217
+ :rtype: :class:`.ITotalizer`, int
1218
+
1219
+ Note that if Minicard is used as a SAT oracle, native
1220
+ cardinality constraints are used instead of
1221
+ :class:`.ITotalizer`.
1222
+ """
1223
+
1224
+ # getting a totalizer object corresponding to assumption
1225
+ t = self.tobj[assump]
1226
+
1227
+ # increment the current bound
1228
+ b = self.bnds[assump] + 1
1229
+
1230
+ if not self.oracle.supports_atmost(): # the case of standard totalizer encoding
1231
+ # increasing its bound
1232
+ t.increase(ubound=b, top_id=self.pool.top)
1233
+
1234
+ # updating top variable id
1235
+ self.pool.top = t.top_id
1236
+
1237
+ # adding its clauses to oracle
1238
+ if t.nof_new:
1239
+ for cl in t.cnf.clauses[-t.nof_new:]:
1240
+ self.oracle.add_clause(cl)
1241
+ else: # the case of cardinality constraints represented natively
1242
+ # right-hand side is always equal to the number of input literals
1243
+ rhs = len(t.lits)
1244
+
1245
+ if b < rhs:
1246
+ # creating an additional bound
1247
+ if not t.rhs[b]:
1248
+ t.rhs[b] = self.pool.id()
1249
+
1250
+ # a new at-most-b constraint
1251
+ amb = [[-t.rhs[b]] * (rhs - b) + t.lits, rhs]
1252
+ self.oracle.add_atmost(*amb)
1253
+
1254
+ return t, b
1255
+
1256
+ def set_bound(self, tobj, rhs, weight=None):
1257
+ """
1258
+ Given a totalizer sum, its right-hand side to be enforced, and a
1259
+ weight, the method creates a new sum assumption literal, which
1260
+ will be used in the following SAT oracle calls. If ``weight`` is
1261
+ left unspecified, the current core's weight, i.e. ``self.minw``,
1262
+ is used.
1263
+
1264
+ :param tobj: totalizer sum
1265
+ :param rhs: right-hand side
1266
+ :param weight: numeric weight of the assumption
1267
+
1268
+ :type tobj: :class:`.ITotalizer`
1269
+ :type rhs: int
1270
+ :type weight: int
1271
+ """
1272
+
1273
+ if weight is None:
1274
+ # if no specific weight is provided, use the core's weight
1275
+ weight = self.minw
1276
+
1277
+ # saving the sum and its weight in a mapping
1278
+ self.tobj[-tobj.rhs[rhs]] = tobj
1279
+ self.bnds[-tobj.rhs[rhs]] = rhs
1280
+ self.wght[-tobj.rhs[rhs]] = weight
1281
+ self.swgt[-tobj.rhs[rhs]] = weight
1282
+
1283
+ # adding a new assumption to force the sum to be at most rhs
1284
+ self.sums.append(-tobj.rhs[rhs])
1285
+
1286
+ def filter_assumps(self):
1287
+ """
1288
+ Filter out unnecessary selectors and sums from the list of
1289
+ assumption literals. The corresponding values are also
1290
+ removed from the dictionaries of bounds and weights.
1291
+
1292
+ Note that assumptions marked as garbage are collected in
1293
+ the core processing methods, i.e. in :func:`process_core`,
1294
+ :func:`process_sels`, and :func:`process_sums`.
1295
+ """
1296
+
1297
+ # updating the list of selectors and sums
1298
+ self.sels = [l for l in self.sels if l not in self.garbage]
1299
+ self.sums = [l for l in self.sums if l not in self.garbage]
1300
+
1301
+ # cleaning the dictionaries
1302
+ for l in list(self.garbage):
1303
+ if l in self.bnds:
1304
+ del self.bnds[l]
1305
+ if l in self.wght:
1306
+ del self.wght[l]
1307
+
1308
+ # removing garbage from the set of selectors
1309
+ self.sels_set.difference_update(set(self.garbage))
1310
+
1311
+ self.garbage.clear()
1312
+
1313
+ def oracle_time(self):
1314
+ """
1315
+ Report the total SAT solving time.
1316
+ """
1317
+
1318
+ return self.oracle.time_accum()
1319
+
1320
+ def _map_extlit(self, l):
1321
+ """
1322
+ Map an external variable to an internal one if necessary.
1323
+
1324
+ This method is used when new clauses are added to the
1325
+ formula incrementally, which may result in introducing new
1326
+ variables clashing with the previously used *clause
1327
+ selectors*. The method makes sure no clash occurs, i.e. it
1328
+ maps the original variables used in the new problem
1329
+ clauses to the newly introduced auxiliary variables (see
1330
+ :func:`add_clause`).
1331
+
1332
+ Given an integer literal, a fresh literal is returned. The
1333
+ returned integer has the same sign as the input literal.
1334
+
1335
+ :param l: literal to map
1336
+ :type l: int
1337
+
1338
+ :rtype: int
1339
+ """
1340
+
1341
+ v = abs(l)
1342
+
1343
+ if v in self.vmap.e2i:
1344
+ return int(copysign(self.vmap.e2i[v], l))
1345
+ else:
1346
+ i = self.pool.id()
1347
+
1348
+ self.vmap.e2i[v] = i
1349
+ self.vmap.i2e[i] = v
1350
+
1351
+ return int(copysign(i, l))
1352
+
1353
+ def interrupt(self):
1354
+ """
1355
+ Interrupt the execution of the current *limited* SAT call in the
1356
+ RC2 algorithm. Can be used to enforce time limits using timer
1357
+ objects. The interrupt must be cleared before performing another
1358
+ `compute` call (see :meth:`clear_interrupt`).
1359
+
1360
+ Importantly, interruption is implemented such that it can work
1361
+ incrementally with multiple MaxSAT calls, i.e. upon an interrupted
1362
+ invocation a user may extend the resources / time and call the
1363
+ solver again. To make this work, none of the SAT calls used by the
1364
+ heuristics are interrupted. For this reason, the solver may take
1365
+ slightly more time than assumed (spent to properly finish the
1366
+ processing of the last unsatisfiable core).
1367
+
1368
+ **Note** that this method can be called if the `compute` call was
1369
+ made with the option ``expect_interrupt`` set to ``True``.
1370
+ Behaviour is **undefined** if used to ``expect_interrupt`` was set
1371
+ to ``False``.
1372
+
1373
+ Example:
1374
+
1375
+ .. code-block:: python
1376
+
1377
+ >>> from pysat.examples.rc2 import RC2
1378
+ >>> from pysat.formula import WCNF
1379
+ >>> from threading import Timer
1380
+ >>>
1381
+ >>> wcnf = WCNF(from_file='somefile.wcnf')
1382
+ >>>
1383
+ >>> with RC2(wcnf) as rc2:
1384
+ >>>
1385
+ >>> def interrupt(s):
1386
+ >>> print('interrupted!')
1387
+ >>> s.interrupt()
1388
+ >>>
1389
+ >>> timer = Timer(1, interrupt, [rc2])
1390
+ >>> timer.start()
1391
+ >>> print('computing...')
1392
+ >>> rc2.compute(expect_interrupt=True)
1393
+ >>> print('done')
1394
+ """
1395
+
1396
+ if self.oracle:
1397
+ self.oracle.interrupt()
1398
+
1399
+ # recording the interruption request
1400
+ self.interrupted = True
1401
+
1402
+ def clear_interrupt(self):
1403
+ """
1404
+ Clears a previous interrupt. Technically, this method should be
1405
+ used every time after the previous MaxSAT call gets interrupted.
1406
+ **However**, the current implementation handles this on its own.
1407
+ """
1408
+
1409
+ if self.oracle:
1410
+ self.oracle.clear_interrupt()
1411
+
1412
+
1413
+ #
1414
+ #==============================================================================
1415
+ class RC2Stratified(RC2, object):
1416
+ """
1417
+ RC2 augmented with BLO and stratification techniques. Although
1418
+ class :class:`RC2` can deal with weighted formulas, there are
1419
+ situations when it is necessary to apply additional heuristics
1420
+ to improve the performance of the solver on weighted MaxSAT
1421
+ formulas. This class extends capabilities of :class:`RC2` with
1422
+ two heuristics, namely
1423
+
1424
+ 1. Boolean lexicographic optimization (BLO) [5]_
1425
+ 2. diversity-based stratification [6]_
1426
+ 3. cluster-based stratification
1427
+
1428
+ To specify which heuristics to apply, a user can assign the ``blo``
1429
+ parameter to one of the values (by default it is set to ``'div'``):
1430
+
1431
+ - ``'basic'`` ('BLO' only)
1432
+ - ``div`` ('BLO' + diversity-based stratification)
1433
+ - ``cluster`` ('BLO' + cluster-based stratification)
1434
+ - ``full`` ('BLO' + diversity- + cluster-based stratification)
1435
+
1436
+ Except for the aforementioned additional techniques, every other
1437
+ component of the solver remains as in the base class :class:`RC2`.
1438
+ Therefore, a user is referred to the documentation of :class:`RC2` for
1439
+ details.
1440
+ """
1441
+
1442
+ def __init__(self, formula, solver='g3', adapt=False, blo='div',
1443
+ exhaust=False, incr=False, minz=False, nohard=False, process=0,
1444
+ trim=0, verbose=0):
1445
+ """
1446
+ Constructor.
1447
+ """
1448
+
1449
+ # calling the constructor for the basic version
1450
+ super(RC2Stratified, self).__init__(formula, solver=solver,
1451
+ adapt=adapt, exhaust=exhaust, incr=incr, minz=minz,
1452
+ process=process, trim=trim, verbose=verbose)
1453
+
1454
+ self.levl = 0 # initial optimization level
1455
+ self.blop = [] # a list of blo levels
1456
+
1457
+ # BLO strategy
1458
+ assert blo and blo in blomap, 'Unknown BLO strategy'
1459
+ self.bstr = blomap[blo]
1460
+
1461
+ # do clause hardening
1462
+ self.hard = nohard == False
1463
+
1464
+ # backing up selectors
1465
+ self.bckp, self.bckp_set = self.sels, self.sels_set
1466
+ self.sels = []
1467
+
1468
+ # initialize Boolean lexicographic optimization
1469
+ self.init_wstr()
1470
+
1471
+ def init_wstr(self):
1472
+ """
1473
+ Compute and initialize optimization levels for BLO and
1474
+ stratification. This method is invoked once, from the
1475
+ constructor of an object of :class:`RC2Stratified`. Given
1476
+ the weights of the soft clauses, the method divides the
1477
+ MaxSAT problem into several optimization levels.
1478
+ """
1479
+
1480
+ # a mapping for stratified problem solving,
1481
+ # i.e. from a weight to a list of selectors
1482
+ self.wstr = collections.defaultdict(lambda: [])
1483
+
1484
+ for s, w in six.iteritems(self.wght):
1485
+ self.wstr[w].append(s)
1486
+
1487
+ # sorted list of distinct weight levels
1488
+ self.blop = sorted([w for w in self.wstr], reverse=True)
1489
+
1490
+ # diversity parameter for stratification
1491
+ self.sdiv = len(self.blop) / 2.0
1492
+
1493
+ # number of finished levels
1494
+ self.done = 0
1495
+
1496
+ def compute(self, expect_interrupt=False):
1497
+ """
1498
+ This method solves the MaxSAT problem iteratively. Each
1499
+ optimization level is tackled the standard way, i.e. by
1500
+ calling :func:`compute_`. A new level is started by
1501
+ calling :func:`next_level` and finished by calling
1502
+ :func:`finish_level`. Each new optimization level
1503
+ activates more soft clauses by invoking
1504
+ :func:`activate_clauses`.
1505
+ """
1506
+
1507
+ # keeping the current interruption preference
1508
+ self.expect_interrupt = expect_interrupt
1509
+
1510
+ if self.done == 0 and self.levl != None:
1511
+ # it is a fresh start of the solver
1512
+ # i.e. no optimization level is finished yet
1513
+
1514
+ # first attempt to get an optimization level
1515
+ self.next_level()
1516
+
1517
+ while self.levl != None and self.done < len(self.blop):
1518
+ # add more clauses
1519
+ self.done = self.activate_clauses(self.done)
1520
+
1521
+ if self.verbose > 1:
1522
+ print('c wght str:', self.blop[self.levl])
1523
+
1524
+ # call RC2
1525
+ if self.compute_() != True: # can be either False or None
1526
+ return
1527
+
1528
+ # updating the list of distinct weight levels
1529
+ self.blop = sorted([w for w in self.wstr], reverse=True)
1530
+
1531
+ if self.done < len(self.blop):
1532
+ if self.verbose > 1:
1533
+ print('c curr opt:', self.cost)
1534
+
1535
+ # done with this level
1536
+ if self.hard:
1537
+ # harden the clauses if necessary
1538
+ self.finish_level()
1539
+
1540
+ self.levl += 1
1541
+
1542
+ # get another level
1543
+ self.next_level()
1544
+
1545
+ if self.verbose > 1:
1546
+ print('c')
1547
+ else:
1548
+ # we seem to be in the model enumeration mode
1549
+ # with the first model being already computed
1550
+ # i.e. all levels are finished and so all clauses are present
1551
+ # thus, we need to simply call RC2 for the next model
1552
+ self.done = -1 # we are done with stratification, disabling it
1553
+ if self.compute_() != True:
1554
+ return
1555
+
1556
+ # extracting a model
1557
+ self.model = self.oracle.get_model()
1558
+
1559
+ if self.model is None and self.pool.top == 0:
1560
+ # we seem to have been given an empty formula
1561
+ # so let's transform the None model returned to []
1562
+ self.model = []
1563
+
1564
+ self.model = filter(lambda l: abs(l) in self.vmap.i2e, self.model)
1565
+ self.model = map(lambda l: int(copysign(self.vmap.i2e[abs(l)], l)), self.model)
1566
+ self.model = sorted(self.model, key=lambda l: abs(l))
1567
+
1568
+ # if formula processing was used, we should
1569
+ # restore the model for the original formula
1570
+ if self.processor:
1571
+ self.model = self.processor.restore(self.model)
1572
+
1573
+ return self.model
1574
+
1575
+ def next_level(self):
1576
+ """
1577
+ Compute the next optimization level (starting from the
1578
+ current one). The procedure represents a loop, each
1579
+ iteration of which checks whether or not one of the
1580
+ conditions holds:
1581
+
1582
+ - partial BLO condition
1583
+ - diversity-based stratification condition
1584
+ - cluster-based stratification condition
1585
+
1586
+ If any of these holds, the loop stops.
1587
+ """
1588
+
1589
+ if self.levl >= len(self.blop):
1590
+ self.levl = None
1591
+ return
1592
+
1593
+ # determining which heuristics to use
1594
+ div_str, clu_str = (self.bstr >> 1) & 1, (self.bstr >> 2) & 1
1595
+
1596
+ # cluster of weights to build (if needed)
1597
+ cluster = [self.levl]
1598
+
1599
+ while self.levl < len(self.blop) - 1:
1600
+ # current weight
1601
+ wght = self.blop[self.levl]
1602
+
1603
+ # number of selectors with weight less than current weight
1604
+ numr = sum([len(self.wstr[w]) for w in self.blop[(self.levl + 1):]])
1605
+
1606
+ # sum of their weights
1607
+ sumr = sum([w * len(self.wstr[w]) for w in self.blop[(self.levl + 1):]])
1608
+
1609
+ # partial BLO
1610
+ if wght > sumr and sumr != 0:
1611
+ break
1612
+
1613
+ # diversity-based stratification
1614
+ if div_str and numr / float(len(self.blop) - self.levl - 1) > self.sdiv:
1615
+ break
1616
+
1617
+ # last resort = cluster-based stratification
1618
+ if clu_str:
1619
+ # is the distance from current weight to the cluster
1620
+ # being built larger than the distance to the mean of
1621
+ # smaller weights?
1622
+ numc = sum([len(self.wstr[self.blop[l]]) for l in cluster])
1623
+ sumc = sum([self.blop[l] * len(self.wstr[self.blop[l]]) for l in cluster])
1624
+
1625
+ if abs(wght - sumc / numc) > abs(wght - sumr / numr):
1626
+ # remaining weights are too far from the cluster; stop
1627
+ # here and report the splitting to be last-added weight
1628
+ self.levl = cluster[-1]
1629
+ break
1630
+
1631
+ cluster.append(self.levl)
1632
+
1633
+ self.levl += 1
1634
+
1635
+ def activate_clauses(self, beg):
1636
+ """
1637
+ This method is used for activating the clauses that belong
1638
+ to optimization levels up to the newly computed level. It
1639
+ also reactivates previously deactivated clauses (see
1640
+ :func:`process_sels` and :func:`process_sums` for
1641
+ details).
1642
+ """
1643
+
1644
+ end = min(self.levl + 1, len(self.blop))
1645
+
1646
+ for l in range(beg, end):
1647
+ for sel in self.wstr[self.blop[l]]:
1648
+ if sel in self.bckp_set:
1649
+ self.sels.append(sel)
1650
+ else:
1651
+ self.sums.append(sel)
1652
+
1653
+ # updating set of selectors
1654
+ self.sels_set = set(self.sels)
1655
+
1656
+ return end
1657
+
1658
+ def finish_level(self):
1659
+ """
1660
+ This method does postprocessing of the current
1661
+ optimization level after it is solved. This includes
1662
+ *hardening* some of the soft clauses (depending on their
1663
+ remaining weights) and also garbage collection.
1664
+ """
1665
+
1666
+ # assumptions to remove
1667
+ self.garbage = set()
1668
+
1669
+ # sum of weights of the remaining levels
1670
+ sumw = sum([w * len(self.wstr[w]) for w in self.blop[(self.levl + 1):]])
1671
+
1672
+ # trying to harden selectors and sums
1673
+ for s in self.sels + self.sums:
1674
+ if self.wght[s] > sumw:
1675
+ self.oracle.add_clause([s])
1676
+ self.garbage.add(s)
1677
+
1678
+ if self.verbose > 1:
1679
+ print('c hardened:', len(self.garbage))
1680
+
1681
+ # remove unnecessary assumptions
1682
+ self.filter_assumps()
1683
+
1684
+ def process_am1(self, am1):
1685
+ """
1686
+ Due to the solving process involving multiple optimization
1687
+ levels to be treated individually, new soft clauses for
1688
+ the detected intrinsic AtMost1 constraints should be
1689
+ remembered. The method is a slightly modified version of
1690
+ the base method :func:`RC2.process_am1` taking care of
1691
+ this.
1692
+ """
1693
+
1694
+ # assumptions to remove
1695
+ self.garbage = set()
1696
+
1697
+ # clauses to deactivate
1698
+ to_deactivate = set([])
1699
+
1700
+ while len(am1) > 1:
1701
+ # computing am1's weight
1702
+ self.minw = min(map(lambda l: self.wght[l], am1))
1703
+
1704
+ # pretending am1 to be a core, and the bound is its size - 1
1705
+ self.core_sels, b = am1, len(am1) - 1
1706
+
1707
+ # incrementing the cost
1708
+ self.cost += b * self.minw
1709
+
1710
+ # splitting and relaxing if needed
1711
+ super(RC2Stratified, self).process_sels()
1712
+
1713
+ # updating the list of literals in am1 after splitting the weights
1714
+ am1 = [l for l in am1 if l not in self.garbage]
1715
+
1716
+ # new selector
1717
+ selv = self.pool.id()
1718
+
1719
+ # adding a new clause
1720
+ self.oracle.add_clause([-l for l in self.rels] + [-selv])
1721
+
1722
+ # integrating the new selector
1723
+ self.sels.append(selv)
1724
+ self.wght[selv] = self.minw
1725
+ self.smap[selv] = len(self.wght) - 1
1726
+
1727
+ # do not forget this newly selector!
1728
+ self.bckp_set.add(selv)
1729
+
1730
+ if self.done != -1 and self.wght[selv] < self.blop[self.levl]:
1731
+ self.wstr[self.wght[selv]].append(selv)
1732
+ to_deactivate.add(selv)
1733
+
1734
+ # marking all remaining literals with small weights to be deactivated
1735
+ for l in am1:
1736
+ if self.done != -1 and self.wght[l] < self.blop[self.levl]:
1737
+ self.wstr[self.wght[l]].append(l)
1738
+ to_deactivate.add(l)
1739
+
1740
+ # deactivating unnecessary selectors
1741
+ self.sels = [l for l in self.sels if l not in to_deactivate]
1742
+
1743
+ # removing unnecessary assumptions
1744
+ self.filter_assumps()
1745
+
1746
+ def process_sels(self):
1747
+ """
1748
+ A redefined version of :func:`RC2.process_sels`. The only
1749
+ modification affects the clauses whose weight after
1750
+ splitting becomes less than the weight of the current
1751
+ optimization level. Such clauses are deactivated and to be
1752
+ reactivated at a later stage.
1753
+ """
1754
+
1755
+ # new relaxation variables
1756
+ self.rels = []
1757
+
1758
+ # selectors that should be deactivated (but not removed completely)
1759
+ to_deactivate = set([])
1760
+
1761
+ for l in self.core_sels:
1762
+ if self.wght[l] == self.minw:
1763
+ # marking variable as being a part of the core
1764
+ # so that next time it is not used as an assump
1765
+ self.garbage.add(l)
1766
+ else:
1767
+ # do not remove this variable from assumps
1768
+ # since it has a remaining non-zero weight
1769
+ self.wght[l] -= self.minw
1770
+
1771
+ # deactivate this assumption and put at a lower level
1772
+ # if self.done != -1, i.e. if stratification is disabled
1773
+ if self.done != -1 and self.wght[l] < self.blop[self.levl]:
1774
+ self.wstr[self.wght[l]].append(l)
1775
+ to_deactivate.add(l)
1776
+
1777
+ # reuse assumption variable as relaxation
1778
+ self.rels.append(-l)
1779
+
1780
+ # deactivating unnecessary selectors
1781
+ self.sels = [l for l in self.sels if l not in to_deactivate]
1782
+
1783
+ def process_sums(self):
1784
+ """
1785
+ A redefined version of :func:`RC2.process_sums`. The only
1786
+ modification affects the clauses whose weight after
1787
+ splitting becomes less than the weight of the current
1788
+ optimization level. Such clauses are deactivated and to be
1789
+ reactivated at a later stage.
1790
+ """
1791
+
1792
+ # sums that should be deactivated (but not removed completely)
1793
+ to_deactivate = set([])
1794
+
1795
+ for l in self.core_sums:
1796
+ if self.wght[l] == self.minw:
1797
+ # marking variable as being a part of the core
1798
+ # so that next time it is not used as an assump
1799
+ self.garbage.add(l)
1800
+ else:
1801
+ # do not remove this variable from assumps
1802
+ # since it has a remaining non-zero weight
1803
+ self.wght[l] -= self.minw
1804
+
1805
+ # deactivate this assumption and put at a lower level
1806
+ # if self.done != -1, i.e. if stratification is disabled
1807
+ if self.done != -1 and self.wght[l] < self.blop[self.levl]:
1808
+ self.wstr[self.wght[l]].append(l)
1809
+ to_deactivate.add(l)
1810
+
1811
+ # increase bound for the sum
1812
+ t, b = self.update_sum(l)
1813
+
1814
+ # updating bounds and weights
1815
+ if b < len(t.rhs):
1816
+ lnew = -t.rhs[b]
1817
+ if lnew not in self.swgt:
1818
+ self.set_bound(t, b, self.swgt[l])
1819
+
1820
+ # put this assumption to relaxation vars
1821
+ self.rels.append(-l)
1822
+
1823
+ # deactivating unnecessary sums
1824
+ self.sums = [l for l in self.sums if l not in to_deactivate]
1825
+
1826
+
1827
+ #
1828
+ #==============================================================================
1829
+ def parse_options():
1830
+ """
1831
+ Parses command-line option
1832
+ """
1833
+
1834
+ try:
1835
+ opts, args = getopt.getopt(sys.argv[1:], 'ab:c:e:hil:mp:s:t:T:vx',
1836
+ ['adapt', 'block=', 'comp=', 'enum=', 'exhaust', 'help',
1837
+ 'incr', 'blo=', 'minimize', 'process=', 'solver=',
1838
+ 'trim=', 'timeout=', 'verbose', 'vnew'])
1839
+ except getopt.GetoptError as err:
1840
+ sys.stderr.write(str(err).capitalize())
1841
+ usage()
1842
+ sys.exit(1)
1843
+
1844
+ adapt = False
1845
+ block = 'model'
1846
+ exhaust = False
1847
+ cmode = None
1848
+ to_enum = 1
1849
+ incr = False
1850
+ blo = 'none'
1851
+ minz = False
1852
+ process = 0
1853
+ solver = 'g3'
1854
+ trim = 0
1855
+ timeout = None
1856
+ verbose = 1
1857
+ vnew = False
1858
+
1859
+ for opt, arg in opts:
1860
+ if opt in ('-a', '--adapt'):
1861
+ adapt = True
1862
+ elif opt in ('-b', '--block'):
1863
+ block = str(arg)
1864
+ elif opt in ('-c', '--comp'):
1865
+ cmode = str(arg)
1866
+ elif opt in ('-e', '--enum'):
1867
+ to_enum = str(arg)
1868
+ if to_enum != 'all':
1869
+ to_enum = int(to_enum)
1870
+ else:
1871
+ to_enum = 0
1872
+ elif opt in ('-h', '--help'):
1873
+ usage()
1874
+ sys.exit(0)
1875
+ elif opt in ('-i', '--incr'):
1876
+ incr = True
1877
+ elif opt in ('-l', '--blo'):
1878
+ blo = str(arg)
1879
+ elif opt in ('-m', '--minimize'):
1880
+ minz = True
1881
+ elif opt in ('-p', '--process'):
1882
+ process = int(arg)
1883
+ elif opt in ('-s', '--solver'):
1884
+ solver = str(arg)
1885
+ elif opt in ('-t', '--trim'):
1886
+ trim = int(arg)
1887
+ elif opt in ('-T', '--timeout'):
1888
+ if str(arg) != 'none':
1889
+ timeout = float(arg)
1890
+ elif opt in ('-v', '--verbose'):
1891
+ verbose += 1
1892
+ elif opt == '--vnew':
1893
+ vnew = True
1894
+ elif opt in ('-x', '--exhaust'):
1895
+ exhaust = True
1896
+ else:
1897
+ assert False, 'Unhandled option: {0} {1}'.format(opt, arg)
1898
+
1899
+ # solution blocking
1900
+ bmap = {'mcs': -1, 'mcses': -1, 'model': 0, 'models': 0, 'mss': 1, 'msses': 1}
1901
+ assert block in bmap, 'Unknown solution blocking'
1902
+ block = bmap[block]
1903
+
1904
+ return adapt, blo, block, cmode, to_enum, exhaust, incr, minz, \
1905
+ process, solver, trim, timeout, verbose, vnew, args
1906
+
1907
+
1908
+ #
1909
+ #==============================================================================
1910
+ def usage():
1911
+ """
1912
+ Prints usage message.
1913
+ """
1914
+
1915
+ print('Usage:', os.path.basename(sys.argv[0]), '[options] dimacs-file')
1916
+ print('Options:')
1917
+ print(' -a, --adapt Try to adapt (simplify) input formula')
1918
+ print(' -b, --block=<string> When enumerating MaxSAT models, how to block previous solutions')
1919
+ print(' Available values: mcs, model, mss (default = model)')
1920
+ print(' -c, --comp=<string> Enable one of the MSE18 configurations')
1921
+ print(' Available values: a, b, none (default = none)')
1922
+ print(' -e, --enum=<int> Number of MaxSAT models to compute')
1923
+ print(' Available values: [1 .. INT_MAX], all (default = 1)')
1924
+ print(' -h, --help Show this message')
1925
+ print(' -i, --incr Use SAT solver incrementally (only for g3 and g4)')
1926
+ print(' -l, --blo=<string> Use BLO and stratification')
1927
+ print(' Available values: basic, div, cluster, none, full (default = none)')
1928
+ print(' -m, --minimize Use a heuristic unsatisfiable core minimizer')
1929
+ print(' -p, --process=<int> Number of processing rounds')
1930
+ print(' Available values: [0 .. INT_MAX] (default = 0)')
1931
+ print(' -s, --solver=<string> SAT solver to use')
1932
+ print(' Available values: cd15, cd19, g3, g4, lgl, mcb, mcm, mpl, m22, mc, mgh (default = g3)')
1933
+ print(' -t, --trim=<int> How many times to trim unsatisfiable cores')
1934
+ print(' Available values: [0 .. INT_MAX] (default = 0)')
1935
+ print(' -T, --timeout=<float> Set time limit for MaxSAT solver')
1936
+ print(' Available values: [0 .. FLOAT_MAX], none (default: none)')
1937
+ print(' -v, --verbose Be verbose')
1938
+ print(' --vnew Print v-line in the new format')
1939
+ print(' -x, --exhaust Exhaust new unsatisfiable cores')
1940
+
1941
+
1942
+ #
1943
+ #==============================================================================
1944
+ if __name__ == '__main__':
1945
+ adapt, blo, block, cmode, to_enum, exhaust, incr, minz, process, solver, \
1946
+ trim, timeout, verbose, vnew, files = parse_options()
1947
+
1948
+ if files:
1949
+ # parsing the input formula
1950
+ if re.search(r'\.wcnf[p|+]?(\.(gz|bz2|lzma|xz|zst))?$', files[0]):
1951
+ formula = WCNFPlus(from_file=files[0])
1952
+ else: # expecting '*.cnf[,p,+].*'
1953
+ formula = CNFPlus(from_file=files[0]).weighted()
1954
+
1955
+ # enabling the competition mode
1956
+ if cmode:
1957
+ assert cmode in ('a', 'b'), 'Wrong MSE18 mode chosen: {0}'.format(cmode)
1958
+ adapt, blo, exhaust, solver, verbose = True, 'div', True, 'g3', 3
1959
+
1960
+ if cmode == 'a':
1961
+ trim = 5 if max(formula.wght) > min(formula.wght) else 0
1962
+ minz = False
1963
+ else:
1964
+ trim, minz = 0, True
1965
+
1966
+ # trying to use unbuffered standard output
1967
+ if sys.version_info.major == 2:
1968
+ sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
1969
+
1970
+ # deciding whether or not to stratify
1971
+ if blo != 'none' and max(formula.wght) > min(formula.wght):
1972
+ MXS = RC2Stratified
1973
+ else:
1974
+ MXS = RC2
1975
+
1976
+ # starting the solver
1977
+ with MXS(formula, solver=solver, adapt=adapt, exhaust=exhaust,
1978
+ incr=incr, minz=minz, process=process, trim=trim,
1979
+ verbose=verbose) as rc2:
1980
+
1981
+ if isinstance(rc2, RC2Stratified):
1982
+ rc2.bstr = blomap[blo] # select blo strategy
1983
+ if to_enum != 1:
1984
+ # no clause hardening in case we enumerate multiple models
1985
+ print('c hardening is disabled for model enumeration')
1986
+ rc2.hard = False
1987
+
1988
+ # setting a timer if necessary
1989
+ if timeout is not None:
1990
+ if verbose > 1:
1991
+ print('c timeout: {0}'.format(timeout))
1992
+
1993
+ timer = Timer(timeout, lambda s: s.interrupt(), [rc2])
1994
+ timer.start()
1995
+ else:
1996
+ timer = None
1997
+
1998
+ optimum_found = False
1999
+ for i, model in enumerate(rc2.enumerate(block=block,
2000
+ expect_interrupt=timeout is not None), 1):
2001
+ optimum_found = True
2002
+
2003
+ if verbose:
2004
+ if i == 1:
2005
+ if not rc2.interrupted:
2006
+ print('s OPTIMUM FOUND')
2007
+ print('o {0}'.format(rc2.cost))
2008
+
2009
+ if verbose > 2:
2010
+ if vnew: # new format of the v-line
2011
+ print('v', ''.join(str(int(l > 0)) for l in model))
2012
+ else:
2013
+ print('v', ' '.join([str(l) for l in model]))
2014
+
2015
+ if i == to_enum:
2016
+ break
2017
+ else:
2018
+ # needed for MSE'20
2019
+ if verbose > 2 and vnew and to_enum != 1 and block == 1:
2020
+ print('v')
2021
+
2022
+ if verbose:
2023
+ if not optimum_found and not rc2.interrupted:
2024
+ print('s UNSATISFIABLE')
2025
+ elif to_enum != 1:
2026
+ print('c models found:', i)
2027
+
2028
+ if verbose > 1:
2029
+ print('c oracle time: {0:.4f}'.format(rc2.oracle_time()))
2030
+
2031
+ # cancelling the timer (if any) because we are done
2032
+ if timer:
2033
+ timer.cancel()