在同情中考虑多元化

时间:2013-03-02 23:07:52

标签: python symbolic-math sympy

我正在进行一个非常简单的概率计算,从A-Z集合中得到X,Y,Z的子集(具有相应的概率x,y,z)。

由于公式很重,为了处理它们,我正在尝试简化(或收集因子 - 我不知道确切的定义)这些多项式表达式使用 sympy

所以...有这个(一个非常简单的概率计算表达式,从A-Z的集合获得X,Y,Z的子集,具有相应的概率x,y,z)

import sympy as sp

x, y, z = sp.symbols('x y z')

expression = (
    x * (1 - x) * y * (1 - x - y) * z +
    x * (1 - x) * z * (1 - x - z) * y +

    y * (1 - y) * x * (1 - y - x) * z +
    y * (1 - y) * z * (1 - y - z) * x +

    z * (1 - z) * y * (1 - z - y) * x +
    z * (1 - z) * x * (1 - z - x) * y
)

我想得到这样的东西

x * y * z * (6 * (1 - x - y - z) + (x + y) ** 2 + (y + z) ** 2 + (x + z) ** 2)

一个多边形,以尽可能少的操作(+-***,......)重写


我尝试了factor()collect()simplify()。但结果与我的期望不同。我大部分都是

2*x*y*z*(x**2 + x*y + x*z - 3*x + y**2 + y*z - 3*y + z**2 - 3*z + 3)

我知道,sympy可以多项式组合成简单的形式:

sp.factor(x**2 + 2*x*y + y**2)  # gives (x + y)**2

但是如何与上面的表达式中的组合多项式相交?


如果这是不可能的任务,可能还有其他选择吗?

3 个答案:

答案 0 :(得分:4)

这次把一些方法放在一起给出了一个很好的答案。有趣的是,看看这个策略是否比你生成的方程更常用,或者顾名思义,这次只是一个幸运的结果。

def iflfactor(eq):
    """Return the "I'm feeling lucky" factored form of eq."""
    e = Mul(*[horner(e) if e.is_Add else e for e in
        Mul.make_args(factor_terms(expand(eq)))])
    r, e = cse(e)
    s = [ri[0] for ri in r]
    e = Mul(*[collect(ei.expand(), s) if ei.is_Add else ei for ei in
        Mul.make_args(e[0])]).subs(r)
    return e

>>> iflfactor(eq)  # using your equation as eq
2*x*y*z*(x**2 + x*y + y**2 + (z - 3)*(x + y + z) + 3)
>>> _.count_ops()
15
BTW,factor_terms和gcd_terms之间的区别在于factor_terms将更加努力地提取常用术语,同时保留表达式的原始结构,非常类似于您手动执行(即在Adds中查找可以使用的常用术语)退出)。

>>> factor_terms(x/(z+z*y)+x/z)
x*(1 + 1/(y + 1))/z
>>> gcd_terms(x/(z+z*y)+x/z)
x*(y*z + 2*z)/(z*(y*z + z))

为了它的价值,

克里斯

答案 1 :(得分:1)

据我所知,没有任何功能可以做到这一点。我认为这实际上是一个非常棘手的问题。有关它的一些讨论,请参见Reduce the number of operations on a simple expression

但是,您可以尝试在SymPy中进行相当多的简化功能。你没有提到的一个给出不同结果的是gcd_terms,它可以在不进行扩展的情况下将符号gcd排除在外。它给出了

>>> gcd_terms(expression)
x*y*z*((-x + 1)*(-x - y + 1) + (-x + 1)*(-x - z + 1) + (-y + 1)*(-x - y + 1) + (-y + 1)*(-y - z + 1) + (-z + 1)*(-x - z + 1) + (-z + 1)*(-y - z + 1))

另一个有用的函数是.count_ops,它计算表达式中的操作数。例如

>>> expression.count_ops()
47
>>> factor(expression).count_ops()
22
>>> e = x * y * z * (6 * (1 - x - y - z) + (x + y) ** 2 + (y + z) ** 2 + (x + z) ** 2)
>>> e.count_ops()
18

(请注意,e.count_ops()与您自己计算的不同,因为SymPy会自动将6*(1 - x - y - z)分发到6 - 6*x - 6*y - 6*z。)

其他有用的功能:

  • cse:对表达式执行公共子表达式消除。有时您可以简化各个零件,然后将它们重新组合在一起。这通常有助于避免重复计算。

  • horner:将Horner scheme应用于多项式。如果多项式在一个变量中,这最小化了操作的数量。

  • factor_terms:与gcd_terms类似。我其实并不完全清楚区别的是什么。

请注意,默认情况下,simplify会尝试多次简化,并返回count_ops最小化的简化。

答案 2 :(得分:0)

我遇到了类似的问题,最终在偶然发现这个解决方案之前实施了自己的解决方案。矿山似乎在减少作业数量方面做得更好。但是,我的变量集合的所有组合也都采用蛮力风格的集合。因此,它的运行时变量数量呈指数增长。 OTOH,我已经设法在没有不合理的时间范围内(但远非实时)在具有7个变量的方程上运行它。

可能有一些方法可以修剪此处的某些搜索分支,但是我并没有为此而烦恼。欢迎进一步优化。

def collect_best(expr, measure=sympy.count_ops):
    # This method performs sympy.collect over all permutations of the free variables, and returns the best collection
    best = expr
    best_score = measure(expr)
    perms = itertools.permutations(expr.free_symbols)
    permlen = np.math.factorial(len(expr.free_symbols))
    print(permlen)
    for i, perm in enumerate(perms):
        if (permlen > 1000) and not (i%int(permlen/100)):
            print(i)
        collected = sympy.collect(expr, perm)
        if measure(collected) < best_score:
            best_score = measure(collected)
            best = collected
    return best

def product(args):
    arg = next(args)
    try:
        return arg*product(args)
    except:
        return arg

def rcollect_best(expr, measure=sympy.count_ops):
    # This method performs collect_best recursively on the collected terms
    best = collect_best(expr, measure)
    best_score = measure(best)
    if expr == best:
        return best
    if isinstance(best, sympy.Mul):
        return product(map(rcollect_best, best.args))
    if isinstance(best, sympy.Add):
        return sum(map(rcollect_best, best.args))

为说明性能,this paper(付费墙,很抱歉)有7个公式,它们是7个变量的5次多项式,具有最多29个项和158个扩展形式的运算。同时应用rcollect_best和@smichr的iflfactor之后,这7个公式中的运算数为:

[6, 15, 100, 68, 39, 13, 2]

[32, 37, 113, 73, 40, 15, 2]

分别。对于其中一个公式,iflfactor的操作比rcollect_best多433%。此外,扩展公式中的运算数为:

[39, 49, 158, 136, 79, 27, 2]