我正在进行一个非常简单的概率计算,从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
但是如何与上面的表达式中的组合多项式相交?
如果这是不可能的任务,可能还有其他选择吗?
答案 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]