如何在解决另一个分段函数得到的分段函数中对ExprCondPair进行排序?

时间:2018-05-19 19:49:30

标签: python sympy solver

让我们考虑以下示例:

import sympy as sym

x, y = sym.symbols(['x', 'y'])

cdf = sym.Piecewise((0, y < 0), 
                    (y, y < 1), 
                    (2*y - 1, y <= 2), 
                    (3, True))
eq = sym.Eq(x, cdf)
inverse = sym.solve(eq, y, rational=False)  # rational prevents buggy exception
print(inverse)

输出:

[Piecewise((x, x < 1), (nan, True)), 
 Piecewise((x/2 + 1/2, x/2 + 1/2 <= 2), (nan, True))]

使用以下功能可以轻松将其转换为单个分段:

from typing import List

def recreate_piecewise(functions: List[sym.Piecewise]) -> sym.Piecewise:
    """Collects Piecewise from a list of Piecewise functions"""
    return sym.Piecewise(*[piecewise.args[0]
                           for piecewise in functions])


print(recreate_piecewise(inverse))

输出:

Piecewise((x, x < 1), (x/2 + 1/2, x/2 + 1/2 <= 2))

此分段条件的根是 1 3 。这些根的排序从最小到最大

当解决任何其他Piecewise函数时,我希望它们的解决方案的部分以相同的方式排序。但不幸的是,事实并非如此。

这可以通过以下示例显示:

cdf = sym.Piecewise((0, y < 4.3), 
                    (y - 4.3, y < 12.9), 
                    (5*y - 55.9, y <= 13.5), 
                    (11.6, True))
eq = sym.Eq(x, cdf)
inverse = sym.solve(eq, y, rational=False)
print(recreate_piecewise(inverse))

输出:

Piecewise((x/5 + 11.18, x/5 + 11.18 <= 13.5), 
          (x + 4.3, x + 4.3 < 12.9))

这里的根是 11.6 8.6 ,这是一个不同的顺序。

问题:
如何将Piecewise解决方案按顺序排序?

我尝试了什么:
我已经实现了以下帮助函数。该解决方案有效,但遗憾的是并非适用于所有情况。此外,我觉得我在这里使用了太多变通办法,而且有些事情可以做得更容易。

from sympy.functions.elementary.piecewise import ExprCondPair


def replace_inequalities(expression: sym.Expr) -> sym.Expr:
    """Replaces <, <=, >, >= by == in expression"""
    conditions = [sym.Lt, sym.Le, sym.Gt, sym.Ge]
    for condition in conditions:
        expression = expression.replace(condition, sym.Eq)
    return expression


def piecewise_part_condition_root(expression_condition: ExprCondPair) -> float:
    """Returns a root of inequality part"""
    condition = expression_condition[1]
    equation = replace_inequalities(condition)
    return sym.solve(equation, x)[0]


def to_be_sorted(piecewise: sym.Function) -> bool:
    """Checks if elements of Piecewise have to be sorted"""
    first_line = piecewise.args[0]
    last_line = piecewise.args[-1]

    first_root = piecewise_part_condition_root(first_line)
    last_root = piecewise_part_condition_root(last_line)

    return last_root < first_root


def sort_piecewise(piecewise: sym.Piecewise) -> sym.Piecewise:
    """Inverts the order of elements in Piecewise"""
    return sym.Piecewise(*[part
                           for part in piecewise.args[::-1]])

对于第一个和第二个例子,它都可以工作 第一个:

cdf = sym.Piecewise((0, y < 0), 
                    (y, y < 1), 
                    (2*y - 1, y <= 2), 
                    (3, True))
eq = sym.Eq(x, cdf)
inverse = sym.solve(eq, y, rational=False)
inverse = recreate_piecewise(inverse)

if to_be_sorted(inverse):
    inverse = sort_piecewise(inverse)
print(inverse)

输出:

Piecewise((x, x < 1), 
          (x/2 + 1/2, x/2 + 1/2 <= 2))

第二个:

cdf = sym.Piecewise((0, y < 4.3), 
                    (y - 4.3, y < 12.9), 
                    (5*y - 55.9, y <= 13.5), 
                    (11.6, True))
eq = sym.Eq(x, cdf)
inverse = sym.solve(eq, y, rational=False)
inverse = recreate_piecewise(inverse)

if to_be_sorted(inverse):
    inverse = sort_piecewise(inverse)
print(inverse)    

输出:

Piecewise((x + 4.3, x + 4.3 < 12.9), 
          (x/5 + 11.18, x/5 + 11.18 <= 13.5))

但是,如果我举一个解决方案包含LambertW函数的例子,我的方法将会失败:

def to_lower_lambertw_branch(*args) -> sym.Function:
    """
    Wraps the first argument from a given list of arguments
    as a lower branch of LambertW function.
    """
    return sym.LambertW(args[0], -1)


def replace_lambertw_branch(expression: sym.Function) -> sym.Function:
    """
    Replaces upper branch of LambertW function with the lower one.
    For details of the bug see:
    https://stackoverflow.com/questions/49817984/sympy-solve-doesnt-give-one-of-the-solutions-with-lambertw
    Solution is based on the 2nd example from:
    http://docs.sympy.org/latest/modules/core.html?highlight=replace#sympy.core.basic.Basic.replace
    """
    return expression.replace(sym.LambertW,
                              to_lower_lambertw_branch)


cdf = sym.Piecewise((0, y <= 0.0), 
                    ((-y - 1)*sym.exp(-y) + 1, y <= 10.0), 
                    (0.999500600772613, True))
eq = sym.Eq(x, cdf)
# Intermediate results are in inline comments
inverse = sym.solve(eq, y, rational=False)  # [Piecewise((-LambertW((x - 1)*exp(-1)) - 1, -LambertW((x - 1)*exp(-1)) - 1 <= 10.0), (nan, True))]
inverse = recreate_piecewise(inverse)  # Piecewise((-LambertW((x - 1)*exp(-1)) - 1, -LambertW((x - 1)*exp(-1)) - 1 <= 10.0))
inverse = replace_lambertw_branch(inverse)  # Piecewise((-LambertW((x - 1)*exp(-1), -1) - 1, -LambertW((x - 1)*exp(-1), -1) - 1 <= 10.0))

if to_be_sorted(inverse):  # -> this throws an error
    inverse = sort_piecewise(inverse)
print(inverse)    

在标记的行上会抛出错误:

---------------------------------------------------------------------------
NotImplementedError                       Traceback (most recent call last)
<ipython-input-8-4ebf5c3828fb> in <module>()
     28 inverse = replace_lambertw_branch(inverse)  # Piecewise((-LambertW((x - 1)*exp(-1), -1) - 1, -LambertW((x - 1)*exp(-1), -1) - 1 <= 10.0))
     29 
---> 30 if to_be_sorted(inverse):  # -> this throws error
     31     inverse = sort_piecewise(inverse)
     32 print(inverse)

<ipython-input-5-d8df4b6ed407> in to_be_sorted(piecewise)
     22     last_line = piecewise.args[-1]
     23 
---> 24     first_root = piecewise_part_condition_root(first_line)
     25     last_root = piecewise_part_condition_root(last_line)
     26 

<ipython-input-5-d8df4b6ed407> in piecewise_part_condition_root(expression_condition)
     14     condition = expression_condition[1]
     15     equation = replace_inequalities(condition)
---> 16     return sym.solve(equation, x)[0]
     17 
     18 

~/.local/lib/python3.6/site-packages/sympy/solvers/solvers.py in solve(f, *symbols, **flags)
   1063     ###########################################################################
   1064     if bare_f:
-> 1065         solution = _solve(f[0], *symbols, **flags)
   1066     else:
   1067         solution = _solve_system(f, symbols, **flags)

~/.local/lib/python3.6/site-packages/sympy/solvers/solvers.py in _solve(f, *symbols, **flags)
   1632 
   1633     if result is False:
-> 1634         raise NotImplementedError('\n'.join([msg, not_impl_msg % f]))
   1635 
   1636     if flags.get('simplify', True):

NotImplementedError: 
No algorithms are implemented to solve equation -LambertW((x - 1)*exp(-1), -1) - 11

1 个答案:

答案 0 :(得分:2)

一种方法是以数字方式(JPanel)求解断点,这可能足以用于排序。另一个利用CDF增加函数的事实是按y的值而不是x的值排序;也就是说,你在nsolve分段中得到的不等式的右边。第二个例子说明了:

inverse

打印

cdf = sym.Piecewise((0, y < 4.3), (y - 4.3, y < 12.9), (5*y - 55.9, y <= 13.5), (11.6, True))
inverse = sym.solve(sym.Eq(x, cdf), y, rational=False)
solutions = [piecewise.args[0] for piecewise in inverse]
solutions.sort(key=lambda case: case[1].args[1])
print(sym.Piecewise(*solutions))

这应该适用于任何递增函数,因为y值的递增顺序与x值的递增顺序相匹配。