python-sat 1.8.dev21__tar.gz → 1.8.dev22__tar.gz

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 (98) hide show
  1. {python_sat-1.8.dev21/python_sat.egg-info → python_sat-1.8.dev22}/PKG-INFO +1 -1
  2. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/README.rst +25 -0
  3. python_sat-1.8.dev22/examples/bbscan.py +659 -0
  4. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/examples/bica.py +2 -3
  5. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/examples/fm.py +1 -1
  6. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/examples/lbx.py +5 -5
  7. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/examples/lsu.py +5 -3
  8. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/examples/mcsls.py +5 -5
  9. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/examples/musx.py +1 -1
  10. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/examples/optux.py +6 -6
  11. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/examples/primer.py +2 -2
  12. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/examples/rc2.py +1 -1
  13. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/pysat/__init__.py +1 -1
  14. {python_sat-1.8.dev21 → python_sat-1.8.dev22/python_sat.egg-info}/PKG-INFO +1 -1
  15. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/python_sat.egg-info/SOURCES.txt +1 -0
  16. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/setup.py +2 -2
  17. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/LICENSE.txt +0 -0
  18. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/MANIFEST.in +0 -0
  19. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/allies/__init__.py +0 -0
  20. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/allies/approxmc.py +0 -0
  21. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/allies/unigen.py +0 -0
  22. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/cardenc/bitwise.hh +0 -0
  23. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/cardenc/card.hh +0 -0
  24. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/cardenc/clset.hh +0 -0
  25. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/cardenc/common.hh +0 -0
  26. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/cardenc/itot.hh +0 -0
  27. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/cardenc/ladder.hh +0 -0
  28. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/cardenc/mto.hh +0 -0
  29. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/cardenc/pairwise.hh +0 -0
  30. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/cardenc/ptypes.hh +0 -0
  31. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/cardenc/pycard.cc +0 -0
  32. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/cardenc/seqcounter.hh +0 -0
  33. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/cardenc/sortcard.hh +0 -0
  34. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/cardenc/utils.hh +0 -0
  35. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/examples/__init__.py +0 -0
  36. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/examples/genhard.py +0 -0
  37. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/examples/hitman.py +0 -0
  38. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/examples/models.py +0 -0
  39. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/examples/usage.py +0 -0
  40. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/pysat/_fileio.py +0 -0
  41. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/pysat/_utils.py +0 -0
  42. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/pysat/card.py +0 -0
  43. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/pysat/engines.py +0 -0
  44. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/pysat/formula.py +0 -0
  45. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/pysat/pb.py +0 -0
  46. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/pysat/process.py +0 -0
  47. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/pysat/solvers.py +0 -0
  48. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/python_sat.egg-info/dependency_links.txt +0 -0
  49. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/python_sat.egg-info/requires.txt +0 -0
  50. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/python_sat.egg-info/top_level.txt +0 -0
  51. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/requirements.txt +0 -0
  52. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/setup.cfg +0 -0
  53. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/solvers/cadical103.tar.gz +0 -0
  54. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/solvers/cadical153.tar.gz +0 -0
  55. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/solvers/cadical195.tar.gz +0 -0
  56. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/solvers/glucose30.tar.gz +0 -0
  57. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/solvers/glucose41.tar.gz +0 -0
  58. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/solvers/glucose421.tar.gz +0 -0
  59. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/solvers/lingeling.tar.gz +0 -0
  60. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/solvers/maplechrono.zip +0 -0
  61. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/solvers/maplecm.zip +0 -0
  62. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/solvers/maplesat.zip +0 -0
  63. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/solvers/mergesat3.tar.gz +0 -0
  64. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/solvers/minicard.tar.gz +0 -0
  65. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/solvers/minisat22.tar.gz +0 -0
  66. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/solvers/minisatgh.zip +0 -0
  67. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/solvers/patches/cadical103.patch +0 -0
  68. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/solvers/patches/cadical153.patch +0 -0
  69. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/solvers/patches/cadical195.patch +0 -0
  70. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/solvers/patches/glucose30.patch +0 -0
  71. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/solvers/patches/glucose41.patch +0 -0
  72. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/solvers/patches/glucose421.patch +0 -0
  73. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/solvers/patches/gluecard30.patch +0 -0
  74. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/solvers/patches/gluecard41.patch +0 -0
  75. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/solvers/patches/lingeling.patch +0 -0
  76. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/solvers/patches/maplechrono.patch +0 -0
  77. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/solvers/patches/maplecm.patch +0 -0
  78. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/solvers/patches/maplesat.patch +0 -0
  79. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/solvers/patches/mergesat3.patch +0 -0
  80. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/solvers/patches/minicard.patch +0 -0
  81. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/solvers/patches/minisat22.patch +0 -0
  82. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/solvers/patches/minisatgh.patch +0 -0
  83. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/solvers/prepare.py +0 -0
  84. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/solvers/pysolvers.cc +0 -0
  85. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/tests/test_accum_stats.py +0 -0
  86. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/tests/test_atmost.py +0 -0
  87. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/tests/test_atmost1.py +0 -0
  88. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/tests/test_atmostk.py +0 -0
  89. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/tests/test_boolengine.py +0 -0
  90. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/tests/test_clausification.py +0 -0
  91. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/tests/test_cnf.py +0 -0
  92. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/tests/test_equals1.py +0 -0
  93. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/tests/test_formula_unique.py +0 -0
  94. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/tests/test_process.py +0 -0
  95. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/tests/test_propagate.py +0 -0
  96. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/tests/test_unique_model.py +0 -0
  97. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/tests/test_unique_mus.py +0 -0
  98. {python_sat-1.8.dev21 → python_sat-1.8.dev22}/tests/test_warmstart.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-sat
3
- Version: 1.8.dev21
3
+ Version: 1.8.dev22
4
4
  Summary: A Python library for prototyping with SAT oracles
5
5
  Home-page: https://github.com/pysathq/pysat
6
6
  Author: Alexey Ignatiev, Joao Marques-Silva, Antonio Morgado
@@ -1,6 +1,31 @@
1
1
  PySAT: SAT technology in Python
2
2
  ===============================
3
3
 
4
+ |version|
5
+ |tests|
6
+ |downloads|
7
+ |license|
8
+ |sat18|
9
+ |sat24|
10
+
11
+ .. |version| image:: https://img.shields.io/pypi/v/python-sat
12
+ :target: https://pysathq.github.io/
13
+
14
+ .. |tests| image:: https://img.shields.io/github/actions/workflow/status/pysathq/pysat/test.yml?label=tests
15
+ :target: https://github.com/pysathq/pysat
16
+
17
+ .. |downloads| image:: https://img.shields.io/pypi/dm/python-sat
18
+ :target: https://pypi.org/project/python-sat/
19
+
20
+ .. |license| image:: https://img.shields.io/github/license/pysathq/pysat
21
+ :target: https://github.com/pysathq/pysat/blob/master/LICENSE.txt
22
+
23
+ .. |sat18| image:: https://img.shields.io/badge/doi-10.1007%2F978--3--319--94144--8_26-blue
24
+ :target: https://doi.org/10.1007/978-3-319-94144-8_26
25
+
26
+ .. |sat24| image:: https://img.shields.io/badge/doi-10.4230%2FLIPICS.SAT.2024.16-blue
27
+ :target: https://doi.org/10.4230/LIPIcs.SAT.2024.16
28
+
4
29
  PySAT is a Python (2.7, 3.4+) toolkit, which aims at providing a simple and
5
30
  unified interface to a number of state-of-art `Boolean satisfiability (SAT)
6
31
  <https://en.wikipedia.org/wiki/Boolean_satisfiability_problem>`__ solvers as
@@ -0,0 +1,659 @@
1
+ #!/usr/bin/env python
2
+ #-*- coding:utf-8 -*-
3
+ ##
4
+ ## bbscan.py
5
+ ##
6
+ ## Created on: Sep 21, 2025
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
+ BBScan
20
+
21
+ ==================
22
+ Module description
23
+ ==================
24
+
25
+ A simple implementation of a few backbone extraction algorithms described
26
+ in [1]_. Given a Boolean formula :math:`\\varphi`, a backbone literal is a
27
+ literal that holds true in every satisfying assignment of
28
+ :math:`\\varphi`. The backbone of formula :math:`\\varphi` is the set of
29
+ all backbone literals. This implementation includes the following
30
+ algorithms from [1]_:
31
+
32
+ * implicant enumeration algorithm,
33
+ * iterative algorithm (with one test per variable),
34
+ * complement of backbone estimate algorithm,
35
+ * chunking algorithm,
36
+ * core-based algorithm,
37
+ * core-based algorithm with chunking.
38
+
39
+ .. [1] Mikolás Janota, Inês Lynce, Joao Marques-Silva. *Algorithms for
40
+ computing backbones of propositional formulae*. AI Commun. 28(2).
41
+ 2015. pp. 161-177
42
+
43
+ The implementation can be used as an executable (the list of
44
+ available command-line options can be shown using ``bbscan.py -h``)
45
+ in the following way:
46
+
47
+ ::
48
+
49
+ $ xzcat formula.cnf.xz
50
+ p cnf 3 4
51
+ 1 2 0
52
+ 1 -2 0
53
+ 2 -3 0
54
+ -2 -3 0
55
+
56
+ $ bbscan.py -v formula.cnf.xz
57
+ c formula: 3 vars, 4 clauses
58
+ c using iterative algorithm
59
+ v +1 -3 0
60
+ c backbone size: 2 (66.67% of all variables)
61
+ c oracle time: 0.0000
62
+ c oracle calls: 4
63
+
64
+ Alternatively, the algorithms can be accessed and invoked through the
65
+ standard ``import`` interface of Python, e.g.
66
+
67
+ .. code-block:: python
68
+
69
+ >>> from pysat.examples.bbscan import BBScan
70
+ >>> from pysat.formula import CNF
71
+ >>>
72
+ >>> # reading a formula from file
73
+ >>> cnf = CNF(from_file='formula.wcnf.xz')
74
+ >>>
75
+ >>> # creating BBScan and running it
76
+ >>> with BBScan(cnf, solver='g3', lift=False, rotate=True) as bbscan:
77
+ ... bbone = bbscan.compute(algorithm='core')
78
+ ...
79
+ ... # outputting the results
80
+ ... if bbone:
81
+ ... print(bbone)
82
+ [1, -3]
83
+
84
+ Each of the available algorithms can be augmented with two simple
85
+ heuristics aiming at reducing satisfying assignments and, thus, filtering
86
+ out unnecessary literals: literal lifting and filtering rotatable
87
+ literals. Both are described in the aforementioned paper.
88
+
89
+ Note that most of the methods of the class :class:`BBScan` are made
90
+ private. Therefore, the tool provides a minimalistic interface via which a
91
+ user can specify all the necessary parameters.
92
+
93
+ ==============
94
+ Module details
95
+ ==============
96
+ """
97
+
98
+ #
99
+ #==============================================================================
100
+ import getopt
101
+ import itertools
102
+ import os
103
+ from pysat.formula import CNF, IDPool
104
+ from pysat.solvers import Solver, SolverNames
105
+ import sys
106
+ import re
107
+ import time
108
+
109
+
110
+ #
111
+ #==============================================================================
112
+ class BBScan:
113
+ """
114
+ A solver for computing all backbone literals of a given Boolean
115
+ formula. It implements 6 algorithms for backbone computation described
116
+ in [1]_ augmented with two simple heuristics that can be speed up the
117
+ process.
118
+
119
+ Note that the input formula can be a :class:`.CNF` object but also any
120
+ object of class :class:`.Formula`, thus the tool can used for
121
+ computing backbone literals of non-clausal formulas.
122
+
123
+ A user can select one of the SAT solvers available at hand
124
+ (``'glucose3'`` is used by default). The optional two heuristics can
125
+ be also specified as Boolean input arguments ``lift``, and ``rotate``
126
+ (turned off by default).
127
+
128
+ The list of initialiser's arguments is as follows:
129
+
130
+ :param formula: input formula whose backbone is sought
131
+ :param solver: SAT oracle name
132
+ :param lift: apply literal lifting heuristic
133
+ :param rotate: apply rotatable literal filtering
134
+ :param verbose: verbosity level
135
+
136
+ :type formula: :class:`.Formula` or :class:`.CNF`
137
+ :type solver: str
138
+ :type lift: bool
139
+ :type rotate: bool
140
+ :type verbose: int
141
+ """
142
+
143
+ def __init__(self, formula, solver='g3', lift=False, rotate=False, verbose=0):
144
+ """
145
+ Constructor.
146
+ """
147
+
148
+ self.formula = formula
149
+ self.verbose = verbose
150
+ self.oracle = None
151
+
152
+ # implicant reduction heuristics
153
+ self.lift, self.rotate = lift, rotate
154
+
155
+ # basic stats
156
+ self.calls, self.filtered = 0, 0
157
+
158
+ # this is going to be our workhorse
159
+ self.oracle = Solver(name=solver, bootstrap_with=formula, use_timer=True)
160
+
161
+ def __del__(self):
162
+ """
163
+ Destructor.
164
+ """
165
+
166
+ self.delete()
167
+
168
+ def __enter__(self):
169
+ """
170
+ 'with' constructor.
171
+ """
172
+
173
+ return self
174
+
175
+ def __exit__(self, exc_type, exc_value, traceback):
176
+ """
177
+ 'with' destructor.
178
+ """
179
+
180
+ self.delete()
181
+
182
+ def delete(self):
183
+ """
184
+ Explicit destructor of the internal SAT oracle.
185
+ """
186
+
187
+ if self.oracle:
188
+ self.oracle.delete()
189
+ self.oracle = None
190
+
191
+ def compute(self, algorithm='iter', chunk_size=100, focus_on=None):
192
+ """
193
+ Main solving method. A user is supposed to specify which backbone
194
+ computation algorithm they want to use:
195
+
196
+ * ``'enum'`` (implicant enumeration algorithm),
197
+ * ``'iter'`` (iterative algorithm, with one test per variable),
198
+ * ``'compl'`` (complement of backbone estimate algorithm),
199
+ * ``'chunk'`` (chunking algorithm),
200
+ * ``'core'`` (core-based algorithm),
201
+ * ``'corechunk'`` (core-based algorithm with chunking).
202
+
203
+ By default, :class:`.BBScan` opts for ``'iter'``.
204
+
205
+ If the chunking algorithm is selected (either ``'chunk'`` or
206
+ ``'corechunk'``), the user may specify the size of the chunk,
207
+ which is set to 100 by default.
208
+
209
+ Finally, one may opt for computing backbone literals out of a
210
+ particular subset of literals / variables, which may run faster
211
+ than computing the entire formula's backbone. To focus on a
212
+ particular set of variables / literals, the user should use the
213
+ parameter ``focus_on``, which is set to ``None`` by default.
214
+
215
+ .. note::
216
+
217
+ The method raises ``ValueError`` if the input formula is
218
+ unsatisfiable.
219
+
220
+ :param algorithm: backbone computation algorithm
221
+ :param chunk_size: number of literals in the chunk (for chunking algorithms)
222
+ :param focus_on: a list of literals to focus on
223
+
224
+ :type algorithm: str
225
+ :type chunk_size: int
226
+ :type focus_on: iterable(int)
227
+ """
228
+
229
+ self.calls += 1
230
+
231
+ if self.oracle.solve():
232
+ self.model = self.oracle.get_model()
233
+ if focus_on is not None:
234
+ model = set(self.model)
235
+ self.model = [l for l in focus_on if l in model]
236
+ else:
237
+ raise ValueError('Unsatisfiable formula')
238
+
239
+ if algorithm == 'enum':
240
+ result = self._compute_enum()
241
+ elif algorithm == 'iter':
242
+ result = self._compute_iter()
243
+ elif algorithm == 'compl':
244
+ result = self._compute_compl()
245
+ elif algorithm == 'chunk':
246
+ result = self._compute_chunking(chunk_size)
247
+ elif algorithm == 'core':
248
+ result = self._compute_core_based()
249
+ elif algorithm == 'corechunk':
250
+ result = self._compute_core_chunking(chunk_size)
251
+ else:
252
+ assert 0, f'Unknown algorithm: {algorithm}'
253
+
254
+ return sorted(result, key=lambda l: abs(l))
255
+
256
+ def _get_implicant(self, model):
257
+ """
258
+ Simple literal lifting used to reduce a given model.
259
+ """
260
+
261
+ res, model = set(), set(model)
262
+
263
+ # traversing the clauses and collecting all literals
264
+ # that satisfy at least one clause of the formula
265
+ for cl in self.formula:
266
+ res |= set([l for l in cl if l in model])
267
+
268
+ return list(res)
269
+
270
+ def _filter_rotatable(self, model):
271
+ """
272
+ Filter out rotatable literals.
273
+ """
274
+
275
+ units, model = set([]), set(model)
276
+
277
+ # determining unit literals
278
+ for cl in self.formula:
279
+ satisfied = [l for l in cl if l in model]
280
+ if len(satisfied) == 1:
281
+ units.add(satisfied[0])
282
+
283
+ self.filtered += len(model) - len(units)
284
+ return list(units)
285
+
286
+ def _process_model(self, model):
287
+ """
288
+ Heuristically reduce a model.
289
+ """
290
+
291
+ if self.lift:
292
+ model = self._get_implicant(model)
293
+
294
+ if self.rotate:
295
+ model = self._filter_rotatable(model)
296
+
297
+ return model
298
+
299
+ def _compute_enum(self, focus_on=None):
300
+ """
301
+ Enumeration-based backbone computation.
302
+ """
303
+
304
+ if self.verbose:
305
+ print('c using enumeration-based algorithm')
306
+
307
+ # initial backbone estimate contains all literals
308
+ bbone = set(self.model) if focus_on is None else set(focus_on)
309
+
310
+ while bbone:
311
+ # counting the number of calls
312
+ self.calls += 1
313
+
314
+ # stop if there are no more models
315
+ if not self.oracle.solve():
316
+ break
317
+
318
+ self.model = self.oracle.get_model()
319
+ # if self.lift:
320
+ # self.model = self._get_implicant(self.model)
321
+
322
+ self.model = self._process_model(self.model)
323
+
324
+ # updating backbone estimate - intersection
325
+ bbone &= set(self.model)
326
+
327
+ # blocking the previously found implicant
328
+ self.oracle.add_clause([-l for l in self.model])
329
+
330
+ return list(bbone)
331
+
332
+ def _compute_iter(self, focus_on=None):
333
+ """
334
+ Iterative algorithm with one test per variable.
335
+ """
336
+
337
+ if self.verbose:
338
+ print('c using iterative algorithm')
339
+
340
+ bbone, model = [], self.model if focus_on is None else focus_on
341
+
342
+ while model:
343
+ # counting the number of oracle calls
344
+ self.calls += 1
345
+
346
+ # checking this literal
347
+ lit = model.pop()
348
+
349
+ if self.oracle.solve(assumptions=[-lit]) == False:
350
+ # it is a backbone literal
351
+ bbone.append(lit)
352
+ else:
353
+ # it isn't and we've got a counterexample
354
+ # => using it to filter out more literals
355
+ coex = set(self.oracle.get_model())
356
+ model = [l for l in model if l in coex]
357
+ model = self._process_model(model)
358
+
359
+ return bbone
360
+
361
+ def _compute_compl(self, focus_on=None):
362
+ """
363
+ Iterative algorithm with complement of backbone estimate.
364
+ """
365
+
366
+ if self.verbose:
367
+ print('c using complement of backbone estimate algorithm')
368
+
369
+ # initial estimate
370
+ bbone = set(self.model) if focus_on is None else set(focus_on)
371
+
372
+ # iterating until we find the backbone or determine there is none
373
+ while bbone:
374
+ self.calls += 1
375
+
376
+ # first, adding a new D-clause
377
+ self.oracle.add_clause([-l for l in bbone])
378
+
379
+ # testing for unsatisfiability
380
+ if self.oracle.solve() == False:
381
+ break
382
+ else:
383
+ model = self._process_model(self.oracle.get_model())
384
+ model = self._process_model(model)
385
+ bbone &= set(model)
386
+
387
+ return list(bbone)
388
+
389
+ def _compute_chunking(self, chunk_size=100, focus_on=None):
390
+ """
391
+ Algorithm 5: Chunking algorithm.
392
+ """
393
+
394
+ if self.verbose:
395
+ print('c using chunking algorithm, with chunk size:', chunk_size)
396
+
397
+ # initial estimate
398
+ bbone, model = [], self.model if focus_on is None else focus_on
399
+
400
+ # we are going to use clause selectors
401
+ vpool = IDPool(start_from=self.formula.nv + 1)
402
+
403
+ # iterating until we find the backbone or determine there is none
404
+ while model:
405
+ self.calls += 1
406
+
407
+ # preparing the call
408
+ size = min(chunk_size, len(model))
409
+ selv = vpool.id()
410
+
411
+ # first, adding a new D-clause for the selected chunk
412
+ self.oracle.add_clause([-model[-i] for i in range(1, size + 1)] + [-selv])
413
+
414
+ # testing for unsatisfiability
415
+ if self.oracle.solve(assumptions=[selv]) == False:
416
+ # all literals in the chunk are in the backbone
417
+ for _ in range(size):
418
+ lit = model.pop()
419
+ bbone.append(lit)
420
+ self.oracle.add_clause([lit])
421
+ else:
422
+ coex = set(self.oracle.get_model())
423
+ model = [l for l in model if l in coex]
424
+ model = self._process_model(model)
425
+
426
+ return bbone
427
+
428
+ def _compute_core_based(self, focus_on=None):
429
+ """
430
+ Core-based algorithm.
431
+ """
432
+
433
+ if self.verbose:
434
+ print('c using core-based algorithm')
435
+
436
+ # initial estimate
437
+ bbone, model = [], self.model if focus_on is None else focus_on
438
+
439
+ # iterating until we find the backbone or determine there is none
440
+ while model:
441
+ # flipping all the literals
442
+ assumps = [-l for l in model]
443
+
444
+ # getting unsatisfiable cores with them
445
+ while True:
446
+ self.calls += 1
447
+
448
+ if self.oracle.solve(assumptions=assumps):
449
+ coex = set(self.oracle.get_model())
450
+ model = [l for l in model if l in coex]
451
+ model = self._process_model(model)
452
+ break
453
+
454
+ else:
455
+ core = self.oracle.get_core()
456
+ if len(core) == 1:
457
+ bbone.append(-core[0])
458
+ self.oracle.add_clause([-core[0]])
459
+
460
+ # remove from the working model
461
+ indx = model.index(-core[0]) # may be slow
462
+ if indx < len(model) - 1:
463
+ model[indx] = model.pop()
464
+ else:
465
+ model.pop()
466
+
467
+ # filtering out unnecessary flipped literals
468
+ core = set(core)
469
+ assumps = [l for l in assumps if l not in core]
470
+
471
+ if not assumps:
472
+ # resorting to the iterative traversal algorithm
473
+ self.model = model
474
+ return bbone + self._compute_iter()
475
+
476
+ return bbone
477
+
478
+ def _compute_core_chunking(self, chunk_size=100, focus_on=None):
479
+ """
480
+ Core-based algorithm with chunking.
481
+ """
482
+
483
+ if self.verbose:
484
+ print('c using core-based chunking, with chunk size:', chunk_size)
485
+
486
+ # initial estimate
487
+ bbone, model = [], self.model if focus_on is None else focus_on
488
+
489
+ # we are going to use clause selectors
490
+ vpool = IDPool(start_from=self.formula.nv + 1)
491
+
492
+ # iterating until we find the backbone or determine there is none
493
+ while model:
494
+ # preparing the chunking
495
+ size = min(chunk_size, len(model))
496
+
497
+ # flipping all the literals
498
+ assumps, skipped = [-model.pop() for i in range(size)], []
499
+
500
+ # getting unsatisfiable cores with them
501
+ while True:
502
+ self.calls += 1
503
+
504
+ if self.oracle.solve(assumptions=assumps):
505
+ coex = set(self.oracle.get_model())
506
+ model = [l for l in model if l in coex]
507
+ model = self._process_model(model)
508
+
509
+ if skipped:
510
+ bbone += self._compute_iter(focus_on=skipped)
511
+
512
+ break
513
+
514
+ else:
515
+ core = self.oracle.get_core()
516
+
517
+ if len(core) == 1:
518
+ # a unit-size core must contain a backbone literal
519
+ bbone.append(-core[0])
520
+ self.oracle.add_clause([-core[0]])
521
+ else:
522
+ # all removed literals are going to be tested later
523
+ skipped += [-l for l in core]
524
+
525
+ # filtering out unnecessary flipped literals
526
+ core = set(core)
527
+ assumps = [l for l in assumps if l not in core]
528
+
529
+ if not assumps:
530
+ # resorting to the iterative traversal algorithm
531
+ # in order to test all the removed literals
532
+ bbone += self._compute_iter(focus_on=skipped)
533
+ break
534
+
535
+ return bbone
536
+
537
+ def oracle_time(self):
538
+ """
539
+ This method computes and returns the total SAT solving time
540
+ involved, including the time spent by the hitting set enumerator
541
+ and the two SAT oracles.
542
+
543
+ :rtype: float
544
+ """
545
+
546
+ return self.oracle.time_accum()
547
+
548
+
549
+ #
550
+ #==============================================================================
551
+ def parse_options():
552
+ """
553
+ Parses command-line options.
554
+ """
555
+
556
+ try:
557
+ opts, args = getopt.getopt(sys.argv[1:],
558
+ 'a:c:hlrs:v',
559
+ ['algo=',
560
+ 'chunk=',
561
+ 'help',
562
+ 'lift',
563
+ 'rotate',
564
+ 'solver=',
565
+ 'verbose'])
566
+ except getopt.GetoptError as err:
567
+ sys.stderr.write(str(err).capitalize() + '\n')
568
+ usage()
569
+ sys.exit(1)
570
+
571
+ algo = 'iter'
572
+ chunk = 100
573
+ lift = False
574
+ rotate = False
575
+ solver = 'g3'
576
+ verbose = 0
577
+
578
+ for opt, arg in opts:
579
+ if opt in ('-a', '--algo'):
580
+ algo = str(arg)
581
+ assert algo in ('enum', 'iter', 'compl', 'chunk', 'core', 'corechunk'), 'Unknown algorithm'
582
+ elif opt in ('-c', '--chunk'):
583
+ chunk = int(arg)
584
+ elif opt in ('-h', '--help'):
585
+ usage()
586
+ sys.exit(0)
587
+ elif opt in ('-l', '--lift'):
588
+ lift = True
589
+ elif opt in ('-r', '--rotate'):
590
+ rotate = True
591
+ elif opt in ('-s', '--solver'):
592
+ solver = str(arg)
593
+ elif opt in ('-v', '--verbose'):
594
+ verbose += 1
595
+ else:
596
+ assert False, 'Unhandled option: {0} {1}'.format(opt, arg)
597
+
598
+ return algo, chunk, lift, rotate, solver, verbose, args
599
+
600
+
601
+ #
602
+ #==============================================================================
603
+ def usage():
604
+ """
605
+ Prints usage message.
606
+ """
607
+
608
+ print('Usage:', os.path.basename(sys.argv[0]), '[options] file')
609
+ print('Options:')
610
+ print(' -a, --algo=<string> Algorithm to use')
611
+ print(' Available values: enum, iter, compl, chunk, core, corechunk (default: iter)')
612
+ print(' -c, --chunk=<int> Chunk size for chunking algorithms')
613
+ print(' Available values: [1 .. INT_MAX] (default: 100)')
614
+ print(' -h, --help Show this message')
615
+ print(' -l, --lift Apply literal lifting for heuristic model reduction')
616
+ print(' -r, --rotate Heuristically filter out rotatable literals')
617
+ print(' -s, --solver=<string> SAT solver to use')
618
+ print(' Available values: cd15, cd19, g3, g4, lgl, mcb, mcm, mpl, m22, mc, mgh (default: g3)')
619
+ print(' -v, --verbose Be verbose (can be used multiple times)')
620
+
621
+
622
+ #
623
+ #==============================================================================
624
+ if __name__ == '__main__':
625
+ algo, chunk, lift, rotate, solver, verbose, files = parse_options()
626
+
627
+ if files:
628
+ # read CNF from file
629
+ assert re.search(r'cnf(\.(gz|bz2|lzma|xz|zst))?$', files[0]), \
630
+ 'Unknown input file extension'
631
+ formula = CNF(from_file=files[0])
632
+
633
+ if verbose:
634
+ print('c formula: {0} vars, {1} clauses'.format(formula.nv,
635
+ len(formula.clauses)))
636
+
637
+ # computing the backbone
638
+ with BBScan(formula, solver=solver, lift=lift, rotate=rotate,
639
+ verbose=verbose) as bbscan:
640
+ try:
641
+ bbone = bbscan.compute(algorithm=algo, chunk_size=chunk)
642
+
643
+ except ValueError as e:
644
+ print('s UNSATISFIABLE')
645
+ print('c', str(e))
646
+ sys.exit(1)
647
+
648
+ # outputting the results
649
+ if bbone:
650
+ print('v {0} 0'.format(' '.join(['{0}{1}'.format('+' if v > 0 else '', v) for v in bbone])))
651
+ print('c backbone size: {0} ({1:.2f}% of all variables)'.format(len(bbone), 100. * len(bbone) / formula.nv))
652
+ else:
653
+ print('v 0')
654
+
655
+ if verbose > 1:
656
+ print('c filtered: {0}; ({1:.2f})'.format(bbscan.filtered, 100. * bbscan.filtered / formula.nv))
657
+
658
+ print('c oracle time: {0:.4f}'.format(bbscan.oracle_time()))
659
+ print('c oracle calls: {0}'.format(bbscan.calls))
@@ -259,7 +259,6 @@ class Bica:
259
259
  :type otrim: int
260
260
  :type weighted: bool
261
261
  :type verbose: int
262
-
263
262
  """
264
263
 
265
264
  def __init__(self, formula, negated=None, target='cnf', psolver='cd19',
@@ -662,8 +661,8 @@ if __name__ == '__main__':
662
661
 
663
662
  if files:
664
663
  # read CNF from file
665
- if re.search(r'cnf(\.(gz|bz2|lzma|xz))?$', files[0]):
666
- formula = CNF(from_file=files[0])
664
+ assert re.search(r'cnf(\.(gz|bz2|lzma|xz|zst))?$', files[0]), 'Unknown input file extension'
665
+ formula = CNF(from_file=files[0])
667
666
 
668
667
  # creating an object of Primer
669
668
  with Bica(formula, negated=None, target=mode, psolver=psolver,
@@ -506,7 +506,7 @@ if __name__ == '__main__':
506
506
 
507
507
  if files:
508
508
  # parsing the input formula
509
- if re.search(r'\.wcnf[p|+]?(\.(gz|bz2|lzma|xz))?$', files[0]):
509
+ if re.search(r'\.wcnf[p|+]?(\.(gz|bz2|lzma|xz|zst))?$', files[0]):
510
510
  formula = WCNFPlus(from_file=files[0])
511
511
  else: # expecting '*.cnf[,p,+].*'
512
512
  formula = CNFPlus(from_file=files[0]).weighted()
@@ -614,11 +614,11 @@ if __name__ == '__main__':
614
614
 
615
615
  if files:
616
616
  # reading standard CNF, WCNF, or (W)CNF+
617
- if re.search(r'cnf[p|+]?(\.(gz|bz2|lzma|xz))?$', files[0]):
618
- if re.search(r'\.wcnf[p|+]?(\.(gz|bz2|lzma|xz))?$', files[0]):
619
- formula = WCNFPlus(from_file=files[0])
620
- else: # expecting '*.cnf[,p,+].*'
621
- formula = CNFPlus(from_file=files[0]).weighted()
617
+ assert re.search(r'cnf[p|+]?(\.(gz|bz2|lzma|xz|zst))?$', files[0]), 'Unknown input file extension'
618
+ if re.search(r'\.wcnf[p|+]?(\.(gz|bz2|lzma|xz|zst))?$', files[0]):
619
+ formula = WCNFPlus(from_file=files[0])
620
+ else: # expecting '*.cnf[,p,+].*'
621
+ formula = CNFPlus(from_file=files[0]).weighted()
622
622
 
623
623
  with LBX(formula, use_cld=dcalls, solver_name=solver, process=process,
624
624
  use_timer=True) as mcsls:
@@ -458,8 +458,8 @@ if __name__ == '__main__':
458
458
 
459
459
  if files:
460
460
  # reading standard CNF or WCNF
461
- if re.search(r'cnf(\.(gz|bz2|lzma|xz))?$', files[0]):
462
- if re.search(r'\.wcnf(\.(gz|bz2|lzma|xz))?$', files[0]):
461
+ if re.search(r'cnf(\.(gz|bz2|lzma|xz|zst))?$', files[0]):
462
+ if re.search(r'\.wcnf(\.(gz|bz2|lzma|xz|zst))?$', files[0]):
463
463
  formula = WCNF(from_file=files[0])
464
464
  else: # expecting '*.cnf'
465
465
  formula = CNF(from_file=files[0]).weighted()
@@ -468,10 +468,12 @@ if __name__ == '__main__':
468
468
  expect_interrupt=(timeout != None), verbose=verbose)
469
469
 
470
470
  # reading WCNF+
471
- elif re.search(r'\.wcnf[p,+](\.(gz|bz2|lzma|xz))?$', files[0]):
471
+ elif re.search(r'\.wcnf[p,+](\.(gz|bz2|lzma|xz|zst))?$', files[0]):
472
472
  formula = WCNFPlus(from_file=files[0])
473
473
  lsu = LSUPlus(formula, solver=solver, incr=incr,
474
474
  expect_interrupt=(timeout != None), verbose=verbose)
475
+ else:
476
+ assert False, 'Unknown input file extension'
475
477
 
476
478
  # setting a timer if necessary
477
479
  if timeout is not None:
@@ -586,11 +586,11 @@ if __name__ == '__main__':
586
586
 
587
587
  if files:
588
588
  # reading standard CNF, WCNF, or (W)CNF+
589
- if re.search(r'cnf[p|+]?(\.(gz|bz2|lzma|xz))?$', files[0]):
590
- if re.search(r'\.wcnf[p|+]?(\.(gz|bz2|lzma|xz))?$', files[0]):
591
- formula = WCNFPlus(from_file=files[0])
592
- else: # expecting '*.cnf[,p,+].*'
593
- formula = CNFPlus(from_file=files[0]).weighted()
589
+ assert re.search(r'cnf[p|+]?(\.(gz|bz2|lzma|xz|zst))?$', files[0]), 'Unknown input file extension'
590
+ if re.search(r'\.wcnf[p|+]?(\.(gz|bz2|lzma|xz|zst))?$', files[0]):
591
+ formula = WCNFPlus(from_file=files[0])
592
+ else: # expecting '*.cnf[,p,+].*'
593
+ formula = CNFPlus(from_file=files[0]).weighted()
594
594
 
595
595
  with MCSls(formula, use_cld=dcalls, solver_name=solver,
596
596
  process=process, use_timer=True) as mcsls:
@@ -327,7 +327,7 @@ if __name__ == '__main__':
327
327
 
328
328
  if files:
329
329
  # parsing the input formula
330
- if re.search(r'\.wcnf[p|+]?(\.(gz|bz2|lzma|xz))?$', files[0]):
330
+ if re.search(r'\.wcnf[p|+]?(\.(gz|bz2|lzma|xz|zst))?$', files[0]):
331
331
  formula = WCNFPlus(from_file=files[0])
332
332
  else: # expecting '*.cnf[,p,+].*'
333
333
  formula = CNFPlus(from_file=files[0]).weighted()
@@ -676,14 +676,14 @@ if __name__ == '__main__':
676
676
 
677
677
  if files:
678
678
  # reading standard CNF, WCNF, or (W)CNF+
679
- if re.search(r'cnf[p|+]?(\.(gz|bz2|lzma|xz))?$', files[0]):
680
- if re.search(r'\.wcnf[p|+]?(\.(gz|bz2|lzma|xz))?$', files[0]):
681
- formula = WCNFPlus(from_file=files[0])
682
- else: # expecting '*.cnf[,p,+].*'
683
- formula = CNFPlus(from_file=files[0]).weighted()
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
684
 
685
685
  if cover: # expecting '*.cnf[,p,+].*' only!
686
- assert re.search(r'cnf[p|+]?(\.(gz|bz2|lzma|xz))?$', cover), 'wrong file for formula to cover'
686
+ assert re.search(r'cnf[p|+]?(\.(gz|bz2|lzma|xz|zst))?$', cover), 'Wrong file for formula to cover'
687
687
  cover = CNFPlus(from_file=cover)
688
688
 
689
689
  # creating an object of OptUx
@@ -594,8 +594,8 @@ if __name__ == '__main__':
594
594
 
595
595
  if files:
596
596
  # read CNF from file
597
- if re.search(r'cnf(\.(gz|bz2|lzma|xz))?$', files[0]):
598
- formula = CNF(from_file=files[0])
597
+ assert re.search(r'cnf(\.(gz|bz2|lzma|xz|zst))?$', files[0]), 'Unknown input file extension'
598
+ formula = CNF(from_file=files[0])
599
599
 
600
600
  # creating an object of Primer
601
601
  with Primer(formula, negated=None, solver=solver, implicates=mode,
@@ -1788,7 +1788,7 @@ if __name__ == '__main__':
1788
1788
 
1789
1789
  if files:
1790
1790
  # parsing the input formula
1791
- if re.search(r'\.wcnf[p|+]?(\.(gz|bz2|lzma|xz))?$', files[0]):
1791
+ if re.search(r'\.wcnf[p|+]?(\.(gz|bz2|lzma|xz|zst))?$', files[0]):
1792
1792
  formula = WCNFPlus(from_file=files[0])
1793
1793
  else: # expecting '*.cnf[,p,+].*'
1794
1794
  formula = CNFPlus(from_file=files[0]).weighted()
@@ -10,7 +10,7 @@
10
10
 
11
11
  # current version
12
12
  #==============================================================================
13
- VERSION = (1, 8, 'dev', 21)
13
+ VERSION = (1, 8, 'dev', 22)
14
14
 
15
15
 
16
16
  # PEP440 Format
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-sat
3
- Version: 1.8.dev21
3
+ Version: 1.8.dev22
4
4
  Summary: A Python library for prototyping with SAT oracles
5
5
  Home-page: https://github.com/pysathq/pysat
6
6
  Author: Alexey Ignatiev, Joao Marques-Silva, Antonio Morgado
@@ -21,6 +21,7 @@ cardenc/seqcounter.hh
21
21
  cardenc/sortcard.hh
22
22
  cardenc/utils.hh
23
23
  examples/__init__.py
24
+ examples/bbscan.py
24
25
  examples/bica.py
25
26
  examples/fm.py
26
27
  examples/genhard.py
@@ -75,8 +75,8 @@ to_install = ['cadical103', 'cadical153', 'cadical195', 'gluecard30',
75
75
 
76
76
  # example and allies scripts to install as standalone executables
77
77
  #==============================================================================
78
- example_scripts = ['bica', 'fm', 'genhard', 'lbx', 'lsu', 'mcsls', 'models',
79
- 'musx', 'optux', 'primer', 'rc2']
78
+ example_scripts = ['bbscan', 'bica', 'fm', 'genhard', 'lbx', 'lsu', 'mcsls',
79
+ 'models', 'musx', 'optux', 'primer', 'rc2']
80
80
  allies_scripts = ['approxmc', 'unigen']
81
81
 
82
82
 
File without changes