使用 Sympy 保护分区

问题描述 投票:0回答:3

受保护除法是普通除法,但当除以 0 时,它会返回固定常数(通常为 1)。

def protected_div(x, y):
    if y == 0:
        return 1
    return x/y

有没有办法在 sympy 上使用它作为运算符(例如替换标准除法)?

这是我想要的示例:

>>> import sympy as sym
>>> x = sym.Symbol('x')
>>> expr = 1/x #(protected division goes here?)
>>> expr.subs(x, 0)
1

在评估时必须保护该部门。

编辑1:

我尝试过的:

1. 将 sym.lambidify 与模块参数集一起使用:

>>> x = sym.Symbol('x')
>>> expr = 1/x
>>> lamb = sym.lambdify(x, expr, modules={'/':protected_div})
>>> print(lamb(0))
ZeroDivisionError: 0.0 cannot be raised to a negative power

这不起作用,因为 sympy 在兰比化时会将

1/x
转换为
x**(-1)
。我尝试覆盖幂运算符,但我不知道函数名称。我尝试过“Pow”、“pow”、“**”,但都不起作用。

但是,如果我将表达式声明为

expr = 1.0/x
它实际上不会转换为负幂,但它不使用我的自定义除法函数。我认为这些类型的函数不能使用
module
参数覆盖。

2. @Zaz建议:

class floatsafe(float):
    def __truediv__(self, __x):
        if __x == 0:
            return floatsafe(1)
        return super().__truediv__(__x)

x = sym.Symbol('x')
expr = floatsafe(1)/x
print(expr.subs(x, floatsafe(0)))

退货

zoo

这是复无穷大。

我尝试将此方法与

sym.lambdify
结合使用,但在我对函数进行羔羊化后,股息被转换为浮点数。

在股息可变的情况下,它也不起作用:

x = sym.Symbol('x')
expr = x/0.0
a = sym.lambdify(x, expr, modules={'/':floatsafe.__truediv__})
print(inspect.getsource(a))
print(a(floatsafe(0)))

输出

def _lambdifygenerated(x):
    return nan*x

nan

编辑:对于我为什么想要这个似乎有些困惑。它用于使用 sympy 的遗传编程算法。受保护的除法是 GP 中的常见运算符,因此创建的解决方案是有效的。

python sympy
3个回答
1
投票

我们日常使用的常规数学是实数集上的环,ℝ:环的属性是您有两个运算(例如乘法和加法)以及其中一个运算(例如加法)总会在集合中产生另一个数字。

您可以通过删除 0 或将集合扩展到超实数来创建更具体的字段概念(这样两个操作总是会在集合中生成另一个成员)。

我的观点是,在不知道您到底要解决什么问题的情况下,我猜想与其重新定义除法,不如重新定义您正在使用的数字系统更有意义:无论出于何种原因,您都有一些系统除以零时应返回 1 的数字,那么为什么不创建

float
的子类呢?

class floatD01(float):
    def __truediv__(self, divisor):
        if divisor == 0:
            return 1
        return self/divisor

您可能还想扫描

help(float)
查找您可能想要更改的与除法相关的任何其他方法,例如
__divmod__
__floordiv__
(
7//3 == 2
) 等,并认真思考您想要的方式您正在创建的新数学小组及其工作原因。

其他可能更强大的选项是去核并尝试捕获所有

ZeroDivisionError
并将它们替换为一个(通过修改类)或在您正在运行的任何代码中,或者如果合适的话,实现就像语言
R
广泛使用的东西:
NA
值。我确信有某种方法(我相信
numpy
)可以做一些类似的事情:
C = [1/3, 2/2, 3/1, 4/0] # == [1/3, 2/2, 3/1, NA]
sum(C) = 4.333


1
投票

解决方案实际上非常简单,尽管我实际上无法重载除法运算符,但我所要做的就是为受保护的除法创建一个 sympy 函数并使用它。

class protected_division(sym.Function):
    @classmethod
    def eval(cls, x, y):
        if y.is_Number:
            if y.is_zero:
                return sym.S.One
            else:
                return x/y

然后在表达式中使用它:

>>> expr = protected_division(1, sym.Symbol('x'))
protected_division(1, x)
>>> expr.subs(sym.Symbol('x'), 0)
1
>>> expr.subs(sym.Symbol('x'), 3)
1/3

我没有找到如何让班级告诉

sym.lambdify
在“lambdification”的情况下该怎么做,但你可以使用
modules
参数:

>>> def pd(x, y):
...     if y == 0:
...         return 1
...     return x/y
... 
>>> l = sym.lambdify(sym.Symbol('x'), expr, modules={'protected_division': pd})
>>> l(3)
1.6666666666666667
>>> l(0)
1

0
投票

感谢您的解决方案!我面临另一个问题。事实上,我想重载 sympy 中的除法运算符来处理除以零。

上下文如下:我有一些表达式,通过

sp.simplify
进行了简化。因此,sympy 可能会将表达式简化为出现除法运算符
/
的表达式。例如考虑这些代码行:

import sympy as sp
x = sp.Symbol('x', real=True)
expr = 5*sp.exp(-1*sp.log(x))
print(expr)

打印

5/x
。我设法“覆盖”像
log
这样的函数,因为我只是使用了你的方法并且它有效:

import math

# protected log function for evaluation
def pl(x):
    if x == 0:
        return x
    if x < 0:
        return math.log(-x)
    else:
        return math.log(x)

expr = sp.log(x)
f = sp.lambdify(x, expr, modules={'log': pl})
print(f(2))

按预期打印

0.6931471805599453
。我认为这是有效的,因为
log
出现在表达式中。对于
pow
来说,情况有点复杂。我必须使用
ccode
功能:

# protected pow function
def pp(x, p):
    if not isinstance(p, int) and x < 0:
        return math.pow(-x, p)
    if p < 0 and x == 0:
        return 0
    return math.pow(x, p)

expr = sp.Pow(x, 5.5)
print(expr)
f = sp.lambdify(x, expr, modules={'pow': pp})
g = sp.lambdify(x, sp.ccode(expr), modules={'pow': pp})
print(f(-2), g(-2))

打印出

x**5.5 pow(x, 5.5)
(-1.1087038706409865e-13-45.254833995939045j) 45.254833995939045
。因此,对于
log
exp
pow
函数,我设法在评估时覆盖。但对于除法运算符我没有。
sp.ccode(5/x)
5/x
打印相同。我希望
5/x
显示为
div(5, x)
那么我可以使用
module
关键字参数在评估时覆盖。我理解 sympy 变换
/
Mul
Pow
函数的组合。但是如何使这些运算符(
/
+
-
*
)看起来像
ccode
x**5
中那样给出
pow(x, 5)

© www.soinside.com 2019 - 2024. All rights reserved.