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