Search code examples
pythonsympy

Protected Division Using Sympy


A protected division is a normal division but when you divide by 0 it returns a fixed constant (usually 1).

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

Is there a way to use this as an operator on sympy (For example replacing the standard division)?

Here is an example of what I want:

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

The division has to be protected at evaluation time.

EDIT 1:

What I've tried:

1. Using sym.lambidify with the modules parameter set:

>>> 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

This does not work because sympy converts 1/x to x**(-1) when lambidifying. I tried overriding the power operator but I don't know the function name. I've tried 'Pow', 'pow', '**' and none worked.

However if i declare the expression as expr = 1.0/x it actually does not convert to a negative power, however it does not use my custom division function. I think these types of functions are not overridable using the module parameter.

2. @Zaz suggestion:

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)))

Returns

zoo

Which is complex infinity.

I tried combining this approach with sym.lambdify, but the dividend is converted to a float after I lambdify the function.

In the case that the dividend is variable it also does not work:

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

Outputs

def _lambdifygenerated(x):
    return nan*x

nan

EDIT: There seems to some confusion around why I'd want that. It's for a genetic programming algorithm using sympy. A protected division is a common operator in GP so that the created solutions are valid.


Solution

  • The solution was pretty simple actually, although I was not able to actualy overload the division operator all I had to do was create a sympy function for the protected division and use that instead.

    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
    

    Then just use that in an expression:

    >>> 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
    

    I did not find out how to make the class tell sym.lambdify what to do in case of a "lambdification", but you can use the modules parameters for that:

    >>> 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