sympy中的递归替换

Recursive substitution in sympy

我有一个 sympy 表达式,其中包含多个需要替换的变量。问题是一些要替换的表达式也包含需要替换的变量实例。

from sympy import *
from sympy.abs import a,b, x,y

expr = a + b
replace = [[a, x+y], [b, 2*a]]

expr.subs(replace) # 2*a + x + y, I want 3*x + 3*y

如果替换列表的顺序正确,它将按顺序应用每个替换,但在我的实际应用程序中我不知道什么顺序是合适的:

expr.subs(reversed(replace)) # 3*x + 3*y

我可以通过对 exprreplace 应用 n 次替换来强制替换,但这在计算上似乎很浪费:

result = expr
for _ in replace:
    # Applying n times
    result = result.subs(replace)

我希望 subs 有一个 recursive 选项,但似乎不存在。还有更好的选择吗?

如果存在递归选项,它可能会执行替换直到表达式停止变化。这是您可以自己做的事情;而且我不认为这是浪费,毕竟 sympy 也是用 Python 写的。

这是一个 returns 替换结果及其成功指标的函数:替换后表达式是否达到稳定形式。对于导致无限循环的替换规则,这将是错误的,例如 replace = [[x, y+1], [y, x+1]].

def recursive_sub(expr, replace):
    for _ in range(0, len(replace) + 1):
        new_expr = expr.subs(replace)
        if new_expr == expr:
            return new_expr, True
        else:
            expr = new_expr
    return new_expr, False

现在 res, _ = recursive_sub(expr, replace) returns 3*x + 3*yexprreplace 一起使用时

如果您按正确的顺序执行,替换将迭代执行(除非您使用 subs(replacement, simultaneous=True),它一次执行所有替换)。

您的问题是正确订购替换件。您想要的是 topological sort 个替换项。即,每个替换都是图中的一个节点,如果new1包含old2,则从(old1, new1)(old2, new2)有一条边(即应该先替换它)。

SymPy 在 sympy.utilities.iterables 中实现了 topological_sort。它需要一个顶点列表和一个边列表(顶点元组)。假设你有

replace = [(y, z + 1), (x, y + z), (z, a)]

我们可以用

创建一个边列表
from itertools import combinations
edges = [(i, j) for i, j in permutations(replace, 2) if i[1].has(j[0])]

排序得到

>>> from sympy import default_sort_key, topological_sort
>>> topological_sort([replace, edges], default_sort_key)
[(x, y + z), (y, z + 1), (z, a)]

topological_sort的第三个参数是一个用来断和的键。由于 SymPy 对象没有在其上定义隐式排序(通常 <> 提高 TypeError),因此有一个名为 default_sort_key 的排序键实现提供了一个SymPy 对象的规范和一致(但任意)排序。

在像 404 所示的情况下会出现无限循环,topological_sort 会提醒您存在循环

>>> replace = [(x, y+1), (y, x+1)]
>>> edges = [(i, j) for i, j in permutations(replace, 2) if i[1].has(j[0])]
>>> topological_sort([replace, edges], default_sort_key)
Traceback (most recent call last):
  File "<ipython-input-51-72f3bfcfd4ad>", line 1, in <module>
    topological_sort([replace, edges], default_sort_key)
  File "/Users/aaronmeurer/Documents/Python/sympy/sympy/sympy/utilities/iterables.py", line 882, in topological_sort
    raise ValueError("cycle detected")
ValueError: cycle detected

老实说,这应该直接在 subs 中通过关键字参数实现。参见 https://github.com/sympy/sympy/issues/6257

我 运行 遇到了同样的问题,目前看来,在 SymPy 中仍然没有针对此问题的简单通用解决方案。

也许我的快速简便的解决方法对您有帮助:

代码前言

import sympy
x, y = sympy.symbols("x, y")
reps = [(y, x**2), (x, 2)]

显示替换顺序很重要的示例

直接取自 http://docs.sympy.org/dev/modules/core.html#sympy.core.basic.Basic.subs

的官方 SymPy 文档
>>> (x + y).subs(reps)
6
>>> (x + y).subs(reversed(reps))
x**2 + 2

我的解决方法,适用于任一替换顺序:

只需多次替换您的变量。

>>> (x + y).subs(100 * reps)
6
>>> (x + y).subs(reversed(100 * reps))
6

显然,这只适用于固定的 "recursion depth",我想如果您使用大表达式或许多替换。