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