SymPy中的非顺序替换

13 投票
5 回答
2690 浏览
提问于 2025-04-16 00:13

我正在尝试使用 [SymPy][1] 同时替换一个表达式中的多个项。我用一个字典作为参数尝试了 [subs 函数][2],但发现它是一个一个地替换的。

In : a.subs({a:b, b:c})
Out: c

问题是第一次替换后产生的项可以被第二次替换替代,但这对我来说是不应该的。

有没有什么办法可以让替换同时进行,而不互相干扰呢?

编辑: 这是一个真实的例子

In [1]: I_x, I_y, I_z = Symbol("I_x"), Symbol("I_y"), Symbol("I_z")

In [2]: S_x, S_y, S_z = Symbol("S_x"), Symbol("S_y"), Symbol("S_z")

In [3]: J_is = Symbol("J_IS")

In [4]: t = Symbol("t")

In [5]: substitutions = (
(2 * I_x * S_z, 2 * I_x * S_z * cos(2 * pi * J_is * t) + I_y * sin(2 * pi * J_is * t)),
(I_x,  I_x * cos(2 * pi * J_is * t) + 2 * I_x * S_z * sin(2 * pi * J_is * t)),
(I_y,  I_y * cos(2 * pi * J_is * t) - 2 * I_x * S_z * sin(2 * pi * J_is * t))
)

In [6]: (2 * I_x * S_z).subs(substitutions)
Out[7]: (I_y*cos(2*pi*J_IS*t) - 2*I_x*S_z*sin(2*pi*J_IS*t))*sin(2*pi*J_IS*t) + 2*S_z*(I_x*cos(2*pi*J_IS*t) + 2*I_x*S_z*sin(2*pi*J_IS*t))*cos(2*pi*J_IS*t)

在这种情况下,应该只进行适当的替换,也就是只进行第一次替换。所以期望的输出应该是这样的:

In [6]: (2 * I_x * S_z).subs(substitutions)
Out[7]: I_y*sin(2*pi*J_IS*t) + 2*I_x*S_z*cos(2*pi*J_IS*t)

5 个回答

1

这是一个关于@~unutbu的回答的例子:

>>> import ordereddict # collections.OrderedDict in Python 2.7+
>>> from sympy import *
>>> x,y,z = symbols('xyz')
>>> x.subs(ordereddict.OrderedDict([(x,y),(y,z)]))
y
>>> x.subs(ordereddict.OrderedDict([(y,z),(x,y)]))
z
2

这个 subs(self,*args) 方法的定义大致是这样的:

In [11]: x.subs??
...
sequence = args[0]
if isinstance(sequence, dict):
    return self._subs_dict(sequence)
elif isinstance(sequence, (list, tuple)):
    return self._subs_list(sequence)

如果你给 subs 传一个字典,你就无法控制替换的顺序。可是如果你传一个列表或元组,你就可以控制顺序。

不过,这个方法不支持同时替换。如果用户传入像 x.subs([(x,y),(y,x)]) 这样的内容,就会出现问题。所以我觉得 sympy 可能没有处理同时替换的功能。相反,我认为所有的替换要么是无序的(如果你传的是字典),要么在最好的情况下是按顺序进行一次性替换(如果你传的是列表或元组):

In [17]: x.subs([(x,y),(y,z)])
Out[18]: z

In [19]: x.subs([(y,z),(x,y)])
Out[19]: y

另外,_subs_list(self, sequence) 方法的定义大致是这样的:

In [14]: x._subs_list??
...
    for old, new in sequence:
        result = result.subs(old, new)

这就确定了替换的顺序。

14

现在的sympy版本提供了一个叫做simultaneous的关键词。之前那些复杂的操作现在都不需要了:

In [1]: (x*sin(y)).subs([(x,y),(y,x)],simultaneous=True)
Out[1]: y⋅sin(x)

撰写回答