Why does SymPy not simplify (-x**3)**(2/3) to x**2?

Viewed 119

I'm trying to use SymPy to evaluate the following integral: $$ \int_{lower}^{0} $$

When evaluating by hand, the answer is −½ log(28).

My work matches up with SymPy until I integrate with respect to x:

x, y = sp.symbols('x y', real=True)
z = 1 / (sp.root(y, 3)*(x**3+1))
iz = z.integrate((y, -x**3, 0)) # integrate with respect to y
print(iz)
# -3*(-x**3)**(2/3)/(2*(x**3 + 1))
iiz = iz.integrate((x, 0, 3)) # integrate with respect to x
print(iiz)
# -3*Integral((-x**3)**(2/3)/(x**3 + 1), (x, 0, 3))/2
print(sp.N(iiz))
# 0.833051127543801 - 1.4428868782084*I

It seems what’s throwing SymPy off is (-x**3)**(2/3). This should simplify to x**2 but SymPy doesn’t think so. Manually simplifying, produces the same answer I got by hand:

print( sp.integrate(-3*x**2/(2*(x**3 + 1)), (x, 0, 3)) )
# -log(28)/2

Is there a better way to approach this?

2 Answers

Your problem is that sympy.root by default returns the principal root, not the real root. To avoid this, you can use the third optional argument of sympy.root to specify that you would like the real root. The following produces the desired result:

import sympy as sp

x, y = sp.symbols('x y', real=True)
z = 1 / (sp.root(y,3,1)*(x**3+1))
iz = z.integrate((y, -x**3, 0))
iiz = iz.integrate((x, 0, 3))
print(iiz)
# -log(28)/2

To somewhat address your titular question, (-x**3)**(2/3) is actually (-x**3)**0.666666666666667 because that’s a Python fraction you have there. To have something closer to what you want, you need to do:

import sympy as sp
x = sp.symbols('x', positive=True)
solution = (-x**3)**sp.Rational(2,3)
print(solution)
# (-1)**(2/3)*x**2

In general, I would recommend to avoid rational powers unless you really need to account for them in all their multiple solutions, complexity, etc.

In my isympy session: SymPy 1.6.2

In [131]: z = 1 / (root(y,3)*(x**3+1))

In [132]: iz = z.integrate((y, -x**3, 0))

In [133]: iiz = iz.integrate((x,0,3))

In [134]: iiz
Out[134]: 
     2/3         
-(-1)   ⋅log(28) 
─────────────────
        2        

In [135]: N(iiz)
Out[135]: 0.833051127543801 - 1.4428868782084⋅ⅈ

In [136]: abs(iiz)
Out[136]: 
log(28)
───────
   2   

The root docs talk about returning the principal root, and in addition to providing a k parameter, suggest using real_root:

In [137]: z = 1 / (real_root(y,3)*(x**3+1))

In [138]: iz = z.integrate((y, -x**3, 0))

In [139]: iiz = iz.integrate((x,0,3))

In [140]: iiz
Out[140]: 
-log(28) 
─────────
    2    

In [141]: N(iiz)
Out[141]: -1.66610225508760

So evidently the double integral has multiple solutions, depending on the root. Looks like they all have the same magnitude. That sounds reasonable, but my complex math studies were in the distant past, so I can't provide a theoretical justification.

and with k=2 we get a third solution:

In [146]: z = 1 / (root(y,3,2)*(x**3+1))

In [147]: iz = z.integrate((y, -x**3, 0))

In [148]: iiz = iz.integrate((x,0,3))

    In [149]: iiz
    Out[149]: 
    3 ____        
    ╲╱ -1 ⋅log(28)
    ──────────────
          2     

So there are 3 solutions in the complex plane, with multipliers, -1, (-1)**(1/3), -(-1)**(2/3), and the same magnitude.

-1.66610225508760
0.833051127543801 - 1.4428868782084⋅ⅈ
0.833051127543801 + 1.4428868782084⋅ⅈ

If we introduce an integer symbol k into z:

In [158]: z = 1 / (root(y,3,k)*(x**3+1))

In [159]: z
Out[159]: 
      -2⋅k    
      ─────   
        3     
  (-1)        
──────────────
3 ___ ⎛ 3    ⎞
╲╱ y ⋅⎝x  + 1⎠

the double integral becomes:

In [164]: iiz =z.integrate((y, -x**3,0)).integrate((x,0,3))

In [165]: iiz
Out[165]: 
             -2⋅k          
             ─────         
     2/3       3           
-(-1)   ⋅(-1)     ⋅log(28) 
───────────────────────────
             2             

and doing iiz.subs({k:0}) etc, produces the above complex solutions.

Related