python-sat 1.8.dev27__cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.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.
- pycard.cpython-314t-aarch64-linux-gnu.so +0 -0
- pysat/__init__.py +24 -0
- pysat/_fileio.py +209 -0
- pysat/_utils.py +58 -0
- pysat/allies/__init__.py +0 -0
- pysat/allies/approxmc.py +385 -0
- pysat/allies/unigen.py +435 -0
- pysat/card.py +802 -0
- pysat/engines.py +1302 -0
- pysat/examples/__init__.py +0 -0
- pysat/examples/bbscan.py +663 -0
- pysat/examples/bica.py +691 -0
- pysat/examples/fm.py +527 -0
- pysat/examples/genhard.py +516 -0
- pysat/examples/hitman.py +653 -0
- pysat/examples/lbx.py +638 -0
- pysat/examples/lsu.py +496 -0
- pysat/examples/mcsls.py +610 -0
- pysat/examples/models.py +189 -0
- pysat/examples/musx.py +344 -0
- pysat/examples/optux.py +710 -0
- pysat/examples/primer.py +620 -0
- pysat/examples/rc2.py +1999 -0
- pysat/examples/usage.py +63 -0
- pysat/formula.py +5619 -0
- pysat/pb.py +463 -0
- pysat/process.py +363 -0
- pysat/solvers.py +7591 -0
- pysolvers.cpython-314t-aarch64-linux-gnu.so +0 -0
- python_sat-1.8.dev27.data/scripts/approxmc.py +385 -0
- python_sat-1.8.dev27.data/scripts/bbscan.py +663 -0
- python_sat-1.8.dev27.data/scripts/bica.py +691 -0
- python_sat-1.8.dev27.data/scripts/fm.py +527 -0
- python_sat-1.8.dev27.data/scripts/genhard.py +516 -0
- python_sat-1.8.dev27.data/scripts/lbx.py +638 -0
- python_sat-1.8.dev27.data/scripts/lsu.py +496 -0
- python_sat-1.8.dev27.data/scripts/mcsls.py +610 -0
- python_sat-1.8.dev27.data/scripts/models.py +189 -0
- python_sat-1.8.dev27.data/scripts/musx.py +344 -0
- python_sat-1.8.dev27.data/scripts/optux.py +710 -0
- python_sat-1.8.dev27.data/scripts/primer.py +620 -0
- python_sat-1.8.dev27.data/scripts/rc2.py +1999 -0
- python_sat-1.8.dev27.data/scripts/unigen.py +435 -0
- python_sat-1.8.dev27.dist-info/METADATA +45 -0
- python_sat-1.8.dev27.dist-info/RECORD +48 -0
- python_sat-1.8.dev27.dist-info/WHEEL +6 -0
- python_sat-1.8.dev27.dist-info/licenses/LICENSE.txt +21 -0
- python_sat-1.8.dev27.dist-info/top_level.txt +3 -0
|
Binary file
|
pysat/__init__.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
#-*- coding:utf-8 -*-
|
|
3
|
+
##
|
|
4
|
+
## __init__.py
|
|
5
|
+
##
|
|
6
|
+
## Created on: Mar 4, 2017
|
|
7
|
+
## Author: Alexey S. Ignatiev
|
|
8
|
+
## E-mail: aignatiev@ciencias.ulisboa.pt
|
|
9
|
+
##
|
|
10
|
+
|
|
11
|
+
# current version
|
|
12
|
+
#==============================================================================
|
|
13
|
+
VERSION = (1, 8, 'dev', 27)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# PEP440 Format
|
|
17
|
+
#==============================================================================
|
|
18
|
+
__version__ = '%d.%d.%s%d' % VERSION if len(VERSION) == 4 else \
|
|
19
|
+
'%d.%d.%d' % VERSION
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# all submodules
|
|
23
|
+
#==============================================================================
|
|
24
|
+
__all__ = ['card', 'engines', 'formula', 'pb', 'process', 'solvers']
|
pysat/_fileio.py
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
#-*- coding:utf-8 -*-
|
|
3
|
+
##
|
|
4
|
+
## _fileio.py
|
|
5
|
+
##
|
|
6
|
+
## Created on: Aug 18, 2018
|
|
7
|
+
## Author: Alexey S. Ignatiev
|
|
8
|
+
## E-mail: aignatiev@ciencias.ulisboa.pt
|
|
9
|
+
##
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
===============
|
|
13
|
+
List of classes
|
|
14
|
+
===============
|
|
15
|
+
|
|
16
|
+
.. autosummary::
|
|
17
|
+
:nosignatures:
|
|
18
|
+
|
|
19
|
+
FileObject
|
|
20
|
+
|
|
21
|
+
==================
|
|
22
|
+
Module description
|
|
23
|
+
==================
|
|
24
|
+
|
|
25
|
+
This simple module provides a basic interface to input/output operations
|
|
26
|
+
on files. Its key design feature is the ability to work with both
|
|
27
|
+
uncompressed and compressed files through a unified interface, thus,
|
|
28
|
+
making it easier for a user to deal with various types of compressed
|
|
29
|
+
files. The compression types supported include gzip, bzip2, lzma (xz), and
|
|
30
|
+
zstandard (zstd).
|
|
31
|
+
|
|
32
|
+
The module is supposed to be mainly used by :mod:`pysat.formula`.
|
|
33
|
+
|
|
34
|
+
A simple usage example is the following:
|
|
35
|
+
|
|
36
|
+
.. code-block:: python
|
|
37
|
+
|
|
38
|
+
>>> from pysat._fileio import FileObject
|
|
39
|
+
>>>
|
|
40
|
+
>>> with FileObject(name='formula.cnf', mode='r') as fp1:
|
|
41
|
+
... contents1 = fp1.readlines()
|
|
42
|
+
>>>
|
|
43
|
+
>>> with FileObject(name='filename.txt.gz', compression='use_ext') as fp2:
|
|
44
|
+
... contents2 = fp2.readlines()
|
|
45
|
+
>>>
|
|
46
|
+
>>> with FileObject(name='f.txt.bz2', mode='w', compression='bzip2') as fp3:
|
|
47
|
+
... fp3.write('hello, world!\n')
|
|
48
|
+
|
|
49
|
+
==============
|
|
50
|
+
Module details
|
|
51
|
+
==============
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
#
|
|
55
|
+
#==============================================================================
|
|
56
|
+
import bz2
|
|
57
|
+
import codecs
|
|
58
|
+
import gzip
|
|
59
|
+
import os
|
|
60
|
+
|
|
61
|
+
lzma_present = True
|
|
62
|
+
try: # for Python3
|
|
63
|
+
import lzma
|
|
64
|
+
except ImportError:
|
|
65
|
+
try: # for Python2 + backports.lzma installed
|
|
66
|
+
from backports import lzma
|
|
67
|
+
except ImportError: # for Python2 without lzma
|
|
68
|
+
lzma_present = False
|
|
69
|
+
|
|
70
|
+
zstd_present = True
|
|
71
|
+
try:
|
|
72
|
+
from compression import zstd
|
|
73
|
+
except ImportError: # zstandard is introduced in Python 3.14
|
|
74
|
+
zstd_present = False
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
#
|
|
79
|
+
#==============================================================================
|
|
80
|
+
class FileObject(object):
|
|
81
|
+
"""
|
|
82
|
+
Auxiliary class for convenient and uniform file manipulation, e.g. to
|
|
83
|
+
open files creating standard file pointers and closing them. The class
|
|
84
|
+
is used when opening DIMACS files for reading and writing. Supports
|
|
85
|
+
both uncompressed and compressed files. Compression algorithms
|
|
86
|
+
supported are ``gzip``, ``bzip2``, ``lzma``, and ``zstd``. Algorithm
|
|
87
|
+
``lzma`` can be used in Python 3 by default and also in Python 2 if
|
|
88
|
+
the ``backports.lzma`` package is installed. Algorithm ``zstandard``
|
|
89
|
+
can be used in Python 3.14 and later.
|
|
90
|
+
|
|
91
|
+
Note that the class opens a file in text mode.
|
|
92
|
+
|
|
93
|
+
:param name: a file name to open
|
|
94
|
+
:param mode: opening mode
|
|
95
|
+
:param compression: compression type
|
|
96
|
+
|
|
97
|
+
:type name: str
|
|
98
|
+
:type mode: str
|
|
99
|
+
:type compression: str
|
|
100
|
+
|
|
101
|
+
Compression type can be ``None``, ``'gzip'``, ``'bzip2'``, ``'lzma'``,
|
|
102
|
+
``'zstd'``, as well as ``'use_ext'``. If ``'use_ext'`` is specified,
|
|
103
|
+
compression algorithm is defined by the extension of the given file
|
|
104
|
+
name.
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
def __init__(self, name, mode='r', compression=None):
|
|
108
|
+
"""
|
|
109
|
+
Constructor.
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
self.fp = None # file pointer to give access to
|
|
113
|
+
self.ctype = None # compression type
|
|
114
|
+
|
|
115
|
+
# in some cases an additional file pointer is needed
|
|
116
|
+
self.fp_extra = None
|
|
117
|
+
|
|
118
|
+
self.open(name, mode=mode, compression=compression)
|
|
119
|
+
|
|
120
|
+
def open(self, name, mode='r', compression=None):
|
|
121
|
+
"""
|
|
122
|
+
Open a file pointer. Note that a file is *always* opened in
|
|
123
|
+
text mode. The method inherits its input parameters from the
|
|
124
|
+
constructor of :class:`FileObject`.
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
if compression == 'use_ext':
|
|
128
|
+
self.get_compression_type(name)
|
|
129
|
+
else:
|
|
130
|
+
self.ctype = compression
|
|
131
|
+
|
|
132
|
+
if not self.ctype:
|
|
133
|
+
self.fp = open(name, mode)
|
|
134
|
+
elif self.ctype == 'gzip':
|
|
135
|
+
self.fp = gzip.open(name, mode + 't')
|
|
136
|
+
elif self.ctype == 'bzip2':
|
|
137
|
+
try:
|
|
138
|
+
# Python 3 supports opening bzip2 files in text mode
|
|
139
|
+
# therefore, we prefer to open them this way
|
|
140
|
+
self.fp = bz2.open(name, mode + 't')
|
|
141
|
+
except:
|
|
142
|
+
# BZ2File opens a file in binary mode
|
|
143
|
+
# thus, we have to use codecs.getreader()
|
|
144
|
+
# to be able to use it in text mode
|
|
145
|
+
self.fp_extra = bz2.BZ2File(name, mode)
|
|
146
|
+
|
|
147
|
+
if mode == 'r':
|
|
148
|
+
self.fp = codecs.getreader('ascii')(self.fp_extra)
|
|
149
|
+
else: # mode == 'w'
|
|
150
|
+
self.fp = codecs.getwriter('ascii')(self.fp_extra)
|
|
151
|
+
elif self.ctype == 'lzma':
|
|
152
|
+
# LZMA is available in Python 2 only if backports.lzma is installed
|
|
153
|
+
# Python 3 supports it by default
|
|
154
|
+
assert lzma_present, 'LZMA compression is unavailable.'
|
|
155
|
+
self.fp = lzma.open(name, mode=mode + 't')
|
|
156
|
+
else: # self.ctype == 'zstd'
|
|
157
|
+
# Zstandard is available only in Python 3.14 and later
|
|
158
|
+
assert zstd_present, 'Zstandard compression is unavailable.'
|
|
159
|
+
self.fp = zstd.open(name, mode=mode + 't')
|
|
160
|
+
|
|
161
|
+
def close(self):
|
|
162
|
+
"""
|
|
163
|
+
Close a file pointer.
|
|
164
|
+
"""
|
|
165
|
+
|
|
166
|
+
if self.fp:
|
|
167
|
+
self.fp.close()
|
|
168
|
+
self.fp = None
|
|
169
|
+
|
|
170
|
+
if self.fp_extra:
|
|
171
|
+
self.fp_extra.close()
|
|
172
|
+
self.fp_extra = None
|
|
173
|
+
|
|
174
|
+
self.ctype = None
|
|
175
|
+
|
|
176
|
+
def get_compression_type(self, file_name):
|
|
177
|
+
"""
|
|
178
|
+
Determine compression type for a given file using its extension.
|
|
179
|
+
|
|
180
|
+
:param file_name: a given file name
|
|
181
|
+
:type file_name: str
|
|
182
|
+
"""
|
|
183
|
+
|
|
184
|
+
ext = os.path.splitext(file_name)[1]
|
|
185
|
+
|
|
186
|
+
if ext == '.gz':
|
|
187
|
+
self.ctype = 'gzip'
|
|
188
|
+
elif ext == '.bz2':
|
|
189
|
+
self.ctype = 'bzip2'
|
|
190
|
+
elif ext in ('.xz', '.lzma'):
|
|
191
|
+
self.ctype = 'lzma'
|
|
192
|
+
elif ext == '.zst':
|
|
193
|
+
self.ctype = 'zstd'
|
|
194
|
+
else:
|
|
195
|
+
self.ctype = None
|
|
196
|
+
|
|
197
|
+
def __enter__(self):
|
|
198
|
+
"""
|
|
199
|
+
'with' constructor.
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
return self
|
|
203
|
+
|
|
204
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
205
|
+
"""
|
|
206
|
+
'with' destructor.
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
self.close()
|
pysat/_utils.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
#-*- coding:utf-8 -*-
|
|
3
|
+
##
|
|
4
|
+
## _utils.py
|
|
5
|
+
##
|
|
6
|
+
## Created on: Nov 27, 2019
|
|
7
|
+
## Author: Alexey S. Ignatiev
|
|
8
|
+
## E-mail: aignatiev@ciencias.ulisboa.pt
|
|
9
|
+
##
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
===============
|
|
13
|
+
List of classes
|
|
14
|
+
===============
|
|
15
|
+
|
|
16
|
+
.. autosummary::
|
|
17
|
+
:nosignatures:
|
|
18
|
+
|
|
19
|
+
MainThread
|
|
20
|
+
|
|
21
|
+
==================
|
|
22
|
+
Module description
|
|
23
|
+
==================
|
|
24
|
+
|
|
25
|
+
This simple module is supposed to implement auxiliary classes and routines
|
|
26
|
+
that are used by the other (main) modules of PySAT.
|
|
27
|
+
|
|
28
|
+
==============
|
|
29
|
+
Module details
|
|
30
|
+
==============
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
#
|
|
34
|
+
#==============================================================================
|
|
35
|
+
import threading
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
#
|
|
39
|
+
#==============================================================================
|
|
40
|
+
class MainThread(object):
|
|
41
|
+
"""
|
|
42
|
+
A dummy class for checking whether the current thread is the main one.
|
|
43
|
+
This is currently necessary for proper signal handling when making
|
|
44
|
+
oracle calls and creating cardinality encodings.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
@staticmethod
|
|
48
|
+
def check():
|
|
49
|
+
"""
|
|
50
|
+
The actual checker.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
try: # for Python > 3.4
|
|
54
|
+
res = threading.current_thread() is threading.main_thread()
|
|
55
|
+
except AttributeError:
|
|
56
|
+
res = isinstance(threading.current_thread(), threading._MainThread)
|
|
57
|
+
|
|
58
|
+
return res
|
pysat/allies/__init__.py
ADDED
|
File without changes
|
pysat/allies/approxmc.py
ADDED
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
#-*- coding:utf-8 -*-
|
|
3
|
+
##
|
|
4
|
+
## approxmc.py
|
|
5
|
+
##
|
|
6
|
+
## Created on: Apr 14, 2023
|
|
7
|
+
## Author: Mate Soos
|
|
8
|
+
## E-mail: soos.mate@gmail.com
|
|
9
|
+
##
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
===============
|
|
13
|
+
List of classes
|
|
14
|
+
===============
|
|
15
|
+
|
|
16
|
+
.. autosummary::
|
|
17
|
+
:nosignatures:
|
|
18
|
+
|
|
19
|
+
Counter
|
|
20
|
+
|
|
21
|
+
==================
|
|
22
|
+
Module description
|
|
23
|
+
==================
|
|
24
|
+
|
|
25
|
+
This module provides interface to `ApproxMCv4
|
|
26
|
+
<https://github.com/meelgroup/approxmc/>`_, a state-of-the-art
|
|
27
|
+
*approximate* model counter utilising an improved version of CryptoMiniSat
|
|
28
|
+
to give approximate model counts to problems of size and complexity that
|
|
29
|
+
are out of reach for earlier approximate model counters. The original work
|
|
30
|
+
on ApproxMCv4 has been published in [1]_ and [2]_.
|
|
31
|
+
|
|
32
|
+
.. [1] Mate Soos, Kuldeep S. Meel. *BIRD: Engineering an Efficient CNF-XOR
|
|
33
|
+
SAT Solver and Its Applications to Approximate Model Counting*. AAAI
|
|
34
|
+
2019. pp. 1592-1599
|
|
35
|
+
|
|
36
|
+
.. [2] Mate Soos, Stephan Gocht, Kuldeep S. Meel. *Tinted, Detached, and
|
|
37
|
+
Lazy CNF-XOR Solving and Its Applications to Counting and Sampling*.
|
|
38
|
+
CAV 2020. pp. 463-484
|
|
39
|
+
|
|
40
|
+
Note that to be functional, the module requires package ``pyapproxmc`` to
|
|
41
|
+
be installed:
|
|
42
|
+
|
|
43
|
+
::
|
|
44
|
+
|
|
45
|
+
$ pip install pyapproxmc
|
|
46
|
+
|
|
47
|
+
The interface gives access to :class:`Counter`, which expects a formula in
|
|
48
|
+
:class:`.CNF` as input. Given a few additional (optional) arguments,
|
|
49
|
+
including a random seed, *tolerance factor* :math:`\\varepsilon`, and
|
|
50
|
+
*confidence* :math:`\\delta`, the class can be used to get an approximate
|
|
51
|
+
number of models of the formula, subject to the given tolerance factor and
|
|
52
|
+
confidence parameter.
|
|
53
|
+
|
|
54
|
+
Namely, given a CNF formula :math:`\\mathcal{F}` with
|
|
55
|
+
:math:`\\#\\mathcal{F}` as the exact number of models, and parameters
|
|
56
|
+
:math:`\\varepsilon\\in(0,1]` and :math:`\\delta\\in[0,1)`, the counter
|
|
57
|
+
computes and reports a value :math:`C`, which is an approximate number of
|
|
58
|
+
models of :math:`\\mathcal{F}`, such that
|
|
59
|
+
:math:`\\textrm{Pr}\\left[\\frac{1}{1+\\varepsilon}\\#\\mathcal{F}\\leq
|
|
60
|
+
C\\leq (1+\\varepsilon)\\#\\mathcal{F}\\right]\\geq 1-\\delta`.
|
|
61
|
+
|
|
62
|
+
The implementation can be used as an executable (the list of available
|
|
63
|
+
command-line options can be shown using ``approxmc.py -h``) in the
|
|
64
|
+
following way:
|
|
65
|
+
|
|
66
|
+
::
|
|
67
|
+
|
|
68
|
+
$ xzcat formula.cnf.xz
|
|
69
|
+
p cnf 20 2
|
|
70
|
+
1 2 3 0
|
|
71
|
+
3 20 0
|
|
72
|
+
|
|
73
|
+
$ approxmc.py -p 1,2,3-9 formula.cnf.xz
|
|
74
|
+
s mc 448
|
|
75
|
+
|
|
76
|
+
Alternatively, the algorithm can be accessed and invoked through the
|
|
77
|
+
standard ``import`` interface of Python, e.g.
|
|
78
|
+
|
|
79
|
+
.. code-block:: python
|
|
80
|
+
|
|
81
|
+
>>> from pysat.allies.approxmc import Counter
|
|
82
|
+
>>> from pysat.formula import CNF
|
|
83
|
+
>>>
|
|
84
|
+
>>> cnf = CNF(from_file='formula.cnf.xz')
|
|
85
|
+
>>>
|
|
86
|
+
>>> with Counter(cnf) as counter:
|
|
87
|
+
... print(counter.counter(projection=range(1, 10))
|
|
88
|
+
448
|
|
89
|
+
|
|
90
|
+
As can be seen in the above example, besides model counting across all the
|
|
91
|
+
variables in a given input formula, the counter supports *projected* model
|
|
92
|
+
counting, i.e. when one needs to approximate the number of models with
|
|
93
|
+
respect to a given list of variables rather than with respect to all
|
|
94
|
+
variables appearing in the formula. This feature comes in handy when the
|
|
95
|
+
formula is obtained, for example, through Tseitin transformation [3]_ with
|
|
96
|
+
a number of auxiliary variables introduced.
|
|
97
|
+
|
|
98
|
+
.. [3] G. S. Tseitin. *On the complexity of derivations in the
|
|
99
|
+
propositional calculus*. Studies in Mathematics and Mathematical
|
|
100
|
+
Logic, Part II. pp. 115–125, 1968
|
|
101
|
+
|
|
102
|
+
==============
|
|
103
|
+
Module details
|
|
104
|
+
==============
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
#
|
|
108
|
+
#==============================================================================
|
|
109
|
+
from __future__ import print_function
|
|
110
|
+
import getopt
|
|
111
|
+
import os
|
|
112
|
+
from pysat.formula import CNF
|
|
113
|
+
import re
|
|
114
|
+
import sys
|
|
115
|
+
|
|
116
|
+
# we need pyapproxmc to be installed:
|
|
117
|
+
pyapproxmc_present = True
|
|
118
|
+
try:
|
|
119
|
+
import pyapproxmc
|
|
120
|
+
except ImportError:
|
|
121
|
+
pyapproxmc_present = False
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
#
|
|
125
|
+
#==============================================================================
|
|
126
|
+
class Counter(object):
|
|
127
|
+
"""
|
|
128
|
+
A wrapper for `ApproxMC <https://github.com/meelgroup/approxmc/>`_, a
|
|
129
|
+
state-of-the-art *approximate* model counter. Given a formula in
|
|
130
|
+
:class:`.CNF`, this class can be used to get an approximate number of
|
|
131
|
+
models of the formula, subject to *tolerance factor* ``epsilon`` and
|
|
132
|
+
*confidence parameter* ``delta``.
|
|
133
|
+
|
|
134
|
+
Namely, given a CNF formula :math:`\\mathcal{F}` and parameters
|
|
135
|
+
:math:`\\varepsilon\\in(0,1]` and :math:`\\delta\\in[0,1)`, the
|
|
136
|
+
counter computes and reports a value :math:`C` such that
|
|
137
|
+
:math:`\\textrm{Pr}\\left[\\frac{1}{1+\\varepsilon}\\#\\mathcal{F}\\leq
|
|
138
|
+
C\\leq (1+\\varepsilon)\\#\\mathcal{F}\\right]\\geq 1-\\delta`. Here,
|
|
139
|
+
:math:`\\#\\mathcal{F}` denotes the exact model count for formula
|
|
140
|
+
:math:`\\mathcal{F}`.
|
|
141
|
+
|
|
142
|
+
The ``formula`` argument can be left unspecified at this stage. In
|
|
143
|
+
this case, a user is expected to add all the relevant clauses using
|
|
144
|
+
:meth:`add_clause`.
|
|
145
|
+
|
|
146
|
+
An additional parameter a user may want to specify is integer ``seed``
|
|
147
|
+
used by ApproxMC. The value of ``seed`` is set to ``1`` by default.
|
|
148
|
+
|
|
149
|
+
:param formula: CNF formula
|
|
150
|
+
:param seed: integer seed value
|
|
151
|
+
:param epsilon: tolerance factor
|
|
152
|
+
:param delta: confidence parameter
|
|
153
|
+
:param verbose: verbosity level
|
|
154
|
+
|
|
155
|
+
:type formula: :class:`.CNF`
|
|
156
|
+
:type seed: int
|
|
157
|
+
:type epsilon: float
|
|
158
|
+
:type delta: float
|
|
159
|
+
:type verbose: int
|
|
160
|
+
|
|
161
|
+
.. code-block:: python
|
|
162
|
+
|
|
163
|
+
>>> from pysat.allies.approxmc import Counter
|
|
164
|
+
>>> from pysat.formula import CNF
|
|
165
|
+
>>>
|
|
166
|
+
>>> cnf = CNF(from_file='some-formula.cnf')
|
|
167
|
+
>>> with Counter(formula=cnf, epsilon=0.1, delta=0.9) as counter:
|
|
168
|
+
... num = counter.count() # an approximate number of models
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
def __init__(self, formula=None, seed=1, epsilon=0.8, delta=0.2, verbose=0):
|
|
172
|
+
"""
|
|
173
|
+
Constructor.
|
|
174
|
+
"""
|
|
175
|
+
|
|
176
|
+
assert pyapproxmc_present, 'Package \'pyapproxmc\' is unavailable. Check your installation.'
|
|
177
|
+
|
|
178
|
+
# there are no initial counts
|
|
179
|
+
self.cellc, self.hashc = None, None
|
|
180
|
+
|
|
181
|
+
# creating the Counter object
|
|
182
|
+
self.counter = pyapproxmc.Counter(verbosity=verbose, seed=seed,
|
|
183
|
+
epsilon=epsilon, delta=delta)
|
|
184
|
+
|
|
185
|
+
# adding clauses to the counter
|
|
186
|
+
if formula:
|
|
187
|
+
for clause in formula:
|
|
188
|
+
self.add_clause(clause)
|
|
189
|
+
|
|
190
|
+
def __del__(self):
|
|
191
|
+
"""
|
|
192
|
+
Destructor.
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
self.delete()
|
|
196
|
+
|
|
197
|
+
def __enter__(self):
|
|
198
|
+
"""
|
|
199
|
+
'with' constructor.
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
return self
|
|
203
|
+
|
|
204
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
205
|
+
"""
|
|
206
|
+
'with' destructor.
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
self.delete()
|
|
210
|
+
|
|
211
|
+
def add_clause(self, clause):
|
|
212
|
+
"""
|
|
213
|
+
The method for adding a clause to the problem formula. Although
|
|
214
|
+
the input formula can be specified as an argument of the
|
|
215
|
+
constructor of :class:`Counter`, adding clauses may also be
|
|
216
|
+
helpful afterwards, *on the fly*.
|
|
217
|
+
|
|
218
|
+
The clause to add can be any iterable over integer literals.
|
|
219
|
+
|
|
220
|
+
:param clause: a clause to add
|
|
221
|
+
:type clause: iterable(int)
|
|
222
|
+
|
|
223
|
+
.. code-block:: python
|
|
224
|
+
|
|
225
|
+
>>> from pysat.allies.approxmc import Counter
|
|
226
|
+
>>>
|
|
227
|
+
>>> with Counter() as counter:
|
|
228
|
+
... counter.add_clause(range(1, 4))
|
|
229
|
+
... counter.add_clause([3, 20])
|
|
230
|
+
...
|
|
231
|
+
... print(counter.count())
|
|
232
|
+
720896
|
|
233
|
+
"""
|
|
234
|
+
|
|
235
|
+
self.counter.add_clause(clause)
|
|
236
|
+
|
|
237
|
+
def count(self, projection=None):
|
|
238
|
+
"""
|
|
239
|
+
|
|
240
|
+
Given the formula provided by the user either in the constructor
|
|
241
|
+
of :class:`Counter` or through a series of calls to
|
|
242
|
+
:meth:`add_clause`, this method runs the ApproxMC counter with the
|
|
243
|
+
specified values of tolerance :math:`\\varepsilon` and confidence
|
|
244
|
+
:math:`\\delta` parameters, as well as the random ``seed`` value,
|
|
245
|
+
and returns the number of models estimated.
|
|
246
|
+
|
|
247
|
+
A user may specify an argument ``projection``, which is a list of
|
|
248
|
+
integers specifying the variables with respect to which projected
|
|
249
|
+
model counting should be performed. If ``projection`` is left as
|
|
250
|
+
``None``, approximate model counting is performed wrt. all the
|
|
251
|
+
variables of the input formula.
|
|
252
|
+
|
|
253
|
+
:param projection: variables to project on
|
|
254
|
+
:type projection: list(int)
|
|
255
|
+
|
|
256
|
+
.. code-block:: python
|
|
257
|
+
|
|
258
|
+
>>> from pysat.allies.approxmc import Counter
|
|
259
|
+
>>> from pysat.card import CardEnc, EncType
|
|
260
|
+
>>>
|
|
261
|
+
>>> # cardinality constraint with auxiliary variables
|
|
262
|
+
>>> # there are exactly 70 models for the constraint
|
|
263
|
+
>>> # over the 8 original variables
|
|
264
|
+
>>> cnf = CardEnc.equals(lits=range(1, 9), bound=4, encoding=EncType.cardnetwrk)
|
|
265
|
+
>>>
|
|
266
|
+
>>> with Counter(formula=cnf, epsilon=0.05, delta=0.95) as counter:
|
|
267
|
+
... print(counter.count())
|
|
268
|
+
123840
|
|
269
|
+
>>>
|
|
270
|
+
>>> with Counter(formula=cnf, epsilon=0.05, delta=0.95) as counter:
|
|
271
|
+
... print(counter.count(projection=range(1, 8)))
|
|
272
|
+
70
|
|
273
|
+
"""
|
|
274
|
+
|
|
275
|
+
if projection is not None:
|
|
276
|
+
self.cellc, self.hashc = self.counter.count(projection=projection)
|
|
277
|
+
else:
|
|
278
|
+
self.cellc, self.hashc = self.counter.count()
|
|
279
|
+
|
|
280
|
+
return self.cellc * (2 ** self.hashc)
|
|
281
|
+
|
|
282
|
+
def delete(self):
|
|
283
|
+
"""
|
|
284
|
+
Explicit destructor of the internal Counter oracle.
|
|
285
|
+
Delete the actual counter object and sets it to ``None``.
|
|
286
|
+
"""
|
|
287
|
+
|
|
288
|
+
if self.counter:
|
|
289
|
+
del self.counter
|
|
290
|
+
self.counter = None
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
#
|
|
294
|
+
#==============================================================================
|
|
295
|
+
def parse_options():
|
|
296
|
+
"""
|
|
297
|
+
Parses command-line option
|
|
298
|
+
"""
|
|
299
|
+
|
|
300
|
+
try:
|
|
301
|
+
opts, args = getopt.getopt(sys.argv[1:], 'd:e:hp:s:v:',
|
|
302
|
+
['delta=', 'epsilon=', 'help', 'projection=', 'seed=', 'verbose='])
|
|
303
|
+
except getopt.GetoptError as err:
|
|
304
|
+
sys.stderr.write(str(err).capitalize())
|
|
305
|
+
usage()
|
|
306
|
+
sys.exit(1)
|
|
307
|
+
|
|
308
|
+
delta = 0.2
|
|
309
|
+
epsilon = 0.8
|
|
310
|
+
projection = None
|
|
311
|
+
seed = 1
|
|
312
|
+
verbose = 0
|
|
313
|
+
|
|
314
|
+
for opt, arg in opts:
|
|
315
|
+
if opt in ('-d', '--delta'):
|
|
316
|
+
delta = float(arg)
|
|
317
|
+
elif opt in ('-e', '--epsilon'):
|
|
318
|
+
epsilon = float(arg)
|
|
319
|
+
elif opt in ('-h', '--help'):
|
|
320
|
+
usage()
|
|
321
|
+
sys.exit(0)
|
|
322
|
+
elif opt in ('-p', '--projection'):
|
|
323
|
+
# parsing the list of variables
|
|
324
|
+
projection, values = [], str(arg).split(',')
|
|
325
|
+
|
|
326
|
+
# checking if there are intervals
|
|
327
|
+
for value in values:
|
|
328
|
+
if value.isnumeric():
|
|
329
|
+
projection.append(int(value))
|
|
330
|
+
elif '-' in value:
|
|
331
|
+
lb, ub = value.split('-')
|
|
332
|
+
assert int(lb) < int(ub)
|
|
333
|
+
projection.extend(list(range(int(lb), int(ub) + 1)))
|
|
334
|
+
|
|
335
|
+
# removing duplicates, if any
|
|
336
|
+
projection = sorted(set(projection))
|
|
337
|
+
elif opt in ('-s', '--seed'):
|
|
338
|
+
seed = int(arg)
|
|
339
|
+
elif opt in ('-v', '--verbose'):
|
|
340
|
+
verbose = int(arg)
|
|
341
|
+
else:
|
|
342
|
+
assert False, 'Unhandled option: {0} {1}'.format(opt, arg)
|
|
343
|
+
|
|
344
|
+
return delta, epsilon, projection, seed, verbose, args
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
#
|
|
348
|
+
#==============================================================================
|
|
349
|
+
def usage():
|
|
350
|
+
"""
|
|
351
|
+
Prints usage message.
|
|
352
|
+
"""
|
|
353
|
+
|
|
354
|
+
print('Usage:', os.path.basename(sys.argv[0]), '[options] dimacs-file')
|
|
355
|
+
print('Options:')
|
|
356
|
+
print(' -d, --delta=<float> Confidence parameter as per PAC guarantees')
|
|
357
|
+
print(' Available values: [0, 1) (default = 0.2)')
|
|
358
|
+
print(' -e, --epsilon=<float> Tolerance factor as per PAC guarantees')
|
|
359
|
+
print(' Available values: (0 .. 1], all (default = 0.8)')
|
|
360
|
+
print(' -p, --projection=<list> Do model counting projected on this set of variables')
|
|
361
|
+
print(' Available values: comma-separated-list, none (default = none)')
|
|
362
|
+
print(' -s, --seed=<int> Random seed')
|
|
363
|
+
print(' Available values: [0 .. INT_MAX] (default = 1)')
|
|
364
|
+
print(' -v, --verbose=<int> Verbosity level')
|
|
365
|
+
print(' Available values: [0 .. 15] (default = 0)')
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
#
|
|
369
|
+
#==============================================================================
|
|
370
|
+
if __name__ == '__main__':
|
|
371
|
+
delta, epsilon, projection, seed, verbose, files = parse_options()
|
|
372
|
+
|
|
373
|
+
# parsing the input formula
|
|
374
|
+
if files and re.search(r'\.cnf(\.(gz|bz2|lzma|xz))?$', files[0]):
|
|
375
|
+
formula = CNF(from_file=files[0])
|
|
376
|
+
|
|
377
|
+
# creating the counter object
|
|
378
|
+
with Counter(formula, seed=seed, epsilon=epsilon, delta=delta,
|
|
379
|
+
verbose=verbose) as counter:
|
|
380
|
+
|
|
381
|
+
# approximate model counting
|
|
382
|
+
count = counter.count(projection=projection)
|
|
383
|
+
|
|
384
|
+
# printing the result
|
|
385
|
+
print('s mc', count)
|