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