4

I need a unification algorithm to handle the following situation.

Each node in my expression tree has a list (associative) or a set (associative and commutative) of children. I would like to get all possible matches to a specific pattern.

Here is an example. Lets assume that we're dealing with matrices so that + is commutative and * is non-commutative

expression: A + B*C*D + E

alternatively: Add(A, Mul(B, C, D), E)

pattern: X + Y*Z

I see two matches

X: A + E
Y: B
Z: C * D

X: A + E
Y: B * C
Z: D

Question: Are there any standard algorithms that handle this?

MRocklin
  • 55,641
  • 23
  • 163
  • 235

1 Answers1

1

All that comes to mind when I see this is partitions and subsets for matching commutative terms. If you are matching n terms to m > n terms you must partition m into n parts, p.

Then you want the subsets of length p_i which can then be tested against your original pattern. It might make sense to find the most restrictive term and match that one first. e.g., if the mul is going to match you can see if there are muls present...if so then your initial problem has been divided into two parts: the mul part and the rest. The most restrictive term is the one with the highest operation count. so of X + X*Y*Z + X*(Y+Z) the two mul terms tie; the tie might be broken to favor the one with an Add being more complex. So perhaps op_count and number of types of operations might be the complexity measure.

In SymPy:

>>> pat = X + X*Y*Z + X*(Y+Z)
>>> [(p.count_ops(), p.count_ops(visual=True).free_symbols) for p in Add.make_args(pat)]
[(0, set([])), (2, set([MUL])), (2, set([ADD, MUL]))]

As far as working out the mapping, letting binary digits select the items for you could work . In SymPy again with from sympy.utilities.iterables import reshape, runs, permutations in effect:

>>> N=list('ABC')
>>> M=list('abcde')
>>> n=[1]*len(N)
>>> m=[1]*len(M)
>>> n.extend([0]*(len(m) - len(n)))
>>> grouper = lambda x, xx: x==xx==0 or x==0 and xx==1
>>> demo = [1, 0, 0, 1, 1]
>>> runs(demo, grouper)
[[1, 0, 0], [1], [1]]
>>> nn=n[1:] # the first one will stay put; shuffle the rest
>>> for p in permutations(nn):
...   P = [1]+list(p)
...   shape = [[len(r)] for r in runs(P, grouper)]
...   print dict(zip(N, reshape(M, shape)[0])) 
{'A': ['a'], 'C': ['c', 'd', 'e'], 'B': ['b']}
{'A': ['a'], 'C': ['c', 'd', 'e'], 'B': ['b']}
{'A': ['a'], 'C': ['d', 'e'], 'B': ['b', 'c']}
{'A': ['a'], 'C': ['e'], 'B': ['b', 'c', 'd']}
{'A': ['a'], 'C': ['d', 'e'], 'B': ['b', 'c']}
{'A': ['a'], 'C': ['e'], 'B': ['b', 'c', 'd']}
{'A': ['a'], 'C': ['c', 'd', 'e'], 'B': ['b']}
{'A': ['a'], 'C': ['c', 'd', 'e'], 'B': ['b']}
{'A': ['a'], 'C': ['d', 'e'], 'B': ['b', 'c']}
{'A': ['a'], 'C': ['e'], 'B': ['b', 'c', 'd']}
{'A': ['a'], 'C': ['d', 'e'], 'B': ['b', 'c']}
{'A': ['a'], 'C': ['e'], 'B': ['b', 'c', 'd']}
{'A': ['a', 'b'], 'C': ['d', 'e'], 'B': ['c']}
{'A': ['a', 'b'], 'C': ['e'], 'B': ['c', 'd']}
{'A': ['a', 'b'], 'C': ['d', 'e'], 'B': ['c']}
{'A': ['a', 'b'], 'C': ['e'], 'B': ['c', 'd']}
{'A': ['a', 'b', 'c'], 'C': ['e'], 'B': ['d']}
{'A': ['a', 'b', 'c'], 'C': ['e'], 'B': ['d']}
{'A': ['a', 'b'], 'C': ['d', 'e'], 'B': ['c']}
{'A': ['a', 'b'], 'C': ['e'], 'B': ['c', 'd']}
{'A': ['a', 'b'], 'C': ['d', 'e'], 'B': ['c']}
{'A': ['a', 'b'], 'C': ['e'], 'B': ['c', 'd']}
{'A': ['a', 'b', 'c'], 'C': ['e'], 'B': ['d']}
{'A': ['a', 'b', 'c'], 'C': ['e'], 'B': ['d']}

Chris