通过组合尽可能多的自由符号来优化 sympy 表达式评估
Optimize sympy expression evaluation by combining as many free symbols as possible
假设我有一个未知的、非常复杂的表达式,我需要对其进行反复的数值计算,例如:
my_expr = (a*b*c**2 - 2*sqrt(d*(a*b-c-e+x)))/(b - 1)
每次我重新计算表达式时,唯一改变的符号是 'x',所以预先计算所有其他符号对我来说是有意义的(我最终将使用 c 代码生成)。
所以我想要的是提前自动拉出并组合尽可能多的空闲符号,x除外。这有点像 cse,但使最终表达式包含尽可能少的计算。
例如对于以上内容,我最终可能会得到一个等同于此的系统:
var1 = a*b*c**2
var2 = a*b-c-e
var3 = b - 1
my_new_expr = (var1-2*sqrt(d*(var2+x)))/var3
这意味着我可以预先计算var1,var2 & var3,并且重复计算(my_new_expr)在计算上尽可能简单。
无论如何我可以在同情中做到这一点吗?我查看了所有简化方法等,包括收集等,none 完全满足了我的需要。如果做不到这一点,是否可以通过遍历表达式来实现此目的?
虽然我在 sympy/smichr 的 model
分支有一个更全面的解决方案,但以下内容在压缩那些 sub-expressions 不变的方面做得很好:
def condense(eq, *x):
"""collapse additive/multiplicative constants into single
variables, returning condensed expression and replacement
values.
Examples
========
Simple constants are left unchanged
>>> condense(2*x + 2, x)
(2*x + 2, {})
More complex constants are replaced by a single variable
>>> first = condense(eq, x); first
(c6*(c5 - 2*sqrt(d*(c4 + x))), {c4: a*b - c - e, c6: 1/(b - 1), c5: a*b*c**2})
If a condensed expression is expanded, there may be more simplification possible:
>>> second = condense(first[0].expand(), x); second
(c0 + c2*sqrt(c1 + d*x), {c1: c4*d, c2: -2*c6, c0: c5*c6})
>>> full_reps = {k: v.xreplace(first[1]) for k, v in second[1].items()}; full_reps
{c1: d*(a*b - c - e), c2: -2/(b - 1), c0: a*b*c**2/(b - 1)}
More than 1 variable can be designated:
>>> condense(eq, c, e)
(c4*(c**2*c1 - 2*sqrt(d*(-c + c2 - e))), {c4: 1/(b - 1), c1: a*b, c2: a*b + x})
"""
reps = {}
con = numbered_symbols('c')
free = eq.free_symbols
def c():
while True:
rv = next(con)
if rv not in free:
return rv
def do(e):
i, d = e.as_independent(*x)
if not i.args: return e
return e.func(reps.get(i, reps.setdefault(i, c())), d)
rv = eq.replace(lambda x: x.is_Add or x.is_Mul, lambda x: do(x))
reps = {v: k for k, v in reps.items()}
keep = rv.free_symbols & set(reps)
reps = {k: reps[k].xreplace(reps) for k in keep}
return rv, reps
假设我有一个未知的、非常复杂的表达式,我需要对其进行反复的数值计算,例如:
my_expr = (a*b*c**2 - 2*sqrt(d*(a*b-c-e+x)))/(b - 1)
每次我重新计算表达式时,唯一改变的符号是 'x',所以预先计算所有其他符号对我来说是有意义的(我最终将使用 c 代码生成)。
所以我想要的是提前自动拉出并组合尽可能多的空闲符号,x除外。这有点像 cse,但使最终表达式包含尽可能少的计算。
例如对于以上内容,我最终可能会得到一个等同于此的系统:
var1 = a*b*c**2
var2 = a*b-c-e
var3 = b - 1
my_new_expr = (var1-2*sqrt(d*(var2+x)))/var3
这意味着我可以预先计算var1,var2 & var3,并且重复计算(my_new_expr)在计算上尽可能简单。
无论如何我可以在同情中做到这一点吗?我查看了所有简化方法等,包括收集等,none 完全满足了我的需要。如果做不到这一点,是否可以通过遍历表达式来实现此目的?
虽然我在 sympy/smichr 的 model
分支有一个更全面的解决方案,但以下内容在压缩那些 sub-expressions 不变的方面做得很好:
def condense(eq, *x):
"""collapse additive/multiplicative constants into single
variables, returning condensed expression and replacement
values.
Examples
========
Simple constants are left unchanged
>>> condense(2*x + 2, x)
(2*x + 2, {})
More complex constants are replaced by a single variable
>>> first = condense(eq, x); first
(c6*(c5 - 2*sqrt(d*(c4 + x))), {c4: a*b - c - e, c6: 1/(b - 1), c5: a*b*c**2})
If a condensed expression is expanded, there may be more simplification possible:
>>> second = condense(first[0].expand(), x); second
(c0 + c2*sqrt(c1 + d*x), {c1: c4*d, c2: -2*c6, c0: c5*c6})
>>> full_reps = {k: v.xreplace(first[1]) for k, v in second[1].items()}; full_reps
{c1: d*(a*b - c - e), c2: -2/(b - 1), c0: a*b*c**2/(b - 1)}
More than 1 variable can be designated:
>>> condense(eq, c, e)
(c4*(c**2*c1 - 2*sqrt(d*(-c + c2 - e))), {c4: 1/(b - 1), c1: a*b, c2: a*b + x})
"""
reps = {}
con = numbered_symbols('c')
free = eq.free_symbols
def c():
while True:
rv = next(con)
if rv not in free:
return rv
def do(e):
i, d = e.as_independent(*x)
if not i.args: return e
return e.func(reps.get(i, reps.setdefault(i, c())), d)
rv = eq.replace(lambda x: x.is_Add or x.is_Mul, lambda x: do(x))
reps = {v: k for k, v in reps.items()}
keep = rv.free_symbols & set(reps)
reps = {k: reps[k].xreplace(reps) for k in keep}
return rv, reps