SymPy中的非连续替换

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)

你能给出一个实际的替换示例,这样我就可以看到问题所在了。你上面遇到的问题可以通过 ~unutbu 的答案来解决。 - Gary Kerr
@PreludeAndFugue 我已经添加了真实的例子,我的旧例子过于简化,没有什么用处。 - Mad Scientist
5个回答

14

sympy的当前版本提供了关键字simultaneous,不再需要先前答案中的复杂操作:

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

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,你将失去对替换顺序的控制。而如果你把一个列表或元组传递给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

PS. _subs_list(self, sequence)的定义(部分)如下:

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

这确定了子操作的执行顺序。

我已添加了第二个例子,替换的顺序似乎根据某种我不理解的原则而变化。 - Mad Scientist
你可以使用OrderedDict来复制它。https://dev59.com/8nA75IYBdhLWcg3w1sv2#3080803 - jfs

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

谢谢,所以使用普通字典有点不可预测,因为它们是未排序的。不幸的是,在我的情况下,顺序并不重要,替换无论顺序如何都会相互覆盖。 - Mad Scientist

1

回答编辑后的问题。

在您的示例中,您可以使用一些临时变量,这些变量不会被后续的替换覆盖。然后,一旦所有可能重叠的替换都已完成,您可以用真实的变量替换临时变量。

这个例子适用于这个问题,如果您的完整问题包含更复杂的替换,我认为您仍然可以创建临时变量来避免重叠的替换。

from sympy import Symbol, sin, cos, pi

I_x, I_y, I_z = Symbol("I_x"), Symbol("I_y"), Symbol("I_z")
S_x, S_y, S_z = Symbol("S_x"), Symbol("S_y"), Symbol("S_z")
J_is = Symbol("J_IS")
t = Symbol("t")
I_x_temp, I_y_temp, I_z_temp = Symbol("I_x_temp"), Symbol("I_y_temp"), Symbol("I_z_temp")

f = 2*I_x*S_z
answer = I_y*sin(2*pi*J_is*t) + 2*I_x*S_z*cos(2*pi*J_is*t)

subs1a = [
    (2*I_x*S_z, 2*I_x_temp*S_z*cos(2*pi*J_is*t) + I_y_temp*sin(2*pi*J_is*t)),
    (I_x,  I_x_temp*cos(2* pi*J_is*t) + 2*I_x_temp*S_z*sin(2*pi*J_is*t)),
    (I_y,  I_y_temp*cos(2*pi*J_is*t) - 2*I_x_temp*S_z* sin(2*pi*J_is*t))
]

subs_temp = [(I_x_temp, I_x), (I_y_temp, I_y), (I_z_temp, I_z)]

print f
f = f.subs(subs1a)
print f
f = f.subs(subs_temp)
print f
print f == answer # True

注意,您也可以连续执行两个替换操作:

f.subs(subs1a).subs(subs_temp) == answer

0
关键字simultaneous将执行非冲突的替换,无论输入是字典还是序列。
>>> x.subs([(x,y),(y,z)],simultaneous=1)
y
>>> x.subs([(y,z),(x,y)],simultaneous=1)
y

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接