通过组合尽可能多的自由符号来优化 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