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
@@ -0,0 +1,710 @@
1
+ #!/usr/bin/env python
2
+ #-*- coding:utf-8 -*-
3
+ ##
4
+ ## optux.py
5
+ ##
6
+ ## Created on: Mar 22, 2021
7
+ ## Author: Alexey Ignatiev
8
+ ## E-mail: alexey.ignatiev@monash.edu
9
+ ##
10
+
11
+ """
12
+ ===============
13
+ List of classes
14
+ ===============
15
+
16
+ .. autosummary::
17
+ :nosignatures:
18
+
19
+ OptUx
20
+
21
+ ==================
22
+ Module description
23
+ ==================
24
+
25
+ An implementation of an extractor of a smallest size minimal unsatisfiable
26
+ subset (smallest MUS, or SMUS) [1]_ [2]_ [3]_ [4]_ and enumerator of
27
+ SMUSes based on *implicit hitting set enumeration* [1]_. This
28
+ implementation tries to replicate the well-known SMUS extractor Forqes
29
+ [1]_. In contrast to Forqes, this implementation supports not only plain
30
+ DIMACS :class:`.CNF` formulas but also weighted :class:`.WCNF` formulas.
31
+ As a result, the tool is able to compute and enumerate *optimal* MUSes in
32
+ case of weighted formulas. On the other hand, this prototype lacks a
33
+ number of command-line options used in Forqes and so it may be less
34
+ efficient compared to Forqes but the performance difference should not be
35
+ significant.
36
+
37
+ .. [1] Alexey Ignatiev, Alessandro Previti, Mark H. Liffiton, Joao
38
+ Marques-Silva. *Smallest MUS Extraction with Minimal Hitting Set
39
+ Dualization*. CP 2015. pp. 173-182
40
+
41
+ .. [2] Mark H. Liffiton, Maher N. Mneimneh, Ines Lynce, Zaher S. Andraus,
42
+ Joao Marques-Silva, Karem A. Sakallah. *A branch and bound algorithm
43
+ for extracting smallest minimal unsatisfiable subformulas*.
44
+ Constraints An Int. J. 14(4). 2009. pp. 415-442
45
+
46
+ .. [3] Alexey Ignatiev, Mikolas Janota, Joao Marques-Silva. *Quantified
47
+ Maximum Satisfiability: A Core-Guided Approach*. SAT 2013.
48
+ pp. 250-266
49
+
50
+ .. [4] Alexey Ignatiev, Mikolas Janota, Joao Marques-Silva. *Quantified
51
+ maximum satisfiability*. Constraints An Int. J. 21(2). 2016.
52
+ pp. 277-302
53
+
54
+ The file provides a class :class:`OptUx`, which is the basic
55
+ implementation of the algorithm. It can be applied to any formula in the
56
+ :class:`.CNF` or :class:`.WCNF` format.
57
+
58
+ The implementation can be used as an executable (the list of available
59
+ command-line options can be shown using ``optux.py -h``) in the following
60
+ way:
61
+
62
+ ::
63
+
64
+ $ xzcat formula.wcnf.xz
65
+ p wcnf 3 6 4
66
+ 1 1 0
67
+ 1 2 0
68
+ 1 3 0
69
+ 4 -1 -2 0
70
+ 4 -1 -3 0
71
+ 4 -2 -3 0
72
+
73
+ $ optux.py -vvv formula.wcnf.xz
74
+ c mcs: 1 2 0
75
+ c mcses: 0 unit, 1 disj
76
+ c mus: 1 2 0
77
+ c cost: 2
78
+ c oracle time: 0.0001
79
+
80
+ Alternatively, the algorithm can be accessed and invoked through the
81
+ standard ``import`` interface of Python, e.g.
82
+
83
+ .. code-block:: python
84
+
85
+ >>> from pysat.examples.optux import OptUx
86
+ >>> from pysat.formula import WCNF
87
+ >>>
88
+ >>> wcnf = WCNF(from_file='formula.wcnf.xz')
89
+ >>>
90
+ >>> with OptUx(wcnf) as optux:
91
+ ... for mus in optux.enumerate():
92
+ ... print('mus {0} has cost {1}'.format(mus, optux.cost))
93
+ mus [1, 2] has cost 2
94
+ mus [1, 3] has cost 2
95
+ mus [2, 3] has cost 2
96
+
97
+ As can be seen in the example above, the solver can be instructed either
98
+ to compute one optimal MUS of an input formula, or to enumerate a given
99
+ number (or *all*) of its top optimal MUSes.
100
+
101
+ ==============
102
+ Module details
103
+ ==============
104
+ """
105
+
106
+ #
107
+ #==============================================================================
108
+ from __future__ import print_function
109
+ import getopt
110
+ import os
111
+ from pysat.examples.hitman import Atom, Hitman
112
+ from pysat.examples.rc2 import RC2
113
+ from pysat.formula import CNFPlus, WCNFPlus
114
+ from pysat.process import Processor
115
+ from pysat.solvers import Solver, SolverNames
116
+ import re
117
+ import sys
118
+
119
+
120
+ #
121
+ #==============================================================================
122
+ class OptUx(object):
123
+ """
124
+ A simple Python version of the implicit hitting set based optimal MUS
125
+ extractor and enumerator. Given a (weighted) (partial) CNF formula,
126
+ i.e. formula in the :class:`.WCNF` format, this class can be used to
127
+ compute a given number of optimal MUS (starting from the *best* one)
128
+ of the input formula. :class:`OptUx` roughly follows the
129
+ implementation of Forqes [1]_ but lacks a few additional heuristics,
130
+ which however aren't applied in Forqes by default.
131
+
132
+ As a result, OptUx applies exhaustive *disjoint* minimal correction
133
+ subset (MCS) enumeration [1]_, [2]_, [3]_, [4]_ with the incremental
134
+ use of RC2 [5]_ as an underlying MaxSAT solver. Disjoint MCS
135
+ enumeration is run only if the corresponding input parameter
136
+ ``nodisj`` is set to ``False``. Once disjoint MCSes are enumerated,
137
+ they are used to bootstrap a hitting set solver. This implementation
138
+ uses :class:`.Hitman` as a hitting set solver, which is again based on
139
+ RC2.
140
+
141
+ Note that in the main implicit hitting enumeration loop of the
142
+ algorithm, OptUx follows Forqes in that it does not reduce correction
143
+ subsets detected to minimal correction subsets. As a result,
144
+ correction subsets computed in the main loop are added to
145
+ :class:`Hitman` *unreduced*.
146
+
147
+ :class:`OptUx` can use any SAT solver available in PySAT. The default
148
+ SAT solver to use is ``g3``, which stands for Glucose 3 [6]_ (see
149
+ :class:`.SolverNames`). Boolean parameters ``adapt``, ``exhaust``, and
150
+ ``minz`` control whether or not the underlying :class:`.RC2` oracles
151
+ should apply detection and adaptation of intrinsic AtMost1
152
+ constraints, core exhaustion, and core reduction. Also, unsatisfiable
153
+ cores can be trimmed if the ``trim`` parameter is set to a non-zero
154
+ integer. Finally, verbosity level can be set using the ``verbose``
155
+ parameter.
156
+
157
+ Two additional optional parameters ``unsorted`` and ``dcalls`` can be
158
+ used to instruct the tool to enumerate MUSes in the unsorted fashion,
159
+ i.e. optimal MUSes are not guaranteed to go first. For this,
160
+ :class:`OptUx` applies LBX-like MCS enumeration (it uses :class:`.LBX`
161
+ directly). Parameter ``dcalls`` can be applied to instruct the
162
+ underlying MCS enumerator to apply clause D oracle calls.
163
+
164
+ Another optional paramater ``puresat`` can be used to instruct OptUx
165
+ to run a purely SAT-based minimal hitting set enumerator, following
166
+ the ideas of [7]_. The value of ``puresat`` can be either ``False``,
167
+ meaning that no pure SAT enumeration is to be done or be equal to
168
+ ``'mgh'``, ``'cd15'``, or ``'lgl'`` - these are the solvers that
169
+ support *hard* phase setting, i.e. user preferences will not be
170
+ overwritten by the *phase saving* heuristic [8]_.
171
+
172
+ Additionally, the input formula can be preprocessed before running MUS
173
+ enumeration. This is controlled by the input parameter ``process``
174
+ whose integer value signifies the number of processing rounds to be
175
+ applied. The number of rounds is set to 0 by default.
176
+
177
+ Finally, one more optional input parameter ``cover`` is to be used
178
+ when exhaustive enumeration of MUSes is not necessary and the tool can
179
+ stop as soon as a given formula is covered by the set of currently
180
+ computed MUSes. This can be made to work if the soft clauses of
181
+ ``formula`` are of size 1.
182
+
183
+ .. [5] Alexey Ignatiev, Antonio Morgado, Joao Marques-Silva. *RC2: an
184
+ Efficient MaxSAT Solver*. J. Satisf. Boolean Model. Comput. 11(1).
185
+ 2019. pp. 53-64
186
+
187
+ .. [6] Gilles Audemard, Jean-Marie Lagniez, Laurent Simon.
188
+ *Improving Glucose for Incremental SAT Solving with
189
+ Assumptions: Application to MUS Extraction*. SAT 2013.
190
+ pp. 309-317
191
+
192
+ .. [7] Enrico Giunchiglia, Marco Maratea. *Solving Optimization
193
+ Problems with DLL*. ECAI 2006. pp. 377-381
194
+
195
+ .. [8] Knot Pipatsrisawat, Adnan Darwiche. *A Lightweight Component
196
+ Caching Scheme for Satisfiability Solvers*. SAT 2007. pp. 294-299
197
+
198
+
199
+ :param formula: (weighted) (partial) CNFPlus formula
200
+ :param solver: SAT oracle name
201
+ :param adapt: detect and adapt intrinsic AtMost1 constraints
202
+ :param cover: CNFPlus formula to cover when doing MUS enumeration
203
+ :param dcalls: apply clause D oracle calls (for unsorted enumeration only)
204
+ :param exhaust: do core exhaustion
205
+ :param minz: do heuristic core reduction
206
+ :param nodisj: do not enumerate disjoint MCSes
207
+ :param process: apply formula preprocessing this many times
208
+ :param puresat: use pure SAT-based hitting set enumeration
209
+ :param unsorted: apply unsorted MUS enumeration
210
+ :param trim: do core trimming at most this number of times
211
+ :param verbose: verbosity level
212
+
213
+ :type formula: :class:`.WCNFPlus`
214
+ :type solver: str
215
+ :type adapt: bool
216
+ :type cover: :class:`.CNFPlus`
217
+ :type dcalls: bool
218
+ :type exhaust: bool
219
+ :type minz: bool
220
+ :type nodisj: bool
221
+ :type process: int
222
+ :type puresat: str
223
+ :type unsorted: bool
224
+ :type trim: int
225
+ :type verbose: int
226
+ """
227
+
228
+ def __init__(self, formula, solver='g3', adapt=False, cover=None,
229
+ dcalls=False, exhaust=False, minz=False, nodisj=False, process=0,
230
+ puresat=False, unsorted=False, trim=False, verbose=0):
231
+ """
232
+ Constructor.
233
+ """
234
+
235
+ assert not puresat or unsorted, '\'unsorted\' needs to be True for pure SAT mode'
236
+
237
+ # verbosity level
238
+ self.verbose = verbose
239
+
240
+ # constructing a local copy of the formula
241
+ self.formula = WCNFPlus()
242
+ self.formula.hard = formula.hard[:]
243
+ self.formula.wght = formula.wght[:]
244
+ self.formula.topw = formula.topw
245
+ self.formula.nv = formula.nv
246
+
247
+ # copying atmost constraints, if any
248
+ if isinstance(formula, WCNFPlus) and formula.atms:
249
+ self.formula.atms = formula.atms[:]
250
+
251
+ # top variable identifier
252
+ self.topv = formula.nv
253
+
254
+ # processing soft clauses
255
+ self._process_soft(formula)
256
+ self.formula.nv = self.topv
257
+
258
+ # applying formula processing (if any)
259
+ if process:
260
+ # the processor is immediately destroyed,
261
+ # as we do not need to restore the models
262
+ with Processor(bootstrap_with=self.formula.hard) as processor:
263
+ proc = processor.process(rounds=process, freeze=self.sels)
264
+ self.formula.hard = proc.clauses
265
+ self.formula.nv = max(self.formula.nv, proc.nv)
266
+
267
+ # creating an unweighted copy
268
+ unweighted = self.formula.copy()
269
+ unweighted.wght = [1 for w in unweighted.wght]
270
+
271
+ if not nodisj:
272
+ # enumerating disjoint MCSes (including unit-size MCSes)
273
+ to_hit, self.units = self._disjoint(unweighted, solver, adapt, exhaust,
274
+ minz, trim)
275
+ else:
276
+ to_hit, self.units, self.disj_time = [], [], 0.
277
+
278
+ if self.verbose > 2:
279
+ print('c mcses: {0} unit, {1} disj'.format(len(self.units),
280
+ len(to_hit) + len(self.units)))
281
+
282
+ if not unsorted:
283
+ # MaxSAT-based hitting set enumerator
284
+ self.hitman = Hitman(bootstrap_with=to_hit, weights=self.weights,
285
+ solver=solver, htype='sorted', mxs_adapt=adapt,
286
+ mxs_exhaust=exhaust, mxs_minz=minz, mxs_trim=trim)
287
+ elif not puresat:
288
+ # MCS-based hitting set enumerator
289
+ self.hitman = Hitman(bootstrap_with=to_hit, weights=self.weights,
290
+ solver=solver, htype='lbx', mcs_usecld=dcalls)
291
+ else:
292
+ # pure SAT-based hitting set enumerator with preferred phases
293
+ self.hitman = Hitman(bootstrap_with=to_hit, weights=self.weights,
294
+ solver=puresat, htype='sat')
295
+
296
+ # adding the formula to cover to the hitting set enumerator
297
+ self.cover = cover is not None
298
+ if cover:
299
+ # mapping literals to Hitman's atoms
300
+ m = lambda l: Atom(l, sign=True) if -l not in self.weights else Atom(-l, sign=False)
301
+
302
+ for cl in cover:
303
+ if len(cl) != 2 or not type(cl[0]) in (list, tuple, set):
304
+ cl = [m(l) for l in cl]
305
+ else:
306
+ cl = [[m(l) for l in cl[0]], cl[1]]
307
+
308
+ self.hitman.add_hard(cl, weights=self.weights)
309
+
310
+ # SAT oracle bootstrapped with the hard clauses; note that
311
+ # clauses of the unit-size MCSes are enforced to be enabled
312
+ self.oracle = Solver(name=solver, bootstrap_with=unweighted.hard +
313
+ [[mcs] for mcs in self.units], use_timer=True)
314
+
315
+ if unweighted.atms:
316
+ if solver in SolverNames.cadical195:
317
+ # we are using CaDiCaL195 and it can use external linear engine
318
+ self.oracle.activate_atmost()
319
+
320
+ assert self.oracle.supports_atmost(), \
321
+ '{0} does not support native cardinality constraints. Make sure you use the right type of formula.'.format(self.solver)
322
+
323
+ for atm in unweighted.atms:
324
+ self.oracle.add_atmost(*atm)
325
+
326
+ def __del__(self):
327
+ """
328
+ Destructor.
329
+ """
330
+
331
+ self.delete()
332
+
333
+ def __enter__(self):
334
+ """
335
+ 'with' constructor.
336
+ """
337
+
338
+ return self
339
+
340
+ def __exit__(self, exc_type, exc_value, traceback):
341
+ """
342
+ 'with' destructor.
343
+ """
344
+
345
+ self.delete()
346
+
347
+ def delete(self):
348
+ """
349
+ Explicit destructor of the internal hitting set and SAT oracles.
350
+ """
351
+
352
+ if self.hitman:
353
+ self.hitman.delete()
354
+ self.hitman = None
355
+
356
+ if self.oracle:
357
+ self.oracle.delete()
358
+ self.oracle = None
359
+
360
+ def _process_soft(self, formula):
361
+ """
362
+ The method is for processing the soft clauses of the input
363
+ formula. Concretely, it checks which soft clauses must be relaxed
364
+ by a unique selector literal and applies the relaxation.
365
+
366
+ :param formula: input formula
367
+ :type formula: :class:`.WCNF`
368
+ """
369
+
370
+ # list of selectors
371
+ self.sels = []
372
+
373
+ # mapping from selectors to clause ids
374
+ self.smap = {}
375
+
376
+ # duplicate unit clauses
377
+ processed_dups = set()
378
+
379
+ # processing the soft clauses
380
+ for cl in formula.soft:
381
+ # if the clause is unit-size, its sole literal acts a selector
382
+ selv = cl[0]
383
+
384
+ # if clause is not unit, we relax it
385
+ if len(cl) > 1:
386
+ self.topv += 1
387
+ selv = self.topv
388
+ self.formula.hard.append(cl + [-selv])
389
+ elif selv in self.smap:
390
+ # the clause is unit but a there is a previously seen
391
+ # duplicate of this clause; this means we have to
392
+ # reprocess the previous clause again and relax it
393
+ if selv not in processed_dups:
394
+ self.topv += 1
395
+ nsel = self.topv
396
+ self.sels[self.smap[selv] - 1] = nsel
397
+ self.formula.hard.append(self.formula.soft[self.smap[selv] - 1] + [-nsel])
398
+ self.formula.soft[self.smap[selv] - 1] = [nsel]
399
+ self.smap[nsel] = self.smap[selv]
400
+ processed_dups.add(selv)
401
+
402
+ # processing the current clause
403
+ self.topv += 1
404
+ selv = self.topv
405
+ self.formula.hard.append(cl + [-selv])
406
+
407
+ self.sels.append(selv)
408
+ self.formula.soft.append([selv])
409
+ self.smap[selv] = len(self.sels)
410
+
411
+ # garbage-collecting the duplicates
412
+ for selv in processed_dups:
413
+ del self.smap[selv]
414
+
415
+ # these numbers should be equal after the processing
416
+ assert len(self.sels) == len(self.smap) == len(self.formula.wght)
417
+
418
+ # creating a dictionary of weights
419
+ self.weights = {l: w for l, w in zip(self.sels, self.formula.wght)}
420
+
421
+ def _disjoint(self, formula, solver, adapt, exhaust, minz, trim):
422
+ """
423
+ This method constitutes the preliminary step of the implicit
424
+ hitting set paradigm of Forqes. Namely, it enumerates all the
425
+ disjoint *minimal correction subsets* (MCSes) of the formula,
426
+ which will be later used to bootstrap the hitting set solver.
427
+
428
+ Note that the MaxSAT solver in use is :class:`.RC2`. As a result,
429
+ all the input parameters of the method, namely, ``formula``,
430
+ ``solver``, ``adapt``, `exhaust``, ``minz``, and ``trim`` -
431
+ represent the input and the options for the RC2 solver.
432
+
433
+ :param formula: input formula
434
+ :param solver: SAT solver name
435
+ :param adapt: detect and adapt AtMost1 constraints
436
+ :param exhaust: exhaust unsatisfiable cores
437
+ :param minz: apply heuristic core minimization
438
+ :param trim: trim unsatisfiable cores at most this number of times
439
+
440
+ :type formula: :class:`.WCNF`
441
+ :type solver: str
442
+ :type adapt: bool
443
+ :type exhaust: bool
444
+ :type minz: bool
445
+ :type trim: int
446
+ """
447
+
448
+ # these will store disjoint MCSes
449
+ # (unit-size MCSes are stored separately)
450
+ to_hit, units = [], []
451
+
452
+ with RC2(formula, solver=solver, adapt=adapt, exhaust=exhaust,
453
+ minz=minz, trim=trim, verbose=0) as oracle:
454
+
455
+ # iterating over MaxSAT solutions
456
+ while True:
457
+ # a new MaxSAT model
458
+ model = oracle.compute()
459
+
460
+ if model is None:
461
+ # no model => no more disjoint MCSes
462
+ break
463
+
464
+ # extracting the MCS corresponding to the model
465
+ falsified = [l for l in self.sels if model[abs(l) - 1] == -l]
466
+
467
+ # unit size or not?
468
+ if len(falsified) > 1:
469
+ to_hit.append(falsified)
470
+ else:
471
+ units.append(falsified[0])
472
+
473
+ # blocking the MCS;
474
+ # next time, all these clauses will be satisfied
475
+ for l in falsified:
476
+ oracle.add_clause([l])
477
+
478
+ # reporting the MCS
479
+ if self.verbose > 3:
480
+ print('c mcs: {0} 0'.format(' '.join([str(self.smap[s]) for s in falsified])))
481
+
482
+ # RC2 will be destroyed next; let's keep the oracle time
483
+ self.disj_time = oracle.oracle_time()
484
+
485
+ return to_hit, units
486
+
487
+ def compute(self):
488
+ """
489
+ This method implements the main look of the implicit hitting set
490
+ paradigm of Forqes to compute a best-cost MUS. The result MUS is
491
+ returned as a list of integers, each representing a soft clause
492
+ index.
493
+
494
+ :rtype: list(int)
495
+ """
496
+
497
+ # correctly computed cost of the unit-mcs component
498
+ units_cost = sum(map(lambda l: self.weights[l], self.units))
499
+
500
+ while True:
501
+ # computing a new optimal hitting set
502
+ hs = self.hitman.get()
503
+
504
+ if hs is None:
505
+ # no more hitting sets exist
506
+ break
507
+
508
+ # setting all the selector polarities to true
509
+ self.oracle.set_phases(self.sels)
510
+
511
+ # testing satisfiability of the {self.units + hs} subset
512
+ res = self.oracle.solve(assumptions=hs)
513
+
514
+ if res == False:
515
+ # the candidate subset of clauses is unsatisfiable,
516
+ # i.e. it is an optimal MUS we are searching for;
517
+ # therefore, blocking it and returning
518
+ self.hitman.block(hs)
519
+ self.cost = sum(map(lambda l: self.weights[l], hs)) + units_cost
520
+
521
+ if self.units and self.cover:
522
+ # due to additional constraints to cover and unit mcses,
523
+ # there may be duplicate clauses in self.units + hs
524
+ return sorted(map(lambda s: self.smap[s], sorted(set(self.units + hs))))
525
+ else:
526
+ return sorted(map(lambda s: self.smap[s], self.units + hs))
527
+ else:
528
+ # the candidate subset is satisfiable,
529
+ # thus extracting a correction subset
530
+ model = self.oracle.get_model()
531
+ cs = [l for l in self.sels if model[abs(l) - 1] == -l]
532
+
533
+ # hitting the new correction subset
534
+ self.hitman.hit(cs, weights=self.weights)
535
+
536
+ def enumerate(self):
537
+ """
538
+ This is generator method iterating through MUSes and enumerating
539
+ them until the formula has no more MUSes, or a user decides to
540
+ stop the process.
541
+
542
+ :rtype: list(int)
543
+ """
544
+
545
+ done = False
546
+
547
+ while not done:
548
+ mus = self.compute()
549
+
550
+ if mus != None:
551
+ yield mus
552
+ else:
553
+ done = True
554
+
555
+ def oracle_time(self):
556
+ """
557
+ This method computes and returns the total SAT solving time
558
+ involved.
559
+
560
+ :rtype: float
561
+ """
562
+
563
+ return self.disj_time + self.hitman.oracle_time() + self.oracle.time_accum()
564
+
565
+
566
+ #
567
+ #==============================================================================
568
+ def parse_options():
569
+ """
570
+ Parses command-line options.
571
+ """
572
+
573
+ try:
574
+ opts, args = getopt.getopt(sys.argv[1:], 'ac:de:hmnp:P:s:t:uvx',
575
+ ['adapt', 'cover=', 'dcalls', 'enum=',
576
+ 'exhaust', 'help', 'minimize', 'no-disj',
577
+ 'solver=', 'puresat=', 'process=',
578
+ 'unsorted', 'trim=', 'verbose'])
579
+ except getopt.GetoptError as err:
580
+ sys.stderr.write(str(err).capitalize() + '\n')
581
+ usage()
582
+ sys.exit(1)
583
+
584
+ adapt = False
585
+ cover = None
586
+ dcalls = False
587
+ exhaust = False
588
+ minz = False
589
+ no_disj = False
590
+ to_enum = 1
591
+ solver = 'g3'
592
+ process = 0
593
+ puresat = False
594
+ unsorted = False
595
+ trim = 0
596
+ verbose = 1
597
+
598
+ for opt, arg in opts:
599
+ if opt in ('-a', '--adapt'):
600
+ adapt = True
601
+ elif opt in ('-c', '--cover'):
602
+ cover = str(arg)
603
+ if cover.lower() == 'none':
604
+ cover = None
605
+ elif opt in ('-d', '--dcalls'):
606
+ dcalls = True
607
+ elif opt in ('-e', '--enum'):
608
+ to_enum = str(arg)
609
+ if to_enum != 'all':
610
+ to_enum = int(to_enum)
611
+ elif opt in ('-h', '--help'):
612
+ usage()
613
+ sys.exit(0)
614
+ elif opt in ('-m', '--minimize'):
615
+ minz = True
616
+ elif opt in ('-n', '--no-disj'):
617
+ no_disj = True
618
+ elif opt in ('-p', '--puresat'):
619
+ puresat = str(arg)
620
+ elif opt in ('-P', '--process'):
621
+ process = int(arg)
622
+ elif opt in ('-s', '--solver'):
623
+ solver = str(arg)
624
+ elif opt in ('-u', '--unsorted'):
625
+ unsorted = True
626
+ elif opt in ('-t', '--trim'):
627
+ trim = int(arg)
628
+ elif opt in ('-v', '--verbose'):
629
+ verbose += 1
630
+ elif opt in ('-x', '--exhaust'):
631
+ exhaust = True
632
+ else:
633
+ assert False, 'Unhandled option: {0} {1}'.format(opt, arg)
634
+
635
+ return adapt, cover, dcalls, exhaust, minz, no_disj, trim, to_enum, \
636
+ solver, process, puresat, unsorted, verbose, args
637
+
638
+
639
+ #
640
+ #==============================================================================
641
+ def usage():
642
+ """
643
+ Prints help message.
644
+ """
645
+
646
+ print('Usage:', os.path.basename(sys.argv[0]), '[options] file')
647
+ print('Options:')
648
+ print(' -a, --adapt Try to adapt (simplify) input formula')
649
+ print(' -c, --cover=<string> Stop MUS enumeration as soon as this formula is covered')
650
+ print(' Available values: any valid file path, none (default = none)')
651
+ print(' -d, --dcalls Apply clause D calls (in unsorted enumeration only)')
652
+ print(' -e, --enum=<string> Enumerate top-k solutions')
653
+ print(' Available values: [1 .. INT_MAX], all (default: 1)')
654
+ print(' -h, --help Show this message')
655
+ print(' -m, --minimize Use a heuristic unsatisfiable core minimizer')
656
+ print(' -n, --no-disj Do not enumerate disjoint MCSes')
657
+ print(' -p, --puresat=<string> Use a pure SAT-based hitting set enumerator')
658
+ print(' Available values: cd15, cd19, lgl, mgh (default = mgh)')
659
+ print(' Requires: unsorted mode, i.e. \'-u\'')
660
+ print(' -P, --process=<int> Number of processing rounds')
661
+ print(' Available values: [0 .. INT_MAX] (default = 0)')
662
+ print(' -s, --solver SAT solver to use')
663
+ print(' Available values: cd15, cd19, g3, g4, lgl, mcb, mcm, mpl, m22, mc, mgh (default = g3)')
664
+ print(' -t, --trim=<int> How many times to trim unsatisfiable cores')
665
+ print(' Available values: [0 .. INT_MAX] (default = 0)')
666
+ print(' -u, --unsorted Enumerate MUSes in an unsorted way')
667
+ print(' -v, --verbose Be verbose')
668
+ print(' -x, --exhaust Exhaust new unsatisfiable cores')
669
+
670
+
671
+ #
672
+ #==============================================================================
673
+ if __name__ == '__main__':
674
+ adapt, cover, dcalls, exhaust, minz, no_disj, trim, to_enum, solver, \
675
+ process, puresat, unsorted, verbose, files = parse_options()
676
+
677
+ if files:
678
+ # reading standard CNF, WCNF, or (W)CNF+
679
+ assert re.search(r'cnf[p|+]?(\.(gz|bz2|lzma|xz|zst))?$', files[0]), 'Unknown input file extension'
680
+ if re.search(r'\.wcnf[p|+]?(\.(gz|bz2|lzma|xz|zst))?$', files[0]):
681
+ formula = WCNFPlus(from_file=files[0])
682
+ else: # expecting '*.cnf[,p,+].*'
683
+ formula = CNFPlus(from_file=files[0]).weighted()
684
+
685
+ if cover: # expecting '*.cnf[,p,+].*' only!
686
+ assert re.search(r'cnf[p|+]?(\.(gz|bz2|lzma|xz|zst))?$', cover), 'Wrong file for formula to cover'
687
+ cover = CNFPlus(from_file=cover)
688
+
689
+ # creating an object of OptUx
690
+ with OptUx(formula, solver=solver, adapt=adapt, cover=cover,
691
+ dcalls=dcalls, exhaust=exhaust, minz=minz, nodisj=no_disj,
692
+ process=process, puresat=puresat, unsorted=unsorted,
693
+ trim=trim, verbose=verbose) as optux:
694
+
695
+ # iterating over the necessary number of optimal MUSes
696
+ for i, mus in enumerate(optux.enumerate()):
697
+ # reporting the current solution
698
+ if verbose:
699
+ print('c mus:', ' '.join([str(cl_id) for cl_id in mus]), '0')
700
+
701
+ if verbose > 1:
702
+ print('c cost:', optux.cost)
703
+
704
+ # checking if we are done
705
+ if to_enum and i + 1 == to_enum:
706
+ break
707
+
708
+ # reporting the total oracle time
709
+ if verbose > 1:
710
+ print('c oracle time: {0:.4f}'.format(optux.oracle_time()))