Search code examples
pythonsympypowexponentiation

What are the differences between a**b and pow(a,b), in symbolic calculation


EDIT: added assert as suggested by Wrzlprmft, which does not raise any exception.

I have to write code with many expressions of type a**b, under the following circumstances

import sympy as sp
a, b = sp.symbols('a,b', real=True, positive=True)

expr1 = a**b
expr2 = pow(a,b)

assert expr1 is expr2

What are the expected/possible differences between a**b and pow(a,b)? (e.g., when simplifying, collecting, etc., expressions).
Should I expect possible wrong results in any case?

So far, I found none.
I don't think I would use the third argument of pow as my bases will be real (exponents will typically be integers or ratios between one- or two-digit integers), so this or this are not relevant.
This or this are focused on comparisons with math.pow, although some answers also include both ** and pow in comparisons of performance, showing minor differences. None of the questions / answers deal with sympy.

If math.pow or sympy.Pow provide any advantage in any case, I welcome the clarifications. I don't foresee I would perform heavy computations, so minor performance differences will likely not impact my use case.


Solution

  • In your context, a**b and pow(a,b) are as identical as they can possibly be: They refer to the very same object:

    import sympy as sp
    a, b = sp.symbols('a,b', real=True, positive=True)
    
    X = a**b
    Y = pow(a,b)
    
    assert X is Y
    

    is checks for object identity in Python. While such a check is rarely what you need for practical applications, it makes for the easiest answer to your question. (Note that Python objects can be equivalent in every respect, but still be different objects. For example, you shouldn’t rely on object identity being given in future SymPy implementations.)

    Moreover, the Python documentation specifies:

    The two-argument form pow(base, exp) is equivalent to using the power operator: base**exp.

    Thus, the two are the same for any arguments per language definition, i.e., pow(a,b) simply calls a.__pow__(b) (which is what a**b does under the hood). I don’t want to dig into the respective source code now, but consider this:

    class foo(object):
        def __init__(self,name):
            self.name = name
        
        def __pow__(self,other):
            print(f"{self.name}.__pow__({other.name}) called")
    
    a = foo("a")
    b = foo("b")
    
    a**b         # a.__pow__(b) called
    pow(a,b)     # a.__pow__(b) called
    a.__pow__(b) # a.__pow__(b) called