""" SymPy interface to Unification engine See sympy.unify for module level docstring See sympy.unify.core for algorithmic docstring """ from sympy.core import Basic, Add, Mul, Pow from sympy.core.operations import AssocOp, LatticeOp from sympy.matrices import MatAdd, MatMul, MatrixExpr from sympy.sets.sets import Union, Intersection, FiniteSet from sympy.unify.core import Compound, Variable, CondVariable from sympy.unify import core basic_new_legal = [MatrixExpr] eval_false_legal = [AssocOp, Pow, FiniteSet] illegal = [LatticeOp] def sympy_associative(op): assoc_ops = (AssocOp, MatAdd, MatMul, Union, Intersection, FiniteSet) return any(issubclass(op, aop) for aop in assoc_ops) def sympy_commutative(op): comm_ops = (Add, MatAdd, Union, Intersection, FiniteSet) return any(issubclass(op, cop) for cop in comm_ops) def is_associative(x): return isinstance(x, Compound) and sympy_associative(x.op) def is_commutative(x): if not isinstance(x, Compound): return False if sympy_commutative(x.op): return True if issubclass(x.op, Mul): return all(construct(arg).is_commutative for arg in x.args) def mk_matchtype(typ): def matchtype(x): return (isinstance(x, typ) or isinstance(x, Compound) and issubclass(x.op, typ)) return matchtype def deconstruct(s, variables=()): """ Turn a SymPy object into a Compound """ if s in variables: return Variable(s) if isinstance(s, (Variable, CondVariable)): return s if not isinstance(s, Basic) or s.is_Atom: return s return Compound(s.__class__, tuple(deconstruct(arg, variables) for arg in s.args)) def construct(t): """ Turn a Compound into a SymPy object """ if isinstance(t, (Variable, CondVariable)): return t.arg if not isinstance(t, Compound): return t if any(issubclass(t.op, cls) for cls in eval_false_legal): return t.op(*map(construct, t.args), evaluate=False) elif any(issubclass(t.op, cls) for cls in basic_new_legal): return Basic.__new__(t.op, *map(construct, t.args)) else: return t.op(*map(construct, t.args)) def rebuild(s): """ Rebuild a SymPy expression. This removes harm caused by Expr-Rules interactions. """ return construct(deconstruct(s)) def unify(x, y, s=None, variables=(), **kwargs): """ Structural unification of two expressions/patterns. Examples ======== >>> from sympy.unify.usympy import unify >>> from sympy import Basic, S >>> from sympy.abc import x, y, z, p, q >>> next(unify(Basic(S(1), S(2)), Basic(S(1), x), variables=[x])) {x: 2} >>> expr = 2*x + y + z >>> pattern = 2*p + q >>> next(unify(expr, pattern, {}, variables=(p, q))) {p: x, q: y + z} Unification supports commutative and associative matching >>> expr = x + y + z >>> pattern = p + q >>> len(list(unify(expr, pattern, {}, variables=(p, q)))) 12 Symbols not indicated to be variables are treated as literal, else they are wild-like and match anything in a sub-expression. >>> expr = x*y*z + 3 >>> pattern = x*y + 3 >>> next(unify(expr, pattern, {}, variables=[x, y])) {x: y, y: x*z} The x and y of the pattern above were in a Mul and matched factors in the Mul of expr. Here, a single symbol matches an entire term: >>> expr = x*y + 3 >>> pattern = p + 3 >>> next(unify(expr, pattern, {}, variables=[p])) {p: x*y} """ decons = lambda x: deconstruct(x, variables) s = s or {} s = {decons(k): decons(v) for k, v in s.items()} ds = core.unify(decons(x), decons(y), s, is_associative=is_associative, is_commutative=is_commutative, **kwargs) for d in ds: yield {construct(k): construct(v) for k, v in d.items()}