【问题标题】:Recursive substitution in sympysympy中的递归替换
【发布时间】:2016-03-24 09:52:27
【问题描述】:

我有一个带有多个需要替换的变量的 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

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

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

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

【问题讨论】:

    标签: python sympy


    【解决方案1】:

    如果您以正确的顺序执行,替换将被迭代执行(除非您使用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 对象没有定义隐式排序(<> raise 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

    【讨论】:

      【解决方案2】:

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

      这是一个返回替换结果及其成功指示符的函数:替换后表达式是否达到稳定形式。对于导致无限循环的替换规则,这将是错误的,例如 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)exprreplace 一起使用时返回3*x + 3*y

      【讨论】:

      • 从技术上讲,这是一个迭代解决方案,但它的想法是一样的。
      • 如果replace 有无限循环,这将挂起而不是返回不完整,但这很容易改变。
      【解决方案3】:

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

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

      代码前言

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

      说明替换顺序很重要的示例

      直接取自 SymPy 官方文档http://docs.sympy.org/dev/modules/core.html#sympy.core.basic.Basic.subs

      >>> (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
      

      显然,这仅适用于固定的“递归深度”,我猜如果您正在使用大表达式或许多替换,那么不必要的调用(除了实际更改表达式的调用之外)可能非常耗时。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-12-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-03-06
        相关资源
        最近更新 更多