-
Notifications
You must be signed in to change notification settings - Fork 0
/
unify.py
226 lines (197 loc) · 6.57 KB
/
unify.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
""" Generic Unification algorithm for expression trees with lists of children
The implementation is a direct translation of
Artificial Intelligence: A Modern Approach
by
Stuart Russel and Peter Norvig
Second edition, section 9.2, page 276
It is modified in the following ways:
1. We allow associative and commutative Compound expressions. This results in
combinatorial blowup.
2. We explore the tree lazily
3. We provide generic interfaces to symbolic algebra libraries in Python.
A more traditional version can be found here
http://aima.cs.berkeley.edu/python/logic.html
"""
from collections import namedtuple
from itertools import combinations
Compound = namedtuple('Compound', 'op args')
Variable = namedtuple('Variable', 'arg')
CondVariable = namedtuple('Variable', 'arg valid')
def _unify(x, y, s, **fns):
# print 'Unify: ', x, y, s
if x == y:
yield s
elif isinstance(x, (Variable, CondVariable)):
for x in _unify_var(x, y, s, **fns): yield x
elif isinstance(y, (Variable, CondVariable)):
for x in _unify_var(y, x, s, **fns): yield x
elif isinstance(x, Compound) and isinstance(y, Compound):
is_commutative = fns.get('is_commutative', lambda x: False)
is_associative = fns.get('is_associative', lambda x: False)
for sop in _unify(x.op, y.op, s, **fns):
if len(x.args) == len(y.args):
for x in _unify(x.args, y.args, sop, **fns): yield x
elif is_associative(x) and is_associative(y):
a, b = (x, y) if len(x.args) < len(y.args) else (y, x)
if is_commutative(x) and is_commutative(y):
combinations = allcombinations(a.args, b.args, None)
else:
combinations = allcombinations(a.args, b.args, True)
for aaargs, bbargs in combinations:
aa = aaargs
bb = [unpack(Compound(b.op, arg)) for arg in bbargs]
for x in _unify(aa, bb, sop, **fns): yield x
elif iterable(x) and iterable(y) and len(x) == len(y):
if len(x) == 0:
yield s
else:
for shead in _unify(x[0], y[0], s, **fns):
for x in _unify(x[1:], y[1:], shead, **fns):
yield x
def _unify_var(var, x, s, **fns):
# print 'UnVar: ', var, x, s
if var in s:
for x in _unify(s[var], x, s, **fns): yield x
elif occur_check(var, x):
pass
elif isinstance(var, CondVariable) and var.valid(x):
yield assoc(s, var, x)
elif isinstance(var, Variable):
yield assoc(s, var, x)
def occur_check(var, x):
""" var occurs in subtree owned by x? """
if var == x:
return True
elif isinstance(x, Compound):
return occur_check(var, x.args)
elif iterable(x):
if any(occur_check(var, xi) for xi in x): return True
return False
def assoc(d, key, val):
""" Return copy of d with key associated to val """
d = d.copy()
d[key] = val
return d
def iterable(x):
""" Is x a traditional iterable? """
return type(x) in (tuple, list, set)
def unpack(x):
if isinstance(x, Compound) and len(x.args) == 1:
return x.args[0]
else:
return x
def allcombinations(A, B, ordered):
"""
Restructure A and B to have the same number of elements
Assuming either
associativity - ordered == True
commutativity - ordered == None
A and B can be rearranged so that the larger of the two lists is
reorganized into smaller sublists.
>>> for x in allcombinations((1, 2, 3), (5, 6), True): print x
(((1,), (2, 3)), (5, 6))
(((1, 2), (3,)), (5, 6))
>>> for x in allcombinations((1, 2, 3), (5, 6), None): print x
(((1,), (2, 3)), (5, 6))
(((2,), (3, 1)), (5, 6))
(((3,), (1, 2)), (5, 6))
(((1, 2), (3,)), (5, 6))
(((2, 3), (1,)), (5, 6))
(((3, 1), (2,)), (5, 6))
"""
if len(A) == len(B):
yield A, B
raise StopIteration()
sm, bg = (A, B) if len(A) < len(B) else (B, A)
for part in kbin(range(len(bg)), len(sm), ordered=ordered):
if bg == B:
yield A, partition(B, part)
else:
yield partition(A, part), B
def partition(it, part):
""" Partition an iterable into pieces defined by indices
>>> partition((10, 20, 30, 40), [[0, 1, 2], [3]])
((10, 20, 30), (40,))
"""
t = type(it)
return t([index(it, ind) for ind in part])
def index(it, ind):
""" Fancy indexing into an iterable
>>> index([10, 20, 30], (1, 2, 0))
[20, 30, 10]
"""
return type(it)([it[i] for i in ind])
def kbin(l, k, ordered=True):
"""
Return sequence ``l`` partitioned into ``k`` bins.
If ordered is True then the order of the items in the
flattened partition will be the same as the order of the
items in ``l``; if False, all permutations of the items will
be given; if None, only unique permutations for a given
partition will be given.
Examples
========
>>> from sympy.utilities.iterables import kbin
>>> for p in kbin(range(3), 2):
... print p
...
[[0], [1, 2]]
[[0, 1], [2]]
>>> for p in kbin(range(3), 2, ordered=False):
... print p
...
[(0,), (1, 2)]
[(0,), (2, 1)]
[(1,), (0, 2)]
[(1,), (2, 0)]
[(2,), (0, 1)]
[(2,), (1, 0)]
[(0, 1), (2,)]
[(0, 2), (1,)]
[(1, 0), (2,)]
[(1, 2), (0,)]
[(2, 0), (1,)]
[(2, 1), (0,)]
>>> for p in kbin(range(3), 2, ordered=None):
... print p
...
[[0], [1, 2]]
[[1], [2, 0]]
[[2], [0, 1]]
[[0, 1], [2]]
[[1, 2], [0]]
[[2, 0], [1]]
"""
from sympy.utilities.iterables import partitions
from itertools import permutations
def rotations(seq):
for i in range(len(seq)):
yield seq
seq.append(seq.pop(0))
if ordered is None:
func = rotations
else:
func = permutations
for p in partitions(len(l), k):
if sum(p.values()) != k:
continue
for pe in permutations(p.keys()):
rv = []
i = 0
for part in pe:
for do in range(p[part]):
j = i + part
rv.append(l[i: j])
i = j
if ordered:
yield rv
else:
template = [len(i) for i in rv]
for pp in func(l):
rvp = []
ii = 0
for t in template:
jj = ii + t
rvp.append(pp[ii: jj])
ii = jj
yield rvp