我的问题涉及用 .subs() 方法替换 sympy 表达式中的多个符号变量。
我所知道的方法,在某种程度上,按顺序实现单值 .subs() 方法,导致严重依赖于替换值的某些顺序(这可能很难预测/管理)并且无法实现直接进行某些同时替换(例如,交换两个变量)。我注意到一个明显的解决方法可以消除这种顺序依赖性(涉及虚拟变量),但我担心它的效率不是最佳的。是否有一个标准有效的方法来实现同时多值替换?
更详细地说,让我们考虑与表达式
expr
相关的两个示例,具体取决于某些变量 x, y, z
,例如,
from sympy import symbols
x,y,z=symbols(['x','y','z'])
expr=x+y**2-z
首先,对于顺序无关紧要的替换示例,请考虑将
x
发送到 int 1 并将 y
发送到 z
,并且由于顺序无关紧要,我们可以通过多种方法来实现这样的替换。事实上,以下替换语法变体都返回相同的结果:
# order-independent substitution example:
EG1_subs1=expr.subs({y:z,x:1})
EG1_subs2=expr.subs([(y,z),(x,1)])
EG1_subs3=expr.subs([(x,1),(y,z)])
# to compare the results:
for j in [EG1_subs1,EG1_subs2,EG1_subs3]:
print(j)
在这些示例中,.subs() 似乎同时执行替换,但我认为它是按顺序执行单值替换,而该序列的顺序受到 EG1_subs1 变量的某些字典顺序的影响,以及EG1_subs2 和 EG1_subs3 中各自的列表顺序。
现在举一个例子,其中顺序依赖性确实很重要(并且底层顺序实现不足以同时替换),假设有人想要使用 .subs() 来简单地交换
x
和 y
– 即替换每个实例x 到 y 以及 y 的每个实例(来自原始表达式)到 x。重复前面示例的语法将不起作用,而且不同 .subs() 语法变体的结果输出甚至会发生变化。事实上,请考虑以下语法变体:
# order-dependent substitution example:
EG2_subs1=expr.subs({y:x,x:y})
EG2_subs2=expr.subs([(y,x),(x,y)])
EG2_subs3=expr.subs([(x,y),(y,x)])
# to compare the results:
for j in [EG2_subs1,EG2_subs2,EG2_subs3]:
print(j)
请注意,EG2_subs2 和 EG2_subs3 并不相同,清楚地显示了 .subs() 方法如何顺序实现单值替换。
接下来需要注意的是,EG2_subs1 也没有执行同时替换(即简单地交换 x 和 y),而是使用取决于变量标签的顺序顺序实现单值替换。事实上,
EG2_subs1==EG2_subs3
,这就提出了为什么是这样而不是EG2_subs1==EG2_subs2
的问题。答案似乎是,在 .subs() 所依赖的某些词典顺序中,x
位于 y
之前,因此,例如,如果在上一个示例中碰巧使用 a
而不是 y
,则代码也可以产生 EG2_subs1==EG2_subs2
。我将此称为“令人震惊”,只是因为,也许天真地,我期望 sympy 计算不依赖于人们选择分配给符号变量的标签。
同时替换和顺序替换(因此顺序相关)之间的差异仅在原始变量映射到包含原始变量的表达式时出现。因此,模拟同时替换的两步解决方案是首先将原始变量映射到新创建的虚拟变量中的表达式,然后将虚拟变量映射回原始变量。这是这种方法的示例函数(仅用于替换变量,并且仅使用字典格式的子数据):
def simulSubs(expr,dict):
# creating labels for dummy variables:
dummy_labels=['_dummy_'+str(j) for j in dict]
# initializing dummy variables:
for j in dummy_labels:
print(j)
globals().update({j:symbols(j)})
# creating dictionaries to go between original and dummy variables:
varList=[j for j in dict]
dVarList=[eval('_dummy_'+str(j)) for j in varList]
var_to_dummy_dict={varList[j]:dVarList[j] for j in range(len(varList))}
dummy_to_var_dict={dVarList[j]:varList[j] for j in range(len(varList))}
# reformat dict for the intermediate substitution step:
intermediate_subs_dict={a:b.subs(var_to_dummy_dict) for a,b in dict.items()}
# carry out the first subs:
intermediate_expr=expr.subs(intermediate_subs_dict)
# carry out the final subs:
final_expr=intermediate_expr.subs(dummy_to_var_dict)
# delete dummy variables to clean up namespace:
for j in set(dummy_labels):
del globals()[j]
return final_expr
事实上,这个
simulSubs
函数会产生人们期望同时替换的结果。例如,我们可以用它来交换变量:
expr_with_var_interchanged=simulSubs(expr,{x:y,y:x})
但是有没有更优雅/高效/标准的解决方案来进行同时替换呢?是否有一种标准方法来检查表达式
expr
,识别我们想要替换的原始变量的所有实例,然后用各自指定的替换项替换这些实例?
最后一点,一般来说,当我们要替换的值允许是表达式而不仅仅是变量(.subs() 支持)时,同时替换并没有得到很好的定义,因为表达式可以相互包含。也许以这种方式支持更通用的表达式是 .subs() 必须顺序实现单值替换的原因。
这些问题在 here 进行了讨论,并且 SymPy 中已经可以同时替换:
from sympy.abc import x, y
>>> (x + 2*y).subs({x:y,y:x}, simultaneous=True)
2*x + y
topological_sort
中还有sympy.utilities.iterables
例程。