def maybe_add_substitution( sub: Substitution, var: str, replacement: Expression, ) -> Optional[Substitution]: """Add var -> replacement to sub if possible. The addition is possible if, for all a -> b in sub, b(var -> replacement) does not contain a. :param sub: Existing set of substitutions :param var: Variable whose substitution is being defined. :param replacemnet: Expression that :ref:`var` might be replaced with. :returns: New substitutino set including var -> replacement or None if this isn't possible""" new_substitutions = {var: replacement} if matchpy.contains_variables_from_set(replacement, {var}): # print("Failed occurs check", var, "→", replacement) return None # Occurs check failed for v, term in sub.items(): new_term = substitute(term, Substitution({var: replacement})) if matchpy.contains_variables_from_set(new_term, {v}): print("Failed other occurs check", term, "/", v, "→", new_term, "under", var, "→", replacement) return None # Occurs check failed if new_term != term: new_substitutions[v] = new_term return Substitution(sub, **new_substitutions)
def test_matchpy_connector(): if matchpy is None: skip("matchpy not installed") from multiset import Multiset from matchpy import Pattern, Substitution w_ = WildDot("w_") w__ = WildPlus("w__") w___ = WildStar("w___") expr = x + y pattern = x + w_ p, subst = _get_first_match(expr, pattern) assert p == Pattern(pattern) assert subst == Substitution({'w_': y}) expr = x + y + z pattern = x + w__ p, subst = _get_first_match(expr, pattern) assert p == Pattern(pattern) assert subst == Substitution({'w__': Multiset([y, z])}) expr = x + y + z pattern = x + y + z + w___ p, subst = _get_first_match(expr, pattern) assert p == Pattern(pattern) assert subst == Substitution({'w___': Multiset()})
def test_matchpy_optional(): if matchpy is None: skip("matchpy not installed") from matchpy import Pattern, Substitution from matchpy import ManyToOneReplacer, ReplacementRule p = WildDot("p", optional=1) q = WildDot("q", optional=0) pattern = p*x + q expr1 = 2*x pa, subst = _get_first_match(expr1, pattern) assert pa == Pattern(pattern) assert subst == Substitution({'p': 2, 'q': 0}) expr2 = x + 3 pa, subst = _get_first_match(expr2, pattern) assert pa == Pattern(pattern) assert subst == Substitution({'p': 1, 'q': 3}) expr3 = x pa, subst = _get_first_match(expr3, pattern) assert pa == Pattern(pattern) assert subst == Substitution({'p': 1, 'q': 0}) expr4 = x*y + z pa, subst = _get_first_match(expr4, pattern) assert pa == Pattern(pattern) assert subst == Substitution({'p': y, 'q': z}) replacer = ManyToOneReplacer() replacer.add(ReplacementRule(Pattern(pattern), lambda p, q: sin(p)*cos(q))) assert replacer.replace(expr1) == sin(2)*cos(0) assert replacer.replace(expr2) == sin(1)*cos(3) assert replacer.replace(expr3) == sin(1)*cos(0) assert replacer.replace(expr4) == sin(y)*cos(z)
def unify_expressions(left: Expression, right: Expression) -> List[Substitution]: """Return a substitution alpha such that :ref:`left` * alpha == :ref:`right` * alpha, or None if none such exists. For best results, the expressions should not share variables. This function does not ensure that :param left: An expression to unify. :param right: An expression to unify :returns: The unifying substitution, or None""" main_ret = [] root_ret = Substitution() root_to_operate = deque([(left, right)]) operations = [(root_ret, root_to_operate)] while operations: preserve_this = True ret, to_operate = operations.pop() # print("Trace:","Have", ret, "processing", ", ".join(map(lambda x: str((str(x[0]), str(x[1]))), to_operate))) if not to_operate: # Successful unification main_ret.append(ret) continue t1, t2 = to_operate.popleft() # print("Try to unify", t1, "and", t2) if t1 == t2: operations.append((ret, to_operate)) # print("Equality continue") continue any_change = False if isinstance(t1, Wildcard): new_subs = maybe_add_substitution(ret, t1.variable_name, t2) if new_subs is not None: ret = new_subs any_change = True else: continue # Here we drop the branch elif isinstance(t2, Wildcard): new_subs = maybe_add_substitution(ret, t2.variable_name, t1) if new_subs is not None: ret = new_subs any_change = True else: continue elif (get_head(t1) == get_head(t2) and isinstance(t1, Operation) and isinstance(t2, Operation)): # Unify within functions if t1.associative and t1.commutative: potential_unifiers = ac_operand_lists(t1, t2) preserve_this = False for i in potential_unifiers: new_ret = copy(ret) new_to_operate = copy(to_operate) new_to_operate.extend(i) operations.append((new_ret, new_to_operate)) elif t1.associative or t1.commutative: raise (NotImplementedError( "Straight associative or commutative ain't happening") ) # noqa elif len(t1.operands) == len(t2.operands): to_operate.extend((zip(t1.operands, t2.operands))) else: continue else: continue if any_change: new_queue = deque() # type: Deque[Tuple[Expression, Expression]] while to_operate: a, b = to_operate.popleft() new_a = substitute(a, ret) new_b = substitute(b, ret) if a != new_a: pass # print("Substituion modified", a, "→", new_a) if b != new_b: pass # print("Substituion modified", b, "→", new_b) new_queue.append((new_a, new_b)) to_operate = new_queue if preserve_this: operations.append((ret, to_operate)) return main_ret