python-sat 1.9.dev20__cp314-cp314t-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. pycard.cp314t-win_amd64.pyd +0 -0
  2. pyformula.cp314t-win_amd64.pyd +0 -0
  3. pysat/__init__.py +36 -0
  4. pysat/_fileio.py +224 -0
  5. pysat/_utils.py +58 -0
  6. pysat/allies/__init__.py +0 -0
  7. pysat/allies/approxmc.py +385 -0
  8. pysat/allies/unigen.py +435 -0
  9. pysat/card.py +1024 -0
  10. pysat/engines.py +1328 -0
  11. pysat/examples/__init__.py +0 -0
  12. pysat/examples/bbscan.py +663 -0
  13. pysat/examples/bica.py +701 -0
  14. pysat/examples/fm.py +527 -0
  15. pysat/examples/genhard.py +516 -0
  16. pysat/examples/hitman.py +648 -0
  17. pysat/examples/lbx.py +638 -0
  18. pysat/examples/lsu.py +496 -0
  19. pysat/examples/mcsls.py +610 -0
  20. pysat/examples/models.py +189 -0
  21. pysat/examples/musx.py +344 -0
  22. pysat/examples/optux.py +750 -0
  23. pysat/examples/primer.py +620 -0
  24. pysat/examples/rc2.py +2043 -0
  25. pysat/examples/usage.py +63 -0
  26. pysat/formula.py +5899 -0
  27. pysat/integer.py +2768 -0
  28. pysat/pb.py +454 -0
  29. pysat/process.py +363 -0
  30. pysat/solvers.py +10223 -0
  31. pysolvers.cp314t-win_amd64.pyd +0 -0
  32. python_sat-1.9.dev20.data/platlib/msvcp140-a4c2229bdc2a2a630acdc095b4d86008.dll +0 -0
  33. python_sat-1.9.dev20.data/scripts/approxmc.py +385 -0
  34. python_sat-1.9.dev20.data/scripts/bbscan.py +663 -0
  35. python_sat-1.9.dev20.data/scripts/bica.py +701 -0
  36. python_sat-1.9.dev20.data/scripts/fm.py +527 -0
  37. python_sat-1.9.dev20.data/scripts/genhard.py +516 -0
  38. python_sat-1.9.dev20.data/scripts/lbx.py +638 -0
  39. python_sat-1.9.dev20.data/scripts/lsu.py +496 -0
  40. python_sat-1.9.dev20.data/scripts/mcsls.py +610 -0
  41. python_sat-1.9.dev20.data/scripts/models.py +189 -0
  42. python_sat-1.9.dev20.data/scripts/musx.py +344 -0
  43. python_sat-1.9.dev20.data/scripts/optux.py +750 -0
  44. python_sat-1.9.dev20.data/scripts/primer.py +620 -0
  45. python_sat-1.9.dev20.data/scripts/rc2.py +2043 -0
  46. python_sat-1.9.dev20.data/scripts/unigen.py +435 -0
  47. python_sat-1.9.dev20.dist-info/DELVEWHEEL +2 -0
  48. python_sat-1.9.dev20.dist-info/METADATA +47 -0
  49. python_sat-1.9.dev20.dist-info/RECORD +52 -0
  50. python_sat-1.9.dev20.dist-info/WHEEL +5 -0
  51. python_sat-1.9.dev20.dist-info/licenses/LICENSE.txt +21 -0
  52. python_sat-1.9.dev20.dist-info/top_level.txt +4 -0
@@ -0,0 +1,750 @@
1
+ #!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
+ Since the algorithm builds on implicit hitting set dualization,
178
+ correction sets computed along the way can be minimized
179
+ *heuristically*, by limiting the budget on the number of conflicts a
180
+ SAT solver can collect during the minimization process. Parameter
181
+ ``reduce`` is responsible for this behaviour. By default, the budget
182
+ is set to ``0``, meaning that no reduction of correction sets is
183
+ performed. Setting it to ``-1`` forces the solver to compute an exact
184
+ MCS. Otherwise, any positive value of this parameter will indicate the
185
+ upper bound on the number of conflicts.
186
+
187
+ Finally, one more optional input parameter ``cover`` is to be used
188
+ when exhaustive enumeration of MUSes is not necessary and the tool can
189
+ stop as soon as a given formula is covered by the set of currently
190
+ computed MUSes. This can be made to work if the soft clauses of
191
+ ``formula`` are of size 1.
192
+
193
+ .. [5] Alexey Ignatiev, Antonio Morgado, Joao Marques-Silva. *RC2: an
194
+ Efficient MaxSAT Solver*. J. Satisf. Boolean Model. Comput. 11(1).
195
+ 2019. pp. 53-64
196
+
197
+ .. [6] Gilles Audemard, Jean-Marie Lagniez, Laurent Simon.
198
+ *Improving Glucose for Incremental SAT Solving with
199
+ Assumptions: Application to MUS Extraction*. SAT 2013.
200
+ pp. 309-317
201
+
202
+ .. [7] Enrico Giunchiglia, Marco Maratea. *Solving Optimization
203
+ Problems with DLL*. ECAI 2006. pp. 377-381
204
+
205
+ .. [8] Knot Pipatsrisawat, Adnan Darwiche. *A Lightweight Component
206
+ Caching Scheme for Satisfiability Solvers*. SAT 2007. pp. 294-299
207
+
208
+
209
+ :param formula: (weighted) (partial) CNFPlus formula
210
+ :param solver: SAT oracle name
211
+ :param adapt: detect and adapt intrinsic AtMost1 constraints
212
+ :param cover: CNFPlus formula to cover when doing MUS enumeration
213
+ :param dcalls: apply clause D oracle calls (for unsorted enumeration only)
214
+ :param exhaust: do core exhaustion
215
+ :param minz: do heuristic core reduction
216
+ :param nodisj: do not enumerate disjoint MCSes
217
+ :param process: apply formula preprocessing this many times
218
+ :param puresat: use pure SAT-based hitting set enumeration
219
+ :param reduce: minimize correction sets using this budget on the number of conflicts
220
+ :param unsorted: apply unsorted MUS enumeration
221
+ :param trim: do core trimming at most this number of times
222
+ :param verbose: verbosity level
223
+
224
+ :type formula: :class:`.WCNFPlus`
225
+ :type solver: str
226
+ :type adapt: bool
227
+ :type cover: :class:`.CNFPlus`
228
+ :type dcalls: bool
229
+ :type exhaust: bool
230
+ :type minz: bool
231
+ :type nodisj: bool
232
+ :type process: int
233
+ :type puresat: str
234
+ :type reduce: int
235
+ :type unsorted: bool
236
+ :type trim: int
237
+ :type verbose: int
238
+ """
239
+
240
+ def __init__(self, formula, solver='g3', adapt=False, cover=None,
241
+ dcalls=False, exhaust=False, minz=False, nodisj=False, process=0,
242
+ puresat=False, reduce=0, unsorted=False, trim=False, verbose=0):
243
+ """
244
+ Constructor.
245
+ """
246
+
247
+ assert not puresat or unsorted, '\'unsorted\' needs to be True for pure SAT mode'
248
+
249
+ # verbosity level
250
+ self.verbose = verbose
251
+
252
+ # budget on the number of conflicts when reducing corrections sets
253
+ self.reduce_budget = reduce
254
+
255
+ # constructing a local copy of the formula
256
+ self.formula = WCNFPlus()
257
+ self.formula.hard = formula.hard[:]
258
+ self.formula.wght = formula.wght[:]
259
+ self.formula.topw = formula.topw
260
+ self.formula.nv = formula.nv
261
+
262
+ # copying atmost constraints, if any
263
+ if isinstance(formula, WCNFPlus) and formula.atms:
264
+ self.formula.atms = formula.atms[:]
265
+
266
+ # top variable identifier
267
+ self.topv = formula.nv
268
+
269
+ # processing soft clauses
270
+ self._process_soft(formula)
271
+ self.formula.nv = self.topv
272
+
273
+ # applying formula processing (if any)
274
+ if process:
275
+ # the processor is immediately destroyed,
276
+ # as we do not need to restore the models
277
+ with Processor(bootstrap_with=self.formula.hard) as processor:
278
+ proc = processor.process(rounds=process, freeze=self.sels)
279
+ self.formula.hard = proc.clauses
280
+ self.formula.nv = max(self.formula.nv, proc.nv)
281
+
282
+ # creating an unweighted copy
283
+ unweighted = self.formula.copy()
284
+ unweighted.wght = [1 for w in unweighted.wght]
285
+
286
+ if not nodisj:
287
+ # enumerating disjoint MCSes (including unit-size MCSes)
288
+ to_hit, self.units = self._disjoint(unweighted, solver, adapt, exhaust,
289
+ minz, trim)
290
+ else:
291
+ to_hit, self.units, self.disj_time = [], [], 0.
292
+
293
+ if self.verbose > 2:
294
+ print('c mcses: {0} unit, {1} disj'.format(len(self.units),
295
+ len(to_hit) + len(self.units)))
296
+
297
+ if not unsorted:
298
+ # MaxSAT-based hitting set enumerator
299
+ self.hitman = Hitman(bootstrap_with=to_hit, weights=self.weights,
300
+ solver=solver, htype='sorted', mxs_adapt=adapt,
301
+ mxs_exhaust=exhaust, mxs_minz=minz, mxs_trim=trim)
302
+ elif not puresat:
303
+ # MCS-based hitting set enumerator
304
+ self.hitman = Hitman(bootstrap_with=to_hit, weights=self.weights,
305
+ solver=solver, htype='lbx', mcs_usecld=dcalls)
306
+ else:
307
+ # pure SAT-based hitting set enumerator with preferred phases
308
+ self.hitman = Hitman(bootstrap_with=to_hit, weights=self.weights,
309
+ solver=puresat, htype='sat')
310
+
311
+ # adding the formula to cover to the hitting set enumerator
312
+ self.cover = cover is not None
313
+ if cover:
314
+ # mapping literals to Hitman's atoms
315
+ m = lambda l: Atom(l, sign=True) if -l not in self.weights else Atom(-l, sign=False)
316
+
317
+ for cl in cover:
318
+ if len(cl) != 2 or not type(cl[0]) in (list, tuple, set):
319
+ cl = [m(l) for l in cl]
320
+ else:
321
+ cl = [[m(l) for l in cl[0]], cl[1]]
322
+
323
+ self.hitman.add_hard(cl, weights=self.weights)
324
+
325
+ # SAT oracle bootstrapped with the hard clauses; note that
326
+ # clauses of the unit-size MCSes are enforced to be enabled
327
+ self.oracle = Solver(name=solver, bootstrap_with=unweighted.hard +
328
+ [[mcs] for mcs in self.units], use_timer=True)
329
+
330
+ if unweighted.atms:
331
+ if solver in SolverNames.cadical195:
332
+ # we are using CaDiCaL195 and it can use external linear engine
333
+ self.oracle.activate_atmost()
334
+
335
+ assert self.oracle.supports_atmost(), \
336
+ '{0} does not support native cardinality constraints. Make sure you use the right type of formula.'.format(self.solver)
337
+
338
+ for atm in unweighted.atms:
339
+ self.oracle.add_atmost(*atm)
340
+
341
+ def __del__(self):
342
+ """
343
+ Destructor.
344
+ """
345
+
346
+ self.delete()
347
+
348
+ def __enter__(self):
349
+ """
350
+ 'with' constructor.
351
+ """
352
+
353
+ return self
354
+
355
+ def __exit__(self, exc_type, exc_value, traceback):
356
+ """
357
+ 'with' destructor.
358
+ """
359
+
360
+ self.delete()
361
+
362
+ def delete(self):
363
+ """
364
+ Explicit destructor of the internal hitting set and SAT oracles.
365
+ """
366
+
367
+ if self.hitman:
368
+ self.hitman.delete()
369
+ self.hitman = None
370
+
371
+ if self.oracle:
372
+ self.oracle.delete()
373
+ self.oracle = None
374
+
375
+ def _process_soft(self, formula):
376
+ """
377
+ The method is for processing the soft clauses of the input
378
+ formula. Concretely, it checks which soft clauses must be relaxed
379
+ by a unique selector literal and applies the relaxation.
380
+
381
+ :param formula: input formula
382
+ :type formula: :class:`.WCNF`
383
+ """
384
+
385
+ # list of selectors
386
+ self.sels = []
387
+
388
+ # mapping from selectors to clause ids
389
+ self.smap = {}
390
+
391
+ # duplicate unit clauses
392
+ processed_dups = set()
393
+
394
+ # processing the soft clauses
395
+ for cl in formula.soft:
396
+ # if the clause is unit-size, its sole literal acts a selector
397
+ selv = cl[0]
398
+
399
+ # if clause is not unit, we relax it
400
+ if len(cl) > 1:
401
+ self.topv += 1
402
+ selv = self.topv
403
+ self.formula.hard.append(cl + [-selv])
404
+ elif selv in self.smap:
405
+ # the clause is unit but a there is a previously seen
406
+ # duplicate of this clause; this means we have to
407
+ # reprocess the previous clause again and relax it
408
+ if selv not in processed_dups:
409
+ self.topv += 1
410
+ nsel = self.topv
411
+ self.sels[self.smap[selv] - 1] = nsel
412
+ self.formula.hard.append(self.formula.soft[self.smap[selv] - 1] + [-nsel])
413
+ self.formula.soft[self.smap[selv] - 1] = [nsel]
414
+ self.smap[nsel] = self.smap[selv]
415
+ processed_dups.add(selv)
416
+
417
+ # processing the current clause
418
+ self.topv += 1
419
+ selv = self.topv
420
+ self.formula.hard.append(cl + [-selv])
421
+
422
+ self.sels.append(selv)
423
+ self.formula.soft.append([selv])
424
+ self.smap[selv] = len(self.sels)
425
+
426
+ # garbage-collecting the duplicates
427
+ for selv in processed_dups:
428
+ del self.smap[selv]
429
+
430
+ # these numbers should be equal after the processing
431
+ assert len(self.sels) == len(self.smap) == len(self.formula.wght)
432
+
433
+ # creating a dictionary of weights
434
+ self.weights = {l: w for l, w in zip(self.sels, self.formula.wght)}
435
+
436
+ def _disjoint(self, formula, solver, adapt, exhaust, minz, trim):
437
+ """
438
+ This method constitutes the preliminary step of the implicit
439
+ hitting set paradigm of Forqes. Namely, it enumerates all the
440
+ disjoint *minimal correction subsets* (MCSes) of the formula,
441
+ which will be later used to bootstrap the hitting set solver.
442
+
443
+ Note that the MaxSAT solver in use is :class:`.RC2`. As a result,
444
+ all the input parameters of the method, namely, ``formula``,
445
+ ``solver``, ``adapt``, `exhaust``, ``minz``, and ``trim`` -
446
+ represent the input and the options for the RC2 solver.
447
+
448
+ :param formula: input formula
449
+ :param solver: SAT solver name
450
+ :param adapt: detect and adapt AtMost1 constraints
451
+ :param exhaust: exhaust unsatisfiable cores
452
+ :param minz: apply heuristic core minimization
453
+ :param trim: trim unsatisfiable cores at most this number of times
454
+
455
+ :type formula: :class:`.WCNF`
456
+ :type solver: str
457
+ :type adapt: bool
458
+ :type exhaust: bool
459
+ :type minz: bool
460
+ :type trim: int
461
+ """
462
+
463
+ # these will store disjoint MCSes
464
+ # (unit-size MCSes are stored separately)
465
+ to_hit, units = [], []
466
+
467
+ with RC2(formula, solver=solver, adapt=adapt, exhaust=exhaust,
468
+ minz=minz, trim=trim, verbose=0) as oracle:
469
+
470
+ # iterating over MaxSAT solutions
471
+ while True:
472
+ # a new MaxSAT model
473
+ model = oracle.compute()
474
+
475
+ if model is None:
476
+ # no model => no more disjoint MCSes
477
+ break
478
+
479
+ # extracting the MCS corresponding to the model
480
+ falsified = [l for l in self.sels if model[abs(l) - 1] == -l]
481
+
482
+ # unit size or not?
483
+ if len(falsified) > 1:
484
+ to_hit.append(falsified)
485
+ else:
486
+ units.append(falsified[0])
487
+
488
+ # blocking the MCS;
489
+ # next time, all these clauses will be satisfied
490
+ for l in falsified:
491
+ oracle.add_clause([l])
492
+
493
+ # reporting the MCS
494
+ if self.verbose > 3:
495
+ print('c mcs: {0} 0'.format(' '.join([str(self.smap[s]) for s in falsified])))
496
+
497
+ # RC2 will be destroyed next; let's keep the oracle time
498
+ self.disj_time = oracle.oracle_time()
499
+
500
+ return to_hit, units
501
+
502
+ def compute(self):
503
+ """
504
+ This method implements the main look of the implicit hitting set
505
+ paradigm of Forqes to compute a best-cost MUS. The result MUS is
506
+ returned as a list of integers, each representing a soft clause
507
+ index.
508
+
509
+ :rtype: list(int)
510
+ """
511
+
512
+ # correctly computed cost of the unit-mcs component
513
+ units_cost = sum(map(lambda l: self.weights[l], self.units))
514
+
515
+ it = 0
516
+ while True:
517
+ # computing a new optimal hitting set
518
+ hs = self.hitman.get()
519
+ if self.verbose > 3:
520
+ self.cost = sum(map(lambda l: self.weights[l], hs)) + units_cost
521
+
522
+ if hs is None:
523
+ # no more hitting sets exist
524
+ break
525
+
526
+ # setting all the selector polarities to true
527
+ self.oracle.set_phases(self.sels)
528
+
529
+ # testing satisfiability of the {self.units + hs} subset
530
+ res = self.oracle.solve(assumptions=hs)
531
+
532
+ if res == False:
533
+ # the candidate subset of clauses is unsatisfiable,
534
+ # i.e. it is an optimal MUS we are searching for;
535
+ # therefore, blocking it and returning
536
+ self.hitman.block(hs)
537
+ self.cost = sum(map(lambda l: self.weights[l], hs)) + units_cost
538
+
539
+ if self.units and self.cover:
540
+ # due to additional constraints to cover and unit mcses,
541
+ # there may be duplicate clauses in self.units + hs
542
+ return sorted(map(lambda s: self.smap[s], sorted(set(self.units + hs))))
543
+ else:
544
+ return sorted(map(lambda s: self.smap[s], self.units + hs))
545
+ else:
546
+ # the candidate subset is satisfiable,
547
+ # thus extracting a correction subset
548
+ model = self.oracle.get_model()
549
+ cs = [l for l in self.sels if model[abs(l) - 1] == -l]
550
+
551
+ # naive MCS extraction with a budget on the conflict count
552
+ if self.reduce_budget:
553
+ ss = [l for l in self.sels if model[abs(l) - 1] == +l]
554
+ self.oracle.conf_budget(self.reduce_budget)
555
+
556
+ # probing all the literals of the correction set
557
+ for l in cs:
558
+ if self.oracle.solve_limited(assumptions=[l] + ss) is True:
559
+ ss.append(l)
560
+
561
+ cs = sorted(set(cs) - set(ss))
562
+ self.oracle.conf_budget(budget=-1)
563
+
564
+ # hitting the new correction subset
565
+ self.hitman.hit(cs, weights=self.weights)
566
+
567
+ it += 1
568
+ if self.verbose > 3:
569
+ print(f'c iter: {it}, cost: {self.cost}; cs sz: {len(cs)}')
570
+
571
+ def enumerate(self):
572
+ """
573
+ This is generator method iterating through MUSes and enumerating
574
+ them until the formula has no more MUSes, or a user decides to
575
+ stop the process.
576
+
577
+ :rtype: list(int)
578
+ """
579
+
580
+ done = False
581
+
582
+ while not done:
583
+ mus = self.compute()
584
+
585
+ if mus != None:
586
+ yield mus
587
+ else:
588
+ done = True
589
+
590
+ def oracle_time(self):
591
+ """
592
+ This method computes and returns the total SAT solving time
593
+ involved.
594
+
595
+ :rtype: float
596
+ """
597
+
598
+ return self.disj_time + self.hitman.oracle_time() + self.oracle.time_accum()
599
+
600
+
601
+ #
602
+ #==============================================================================
603
+ def parse_options():
604
+ """
605
+ Parses command-line options.
606
+ """
607
+
608
+ try:
609
+ opts, args = getopt.getopt(sys.argv[1:], 'ac:de:hmnp:P:r:s:t:uvx',
610
+ ['adapt', 'cover=', 'dcalls', 'enum=',
611
+ 'exhaust', 'help', 'minimize', 'no-disj',
612
+ 'solver=', 'puresat=', 'process=',
613
+ 'reduce=', 'unsorted', 'trim=', 'verbose'])
614
+ except getopt.GetoptError as err:
615
+ sys.stderr.write(str(err).capitalize() + '\n')
616
+ usage()
617
+ sys.exit(1)
618
+
619
+ adapt = False
620
+ cover = None
621
+ dcalls = False
622
+ exhaust = False
623
+ minz = False
624
+ no_disj = False
625
+ to_enum = 1
626
+ solver = 'g3'
627
+ process = 0
628
+ puresat = False
629
+ reduce = 0
630
+ unsorted = False
631
+ trim = 0
632
+ verbose = 1
633
+
634
+ for opt, arg in opts:
635
+ if opt in ('-a', '--adapt'):
636
+ adapt = True
637
+ elif opt in ('-c', '--cover'):
638
+ cover = str(arg)
639
+ if cover.lower() == 'none':
640
+ cover = None
641
+ elif opt in ('-d', '--dcalls'):
642
+ dcalls = True
643
+ elif opt in ('-e', '--enum'):
644
+ to_enum = str(arg)
645
+ if to_enum != 'all':
646
+ to_enum = int(to_enum)
647
+ elif opt in ('-h', '--help'):
648
+ usage()
649
+ sys.exit(0)
650
+ elif opt in ('-m', '--minimize'):
651
+ minz = True
652
+ elif opt in ('-n', '--no-disj'):
653
+ no_disj = True
654
+ elif opt in ('-p', '--puresat'):
655
+ puresat = str(arg)
656
+ elif opt in ('-P', '--process'):
657
+ process = int(arg)
658
+ elif opt in ('-r', '--reduce'):
659
+ reduce = int(arg)
660
+ elif opt in ('-s', '--solver'):
661
+ solver = str(arg)
662
+ elif opt in ('-u', '--unsorted'):
663
+ unsorted = True
664
+ elif opt in ('-t', '--trim'):
665
+ trim = int(arg)
666
+ elif opt in ('-v', '--verbose'):
667
+ verbose += 1
668
+ elif opt in ('-x', '--exhaust'):
669
+ exhaust = True
670
+ else:
671
+ assert False, 'Unhandled option: {0} {1}'.format(opt, arg)
672
+
673
+ return adapt, cover, dcalls, exhaust, minz, no_disj, trim, to_enum, \
674
+ solver, process, puresat, reduce, unsorted, verbose, args
675
+
676
+
677
+ #
678
+ #==============================================================================
679
+ def usage():
680
+ """
681
+ Prints help message.
682
+ """
683
+
684
+ print('Usage:', os.path.basename(sys.argv[0]), '[options] file')
685
+ print('Options:')
686
+ print(' -a, --adapt Try to adapt (simplify) input formula')
687
+ print(' -c, --cover=<string> Stop MUS enumeration as soon as this formula is covered')
688
+ print(' Available values: any valid file path, none (default = none)')
689
+ print(' -d, --dcalls Apply clause D calls (in unsorted enumeration only)')
690
+ print(' -e, --enum=<string> Enumerate top-k solutions')
691
+ print(' Available values: [1 .. INT_MAX], all (default: 1)')
692
+ print(' -h, --help Show this message')
693
+ print(' -m, --minimize Use a heuristic unsatisfiable core minimizer')
694
+ print(' -n, --no-disj Do not enumerate disjoint MCSes')
695
+ print(' -p, --puresat=<string> Use a pure SAT-based hitting set enumerator')
696
+ print(' Available values: cd15, cd19, lgl, mgh (default = mgh)')
697
+ print(' Requires: unsorted mode, i.e. \'-u\'')
698
+ print(' -P, --process=<int> Number of processing rounds')
699
+ print(' Available values: [0 .. INT_MAX] (default = 0)')
700
+ print(' -r, --reduce=<int> Conflict budget when reducing correction sets')
701
+ print(' Available values: [0 .. INT_MAX], -1 (default = 0, meaning no reduction)')
702
+ print(' -s, --solver SAT solver to use')
703
+ print(' Available values: cd15, cd19, g3, g4, lgl, mcb, mcm, mpl, m22, mc, mgh (default = g3)')
704
+ print(' -t, --trim=<int> How many times to trim unsatisfiable cores')
705
+ print(' Available values: [0 .. INT_MAX] (default = 0)')
706
+ print(' -u, --unsorted Enumerate MUSes in an unsorted way')
707
+ print(' -v, --verbose Be verbose')
708
+ print(' -x, --exhaust Exhaust new unsatisfiable cores')
709
+
710
+
711
+ #
712
+ #==============================================================================
713
+ if __name__ == '__main__':
714
+ adapt, cover, dcalls, exhaust, minz, no_disj, trim, to_enum, solver, \
715
+ process, puresat, reduce, unsorted, verbose, files = parse_options()
716
+
717
+ if files:
718
+ # reading standard CNF, WCNF, or (W)CNF+
719
+ assert re.search(r'cnf[p|+]?(\.(gz|bz2|lzma|xz|zst))?$', files[0]), 'Unknown input file extension'
720
+ if re.search(r'\.wcnf[p|+]?(\.(gz|bz2|lzma|xz|zst))?$', files[0]):
721
+ formula = WCNFPlus(from_file=files[0])
722
+ else: # expecting '*.cnf[,p,+].*'
723
+ formula = CNFPlus(from_file=files[0]).weighted()
724
+
725
+ if cover: # expecting '*.cnf[,p,+].*' only!
726
+ assert re.search(r'cnf[p|+]?(\.(gz|bz2|lzma|xz|zst))?$', cover), 'Wrong file for formula to cover'
727
+ cover = CNFPlus(from_file=cover)
728
+
729
+ # creating an object of OptUx
730
+ with OptUx(formula, solver=solver, adapt=adapt, cover=cover,
731
+ dcalls=dcalls, exhaust=exhaust, minz=minz, nodisj=no_disj,
732
+ process=process, puresat=puresat, reduce=reduce,
733
+ unsorted=unsorted, trim=trim, verbose=verbose) as optux:
734
+
735
+ # iterating over the necessary number of optimal MUSes
736
+ for i, mus in enumerate(optux.enumerate()):
737
+ # reporting the current solution
738
+ if verbose:
739
+ print('c mus:', ' '.join([str(cl_id) for cl_id in mus]), '0')
740
+
741
+ if verbose > 1:
742
+ print('c cost:', optux.cost)
743
+
744
+ # checking if we are done
745
+ if to_enum and i + 1 == to_enum:
746
+ break
747
+
748
+ # reporting the total oracle time
749
+ if verbose > 1:
750
+ print('c oracle time: {0:.4f}'.format(optux.oracle_time()))