python-sat 0.1.8.dev10__cp310-cp310-win_amd64.whl → 1.8.dev26__cp310-cp310-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.

Potentially problematic release.


This version of python-sat might be problematic. Click here for more details.

Files changed (45) hide show
  1. pycard.cp310-win_amd64.pyd +0 -0
  2. pysat/__init__.py +4 -4
  3. pysat/_fileio.py +30 -14
  4. pysat/allies/approxmc.py +22 -22
  5. pysat/allies/unigen.py +435 -0
  6. pysat/card.py +13 -12
  7. pysat/engines.py +1302 -0
  8. pysat/examples/bbscan.py +663 -0
  9. pysat/examples/bica.py +691 -0
  10. pysat/examples/fm.py +12 -8
  11. pysat/examples/genhard.py +24 -23
  12. pysat/examples/hitman.py +53 -37
  13. pysat/examples/lbx.py +56 -15
  14. pysat/examples/lsu.py +28 -14
  15. pysat/examples/mcsls.py +53 -15
  16. pysat/examples/models.py +6 -4
  17. pysat/examples/musx.py +15 -7
  18. pysat/examples/optux.py +71 -32
  19. pysat/examples/primer.py +620 -0
  20. pysat/examples/rc2.py +268 -69
  21. pysat/formula.py +3241 -229
  22. pysat/pb.py +85 -37
  23. pysat/process.py +16 -2
  24. pysat/solvers.py +2119 -724
  25. pysolvers.cp310-win_amd64.pyd +0 -0
  26. {python_sat-0.1.8.dev10.data → python_sat-1.8.dev26.data}/scripts/approxmc.py +22 -22
  27. python_sat-1.8.dev26.data/scripts/bbscan.py +663 -0
  28. python_sat-1.8.dev26.data/scripts/bica.py +691 -0
  29. {python_sat-0.1.8.dev10.data → python_sat-1.8.dev26.data}/scripts/fm.py +12 -8
  30. {python_sat-0.1.8.dev10.data → python_sat-1.8.dev26.data}/scripts/genhard.py +24 -23
  31. {python_sat-0.1.8.dev10.data → python_sat-1.8.dev26.data}/scripts/lbx.py +56 -15
  32. {python_sat-0.1.8.dev10.data → python_sat-1.8.dev26.data}/scripts/lsu.py +28 -14
  33. {python_sat-0.1.8.dev10.data → python_sat-1.8.dev26.data}/scripts/mcsls.py +53 -15
  34. {python_sat-0.1.8.dev10.data → python_sat-1.8.dev26.data}/scripts/models.py +6 -4
  35. {python_sat-0.1.8.dev10.data → python_sat-1.8.dev26.data}/scripts/musx.py +15 -7
  36. {python_sat-0.1.8.dev10.data → python_sat-1.8.dev26.data}/scripts/optux.py +71 -32
  37. python_sat-1.8.dev26.data/scripts/primer.py +620 -0
  38. {python_sat-0.1.8.dev10.data → python_sat-1.8.dev26.data}/scripts/rc2.py +268 -69
  39. python_sat-1.8.dev26.data/scripts/unigen.py +435 -0
  40. {python_sat-0.1.8.dev10.dist-info → python_sat-1.8.dev26.dist-info}/METADATA +19 -5
  41. python_sat-1.8.dev26.dist-info/RECORD +48 -0
  42. {python_sat-0.1.8.dev10.dist-info → python_sat-1.8.dev26.dist-info}/WHEEL +1 -1
  43. python_sat-0.1.8.dev10.dist-info/RECORD +0 -39
  44. {python_sat-0.1.8.dev10.dist-info → python_sat-1.8.dev26.dist-info/licenses}/LICENSE.txt +0 -0
  45. {python_sat-0.1.8.dev10.dist-info → python_sat-1.8.dev26.dist-info}/top_level.txt +0 -0
@@ -118,6 +118,11 @@
118
118
  either to compute one MaxSAT solution of an input formula, or to
119
119
  enumerate a given number (or *all*) of its top MaxSAT solutions.
120
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
+
121
126
  ==============
122
127
  Module details
123
128
  ==============
@@ -133,7 +138,9 @@ from math import copysign
133
138
  import os
134
139
  from pysat.formula import CNFPlus, WCNFPlus, IDPool
135
140
  from pysat.card import ITotalizer
141
+ from pysat.process import Processor
136
142
  from pysat.solvers import Solver, SolverNames
143
+ from threading import Timer
137
144
  import re
138
145
  import six
139
146
  from six.moves import range
@@ -160,17 +167,17 @@ class RC2(object):
160
167
  - *unsatisfiable core reduction* (see method :func:`minimize_core`),
161
168
  - *intrinsic AtMost1 constraints* (see method :func:`adapt_am1`).
162
169
 
163
- :class:`RC2` can use any SAT solver available in PySAT. The
164
- default SAT solver to use is ``g3`` (see
165
- :class:`.SolverNames`). Additionally, if Glucose is chosen,
166
- the ``incr`` parameter controls whether to use the incremental
167
- mode of Glucose [7]_ (turned off by default). Boolean
168
- parameters ``adapt``, ``exhaust``, and ``minz`` control
169
- whether or to apply detection and adaptation of intrinsic
170
- AtMost1 constraints, core exhaustion, and core reduction.
171
- Unsatisfiable cores can be trimmed if the ``trim`` parameter
172
- is set to a non-zero integer. Finally, verbosity level can be
173
- set using the ``verbose`` parameter.
170
+ :class:`RC2` can use any SAT solver available in PySAT. The default
171
+ SAT solver to use is ``g3`` (see :class:`.SolverNames`). Additionally,
172
+ if Glucose is chosen, the ``incr`` parameter controls whether to use
173
+ the incremental mode of Glucose [7]_ (turned off by default). Boolean
174
+ parameters ``adapt``, ``exhaust``, and ``minz`` control whether or to
175
+ apply detection and adaptation of intrinsic AtMost1 constraints, core
176
+ exhaustion, and core reduction. Unsatisfiable cores can be trimmed if
177
+ the ``trim`` parameter is set to a non-zero integer. Formula
178
+ preprocessing can be applied a given number of rounds specified as the
179
+ value of parameter ``process``. Finally, verbosity level can be set
180
+ using the ``verbose`` parameter.
174
181
 
175
182
  .. [7] Gilles Audemard, Jean-Marie Lagniez, Laurent Simon.
176
183
  *Improving Glucose for Incremental SAT Solving with
@@ -183,6 +190,7 @@ class RC2(object):
183
190
  :param exhaust: do core exhaustion
184
191
  :param incr: use incremental mode of Glucose
185
192
  :param minz: do heuristic core reduction
193
+ :param process: apply formula preprocessing this many times
186
194
  :param trim: do core trimming at most this number of times
187
195
  :param verbose: verbosity level
188
196
 
@@ -192,12 +200,13 @@ class RC2(object):
192
200
  :type exhaust: bool
193
201
  :type incr: bool
194
202
  :type minz: bool
203
+ :type process: int
195
204
  :type trim: int
196
205
  :type verbose: int
197
206
  """
198
207
 
199
208
  def __init__(self, formula, solver='g3', adapt=False, exhaust=False,
200
- incr=False, minz=False, trim=0, verbose=0):
209
+ incr=False, minz=False, process=0, trim=0, verbose=0):
201
210
  """
202
211
  Constructor.
203
212
  """
@@ -205,12 +214,20 @@ class RC2(object):
205
214
  # saving verbosity level and other options
206
215
  self.verbose = verbose
207
216
  self.exhaust = exhaust
217
+ self.process = process
208
218
  self.solver = solver
209
219
  self.adapt = adapt
210
220
  self.minz = minz
211
221
  self.trim = trim
212
222
 
223
+ # oracles are initialised to be None
224
+ self.oracle, self.processor = None, None
225
+
226
+ # parameters related to asynchronous interruption
227
+ self.expect_interrupt, self.interrupted = False, False
228
+
213
229
  # clause selectors and mapping from selectors to clause ids
230
+ # .sall, .s2cl, and .sneg are required only for model enumeration
214
231
  self.sels, self.smap, self.sall, self.s2cl, self.sneg = [], {}, [], {}, set([])
215
232
 
216
233
  # other MaxSAT related stuff
@@ -235,6 +252,23 @@ class RC2(object):
235
252
  if not formula.hard and len(self.sels) > 100000 and min(wght) == max(wght):
236
253
  self.minz = False
237
254
 
255
+ def _call_oracle(self, assumptions=[], expect_interrupt=False):
256
+ """
257
+ Makes a call to the internal SAT solver by means of invoking
258
+ `oracle.solve_limited()`. The two arguments are the list of
259
+ assumption literals and the Boolean flag indicating whether the
260
+ call can be interrupted.
261
+
262
+ :param assumptions: a list of assumption literals.
263
+ :param expect_interrupt: whether :meth:`interrupt` may be called
264
+
265
+ :type assumptions: iterable(int)
266
+ :type expect_interrupt: bool
267
+ """
268
+
269
+ return self.oracle.solve_limited(assumptions=assumptions,
270
+ expect_interrupt=expect_interrupt)
271
+
238
272
  def __del__(self):
239
273
  """
240
274
  Destructor.
@@ -278,13 +312,18 @@ class RC2(object):
278
312
  """
279
313
 
280
314
  # creating a solver object
281
- self.oracle = Solver(name=self.solver, bootstrap_with=formula.hard,
282
- incr=incr, use_timer=True)
315
+ self.oracle = Solver(name=self.solver,
316
+ bootstrap_with=formula.hard if self.process == 0 else [],
317
+ incr=incr, use_timer=True)
283
318
 
284
319
  # adding native cardinality constraints (if any) as hard clauses
285
320
  # this can be done only if the Minicard solver is in use
286
321
  # this cannot be done if RC2 is run from the command line
287
322
  if isinstance(formula, WCNFPlus) and formula.atms:
323
+ # we are using CaDiCaL195 and it can use external linear engine
324
+ if self.solver in SolverNames.cadical195:
325
+ self.oracle.activate_atmost()
326
+
288
327
  assert self.oracle.supports_atmost(), \
289
328
  '{0} does not support native cardinality constraints. Make sure you use the right type of formula.'.format(self.solver)
290
329
 
@@ -300,7 +339,14 @@ class RC2(object):
300
339
 
301
340
  self.s2cl[selv] = cl[:]
302
341
  cl.append(-selv)
303
- self.oracle.add_clause(cl)
342
+
343
+ if self.process == 0:
344
+ self.oracle.add_clause(cl)
345
+ else:
346
+ # adding to formula's hard clauses
347
+ # if any preprocessing is required
348
+ formula.hard.append(cl + [-sel])
349
+ formula.soft[i] = [sel]
304
350
 
305
351
  if selv not in self.wght:
306
352
  # record selector and its weight
@@ -315,14 +361,26 @@ class RC2(object):
315
361
  self.sels_set = set(self.sels)
316
362
  self.sall = self.sels[:]
317
363
 
364
+ # we may end up having zero-weighed soft clauses
365
+ self.garbage = set([l for l in self.sels if self.wght[l] == 0])
366
+ if self.garbage:
367
+ self.filter_assumps()
368
+
369
+ # hard clauses are added last if formula processing has to be done
370
+ if self.process > 0:
371
+ self.processor = Processor(bootstrap_with=formula.hard)
372
+ hard = self.processor.process(rounds=self.process, freeze=self.sels)
373
+ self.oracle.append_formula(hard)
374
+
318
375
  # at this point internal and external variables are the same
319
376
  for v in range(1, formula.nv + 1):
320
377
  self.vmap.e2i[v] = v
321
378
  self.vmap.i2e[v] = v
322
379
 
323
380
  if self.verbose > 1:
381
+ nofh = len(hard.clauses) if self.processor else len(formula.hard)
324
382
  print('c formula: {0} vars, {1} hard, {2} soft'.format(formula.nv,
325
- len(formula.hard), len(formula.soft)))
383
+ nofh, len(formula.soft)))
326
384
 
327
385
  def add_clause(self, clause, weight=None):
328
386
  """
@@ -389,7 +447,7 @@ class RC2(object):
389
447
  assert self.oracle.supports_atmost(), \
390
448
  '{0} does not support native cardinality constraints. Make sure you use the right type of formula.'.format(self.solver)
391
449
 
392
- self.oracle.add_atmost(cl, clause[1])
450
+ self.oracle.add_atmost(cl, clause[1], weights=clause[2] if len(clause) == 3 else [])
393
451
  else:
394
452
  # soft clauses should be augmented with a selector
395
453
  selv = cl[0] # for a unit clause, no selector is needed
@@ -416,18 +474,23 @@ class RC2(object):
416
474
  def delete(self):
417
475
  """
418
476
  Explicit destructor of the internal SAT oracle and all the
419
- totalizer objects creating during the solving process.
477
+ totalizer objects creating during the solving process. This also
478
+ destroys the processor (if any).
420
479
  """
421
480
 
422
481
  if self.oracle:
423
- if not self.oracle.supports_atmost(): # for minicard, there is nothing to free
482
+ if not self.oracle.supports_atmost(): # for minicard-like, there is nothing to free
424
483
  for t in six.itervalues(self.tobj):
425
484
  t.delete()
426
485
 
427
486
  self.oracle.delete()
428
487
  self.oracle = None
429
488
 
430
- def compute(self):
489
+ if self.processor:
490
+ self.processor.delete()
491
+ self.processor = None
492
+
493
+ def compute(self, expect_interrupt=False):
431
494
  """
432
495
  This method can be used for computing one MaxSAT solution,
433
496
  i.e. for computing an assignment satisfying all hard
@@ -442,6 +505,12 @@ class RC2(object):
442
505
  can enumerate top-:math:`k` MaxSAT solutions (this can
443
506
  also be done by calling :meth:`enumerate()`).
444
507
 
508
+ This MaxSAT call can be asynchronously interrupted, in which case
509
+ the value of ``expect_interrupt`` must be set to ``True``.
510
+
511
+ :param expect_interrupt: whether :meth:`interrupt` may be called
512
+ :type expect_interrupt: bool
513
+
445
514
  :return: a MaxSAT model
446
515
  :rtype: list(int)
447
516
 
@@ -467,6 +536,9 @@ class RC2(object):
467
536
  >>> rc2.delete()
468
537
  """
469
538
 
539
+ # keeping the current interruption preference
540
+ self.expect_interrupt = expect_interrupt
541
+
470
542
  # simply apply MaxSAT only once
471
543
  res = self.compute_()
472
544
 
@@ -483,9 +555,14 @@ class RC2(object):
483
555
  self.model = map(lambda l: int(copysign(self.vmap.i2e[abs(l)], l)), self.model)
484
556
  self.model = sorted(self.model, key=lambda l: abs(l))
485
557
 
558
+ # if formula processing was used, we should
559
+ # restore the model for the original formula
560
+ if self.processor:
561
+ self.model = self.processor.restore(self.model)
562
+
486
563
  return self.model
487
564
 
488
- def enumerate(self, block=0):
565
+ def enumerate(self, block=0, expect_interrupt=False):
489
566
  """
490
567
  Enumerate top MaxSAT solutions (from best to worst). The
491
568
  method works as a generator, which iteratively calls
@@ -499,8 +576,15 @@ class RC2(object):
499
576
  it to ``-1``. By the default (for blocking MaxSAT models),
500
577
  ``block`` is set to ``0``.
501
578
 
579
+ This MaxSAT enumeration call can be asynchronously interrupted, in
580
+ which case the value of ``expect_interrupt`` must be set to
581
+ ``True``.
582
+
502
583
  :param block: preferred way to block solutions when enumerating
584
+ :param expect_interrupt: whether :meth:`interrupt` may be called
585
+
503
586
  :type block: int
587
+ :type expect_interrupt: bool
504
588
 
505
589
  :return: a MaxSAT model
506
590
  :rtype: list(int)
@@ -530,7 +614,7 @@ class RC2(object):
530
614
 
531
615
  done = False
532
616
  while not done:
533
- model = self.compute()
617
+ model = self.compute(expect_interrupt=expect_interrupt)
534
618
 
535
619
  if model != None:
536
620
  if block == 1:
@@ -584,8 +668,21 @@ class RC2(object):
584
668
  if self.adapt:
585
669
  self.adapt_am1()
586
670
 
671
+ # at the beginning, the solver is not interrupted
672
+ self.interrupted = False
673
+
587
674
  # main solving loop
588
- while not self.oracle.solve(assumptions=self.sels + self.sums):
675
+ while not self._call_oracle(assumptions=self.sels + self.sums,
676
+ expect_interrupt=self.expect_interrupt):
677
+
678
+ # even if the call has been interrupted, we
679
+ # still need to finish the current iteration
680
+ if self.oracle.get_status() is None:
681
+ self.clear_interrupt()
682
+
683
+ if self.verbose > 1:
684
+ print('c interrupted; processing the last found core')
685
+
589
686
  self.get_core()
590
687
 
591
688
  if not self.core:
@@ -598,6 +695,10 @@ class RC2(object):
598
695
  print('c cost: {0}; core sz: {1}; soft sz: {2}'.format(self.cost,
599
696
  len(self.core), len(self.sels) + len(self.sums)))
600
697
 
698
+ # the solver got interrupted => returning None
699
+ if self.interrupted:
700
+ return # None
701
+
601
702
  return True
602
703
 
603
704
  def get_core(self):
@@ -652,9 +753,6 @@ class RC2(object):
652
753
  # updating the cost
653
754
  self.cost += self.minw
654
755
 
655
- # assumptions to remove
656
- self.garbage = set()
657
-
658
756
  if len(self.core_sels) != 1 or len(self.core_sums) > 0:
659
757
  # process selectors in the core
660
758
  self.process_sels()
@@ -691,8 +789,8 @@ class RC2(object):
691
789
  """
692
790
  Detect and adapt intrinsic AtMost1 constraints. Assume
693
791
  there is a subset of soft clauses
694
- :math:`\\mathcal{S}'\subseteq \\mathcal{S}` s.t.
695
- :math:`\sum_{c\in\\mathcal{S}'}{c\leq 1}`, i.e. at most
792
+ :math:`\\mathcal{S}'\\subseteq \\mathcal{S}` s.t.
793
+ :math:`\\sum_{c\\in\\mathcal{S}'}{c\\leq 1}`, i.e. at most
696
794
  one of the clauses of :math:`\\mathcal{S}'` can be
697
795
  satisfied.
698
796
 
@@ -705,7 +803,7 @@ class RC2(object):
705
803
  unit-propagated to be false. Note that this method for
706
804
  detection of AtMost1 constraints is *incomplete*, because
707
805
  in general unit propagation does not suffice to test
708
- whether or not :math:`\\mathcal{F}\wedge l_i\\models
806
+ whether or not :math:`\\mathcal{F}\\wedge l_i\\models
709
807
  \\neg{l_j}`.
710
808
 
711
809
  Each intrinsic AtMost1 constraint detected this way is
@@ -792,7 +890,7 @@ class RC2(object):
792
890
  has cost at least :math:`|\\mathcal{S}'|-1` (assuming
793
891
  *unweighted* MaxSAT). Furthermore, it is safe to replace
794
892
  all clauses of :math:`\\mathcal{S}'` with a single soft
795
- clause :math:`\sum_{c\in\\mathcal{S}'}{c}`.
893
+ clause :math:`\\sum_{c\\in\\mathcal{S}'}{c}`.
796
894
 
797
895
  Here, input parameter ``am1`` plays the role of subset
798
896
  :math:`\\mathcal{S}'` mentioned above. The procedure bumps
@@ -808,9 +906,6 @@ class RC2(object):
808
906
  :type am1: list(int)
809
907
  """
810
908
 
811
- # assumptions to remove
812
- self.garbage = set()
813
-
814
909
  while len(am1) > 1:
815
910
  # computing am1's weight
816
911
  self.minw = min(map(lambda l: self.wght[l], am1))
@@ -825,7 +920,7 @@ class RC2(object):
825
920
  self.process_sels()
826
921
 
827
922
  # updating the list of literals in am1 after splitting the weights
828
- am1 = list(filter(lambda l: l not in self.garbage, am1))
923
+ am1 = [l for l in am1 if l not in self.garbage]
829
924
 
830
925
  # new selector
831
926
  selv = self.pool.id()
@@ -851,7 +946,7 @@ class RC2(object):
851
946
  for i in range(self.trim):
852
947
  # call solver with core assumption only
853
948
  # it must return 'unsatisfiable'
854
- self.oracle.solve(assumptions=self.core)
949
+ self._call_oracle(assumptions=self.core)
855
950
 
856
951
  # extract a new core
857
952
  new_core = self.oracle.get_core()
@@ -889,13 +984,16 @@ class RC2(object):
889
984
  while i < len(self.core):
890
985
  to_test = self.core[:i] + self.core[(i + 1):]
891
986
 
892
- if self.oracle.solve_limited(assumptions=to_test) == False:
987
+ if self._call_oracle(assumptions=to_test) == False:
893
988
  self.core = to_test
894
989
  elif self.oracle.get_status() == True:
895
990
  i += 1
896
991
  else:
897
992
  break
898
993
 
994
+ # disabling the budget
995
+ self.oracle.conf_budget(budget=-1)
996
+
899
997
  def exhaust_core(self, tobj):
900
998
  """
901
999
  Exhaust core by increasing its bound as much as possible.
@@ -903,13 +1001,13 @@ class RC2(object):
903
1001
  optimization* in [6]_.
904
1002
 
905
1003
  Given a totalizer object ``tobj`` representing a sum of
906
- some *relaxation* variables :math:`r\in R` that augment
1004
+ some *relaxation* variables :math:`r\\in R` that augment
907
1005
  soft clauses :math:`\\mathcal{C}_r`, the idea is to
908
1006
  increase the right-hand side of the sum (which is equal to
909
1007
  1 by default) as much as possible, reaching a value
910
1008
  :math:`k` s.t. formula
911
- :math:`\\mathcal{H}\wedge\\mathcal{C}_r\wedge(\sum_{r\in
912
- R}{r\leq k})` is still unsatisfiable while increasing it
1009
+ :math:`\\mathcal{H}\\wedge\\mathcal{C}_r\\wedge(\\sum_{r\\in
1010
+ R}{r\\leq k})` is still unsatisfiable while increasing it
913
1011
  further makes the formula satisfiable (here
914
1012
  :math:`\\mathcal{H}` denotes the hard part of the
915
1013
  formula).
@@ -921,7 +1019,7 @@ class RC2(object):
921
1019
  """
922
1020
 
923
1021
  # the first case is simpler
924
- if self.oracle.solve(assumptions=[-tobj.rhs[1]]):
1022
+ if self._call_oracle(assumptions=[-tobj.rhs[1]]):
925
1023
  return 1
926
1024
  else:
927
1025
  self.cost += self.minw
@@ -934,7 +1032,7 @@ class RC2(object):
934
1032
  # increasing the bound
935
1033
  self.update_sum(-tobj.rhs[i - 1])
936
1034
 
937
- if self.oracle.solve(assumptions=[-tobj.rhs[i]]):
1035
+ if self._call_oracle(assumptions=[-tobj.rhs[i]]):
938
1036
  # the bound should be equal to i
939
1037
  return i
940
1038
 
@@ -1162,12 +1260,18 @@ class RC2(object):
1162
1260
  :func:`process_sels`, and :func:`process_sums`.
1163
1261
  """
1164
1262
 
1165
- self.sels = list(filter(lambda x: x not in self.garbage, self.sels))
1166
- self.sums = list(filter(lambda x: x not in self.garbage, self.sums))
1263
+ # updating the list of selectors and sums
1264
+ self.sels = [l for l in self.sels if l not in self.garbage]
1265
+ self.sums = [l for l in self.sums if l not in self.garbage]
1167
1266
 
1168
- self.bnds = {l: b for l, b in six.iteritems(self.bnds) if l not in self.garbage}
1169
- self.wght = {l: w for l, w in six.iteritems(self.wght) if l not in self.garbage}
1267
+ # cleaning the dictionaries
1268
+ for l in list(self.garbage):
1269
+ if l in self.bnds:
1270
+ del self.bnds[l]
1271
+ if l in self.wght:
1272
+ del self.wght[l]
1170
1273
 
1274
+ # removing garbage from the set of selectors
1171
1275
  self.sels_set.difference_update(set(self.garbage))
1172
1276
 
1173
1277
  self.garbage.clear()
@@ -1212,6 +1316,65 @@ class RC2(object):
1212
1316
 
1213
1317
  return int(copysign(i, l))
1214
1318
 
1319
+ def interrupt(self):
1320
+ """
1321
+ Interrupt the execution of the current *limited* SAT call in the
1322
+ RC2 algorithm. Can be used to enforce time limits using timer
1323
+ objects. The interrupt must be cleared before performing another
1324
+ `compute` call (see :meth:`clear_interrupt`).
1325
+
1326
+ Importantly, interruption is implemented such that it can work
1327
+ incrementally with multiple MaxSAT calls, i.e. upon an interrupted
1328
+ invocation a user may extend the resources / time and call the
1329
+ solver again. To make this work, none of the SAT calls used by the
1330
+ heuristics are interrupted. For this reason, the solver may take
1331
+ slightly more time than assumed (spent to properly finish the
1332
+ processing of the last unsatisfiable core).
1333
+
1334
+ **Note** that this method can be called if the `compute` call was
1335
+ made with the option ``expect_interrupt`` set to ``True``.
1336
+ Behaviour is **undefined** if used to ``expect_interrupt`` was set
1337
+ to ``False``.
1338
+
1339
+ Example:
1340
+
1341
+ .. code-block:: python
1342
+
1343
+ >>> from pysat.examples.rc2 import RC2
1344
+ >>> from pysat.formula import WCNF
1345
+ >>> from threading import Timer
1346
+ >>>
1347
+ >>> wcnf = WCNF(from_file='somefile.wcnf')
1348
+ >>>
1349
+ >>> with RC2(wcnf) as rc2:
1350
+ >>>
1351
+ >>> def interrupt(s):
1352
+ >>> print('interrupted!')
1353
+ >>> s.interrupt()
1354
+ >>>
1355
+ >>> timer = Timer(1, interrupt, [rc2])
1356
+ >>> timer.start()
1357
+ >>> print('computing...')
1358
+ >>> rc2.compute(expect_interrupt=True)
1359
+ >>> print('done')
1360
+ """
1361
+
1362
+ if self.oracle:
1363
+ self.oracle.interrupt()
1364
+
1365
+ # recording the interruption request
1366
+ self.interrupted = True
1367
+
1368
+ def clear_interrupt(self):
1369
+ """
1370
+ Clears a previous interrupt. Technically, this method should be
1371
+ used every time after the previous MaxSAT call gets interrupted.
1372
+ **However**, the current implementation handles this on its own.
1373
+ """
1374
+
1375
+ if self.oracle:
1376
+ self.oracle.clear_interrupt()
1377
+
1215
1378
 
1216
1379
  #
1217
1380
  #==============================================================================
@@ -1243,16 +1406,16 @@ class RC2Stratified(RC2, object):
1243
1406
  """
1244
1407
 
1245
1408
  def __init__(self, formula, solver='g3', adapt=False, blo='div',
1246
- exhaust=False, incr=False, minz=False, nohard=False, trim=0,
1247
- verbose=0):
1409
+ exhaust=False, incr=False, minz=False, nohard=False, process=0,
1410
+ trim=0, verbose=0):
1248
1411
  """
1249
1412
  Constructor.
1250
1413
  """
1251
1414
 
1252
1415
  # calling the constructor for the basic version
1253
1416
  super(RC2Stratified, self).__init__(formula, solver=solver,
1254
- adapt=adapt, exhaust=exhaust, incr=incr, minz=minz, trim=trim,
1255
- verbose=verbose)
1417
+ adapt=adapt, exhaust=exhaust, incr=incr, minz=minz,
1418
+ process=process, trim=trim, verbose=verbose)
1256
1419
 
1257
1420
  self.levl = 0 # initial optimization level
1258
1421
  self.blop = [] # a list of blo levels
@@ -1296,7 +1459,7 @@ class RC2Stratified(RC2, object):
1296
1459
  # number of finished levels
1297
1460
  self.done = 0
1298
1461
 
1299
- def compute(self):
1462
+ def compute(self, expect_interrupt=False):
1300
1463
  """
1301
1464
  This method solves the MaxSAT problem iteratively. Each
1302
1465
  optimization level is tackled the standard way, i.e. by
@@ -1307,6 +1470,9 @@ class RC2Stratified(RC2, object):
1307
1470
  :func:`activate_clauses`.
1308
1471
  """
1309
1472
 
1473
+ # keeping the current interruption preference
1474
+ self.expect_interrupt = expect_interrupt
1475
+
1310
1476
  if self.done == 0 and self.levl != None:
1311
1477
  # it is a fresh start of the solver
1312
1478
  # i.e. no optimization level is finished yet
@@ -1322,7 +1488,7 @@ class RC2Stratified(RC2, object):
1322
1488
  print('c wght str:', self.blop[self.levl])
1323
1489
 
1324
1490
  # call RC2
1325
- if self.compute_() == False:
1491
+ if self.compute_() != True: # can be either False or None
1326
1492
  return
1327
1493
 
1328
1494
  # updating the list of distinct weight levels
@@ -1350,7 +1516,7 @@ class RC2Stratified(RC2, object):
1350
1516
  # i.e. all levels are finished and so all clauses are present
1351
1517
  # thus, we need to simply call RC2 for the next model
1352
1518
  self.done = -1 # we are done with stratification, disabling it
1353
- if self.compute_() == False:
1519
+ if self.compute_() != True:
1354
1520
  return
1355
1521
 
1356
1522
  # extracting a model
@@ -1365,6 +1531,11 @@ class RC2Stratified(RC2, object):
1365
1531
  self.model = map(lambda l: int(copysign(self.vmap.i2e[abs(l)], l)), self.model)
1366
1532
  self.model = sorted(self.model, key=lambda l: abs(l))
1367
1533
 
1534
+ # if formula processing was used, we should
1535
+ # restore the model for the original formula
1536
+ if self.processor:
1537
+ self.model = self.processor.restore(self.model)
1538
+
1368
1539
  return self.model
1369
1540
 
1370
1541
  def next_level(self):
@@ -1506,7 +1677,7 @@ class RC2Stratified(RC2, object):
1506
1677
  super(RC2Stratified, self).process_sels()
1507
1678
 
1508
1679
  # updating the list of literals in am1 after splitting the weights
1509
- am1 = list(filter(lambda l: l not in self.garbage, am1))
1680
+ am1 = [l for l in am1 if l not in self.garbage]
1510
1681
 
1511
1682
  # new selector
1512
1683
  selv = self.pool.id()
@@ -1533,7 +1704,7 @@ class RC2Stratified(RC2, object):
1533
1704
  to_deactivate.add(l)
1534
1705
 
1535
1706
  # deactivating unnecessary selectors
1536
- self.sels = list(filter(lambda x: x not in to_deactivate, self.sels))
1707
+ self.sels = [l for l in self.sels if l not in to_deactivate]
1537
1708
 
1538
1709
  # removing unnecessary assumptions
1539
1710
  self.filter_assumps()
@@ -1573,7 +1744,7 @@ class RC2Stratified(RC2, object):
1573
1744
  self.rels.append(-l)
1574
1745
 
1575
1746
  # deactivating unnecessary selectors
1576
- self.sels = list(filter(lambda x: x not in to_deactivate, self.sels))
1747
+ self.sels = [l for l in self.sels if l not in to_deactivate]
1577
1748
 
1578
1749
  def process_sums(self):
1579
1750
  """
@@ -1616,7 +1787,7 @@ class RC2Stratified(RC2, object):
1616
1787
  self.rels.append(-l)
1617
1788
 
1618
1789
  # deactivating unnecessary sums
1619
- self.sums = list(filter(lambda x: x not in to_deactivate, self.sums))
1790
+ self.sums = [l for l in self.sums if l not in to_deactivate]
1620
1791
 
1621
1792
 
1622
1793
  #
@@ -1627,10 +1798,10 @@ def parse_options():
1627
1798
  """
1628
1799
 
1629
1800
  try:
1630
- opts, args = getopt.getopt(sys.argv[1:], 'ab:c:e:hil:ms:t:vx',
1801
+ opts, args = getopt.getopt(sys.argv[1:], 'ab:c:e:hil:mp:s:t:T:vx',
1631
1802
  ['adapt', 'block=', 'comp=', 'enum=', 'exhaust', 'help',
1632
- 'incr', 'blo=', 'minimize', 'solver=', 'trim=', 'verbose',
1633
- 'vnew'])
1803
+ 'incr', 'blo=', 'minimize', 'process=', 'solver=',
1804
+ 'trim=', 'timeout=', 'verbose', 'vnew'])
1634
1805
  except getopt.GetoptError as err:
1635
1806
  sys.stderr.write(str(err).capitalize())
1636
1807
  usage()
@@ -1644,8 +1815,10 @@ def parse_options():
1644
1815
  incr = False
1645
1816
  blo = 'none'
1646
1817
  minz = False
1818
+ process = 0
1647
1819
  solver = 'g3'
1648
1820
  trim = 0
1821
+ timeout = None
1649
1822
  verbose = 1
1650
1823
  vnew = False
1651
1824
 
@@ -1671,10 +1844,15 @@ def parse_options():
1671
1844
  blo = str(arg)
1672
1845
  elif opt in ('-m', '--minimize'):
1673
1846
  minz = True
1847
+ elif opt in ('-p', '--process'):
1848
+ process = int(arg)
1674
1849
  elif opt in ('-s', '--solver'):
1675
1850
  solver = str(arg)
1676
1851
  elif opt in ('-t', '--trim'):
1677
1852
  trim = int(arg)
1853
+ elif opt in ('-T', '--timeout'):
1854
+ if str(arg) != 'none':
1855
+ timeout = float(arg)
1678
1856
  elif opt in ('-v', '--verbose'):
1679
1857
  verbose += 1
1680
1858
  elif opt == '--vnew':
@@ -1690,7 +1868,7 @@ def parse_options():
1690
1868
  block = bmap[block]
1691
1869
 
1692
1870
  return adapt, blo, block, cmode, to_enum, exhaust, incr, minz, \
1693
- solver, trim, verbose, vnew, args
1871
+ process, solver, trim, timeout, verbose, vnew, args
1694
1872
 
1695
1873
 
1696
1874
  #
@@ -1714,10 +1892,14 @@ def usage():
1714
1892
  print(' -l, --blo=<string> Use BLO and stratification')
1715
1893
  print(' Available values: basic, div, cluster, none, full (default = none)')
1716
1894
  print(' -m, --minimize Use a heuristic unsatisfiable core minimizer')
1895
+ print(' -p, --process=<int> Number of processing rounds')
1896
+ print(' Available values: [0 .. INT_MAX] (default = 0)')
1717
1897
  print(' -s, --solver=<string> SAT solver to use')
1718
- print(' Available values: g3, g4, lgl, mcb, mcm, mpl, m22, mc, mgh (default = g3)')
1898
+ print(' Available values: cd15, cd19, g3, g4, lgl, mcb, mcm, mpl, m22, mc, mgh (default = g3)')
1719
1899
  print(' -t, --trim=<int> How many times to trim unsatisfiable cores')
1720
1900
  print(' Available values: [0 .. INT_MAX] (default = 0)')
1901
+ print(' -T, --timeout=<float> Set time limit for MaxSAT solver')
1902
+ print(' Available values: [0 .. FLOAT_MAX], none (default: none)')
1721
1903
  print(' -v, --verbose Be verbose')
1722
1904
  print(' --vnew Print v-line in the new format')
1723
1905
  print(' -x, --exhaust Exhaust new unsatisfiable cores')
@@ -1726,12 +1908,12 @@ def usage():
1726
1908
  #
1727
1909
  #==============================================================================
1728
1910
  if __name__ == '__main__':
1729
- adapt, blo, block, cmode, to_enum, exhaust, incr, minz, solver, trim, \
1730
- verbose, vnew, files = parse_options()
1911
+ adapt, blo, block, cmode, to_enum, exhaust, incr, minz, process, solver, \
1912
+ trim, timeout, verbose, vnew, files = parse_options()
1731
1913
 
1732
1914
  if files:
1733
1915
  # parsing the input formula
1734
- if re.search('\.wcnf[p|+]?(\.(gz|bz2|lzma|xz))?$', files[0]):
1916
+ if re.search(r'\.wcnf[p|+]?(\.(gz|bz2|lzma|xz|zst))?$', files[0]):
1735
1917
  formula = WCNFPlus(from_file=files[0])
1736
1918
  else: # expecting '*.cnf[,p,+].*'
1737
1919
  formula = CNFPlus(from_file=files[0]).weighted()
@@ -1759,7 +1941,8 @@ if __name__ == '__main__':
1759
1941
 
1760
1942
  # starting the solver
1761
1943
  with MXS(formula, solver=solver, adapt=adapt, exhaust=exhaust,
1762
- incr=incr, minz=minz, trim=trim, verbose=verbose) as rc2:
1944
+ incr=incr, minz=minz, process=process, trim=trim,
1945
+ verbose=verbose) as rc2:
1763
1946
 
1764
1947
  if isinstance(rc2, RC2Stratified):
1765
1948
  rc2.bstr = blomap[blo] # select blo strategy
@@ -1768,14 +1951,26 @@ if __name__ == '__main__':
1768
1951
  print('c hardening is disabled for model enumeration')
1769
1952
  rc2.hard = False
1770
1953
 
1954
+ # setting a timer if necessary
1955
+ if timeout is not None:
1956
+ if verbose > 1:
1957
+ print('c timeout: {0}'.format(timeout))
1958
+
1959
+ timer = Timer(timeout, lambda s: s.interrupt(), [rc2])
1960
+ timer.start()
1961
+ else:
1962
+ timer = None
1963
+
1771
1964
  optimum_found = False
1772
- for i, model in enumerate(rc2.enumerate(block=block), 1):
1965
+ for i, model in enumerate(rc2.enumerate(block=block,
1966
+ expect_interrupt=timeout is not None), 1):
1773
1967
  optimum_found = True
1774
1968
 
1775
1969
  if verbose:
1776
1970
  if i == 1:
1777
- print('s OPTIMUM FOUND')
1778
- print('o {0}'.format(rc2.cost))
1971
+ if not rc2.interrupted:
1972
+ print('s OPTIMUM FOUND')
1973
+ print('o {0}'.format(rc2.cost))
1779
1974
 
1780
1975
  if verbose > 2:
1781
1976
  if vnew: # new format of the v-line
@@ -1791,10 +1986,14 @@ if __name__ == '__main__':
1791
1986
  print('v')
1792
1987
 
1793
1988
  if verbose:
1794
- if not optimum_found:
1989
+ if not optimum_found and not rc2.interrupted:
1795
1990
  print('s UNSATISFIABLE')
1796
1991
  elif to_enum != 1:
1797
1992
  print('c models found:', i)
1798
1993
 
1799
1994
  if verbose > 1:
1800
1995
  print('c oracle time: {0:.4f}'.format(rc2.oracle_time()))
1996
+
1997
+ # cancelling the timer (if any) because we are done
1998
+ if timer:
1999
+ timer.cancel()