python-sat 1.8.dev21__tar.gz → 1.8.dev23__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.dev23}/PKG-INFO +1 -1
  2. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/README.rst +25 -0
  3. python_sat-1.8.dev23/examples/bbscan.py +669 -0
  4. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/examples/bica.py +2 -3
  5. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/examples/fm.py +1 -1
  6. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/examples/lbx.py +5 -5
  7. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/examples/lsu.py +5 -3
  8. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/examples/mcsls.py +5 -5
  9. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/examples/musx.py +1 -1
  10. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/examples/optux.py +6 -6
  11. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/examples/primer.py +2 -2
  12. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/examples/rc2.py +1 -1
  13. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/pysat/__init__.py +1 -1
  14. {python_sat-1.8.dev21 → python_sat-1.8.dev23/python_sat.egg-info}/PKG-INFO +1 -1
  15. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/python_sat.egg-info/SOURCES.txt +1 -0
  16. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/setup.py +2 -2
  17. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/LICENSE.txt +0 -0
  18. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/MANIFEST.in +0 -0
  19. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/allies/__init__.py +0 -0
  20. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/allies/approxmc.py +0 -0
  21. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/allies/unigen.py +0 -0
  22. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/cardenc/bitwise.hh +0 -0
  23. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/cardenc/card.hh +0 -0
  24. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/cardenc/clset.hh +0 -0
  25. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/cardenc/common.hh +0 -0
  26. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/cardenc/itot.hh +0 -0
  27. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/cardenc/ladder.hh +0 -0
  28. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/cardenc/mto.hh +0 -0
  29. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/cardenc/pairwise.hh +0 -0
  30. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/cardenc/ptypes.hh +0 -0
  31. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/cardenc/pycard.cc +0 -0
  32. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/cardenc/seqcounter.hh +0 -0
  33. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/cardenc/sortcard.hh +0 -0
  34. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/cardenc/utils.hh +0 -0
  35. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/examples/__init__.py +0 -0
  36. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/examples/genhard.py +0 -0
  37. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/examples/hitman.py +0 -0
  38. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/examples/models.py +0 -0
  39. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/examples/usage.py +0 -0
  40. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/pysat/_fileio.py +0 -0
  41. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/pysat/_utils.py +0 -0
  42. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/pysat/card.py +0 -0
  43. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/pysat/engines.py +0 -0
  44. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/pysat/formula.py +0 -0
  45. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/pysat/pb.py +0 -0
  46. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/pysat/process.py +0 -0
  47. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/pysat/solvers.py +0 -0
  48. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/python_sat.egg-info/dependency_links.txt +0 -0
  49. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/python_sat.egg-info/requires.txt +0 -0
  50. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/python_sat.egg-info/top_level.txt +0 -0
  51. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/requirements.txt +0 -0
  52. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/setup.cfg +0 -0
  53. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/solvers/cadical103.tar.gz +0 -0
  54. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/solvers/cadical153.tar.gz +0 -0
  55. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/solvers/cadical195.tar.gz +0 -0
  56. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/solvers/glucose30.tar.gz +0 -0
  57. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/solvers/glucose41.tar.gz +0 -0
  58. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/solvers/glucose421.tar.gz +0 -0
  59. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/solvers/lingeling.tar.gz +0 -0
  60. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/solvers/maplechrono.zip +0 -0
  61. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/solvers/maplecm.zip +0 -0
  62. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/solvers/maplesat.zip +0 -0
  63. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/solvers/mergesat3.tar.gz +0 -0
  64. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/solvers/minicard.tar.gz +0 -0
  65. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/solvers/minisat22.tar.gz +0 -0
  66. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/solvers/minisatgh.zip +0 -0
  67. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/solvers/patches/cadical103.patch +0 -0
  68. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/solvers/patches/cadical153.patch +0 -0
  69. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/solvers/patches/cadical195.patch +0 -0
  70. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/solvers/patches/glucose30.patch +0 -0
  71. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/solvers/patches/glucose41.patch +0 -0
  72. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/solvers/patches/glucose421.patch +0 -0
  73. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/solvers/patches/gluecard30.patch +0 -0
  74. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/solvers/patches/gluecard41.patch +0 -0
  75. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/solvers/patches/lingeling.patch +0 -0
  76. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/solvers/patches/maplechrono.patch +0 -0
  77. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/solvers/patches/maplecm.patch +0 -0
  78. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/solvers/patches/maplesat.patch +0 -0
  79. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/solvers/patches/mergesat3.patch +0 -0
  80. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/solvers/patches/minicard.patch +0 -0
  81. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/solvers/patches/minisat22.patch +0 -0
  82. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/solvers/patches/minisatgh.patch +0 -0
  83. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/solvers/prepare.py +0 -0
  84. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/solvers/pysolvers.cc +0 -0
  85. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/tests/test_accum_stats.py +0 -0
  86. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/tests/test_atmost.py +0 -0
  87. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/tests/test_atmost1.py +0 -0
  88. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/tests/test_atmostk.py +0 -0
  89. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/tests/test_boolengine.py +0 -0
  90. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/tests/test_clausification.py +0 -0
  91. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/tests/test_cnf.py +0 -0
  92. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/tests/test_equals1.py +0 -0
  93. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/tests/test_formula_unique.py +0 -0
  94. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/tests/test_process.py +0 -0
  95. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/tests/test_propagate.py +0 -0
  96. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/tests/test_unique_model.py +0 -0
  97. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/tests/test_unique_mus.py +0 -0
  98. {python_sat-1.8.dev21 → python_sat-1.8.dev23}/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.dev23
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,669 @@
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. Note that the chunk_size can be a
208
+ floating-point number in the interval (0, 1], which will set the
209
+ actual chunk_size relative to the total number of literals of
210
+ interest.
211
+
212
+ Finally, one may opt for computing backbone literals out of a
213
+ particular subset of literals, which may run faster than computing
214
+ the entire formula's backbone. To focus on a particular set of
215
+ literals, the user should use the parameter ``focus_on``, which is
216
+ set to ``None`` by default.
217
+
218
+ .. note::
219
+
220
+ The method raises ``ValueError`` if the input formula is
221
+ unsatisfiable.
222
+
223
+ :param algorithm: backbone computation algorithm
224
+ :param chunk_size: number of literals in the chunk (for chunking algorithms)
225
+ :param focus_on: a list of literals to focus on
226
+
227
+ :type algorithm: str
228
+ :type chunk_size: int or float
229
+ :type focus_on: iterable(int)
230
+ """
231
+
232
+ self.calls += 1
233
+
234
+ if self.oracle.solve():
235
+ self.model = self.oracle.get_model()
236
+ if focus_on is not None:
237
+ model = set(self.model)
238
+ self.model = [l for l in focus_on if l in model]
239
+ else:
240
+ raise ValueError('Unsatisfiable formula')
241
+
242
+ if isinstance(chunk_size, float):
243
+ assert 0 < chunk_size <= 1, f'Wrong chunk proportion {chunk_size}'
244
+ chunk_size = int(min(self.formula.nv, len(self.model)) * chunk_size)
245
+
246
+ if algorithm == 'enum':
247
+ result = self._compute_enum()
248
+ elif algorithm == 'iter':
249
+ result = self._compute_iter()
250
+ elif algorithm == 'compl':
251
+ result = self._compute_compl()
252
+ elif algorithm == 'chunk':
253
+ result = self._compute_chunking(chunk_size)
254
+ elif algorithm == 'core':
255
+ result = self._compute_core_based()
256
+ elif algorithm == 'corechunk':
257
+ result = self._compute_core_chunking(chunk_size)
258
+ else:
259
+ assert 0, f'Unknown algorithm: {algorithm}'
260
+
261
+ return sorted(result, key=lambda l: abs(l))
262
+
263
+ def _get_implicant(self, model):
264
+ """
265
+ Simple literal lifting used to reduce a given model.
266
+ """
267
+
268
+ res, model = set(), set(model)
269
+
270
+ # traversing the clauses and collecting all literals
271
+ # that satisfy at least one clause of the formula
272
+ for cl in self.formula:
273
+ res |= set([l for l in cl if l in model])
274
+
275
+ return list(res)
276
+
277
+ def _filter_rotatable(self, model):
278
+ """
279
+ Filter out rotatable literals.
280
+ """
281
+
282
+ units, model = set([]), set(model)
283
+
284
+ # determining unit literals
285
+ for cl in self.formula:
286
+ satisfied = [l for l in cl if l in model]
287
+ if len(satisfied) == 1:
288
+ units.add(satisfied[0])
289
+
290
+ self.filtered += len(model) - len(units)
291
+ return list(units)
292
+
293
+ def _process_model(self, model):
294
+ """
295
+ Heuristically reduce a model.
296
+ """
297
+
298
+ if self.lift:
299
+ model = self._get_implicant(model)
300
+
301
+ if self.rotate:
302
+ model = self._filter_rotatable(model)
303
+
304
+ return model
305
+
306
+ def _compute_enum(self, focus_on=None):
307
+ """
308
+ Enumeration-based backbone computation.
309
+ """
310
+
311
+ if self.verbose:
312
+ print('c using enumeration-based algorithm')
313
+
314
+ # initial backbone estimate contains all literals
315
+ bbone = set(self.model) if focus_on is None else set(focus_on)
316
+
317
+ while bbone:
318
+ # counting the number of calls
319
+ self.calls += 1
320
+
321
+ # stop if there are no more models
322
+ if not self.oracle.solve():
323
+ break
324
+
325
+ coex = set(self.oracle.get_model())
326
+ self.model = [l for l in bbone if l in coex]
327
+ self.model = self._process_model(self.model)
328
+
329
+ # updating backbone estimate - intersection
330
+ bbone &= set(self.model)
331
+
332
+ # blocking the previously found implicant
333
+ self.oracle.add_clause([-l for l in self.model])
334
+
335
+ return list(bbone)
336
+
337
+ def _compute_iter(self, focus_on=None):
338
+ """
339
+ Iterative algorithm with one test per variable.
340
+ """
341
+
342
+ if self.verbose:
343
+ print('c using iterative algorithm')
344
+
345
+ bbone, model = [], self.model if focus_on is None else focus_on
346
+
347
+ while model:
348
+ # counting the number of oracle calls
349
+ self.calls += 1
350
+
351
+ # checking this literal
352
+ lit = model.pop()
353
+
354
+ if self.oracle.solve(assumptions=[-lit]) == False:
355
+ # it is a backbone literal
356
+ bbone.append(lit)
357
+ else:
358
+ # it isn't and we've got a counterexample
359
+ # => using it to filter out more literals
360
+ coex = set(self.oracle.get_model())
361
+ model = [l for l in model if l in coex]
362
+ model = self._process_model(model)
363
+
364
+ return bbone
365
+
366
+ def _compute_compl(self, focus_on=None):
367
+ """
368
+ Iterative algorithm with complement of backbone estimate.
369
+ """
370
+
371
+ if self.verbose:
372
+ print('c using complement of backbone estimate algorithm')
373
+
374
+ # initial estimate
375
+ bbone = set(self.model) if focus_on is None else set(focus_on)
376
+
377
+ # iterating until we find the backbone or determine there is none
378
+ while bbone:
379
+ self.calls += 1
380
+
381
+ # first, adding a new D-clause
382
+ self.oracle.add_clause([-l for l in bbone])
383
+
384
+ # testing for unsatisfiability
385
+ if self.oracle.solve() == False:
386
+ break
387
+ else:
388
+ coex = set(self.oracle.get_model())
389
+ model = [l for l in bbone if l in coex]
390
+ model = self._process_model(model)
391
+ bbone &= set(model)
392
+
393
+ return list(bbone)
394
+
395
+ def _compute_chunking(self, chunk_size=100, focus_on=None):
396
+ """
397
+ Algorithm 5: Chunking algorithm.
398
+ """
399
+
400
+ if self.verbose:
401
+ print('c using chunking algorithm, with chunk size:', chunk_size)
402
+
403
+ # initial estimate
404
+ bbone, model = [], self.model if focus_on is None else focus_on
405
+
406
+ # we are going to use clause selectors
407
+ vpool = IDPool(start_from=self.formula.nv + 1)
408
+
409
+ # iterating until we find the backbone or determine there is none
410
+ while model:
411
+ self.calls += 1
412
+
413
+ # preparing the call
414
+ size = min(chunk_size, len(model))
415
+ selv = vpool.id()
416
+
417
+ # first, adding a new D-clause for the selected chunk
418
+ self.oracle.add_clause([-model[-i] for i in range(1, size + 1)] + [-selv])
419
+
420
+ # testing for unsatisfiability
421
+ if self.oracle.solve(assumptions=[selv]) == False:
422
+ # all literals in the chunk are in the backbone
423
+ for _ in range(size):
424
+ lit = model.pop()
425
+ bbone.append(lit)
426
+ self.oracle.add_clause([lit])
427
+ else:
428
+ coex = set(self.oracle.get_model())
429
+ model = [l for l in model if l in coex]
430
+ model = self._process_model(model)
431
+
432
+ return bbone
433
+
434
+ def _compute_core_based(self, focus_on=None):
435
+ """
436
+ Core-based algorithm.
437
+ """
438
+
439
+ if self.verbose:
440
+ print('c using core-based algorithm')
441
+
442
+ # initial estimate
443
+ bbone, model = [], self.model if focus_on is None else focus_on
444
+
445
+ # iterating until we find the backbone or determine there is none
446
+ while model:
447
+ # flipping all the literals
448
+ assumps = [-l for l in model]
449
+
450
+ # getting unsatisfiable cores with them
451
+ while True:
452
+ self.calls += 1
453
+
454
+ if self.oracle.solve(assumptions=assumps):
455
+ coex = set(self.oracle.get_model())
456
+ model = [l for l in model if l in coex]
457
+ model = self._process_model(model)
458
+ break
459
+
460
+ else:
461
+ core = self.oracle.get_core()
462
+ if len(core) == 1:
463
+ bbone.append(-core[0])
464
+ self.oracle.add_clause([-core[0]])
465
+
466
+ # remove from the working model
467
+ indx = model.index(-core[0]) # may be slow
468
+ if indx < len(model) - 1:
469
+ model[indx] = model.pop()
470
+ else:
471
+ model.pop()
472
+
473
+ # filtering out unnecessary flipped literals
474
+ core = set(core)
475
+ assumps = [l for l in assumps if l not in core]
476
+
477
+ if not assumps:
478
+ # resorting to the iterative traversal algorithm
479
+ self.model = model
480
+ return bbone + self._compute_iter()
481
+
482
+ return bbone
483
+
484
+ def _compute_core_chunking(self, chunk_size=100, focus_on=None):
485
+ """
486
+ Core-based algorithm with chunking.
487
+ """
488
+
489
+ if self.verbose:
490
+ print('c using core-based chunking, with chunk size:', chunk_size)
491
+
492
+ # initial estimate
493
+ bbone, model = [], self.model if focus_on is None else focus_on
494
+
495
+ # we are going to use clause selectors
496
+ vpool = IDPool(start_from=self.formula.nv + 1)
497
+
498
+ # iterating until we find the backbone or determine there is none
499
+ while model:
500
+ # preparing the chunking
501
+ size = min(chunk_size, len(model))
502
+
503
+ # flipping all the literals
504
+ assumps, skipped = [-model.pop() for i in range(size)], []
505
+
506
+ # getting unsatisfiable cores with them
507
+ while True:
508
+ self.calls += 1
509
+
510
+ if self.oracle.solve(assumptions=assumps):
511
+ coex = set(self.oracle.get_model())
512
+ model = [l for l in model if l in coex]
513
+ model = self._process_model(model)
514
+
515
+ if skipped:
516
+ bbone += self._compute_iter(focus_on=skipped)
517
+
518
+ break
519
+
520
+ else:
521
+ core = self.oracle.get_core()
522
+
523
+ if len(core) == 1:
524
+ # a unit-size core must contain a backbone literal
525
+ bbone.append(-core[0])
526
+ self.oracle.add_clause([-core[0]])
527
+ else:
528
+ # all removed literals are going to be tested later
529
+ skipped += [-l for l in core]
530
+
531
+ # filtering out unnecessary flipped literals
532
+ core = set(core)
533
+ assumps = [l for l in assumps if l not in core]
534
+
535
+ if not assumps:
536
+ # resorting to the iterative traversal algorithm
537
+ # in order to test all the removed literals
538
+ bbone += self._compute_iter(focus_on=skipped)
539
+ break
540
+
541
+ return bbone
542
+
543
+ def oracle_time(self):
544
+ """
545
+ This method computes and returns the total SAT solving time
546
+ involved, including the time spent by the hitting set enumerator
547
+ and the two SAT oracles.
548
+
549
+ :rtype: float
550
+ """
551
+
552
+ return self.oracle.time_accum()
553
+
554
+
555
+ #
556
+ #==============================================================================
557
+ def parse_options():
558
+ """
559
+ Parses command-line options.
560
+ """
561
+
562
+ try:
563
+ opts, args = getopt.getopt(sys.argv[1:],
564
+ 'a:c:hlrs:v',
565
+ ['algo=',
566
+ 'chunk=',
567
+ 'help',
568
+ 'lift',
569
+ 'rotate',
570
+ 'solver=',
571
+ 'verbose'])
572
+ except getopt.GetoptError as err:
573
+ sys.stderr.write(str(err).capitalize() + '\n')
574
+ usage()
575
+ sys.exit(1)
576
+
577
+ algo = 'iter'
578
+ chunk = 100
579
+ lift = False
580
+ rotate = False
581
+ solver = 'g3'
582
+ verbose = 0
583
+
584
+ for opt, arg in opts:
585
+ if opt in ('-a', '--algo'):
586
+ algo = str(arg)
587
+ assert algo in ('enum', 'iter', 'compl', 'chunk', 'core', 'corechunk'), 'Unknown algorithm'
588
+ elif opt in ('-c', '--chunk'):
589
+ chunk = float(arg)
590
+ if chunk.is_integer():
591
+ chunk = int(chunk)
592
+ else:
593
+ assert 0 < chunk <= 1, f'Wrong chunk proportion {chunk_size}'
594
+ elif opt in ('-h', '--help'):
595
+ usage()
596
+ sys.exit(0)
597
+ elif opt in ('-l', '--lift'):
598
+ lift = True
599
+ elif opt in ('-r', '--rotate'):
600
+ rotate = True
601
+ elif opt in ('-s', '--solver'):
602
+ solver = str(arg)
603
+ elif opt in ('-v', '--verbose'):
604
+ verbose += 1
605
+ else:
606
+ assert False, 'Unhandled option: {0} {1}'.format(opt, arg)
607
+
608
+ return algo, chunk, lift, rotate, solver, verbose, args
609
+
610
+
611
+ #
612
+ #==============================================================================
613
+ def usage():
614
+ """
615
+ Prints usage message.
616
+ """
617
+
618
+ print('Usage:', os.path.basename(sys.argv[0]), '[options] file')
619
+ print('Options:')
620
+ print(' -a, --algo=<string> Algorithm to use')
621
+ print(' Available values: enum, iter, compl, chunk, core, corechunk (default: iter)')
622
+ print(' -c, --chunk=<int,float> Chunk size for chunking algorithms')
623
+ print(' Available values: [1 .. INT_MAX] or (0 .. 1] (default: 100)')
624
+ print(' -h, --help Show this message')
625
+ print(' -l, --lift Apply literal lifting for heuristic model reduction')
626
+ print(' -r, --rotate Heuristically filter out rotatable literals')
627
+ print(' -s, --solver=<string> SAT solver to use')
628
+ print(' Available values: cd15, cd19, g3, g4, lgl, mcb, mcm, mpl, m22, mc, mgh (default: g3)')
629
+ print(' -v, --verbose Be verbose (can be used multiple times)')
630
+
631
+
632
+ #
633
+ #==============================================================================
634
+ if __name__ == '__main__':
635
+ algo, chunk, lift, rotate, solver, verbose, files = parse_options()
636
+
637
+ if files:
638
+ # read CNF from file
639
+ assert re.search(r'cnf(\.(gz|bz2|lzma|xz|zst))?$', files[0]), \
640
+ 'Unknown input file extension'
641
+ formula = CNF(from_file=files[0])
642
+
643
+ if verbose:
644
+ print('c formula: {0} vars, {1} clauses'.format(formula.nv,
645
+ len(formula.clauses)))
646
+
647
+ # computing the backbone
648
+ with BBScan(formula, solver=solver, lift=lift, rotate=rotate,
649
+ verbose=verbose) as bbscan:
650
+ try:
651
+ bbone = bbscan.compute(algorithm=algo, chunk_size=chunk)
652
+
653
+ except ValueError as e:
654
+ print('s UNSATISFIABLE')
655
+ print('c', str(e))
656
+ sys.exit(1)
657
+
658
+ # outputting the results
659
+ if bbone:
660
+ print('v {0} 0'.format(' '.join(['{0}{1}'.format('+' if v > 0 else '', v) for v in bbone])))
661
+ print('c backbone size: {0} ({1:.2f}% of all variables)'.format(len(bbone), 100. * len(bbone) / formula.nv))
662
+ else:
663
+ print('v 0')
664
+
665
+ if verbose > 1:
666
+ print('c filtered: {0} ({1:.2f})'.format(bbscan.filtered, 100. * bbscan.filtered / formula.nv))
667
+
668
+ print('c oracle time: {0:.4f}'.format(bbscan.oracle_time()))
669
+ 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', 23)
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.dev23
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