部分分解Sympy中的表达式

时间:2018-08-27 20:53:05

标签: python sympy

假设我有一个First Form形式的表达式。我知道我可以像这样简化表达式:Second Form。但是,sympy.simplifysympy.factor都返回原始表达式。

要解决此问题,我一直在较低级别上对该表达式进行操作:

factor_map = defaultdict(set)
additive_terms = expr.as_coeff_add()[-1]
for term1, term2 in combinations(additive_terms, 2):
    common_terms = (
        set(term1.as_coeff_mul()[-1])
        & set(term2.as_coeff_mul()[-1])
    )
    if common_terms:
        common_factor = sympy.Mul(*common_terms)
        factor_map[common_factor] |= {term1, term2}

factor_map现在看起来像这样:

{
    a: {a⋅x, -a⋅y}, 
    b: {b⋅x, -b⋅y}, 
    c: {-c⋅x, c⋅y}, 
    x: {a⋅x, b⋅x, -c⋅x}, 
    y: {-a⋅y, -b⋅y, c⋅y}
}

我按照术语表示的操作次数对其进行排序:

factor_list = sorted(
    factor_map.items(),
    key = lambda i: (i[0].count_ops() + 1) * len(i[1])
)[::-1]

然后我重新构建表达式:

used = set()
new_expr = 0
for item in factor_list:
    factor = item[0]
    appearances = item[-1]
    terms = 0
    for instance in appearances:
        if instance not in used:
            terms += instance.as_coefficient(factor)
            used.add(instance)
    new_expr += factor * terms
for term in set(additive_terms) - used:
    new_expr += term

这得到new_expr = d + x*(a + b - c) + y*(-a - b + c)。不是很好,但是更好。

我还可以通过将加法项的每种组合相互除法,检查结果是否为数字并使用该信息将输出进一步减少到new_expr = d + (x - y)*(a + b - c)来进行改进。

我还尝试将sympy.factor应用于加法项的每种可能组合,但显然,对于任何相当大的表达式,它都会迅速爆炸。


编辑:以下是在一组附加项的所有分区(从this answer借用的分区函数)上使用sympy.factor的实现:

def partition(collection):
    if len(collection) == 1:
        yield [ collection ]
        return

    first = collection[0]
    for smaller in partition(collection[1:]):
        # insert `first` in each of the subpartition's subsets
        for n, subset in enumerate(smaller):
            yield smaller[:n] + [[ first ] + subset]  + smaller[n+1:]
        # put `first` in its own subset 
        yield [ [ first ] ] + smaller


def partial_factor(expr):
    args = list(expr.as_coeff_add()[-1])
    # Groupings is just a cache to avoid duplicating factor operations
    groupings = {}

    unique = set()

    for p in partition(args):
        new_expr = 0
        for group in p:
            add_group = sympy.Add(*group)
            new_expr += groupings.setdefault(add_group, sympy.factor(add_group))
        unique.add(new_expr)
    return sorted(unique, key=sympy.count_ops)[0]

对于像a*x + b*y + c*z + d + e*x + f*y + h*z这样的表达式,在我的计算机上运行需要7.8秒,而另一种方法则需要378微秒,并且得出相同的结果。似乎应该有一种方法比第一种方法更严格,而无需花费2万倍的时间来解决它。


我觉得得到我想要的东西并不难。有更简单的方法吗?

2 个答案:

答案 0 :(得分:2)

This similar question的答案涉及func的{​​{1}}参数。尽管您必须明确提到collect()

d

这在特定情况下有帮助,但是不存在更通用和自动的解决方案。

除了现有的from sympy import * a, b, c, d, x, y = symbols('a, b, c, d, x, y') expr = a * x + b * x - c * x - a * y - b * y + c * y + d collect(expr, d, func=factor) => d + (x - y)*(a + b - c) 参数之外,我也找不到任何文档。

Github issue tracking this problem

答案 1 :(得分:1)

很难建议一种在大多数时间都有效的“部分分解”策略。考虑到您的示例(这是一个由多个变量组成的多项式),在设计时可以尝试一下。

给出一个表达式:尝试将其分解。如果不成功,请查看其包含的每个符号的系数。方法Expr.coeff(Symbol)做到了。系数最小的符号(通过包含的符号数来衡量)被认为是分解的障碍,因此已从表达式中删除。重复。

此逻辑在下面进行了编码,partial_factor(a*x + b*x - c*x - a*y - b*y + c*y + d)确实返回了d + (x - y)*(a + b - c)

def partial_factor(expr):
    to_factor = expr
    non_factorable = 0
    while to_factor != 0:
        if factor(to_factor) != to_factor:
            return factor(to_factor) + non_factorable
        coeffs = {v: to_factor.coeff(v) for v in to_factor.free_symbols}
        min_size = min([len(coeffs[v].free_symbols) for v in coeffs])
        for v in coeffs:
            if len(coeffs[v].free_symbols) == min_size:
                non_factorable += v*coeffs[v]
                to_factor -= v*coeffs[v]
                break
    return expr