受保护除法是普通除法,但当除以 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 中的常见运算符,因此创建的解决方案是有效的。
我们日常使用的常规数学是实数集上的环,ℝ:环的属性是您有两个运算(例如乘法和加法)以及其中一个运算(例如加法)总会在集合中产生另一个数字。
您可以通过删除 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
解决方案实际上非常简单,尽管我实际上无法重载除法运算符,但我所要做的就是为受保护的除法创建一个 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
感谢您的解决方案!我面临另一个问题。事实上,我想重载 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)
?