Repository: sympy/sympy
Pull request: upgrades to manualintegrate
<!-- Your title above should be a short description of what
was changed. Do not include the issue number in the title. -->

#### References to other Issues or PRs
<!-- If this pull request fixes an issue, write "Fixes #NNNN" in that exact
format, e.g. "Fixes #1234" (see
https://tinyurl.com/auto-closing for more information). Also, please
write a comment on that issue linking back to this pull request once it is
open. -->

Fixes #19983 #14329

#### Brief description of what is fixed or changed

I dunno why no one has implemented this yet 😕 

#### Other comments

I've added screenshots of results and verifications. after initial scrutiny I'll add tests and finalise it.
![Screen Shot 2020-08-20 at 7 46 28 PM](https://user-images.githubusercontent.com/28682735/90878411-1b0a2900-e3c3-11ea-9533-035be89807ce.png)
![Screen Shot 2020-08-20 at 7 46 42 PM](https://user-images.githubusercontent.com/28682735/90878421-1d6c8300-e3c3-11ea-8ac3-2fa2d61a6aa2.png)
![Screen Shot 2020-08-20 at 7 47 31 PM](https://user-images.githubusercontent.com/28682735/90878424-1f364680-e3c3-11ea-8b7f-1614ac45f1a7.png)


#### Release Notes

<!-- Write the release notes for this release below. See
https://github.com/sympy/sympy/wiki/Writing-Release-Notes for more information
on how to write release notes. The bot will check your release notes
automatically to see if they are formatted correctly. -->

<!-- BEGIN RELEASE NOTES -->
* integrals
  * upgrades to manualintegrate to support inverse trig functions!
<!-- END RELEASE NOTES -->

Problem statement:
Sympy doesn't Integrate when expr contains asec / acsc 
```python3
>>> integrate(asin(x))
x*asin(x) + sqrt(1 - x**2)
>>> integrate(x*asin(x))
x**2*asin(x)/2 + x*sqrt(1 - x**2)/4 - asin(x)/4
>>> integrate(acos(x))
x*acos(x) - sqrt(1 - x**2)
>>> integrate(x*acos(x))
x**2*acos(x)/2 - x*sqrt(1 - x**2)/4 - acos(x)/4
>>> integrate(atan(x))
x*atan(x) - log(x**2 + 1)/2
>>> integrate(x*atan(x))
x**2*atan(x)/2 - x/2 + atan(x)/2
>>> integrate(asec(x)) # Secant inverse isn't evaluated
Integral(asec(x), x)
>>> integrate(x*asec(x)) # isn't evaluated
Integral(x*asec(x), x)
>>> integrate(acot(x))
x*acot(x) + log(x**2 + 1)/2
>>> integrate(x*acot(x))
x**2*acot(x)/2 + x/2 + acot(x)/2
>>> integrate(acsc(x)) # Cosecant inverse isn't evaluated
Integral(acsc(x), x)
>>> integrate(x*acsc(x)) # isn't evaluated
Integral(x*acsc(x), x)
>>>
```

Hints from issue discussion:
346d9f1f34ce1f729f860fd98c188a5a38efe12a I've tried to implement this in manual-integrate

![Screen Shot 2020-08-20 at 7 46 28 PM](https://user-images.githubusercontent.com/28682735/90783692-0cb60180-e31e-11ea-9890-032da2b7eb40.png)
![Screen Shot 2020-08-20 at 7 46 42 PM](https://user-images.githubusercontent.com/28682735/90783698-0e7fc500-e31e-11ea-9879-3283b87a2553.png)
![Screen Shot 2020-08-20 at 7 47 31 PM](https://user-images.githubusercontent.com/28682735/90783703-0fb0f200-e31e-11ea-9b91-cf3a28fd2fd3.png)

Commit message: upgrades to manualintegrate

Patch to review:
diff --git a/sympy/integrals/manualintegrate.py b/sympy/integrals/manualintegrate.py
index d1d61fb96250..5633db7d8867 100644
--- a/sympy/integrals/manualintegrate.py
+++ b/sympy/integrals/manualintegrate.py
@@ -535,14 +535,14 @@ def pull_out_u_rl(integrand):
 
         return pull_out_u_rl
 
-    liate_rules = [pull_out_u(sympy.log), pull_out_u(sympy.atan, sympy.asin, sympy.acos),
+    liate_rules = [pull_out_u(sympy.log), pull_out_u(sympy.atan, sympy.asin, sympy.acos, sympy.acot, sympy.acsc, sympy.asec),
                    pull_out_algebraic, pull_out_u(sympy.sin, sympy.cos),
                    pull_out_u(sympy.exp)]
 
 
     dummy = sympy.Dummy("temporary")
     # we can integrate log(x) and atan(x) by setting dv = 1
-    if isinstance(integrand, (sympy.log, sympy.atan, sympy.asin, sympy.acos)):
+    if isinstance(integrand, (sympy.log, sympy.atan, sympy.asin, sympy.acos, sympy.acsc, sympy.asec, sympy.acot)):
         integrand = dummy * integrand
 
     for index, rule in enumerate(liate_rules):
@@ -1272,6 +1272,7 @@ def key(integral):
         else:
             for cls in (sympy.Pow, sympy.Symbol, sympy.exp, sympy.log,
                         sympy.Add, sympy.Mul, sympy.atan, sympy.asin,
+                        sympy.asec, sympy.acsc, sympy.acot,
                         sympy.acos, sympy.Heaviside, OrthogonalPolynomial):
                 if isinstance(integrand, cls):
                     return cls
@@ -1312,7 +1313,9 @@ def _integral_is_subclass(integral):
                     integral_is_subclass(sympy.Mul, sympy.Pow),
                     cancel_rule),
                 condition(
-                    integral_is_subclass(sympy.Mul, sympy.log, sympy.atan, sympy.asin, sympy.acos),
+                    integral_is_subclass(sympy.Mul, sympy.log,
+                    sympy.atan, sympy.asin, sympy.acos,
+                    sympy.acsc, sympy.asec, sympy.acot),
                     parts_rule),
                 condition(
                     integral_is_subclass(sympy.Mul, sympy.Pow),
@@ -1430,6 +1433,8 @@ def eval_piecewise(substeps, integrand, symbol):
 @evaluates(TrigSubstitutionRule)
 def eval_trigsubstitution(theta, func, rewritten, substep, restriction, integrand, symbol):
     func = func.subs(sympy.sec(theta), 1/sympy.cos(theta))
+    func = func.subs(sympy.csc(theta), 1/sympy.sin(theta))
+    func = func.subs(sympy.cot(theta), 1/sympy.tan(theta))
 
     trig_function = list(func.find(TrigonometricFunction))
     assert len(trig_function) == 1


Code review comments:
diff_hunk: @@ -535,14 +535,14 @@ def pull_out_u_rl(integrand):
 
         return pull_out_u_rl
 
-    liate_rules = [pull_out_u(sympy.log), pull_out_u(sympy.atan, sympy.asin, sympy.acos),
+    liate_rules = [pull_out_u(sympy.log), pull_out_u(sympy.atan, sympy.asin, sympy.acos, sympy.acot, sympy.acsc, sympy.asec),
path: sympy/integrals/manualintegrate.py
text: @user1:
Maybe it makes sense to define a list like
```
inverse_trig_functions = [sympy.atan, ...]
```
somewhere so that it can be reused in lots of places.

@author:
Hmm yeah that would make sense