PySDD 1.0.6__cp314-cp314-win32.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.
- pysdd/__init__.py +1 -0
- pysdd/__main__.py +19 -0
- pysdd/cli.py +261 -0
- pysdd/iterator.py +223 -0
- pysdd/lib/libsdd-2.0/src/basic/computed.c +91 -0
- pysdd/lib/libsdd-2.0/src/basic/count_and_size.c +173 -0
- pysdd/lib/libsdd-2.0/src/basic/gc.c +104 -0
- pysdd/lib/libsdd-2.0/src/basic/hash.c +221 -0
- pysdd/lib/libsdd-2.0/src/basic/memory.c +180 -0
- pysdd/lib/libsdd-2.0/src/basic/multiply.c +209 -0
- pysdd/lib/libsdd-2.0/src/basic/nodes.c +174 -0
- pysdd/lib/libsdd-2.0/src/basic/partitions.c +351 -0
- pysdd/lib/libsdd-2.0/src/basic/references.c +79 -0
- pysdd/lib/libsdd-2.0/src/basic/replace.c +91 -0
- pysdd/lib/libsdd-2.0/src/basic/shadows.c +384 -0
- pysdd/lib/libsdd-2.0/src/basic/sort.c +148 -0
- pysdd/lib/libsdd-2.0/src/manager/copy.c +84 -0
- pysdd/lib/libsdd-2.0/src/manager/interface.c +411 -0
- pysdd/lib/libsdd-2.0/src/manager/manager.c +308 -0
- pysdd/lib/libsdd-2.0/src/manager/stats.c +121 -0
- pysdd/lib/libsdd-2.0/src/manager/variables.c +267 -0
- pysdd/lib/libsdd-2.0/src/sdds/apply.c +396 -0
- pysdd/lib/libsdd-2.0/src/sdds/bits.c +156 -0
- pysdd/lib/libsdd-2.0/src/sdds/cardinality.c +313 -0
- pysdd/lib/libsdd-2.0/src/sdds/condition.c +107 -0
- pysdd/lib/libsdd-2.0/src/sdds/copy.c +117 -0
- pysdd/lib/libsdd-2.0/src/sdds/count.c +78 -0
- pysdd/lib/libsdd-2.0/src/sdds/essential_vars.c +128 -0
- pysdd/lib/libsdd-2.0/src/sdds/exists.c +29 -0
- pysdd/lib/libsdd-2.0/src/sdds/exists_multiple.c +311 -0
- pysdd/lib/libsdd-2.0/src/sdds/exists_multiple_static.c +165 -0
- pysdd/lib/libsdd-2.0/src/sdds/forall.c +28 -0
- pysdd/lib/libsdd-2.0/src/sdds/io.c +448 -0
- pysdd/lib/libsdd-2.0/src/sdds/model_count.c +146 -0
- pysdd/lib/libsdd-2.0/src/sdds/rename_vars.c +113 -0
- pysdd/lib/libsdd-2.0/src/sdds/size.c +65 -0
- pysdd/lib/libsdd-2.0/src/sdds/wmc.c +412 -0
- pysdd/lib/libsdd-2.0/src/util.c +255 -0
- pysdd/lib/libsdd-2.0/src/verify.c +236 -0
- pysdd/lib/libsdd-2.0/src/version.c +14 -0
- pysdd/lib/libsdd-2.0/src/vtree_fragments/construction.c +329 -0
- pysdd/lib/libsdd-2.0/src/vtree_fragments/moves.c +100 -0
- pysdd/lib/libsdd-2.0/src/vtree_fragments/operations.c +302 -0
- pysdd/lib/libsdd-2.0/src/vtree_operations/cartesian_product.c +166 -0
- pysdd/lib/libsdd-2.0/src/vtree_operations/dissect.c +78 -0
- pysdd/lib/libsdd-2.0/src/vtree_operations/limits.c +237 -0
- pysdd/lib/libsdd-2.0/src/vtree_operations/op_left_rotate.c +143 -0
- pysdd/lib/libsdd-2.0/src/vtree_operations/op_right_rotate.c +150 -0
- pysdd/lib/libsdd-2.0/src/vtree_operations/op_swap.c +112 -0
- pysdd/lib/libsdd-2.0/src/vtree_operations/rollback.c +68 -0
- pysdd/lib/libsdd-2.0/src/vtree_operations/split.c +180 -0
- pysdd/lib/libsdd-2.0/src/vtree_search/auto.c +186 -0
- pysdd/lib/libsdd-2.0/src/vtree_search/search.c +332 -0
- pysdd/lib/libsdd-2.0/src/vtree_search/state.c +127 -0
- pysdd/lib/libsdd-2.0/src/vtrees/compare.c +138 -0
- pysdd/lib/libsdd-2.0/src/vtrees/edit.c +199 -0
- pysdd/lib/libsdd-2.0/src/vtrees/io.c +207 -0
- pysdd/lib/libsdd-2.0/src/vtrees/maps.c +42 -0
- pysdd/lib/libsdd-2.0/src/vtrees/moves.c +156 -0
- pysdd/lib/libsdd-2.0/src/vtrees/static.c +270 -0
- pysdd/lib/libsdd-2.0/src/vtrees/vtree.c +197 -0
- pysdd/lib/sdd-2.0/src/fnf/compiler.c +120 -0
- pysdd/lib/sdd-2.0/src/fnf/fnf.c +57 -0
- pysdd/lib/sdd-2.0/src/fnf/io.c +201 -0
- pysdd/lib/sdd-2.0/src/fnf/utils.c +72 -0
- pysdd/sdd.cp314-win32.pyd +0 -0
- pysdd/sdd.pyx +1808 -0
- pysdd/util.py +450 -0
- pysdd/wmcstochastic.py +188 -0
- pysdd-1.0.6.dist-info/METADATA +216 -0
- pysdd-1.0.6.dist-info/RECORD +75 -0
- pysdd-1.0.6.dist-info/WHEEL +5 -0
- pysdd-1.0.6.dist-info/entry_points.txt +2 -0
- pysdd-1.0.6.dist-info/licenses/LICENSE +13 -0
- pysdd-1.0.6.dist-info/top_level.txt +1 -0
pysdd/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.0.0"
|
pysdd/__main__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
pysdd.__main__
|
|
4
|
+
~~~~~~~~~~~~~~
|
|
5
|
+
|
|
6
|
+
Command Line Interface.
|
|
7
|
+
|
|
8
|
+
Will be run when `python -m pysdd` is used.
|
|
9
|
+
|
|
10
|
+
:author: Wannes Meert, Arthur Choi
|
|
11
|
+
:copyright: Copyright 2017-2019 KU Leuven and Regents of the University of California.
|
|
12
|
+
:license: Apache License, Version 2.0, see LICENSE for details.
|
|
13
|
+
|
|
14
|
+
"""
|
|
15
|
+
from .cli import main
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
if __name__ == "__main__":
|
|
19
|
+
main()
|
pysdd/cli.py
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# encoding: utf-8
|
|
3
|
+
"""
|
|
4
|
+
pysdd-cli
|
|
5
|
+
~~~~~~~~~
|
|
6
|
+
|
|
7
|
+
PySDD command line interface.
|
|
8
|
+
|
|
9
|
+
:author: Wannes Meert, Arthur Choi
|
|
10
|
+
:copyright: Copyright 2019 KU Leuven and Regents of the University of California.
|
|
11
|
+
:license: Apache License, Version 2.0, see LICENSE for details.
|
|
12
|
+
"""
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
import sys
|
|
15
|
+
import time
|
|
16
|
+
import argparse
|
|
17
|
+
import logging
|
|
18
|
+
from .sdd import SddManager, Vtree, Fnf, CompilerOptions
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def main(argv=None):
|
|
25
|
+
options, args = getopt(argv)
|
|
26
|
+
fnf = None
|
|
27
|
+
weights = None
|
|
28
|
+
|
|
29
|
+
if options.cnf_filename is not None:
|
|
30
|
+
print("reading cnf...")
|
|
31
|
+
fnf = Fnf.from_cnf_file(bytes(options.cnf_filename))
|
|
32
|
+
weights = read_weights(options.cnf_filename)
|
|
33
|
+
elif options.dnf_filename is not None:
|
|
34
|
+
print("reading dnf...")
|
|
35
|
+
fnf = Fnf.from_dnf_file(bytes(options.dnf_filename))
|
|
36
|
+
weights = read_weights(options.dnf_filename)
|
|
37
|
+
|
|
38
|
+
if options.vtree_filename is not None:
|
|
39
|
+
print("reading initial vtree...")
|
|
40
|
+
vtree = Vtree.from_file(bytes(options.vtree_filename))
|
|
41
|
+
else:
|
|
42
|
+
if fnf is None:
|
|
43
|
+
raise argparse.ArgumentTypeError("CNF or DNF file required")
|
|
44
|
+
print(f"creating initial vtree {options.initial_vtree_type.decode()}")
|
|
45
|
+
vtree = Vtree(var_count=fnf.var_count, vtree_type=options.initial_vtree_type)
|
|
46
|
+
|
|
47
|
+
print("creating manager...")
|
|
48
|
+
manager = SddManager.from_vtree(vtree)
|
|
49
|
+
manager.set_options(options)
|
|
50
|
+
|
|
51
|
+
if options.sdd_filename is None:
|
|
52
|
+
print("compiling...")
|
|
53
|
+
c1 = time.time()
|
|
54
|
+
node = manager.fnf_to_sdd(fnf)
|
|
55
|
+
c2 = time.time()
|
|
56
|
+
secs = c2 - c1
|
|
57
|
+
print("")
|
|
58
|
+
print(f"compilation time : {secs:.3f} sec")
|
|
59
|
+
else:
|
|
60
|
+
print("reading sdd from file...")
|
|
61
|
+
c1 = time.time()
|
|
62
|
+
node = manager.read_sdd_file(options.sdd_filename)
|
|
63
|
+
c2 = time.time()
|
|
64
|
+
secs = c2 - c1
|
|
65
|
+
print(f"read time : {secs:.3f} sec")
|
|
66
|
+
weights = read_weights(options.sdd_filename)
|
|
67
|
+
|
|
68
|
+
wmc = create_wmc(node, weights, args)
|
|
69
|
+
|
|
70
|
+
print_node(node, wmc)
|
|
71
|
+
if options.verbose:
|
|
72
|
+
manager.print_stdout()
|
|
73
|
+
|
|
74
|
+
if options.minimize_cardinality:
|
|
75
|
+
print("\nminimizing cardinality...", end="")
|
|
76
|
+
c1 = time.time()
|
|
77
|
+
node = manager.global_minimize_cardinality(node)
|
|
78
|
+
c2 = time.time()
|
|
79
|
+
min_card = manager.minimum_cardinality(node)
|
|
80
|
+
print("")
|
|
81
|
+
wmc = create_wmc(node, weights, args)
|
|
82
|
+
print_node(node, wmc)
|
|
83
|
+
print(f" min cardinality : {min_card} {c2-c1:.3f} sec")
|
|
84
|
+
|
|
85
|
+
manager_vtree = manager.vtree()
|
|
86
|
+
|
|
87
|
+
if options.post_search:
|
|
88
|
+
node.ref()
|
|
89
|
+
print("dynamic vtree (post compilation)")
|
|
90
|
+
print(f" sdd initial size : {node.size()}")
|
|
91
|
+
c1 = time.time()
|
|
92
|
+
manager.minimize_limited()
|
|
93
|
+
c2 = time.time()
|
|
94
|
+
print(f" dynamic vtree time : {c2-c1:.3f} sec")
|
|
95
|
+
wmc = create_wmc(node, weights, args)
|
|
96
|
+
print_node(node, wmc)
|
|
97
|
+
node.deref()
|
|
98
|
+
if options.verbose:
|
|
99
|
+
manager.print_stdout()
|
|
100
|
+
|
|
101
|
+
if options.output_sdd_filename is not None:
|
|
102
|
+
print("saving compiled sdd ...", end="")
|
|
103
|
+
node.save(options.output_sdd_filename)
|
|
104
|
+
print("done")
|
|
105
|
+
|
|
106
|
+
if options.output_sdd_dot_filename is not None:
|
|
107
|
+
print("saving compiled sdd (dot) ...", end="")
|
|
108
|
+
node.save_as_dot(options.output_sdd_dot_filename)
|
|
109
|
+
print("done")
|
|
110
|
+
|
|
111
|
+
if options.output_vtree_filename is not None:
|
|
112
|
+
print("saving vtree ...", end="")
|
|
113
|
+
manager_vtree.save(options.output_vtree_filename)
|
|
114
|
+
print("done")
|
|
115
|
+
|
|
116
|
+
if options.output_vtree_dot_filename is not None:
|
|
117
|
+
print("saving vtree (dot) ...", end="")
|
|
118
|
+
manager_vtree.save_as_dot(options.output_vtree_dot_filename)
|
|
119
|
+
print("done")
|
|
120
|
+
|
|
121
|
+
print("done")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def create_wmc(node, weights, args):
|
|
125
|
+
if weights is None:
|
|
126
|
+
return None
|
|
127
|
+
wmc = node.wmc(log_mode=args.log_mode)
|
|
128
|
+
for i in range(len(weights)):
|
|
129
|
+
lit = (i // 2) + 1
|
|
130
|
+
if (i % 2) == 1:
|
|
131
|
+
lit = -lit
|
|
132
|
+
w = weights[i]
|
|
133
|
+
wmc.set_literal_weight(node.manager.literal(lit), w)
|
|
134
|
+
return wmc
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def read_weights(nnf_path):
|
|
138
|
+
"""
|
|
139
|
+
Format: c weights PW_1 NW_1 ... PW_n NW_n
|
|
140
|
+
:param nnf_path: Path to NNF file
|
|
141
|
+
:return: list of weights
|
|
142
|
+
"""
|
|
143
|
+
weight_str = None
|
|
144
|
+
with open(nnf_path, "r") as ifile:
|
|
145
|
+
for line in ifile.readlines():
|
|
146
|
+
if "c weights " in line:
|
|
147
|
+
weight_str = line[10:].strip()
|
|
148
|
+
break
|
|
149
|
+
if weight_str is None:
|
|
150
|
+
return None
|
|
151
|
+
weight_strs = weight_str.split(" ")
|
|
152
|
+
weights = [float(w) for w in weight_strs]
|
|
153
|
+
return weights
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def print_node(node, wmc=None):
|
|
157
|
+
print(f" sdd size : {node.size()}")
|
|
158
|
+
print(f" sdd node count : {node.count()}")
|
|
159
|
+
c1 = time.time()
|
|
160
|
+
mc = node.global_model_count()
|
|
161
|
+
c2 = time.time()
|
|
162
|
+
print(f" sdd model count : {mc} {c2-c1:.3f} sec")
|
|
163
|
+
if wmc is not None:
|
|
164
|
+
c1 = time.time()
|
|
165
|
+
mc = wmc.propagate()
|
|
166
|
+
c2 = time.time()
|
|
167
|
+
print(f" sdd weighted model count: {mc} {c2-c1:.3f} sec")
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class CustomHelpFormatter(argparse.HelpFormatter):
|
|
171
|
+
|
|
172
|
+
def _format_text(self, text):
|
|
173
|
+
text = super()._format_text(text)
|
|
174
|
+
if "Copyright" in text:
|
|
175
|
+
a, b = text.split("Copyright")
|
|
176
|
+
text = a + "\n\n" + super()._format_text("Copyright" + b)
|
|
177
|
+
return text
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def create_parser():
|
|
181
|
+
def bytes_path(p):
|
|
182
|
+
return bytes(Path(p))
|
|
183
|
+
|
|
184
|
+
def bytes_str(s):
|
|
185
|
+
return s.encode()
|
|
186
|
+
|
|
187
|
+
epilog = ("Weighted Model Counting is performed if the NNF file containts a line formatted as follows: "
|
|
188
|
+
"\"c weights PW_1 NW_1 ... PW_n NW_n\"."
|
|
189
|
+
"Copyright 2017-2019, Regents of the University of California and KU Leuven")
|
|
190
|
+
parser = argparse.ArgumentParser(description='Sentential Decision Diagram, Compiler',
|
|
191
|
+
epilog=epilog,
|
|
192
|
+
formatter_class=CustomHelpFormatter)
|
|
193
|
+
input_group = parser.add_mutually_exclusive_group()
|
|
194
|
+
input_group.add_argument('-c', metavar='FILE', type=bytes_path,
|
|
195
|
+
dest='cnf_filename', help='set input CNF file')
|
|
196
|
+
input_group.add_argument('-d', metavar='FILE', type=bytes_path,
|
|
197
|
+
dest='dnf_filename', help='set input DNF file')
|
|
198
|
+
input_group.add_argument('-s', metavar='FILE', type=bytes_path,
|
|
199
|
+
dest='sdd_filename', help='set input SDD file')
|
|
200
|
+
parser.add_argument('-v', metavar='FILE', type=bytes_path,
|
|
201
|
+
dest='vtree_filename', help='set input VTREE file')
|
|
202
|
+
|
|
203
|
+
parser.add_argument('-W', metavar='FILE', type=bytes_path,
|
|
204
|
+
dest='output_vtree_filename', help='set output VTREE file')
|
|
205
|
+
parser.add_argument('-V', metavar='FILE', type=bytes_path,
|
|
206
|
+
dest='output_vtree_dot_filename', help='set output VTREE (dot) file')
|
|
207
|
+
parser.add_argument('-R', metavar='FILE', type=bytes_path,
|
|
208
|
+
dest='output_sdd_filename', help='set output SDD file')
|
|
209
|
+
parser.add_argument('-S', metavar='FILE', type=bytes_path,
|
|
210
|
+
dest='output_sdd_dot_filename', help='set output SDD (dot) file')
|
|
211
|
+
|
|
212
|
+
parser.add_argument('-m', action='store_true',
|
|
213
|
+
dest='minimize_cardinality', help='minimize the cardinality of compiled sdd')
|
|
214
|
+
|
|
215
|
+
parser.add_argument('-t', metavar='TYPE', type=bytes_str, default='balanced',
|
|
216
|
+
choices=[b'left', b'right', b'vertical', b'balanced', b'random'],
|
|
217
|
+
dest='initial_vtree_type',
|
|
218
|
+
help='set initial vtree type (left/right/vertical/balanced/random)')
|
|
219
|
+
|
|
220
|
+
parser.add_argument('-r', metavar='K', type=int, default=-1,
|
|
221
|
+
dest='vtree_search_mode',
|
|
222
|
+
help='if K>0: invoke vtree search every K clauses. If K=0: disable vtree search. ' +
|
|
223
|
+
'By default (no -r option), dynamic vtree search is enabled')
|
|
224
|
+
|
|
225
|
+
parser.add_argument('-q', action='store_true',
|
|
226
|
+
dest='post_search', help='perform post-compilation vtree search')
|
|
227
|
+
|
|
228
|
+
parser.add_argument('-p', action='store_true',
|
|
229
|
+
dest='verbose', help='verbose output')
|
|
230
|
+
|
|
231
|
+
parser.add_argument('--log_mode', action='store_true',
|
|
232
|
+
dest='log_mode', help='weights in log')
|
|
233
|
+
|
|
234
|
+
return parser
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def getopt(argv=None):
|
|
238
|
+
parser = create_parser()
|
|
239
|
+
args = parser.parse_args(argv)
|
|
240
|
+
|
|
241
|
+
if args.verbose:
|
|
242
|
+
logger.setLevel(logging.INFO)
|
|
243
|
+
else:
|
|
244
|
+
logger.setLevel(logging.WARNING)
|
|
245
|
+
logger.addHandler(logging.StreamHandler(sys.stdout))
|
|
246
|
+
|
|
247
|
+
options = CompilerOptions(cnf_filename=args.cnf_filename,
|
|
248
|
+
dnf_filename=args.dnf_filename,
|
|
249
|
+
vtree_filename=args.vtree_filename,
|
|
250
|
+
sdd_filename=args.sdd_filename,
|
|
251
|
+
output_vtree_filename=args.output_vtree_filename,
|
|
252
|
+
output_vtree_dot_filename=args.output_vtree_dot_filename,
|
|
253
|
+
output_sdd_filename=args.output_sdd_filename,
|
|
254
|
+
output_sdd_dot_filename=args.output_sdd_dot_filename,
|
|
255
|
+
initial_vtree_type=args.initial_vtree_type,
|
|
256
|
+
minimize_cardinality=args.minimize_cardinality,
|
|
257
|
+
vtree_search_mode=args.vtree_search_mode,
|
|
258
|
+
post_search=args.post_search,
|
|
259
|
+
verbose=args.verbose)
|
|
260
|
+
# print(str(options))
|
|
261
|
+
return options, args
|
pysdd/iterator.py
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
pysdd.iterator
|
|
4
|
+
~~~~~~~~~~~~~~
|
|
5
|
+
|
|
6
|
+
:author: Wannes Meert, Arthur Choi
|
|
7
|
+
:copyright: Copyright 2017-2019 KU Leuven and Regents of the University of California.
|
|
8
|
+
:license: Apache License, Version 2.0, see LICENSE for details.
|
|
9
|
+
"""
|
|
10
|
+
from collections import deque
|
|
11
|
+
from .sdd import SddManager, Vtree, SddNode
|
|
12
|
+
from .util import BitArray
|
|
13
|
+
|
|
14
|
+
MYPY = False
|
|
15
|
+
if MYPY:
|
|
16
|
+
from typing import Dict, Set, Optional, List, Tuple, Callable, Union
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SddIterator:
|
|
20
|
+
def __init__(self, sdd, smooth=True, smooth_to_root=False):
|
|
21
|
+
"""Simple iterator to iterate over the SDD graph.
|
|
22
|
+
|
|
23
|
+
Supports smoothing: An arithmetic circuit AC(X) is smooth iff
|
|
24
|
+
(1) it contains at least one indicator for each variable in X, and
|
|
25
|
+
(2) for every child c of '+'-node n, we have vars(n) = vars(c).
|
|
26
|
+
|
|
27
|
+
Note, if you know that you will be performing WMC, smoothing can be implemented more efficient
|
|
28
|
+
by keeping track of the expected WMC of used and unused variables in the vtree instead of keeping
|
|
29
|
+
track of the sets of variables as is done in this iterator.
|
|
30
|
+
|
|
31
|
+
:param sdd: WmcManager
|
|
32
|
+
:param smooth: Perform smoothing while iterating over the graph
|
|
33
|
+
:param smooth_to_root: Perform smoothing wrt root instead of a given node
|
|
34
|
+
"""
|
|
35
|
+
self.sdd = sdd # type: SddManager
|
|
36
|
+
self.vtree = sdd.vtree() # type: Vtree
|
|
37
|
+
self._wmc_cache = dict() # type: Dict[SddNode, Union[float, int]]
|
|
38
|
+
# Map Vtree node positions to expected variables
|
|
39
|
+
self._expected_vars = None # type: Optional[Dict[int, Set[int]]]
|
|
40
|
+
# Map Sdd nodes to missing variables
|
|
41
|
+
self._missing_vars = dict() # type: Dict[SddNode, Set[int]]
|
|
42
|
+
self.smooth = smooth # type: bool
|
|
43
|
+
self.smooth_to_root = smooth_to_root # type: bool
|
|
44
|
+
|
|
45
|
+
if self.smooth:
|
|
46
|
+
self._cache_expected_vars()
|
|
47
|
+
|
|
48
|
+
def _cache_expected_vars(self):
|
|
49
|
+
self._expected_vars = dict()
|
|
50
|
+
nb_vtree_nodes = self.sdd.var_count() * 2 - 1
|
|
51
|
+
# visited = [False] * nb_vtree_nodes
|
|
52
|
+
visited = BitArray(nb_vtree_nodes)
|
|
53
|
+
queue = deque([self.vtree])
|
|
54
|
+
while len(queue) > 0:
|
|
55
|
+
node = queue.pop() # type: Vtree
|
|
56
|
+
pos = node.position()
|
|
57
|
+
if node.is_leaf():
|
|
58
|
+
self._expected_vars[pos] = {node.var()}
|
|
59
|
+
else:
|
|
60
|
+
if visited[pos]:
|
|
61
|
+
self._expected_vars[pos] = self._expected_vars[node.left().position()] | \
|
|
62
|
+
self._expected_vars[node.right().position()]
|
|
63
|
+
else:
|
|
64
|
+
visited[pos] = True
|
|
65
|
+
queue.append(node)
|
|
66
|
+
queue.append(node.right())
|
|
67
|
+
queue.append(node.left())
|
|
68
|
+
|
|
69
|
+
def depth_first_from_root(self, func):
|
|
70
|
+
# type: (SddIterator, Callable) -> List[Union[int, float]]
|
|
71
|
+
"""Depth first iterator starting from the root nodes.
|
|
72
|
+
|
|
73
|
+
See `depth_first` for more details.
|
|
74
|
+
"""
|
|
75
|
+
results = []
|
|
76
|
+
for node in self.vtree.get_sdd_rootnodes(self.sdd):
|
|
77
|
+
results.append(self.depth_first(node, func))
|
|
78
|
+
return results
|
|
79
|
+
|
|
80
|
+
def depth_first(self, node, func):
|
|
81
|
+
# type: (SddIterator, SddNode, Callable) -> Union[int, float]
|
|
82
|
+
"""Depth first iterator
|
|
83
|
+
|
|
84
|
+
:param node: Start node
|
|
85
|
+
:param func: Function to be called for each node:
|
|
86
|
+
``rvalue = func(node, [(prime_rvalue, sub_rvalue, prime_vars, sub_vars)],
|
|
87
|
+
expected_prime_vars, expected_sub_vars)``
|
|
88
|
+
The given arguments are:
|
|
89
|
+
* node: Current node, if None, it should be considered a decision node
|
|
90
|
+
* prime_rvalue: Return value from prime node
|
|
91
|
+
* sub_rvalue: Return value from sub node
|
|
92
|
+
* prime_vars: Variables present in prime
|
|
93
|
+
* sub_vars: Variables present in sub
|
|
94
|
+
* expected_prime_vars: Variables that are expected in prime
|
|
95
|
+
* expected_sub_vars: Variables that are expected in sub
|
|
96
|
+
The return value can be any type, there are no assumptions.
|
|
97
|
+
For WMC this is typically float, and for MC int.
|
|
98
|
+
:return:
|
|
99
|
+
"""
|
|
100
|
+
self._wmc_cache = dict()
|
|
101
|
+
if self.smooth and self._expected_vars is None:
|
|
102
|
+
self._cache_expected_vars()
|
|
103
|
+
if self.smooth and (node.is_true() or node.is_literal()):
|
|
104
|
+
wmc = func(node, None, self._expected_vars[self.vtree.position()], set())
|
|
105
|
+
else:
|
|
106
|
+
wmc = self.depth_first_rec(node, func)
|
|
107
|
+
if self.smooth_to_root and not (node.is_true() or node.is_literal() or node.is_false()):
|
|
108
|
+
root = self.vtree.root()
|
|
109
|
+
if root != node.vtree():
|
|
110
|
+
wmc_prime = wmc
|
|
111
|
+
wmc_sub = func(self.sdd.true(), None, None, None)
|
|
112
|
+
used_prime_vars = self._expected_vars[node.vtree().position()]
|
|
113
|
+
used_sub_vars = set()
|
|
114
|
+
rvalues = [(wmc_prime, wmc_sub, used_prime_vars, used_sub_vars)]
|
|
115
|
+
expected_prime_vars = used_prime_vars
|
|
116
|
+
expected_sub_vars = self._expected_vars[root.position()] - used_prime_vars
|
|
117
|
+
wmc = func(None, rvalues, expected_prime_vars, expected_sub_vars)
|
|
118
|
+
return wmc
|
|
119
|
+
|
|
120
|
+
def depth_first_rec(self, node, func):
|
|
121
|
+
# type: (SddIterator, SddNode, Callable) -> Union[int, float]
|
|
122
|
+
if node in self._wmc_cache:
|
|
123
|
+
return self._wmc_cache[node]
|
|
124
|
+
if node.is_false():
|
|
125
|
+
result = func(node, None, None, None)
|
|
126
|
+
self._wmc_cache[node] = result
|
|
127
|
+
return result
|
|
128
|
+
if node.is_true():
|
|
129
|
+
result = func(node, None, None, None)
|
|
130
|
+
self._wmc_cache[node] = result
|
|
131
|
+
return result
|
|
132
|
+
vtree = node.vtree()
|
|
133
|
+
if node.is_decision():
|
|
134
|
+
rvalues = []
|
|
135
|
+
vtree_left = vtree.left()
|
|
136
|
+
if not self.smooth or vtree_left is None:
|
|
137
|
+
expected_prime_vars = set()
|
|
138
|
+
else:
|
|
139
|
+
expected_prime_vars = self._expected_vars[vtree_left.position()]
|
|
140
|
+
vtree_right = vtree.right()
|
|
141
|
+
if not self.smooth or vtree_right is None:
|
|
142
|
+
expected_sub_vars = set()
|
|
143
|
+
else:
|
|
144
|
+
expected_sub_vars = self._expected_vars[vtree_right.position()]
|
|
145
|
+
for prime, sub in node.elements():
|
|
146
|
+
wmc_prime = self.depth_first_rec(prime, func)
|
|
147
|
+
wmc_sub = self.depth_first_rec(sub, func)
|
|
148
|
+
if not self.smooth:
|
|
149
|
+
used_prime_vars = None
|
|
150
|
+
used_sub_vars = None
|
|
151
|
+
else:
|
|
152
|
+
if prime.vtree() is None:
|
|
153
|
+
used_prime_vars = set()
|
|
154
|
+
else:
|
|
155
|
+
used_prime_vars = self._expected_vars[prime.vtree().position()]
|
|
156
|
+
if sub.vtree() is None:
|
|
157
|
+
used_sub_vars = set()
|
|
158
|
+
else:
|
|
159
|
+
used_sub_vars = self._expected_vars[sub.vtree().position()]
|
|
160
|
+
rvalues.append((wmc_prime, wmc_sub, used_prime_vars, used_sub_vars))
|
|
161
|
+
rvalue = func(node, rvalues, expected_prime_vars, expected_sub_vars)
|
|
162
|
+
else:
|
|
163
|
+
rvalue = func(node, None, None, None)
|
|
164
|
+
if self._wmc_cache is not None:
|
|
165
|
+
self._wmc_cache[node] = rvalue
|
|
166
|
+
return rvalue
|
|
167
|
+
|
|
168
|
+
@staticmethod
|
|
169
|
+
def func_modelcounting(node, rvalues, expected_prime_vars, expected_sub_vars):
|
|
170
|
+
# type: (SddNode, List[Tuple[int, int, Set[int], Set[int]]], Set[int], Set[int]) -> int
|
|
171
|
+
"""Method to pass on to ``depth_first`` to perform model counting.
|
|
172
|
+
|
|
173
|
+
Note that the WmcManager method to perform WMC is much more efficient, and supports weights.
|
|
174
|
+
"""
|
|
175
|
+
if rvalues is None:
|
|
176
|
+
# Leaf
|
|
177
|
+
if node.is_true():
|
|
178
|
+
prime_smooth_factor = 2**len(expected_prime_vars) if expected_prime_vars is not None else 1
|
|
179
|
+
sub_smooth_factor = 2**len(expected_sub_vars) if expected_sub_vars is not None else 1
|
|
180
|
+
return prime_smooth_factor * sub_smooth_factor
|
|
181
|
+
|
|
182
|
+
elif node.is_false():
|
|
183
|
+
return 0
|
|
184
|
+
|
|
185
|
+
elif node.is_literal():
|
|
186
|
+
if expected_prime_vars is not None:
|
|
187
|
+
nb_missing_vars = len(expected_prime_vars)
|
|
188
|
+
if abs(node.literal) in expected_prime_vars:
|
|
189
|
+
nb_missing_vars -= 1
|
|
190
|
+
|
|
191
|
+
prime_smooth_factor = 2**nb_missing_vars
|
|
192
|
+
else:
|
|
193
|
+
prime_smooth_factor = 1
|
|
194
|
+
|
|
195
|
+
if expected_sub_vars is not None:
|
|
196
|
+
nb_missing_vars = len(expected_sub_vars)
|
|
197
|
+
if abs(node.literal) in expected_sub_vars:
|
|
198
|
+
nb_missing_vars -= 1
|
|
199
|
+
sub_smooth_factor = 2**nb_missing_vars
|
|
200
|
+
else:
|
|
201
|
+
sub_smooth_factor = 1
|
|
202
|
+
|
|
203
|
+
return prime_smooth_factor * sub_smooth_factor
|
|
204
|
+
else:
|
|
205
|
+
raise Exception("Unknown leaf type for node {}".format(node))
|
|
206
|
+
else:
|
|
207
|
+
# Decision node
|
|
208
|
+
if node is not None and not node.is_decision():
|
|
209
|
+
raise Exception("Expected a decision node for node {}".format(node))
|
|
210
|
+
rvalue = 0
|
|
211
|
+
for mc_prime, mc_sub, prime_vars, sub_vars in rvalues:
|
|
212
|
+
if prime_vars is not None:
|
|
213
|
+
nb_missing_vars = len(expected_prime_vars) - len(prime_vars)
|
|
214
|
+
prime_smooth_factor = 2**nb_missing_vars
|
|
215
|
+
else:
|
|
216
|
+
prime_smooth_factor = 1
|
|
217
|
+
if sub_vars is not None:
|
|
218
|
+
nb_missing_vars = len(expected_sub_vars) - len(sub_vars)
|
|
219
|
+
sub_smooth_factor = 2**nb_missing_vars
|
|
220
|
+
else:
|
|
221
|
+
sub_smooth_factor = 1
|
|
222
|
+
rvalue += mc_prime * prime_smooth_factor * mc_sub * sub_smooth_factor
|
|
223
|
+
return rvalue
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/****************************************************************************************
|
|
2
|
+
* The Sentential Decision Diagram Package
|
|
3
|
+
* sdd version 2.0, January 8, 2018
|
|
4
|
+
* http://reasoning.cs.ucla.edu/sdd
|
|
5
|
+
****************************************************************************************/
|
|
6
|
+
|
|
7
|
+
#include "sdd.h"
|
|
8
|
+
|
|
9
|
+
/****************************************************************************************
|
|
10
|
+
* computation cache
|
|
11
|
+
*
|
|
12
|
+
* there are two hash tables for caching computations, one for conjoin and one for disjoin
|
|
13
|
+
*
|
|
14
|
+
* there are no collision lists in these hash tables: just one entry (similar to CUDD)
|
|
15
|
+
*
|
|
16
|
+
* a cached computation may become invalid when one of its nodes is gc'd; such invalid
|
|
17
|
+
* entries are identified in a lazy manner.
|
|
18
|
+
*
|
|
19
|
+
* a node whose id is 0 must have been gc'd and is sitting on the gc list
|
|
20
|
+
* however, a node whose id is not 0 may have also been gc'd and its structure re-allocated
|
|
21
|
+
*
|
|
22
|
+
* ids of nodes are monotonically increasing: if a node is gc'd and then re-allocated,
|
|
23
|
+
* its new id will be larger than the old one. moreover, id=0 is reserved for nodes
|
|
24
|
+
* on the gc list. hence, if the id of a node is different than the one saved, then
|
|
25
|
+
* the node must have been gc'd
|
|
26
|
+
****************************************************************************************/
|
|
27
|
+
|
|
28
|
+
static inline
|
|
29
|
+
void delete_computed(SddComputed* computed, SddManager* manager) {
|
|
30
|
+
--manager->computed_count;
|
|
31
|
+
computed->result = NULL; //computed now deleted
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static inline
|
|
35
|
+
SddSize hash_key(SddNode* node1, SddNode* node2) {
|
|
36
|
+
return ((16777619*node1->id)^(node2->id)) % COMPUTED_CACHE_SIZE;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/****************************************************************************************
|
|
40
|
+
* save
|
|
41
|
+
****************************************************************************************/
|
|
42
|
+
|
|
43
|
+
void cache_computation(SddNode* node1, SddNode* node2, SddNode* node, BoolOp op, SddManager* manager) {
|
|
44
|
+
assert(!GC_NODE(node) && !GC_NODE(node1) && !GC_NODE(node2));
|
|
45
|
+
assert(NON_TRIVIAL(node1) && NON_TRIVIAL(node2));
|
|
46
|
+
|
|
47
|
+
if(node1->id > node2->id) SWAP(SddNode*,node1,node2); //for hash key
|
|
48
|
+
|
|
49
|
+
SddSize key = hash_key(node1,node2);
|
|
50
|
+
SddComputed* table = op==CONJOIN? manager->conjoin_cache: manager->disjoin_cache;
|
|
51
|
+
SddComputed* computed = table+key;
|
|
52
|
+
|
|
53
|
+
if(computed->result!=NULL) { //override current computed
|
|
54
|
+
delete_computed(computed,manager);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
++manager->computed_count;
|
|
58
|
+
|
|
59
|
+
computed->result = node;
|
|
60
|
+
computed->id = node->id; //needed to check whether result has been gc'd
|
|
61
|
+
computed->id1 = node1->id; //only id is needed for lookup
|
|
62
|
+
computed->id2 = node2->id; //only id is needed for lookup
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/****************************************************************************************
|
|
66
|
+
* lookup
|
|
67
|
+
****************************************************************************************/
|
|
68
|
+
|
|
69
|
+
SddNode* lookup_computation(SddNode* node1, SddNode* node2, BoolOp op, SddManager* manager) {
|
|
70
|
+
assert(!GC_NODE(node1) && !GC_NODE(node2));
|
|
71
|
+
assert(NON_TRIVIAL(node1) && NON_TRIVIAL(node2));
|
|
72
|
+
|
|
73
|
+
if(node1->id > node2->id) SWAP(SddNode*,node1,node2); //for hash key and ordered comparison
|
|
74
|
+
|
|
75
|
+
SddSize key = hash_key(node1,node2);
|
|
76
|
+
SddComputed* table = op==CONJOIN? manager->conjoin_cache: manager->disjoin_cache;
|
|
77
|
+
SddComputed* computed = table+key;
|
|
78
|
+
|
|
79
|
+
++manager->computed_cache_lookup_count;
|
|
80
|
+
|
|
81
|
+
if(computed->result==NULL || computed->id!=computed->result->id) return NULL; //missing or invalid computed
|
|
82
|
+
else if(computed->id1==node1->id && computed->id2==node2->id) {
|
|
83
|
+
++manager->computed_cache_hit_count; //found it
|
|
84
|
+
return computed->result; //hit
|
|
85
|
+
}
|
|
86
|
+
else return NULL; //miss: non-matching computed for this key
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/****************************************************************************************
|
|
90
|
+
* end
|
|
91
|
+
****************************************************************************************/
|