Repository: sympy/sympy

Issue:
why same integral gives exception under different version of Ubuntu and GCC?
I do not know if this is know or been reported before.

I noticed in a test I did that one integral gives exception now when it did not before.

Using sympy 1.10.1 on Ububtu 22.04 with GCC 11.2.0 this fails

```
>cat /etc/*-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=22.04
DISTRIB_CODENAME=jammy
DISTRIB_DESCRIPTION="Ubuntu 22.04 LTS"
PRETTY_NAME="Ubuntu 22.04 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04 LTS (Jammy Jellyfish)"

>uname -r
5.10.102.1-microsoft-standard-WSL2

>python
Python 3.10.4 (main, Apr  2 2022, 09:04:19) [GCC 11.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sympy
>>> sympy.__version__
'1.10.1'
>>> from sympy import *
>>> a,b,x=symbols('a b x')
>>> integrate(1/(b*cos(x)+a*sin(x)),x)

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/me/.local/lib/python3.10/site-packages/sympy/integrals/integrals.py", line 1566, in integrate
    return integral.doit(**doit_flags)
  File "/home/me/.local/lib/python3.10/site-packages/sympy/integrals/integrals.py", line 613, in doit
    antideriv = self._eval_integral(
  File "/home/me/.local/lib/python3.10/site-packages/sympy/integrals/integrals.py", line 1084, in _eval_integral
    h = heurisch_wrapper(g, x, hints=[])
  File "/home/me/.local/lib/python3.10/site-packages/sympy/integrals/heurisch.py", line 177, in heurisch_wrapper
    expr = heurisch(f.subs(sub_dict), x, rewrite, hints, mappings, retries,
  File "/home/me/.local/lib/python3.10/site-packages/sympy/integrals/heurisch.py", line 570, in heurisch
    v_split = _splitter(Q)
  File "/home/me/.local/lib/python3.10/site-packages/sympy/integrals/heurisch.py", line 547, in _splitter
    q_split = _splitter(cancel(q / s))
  File "/home/me/.local/lib/python3.10/site-packages/sympy/integrals/heurisch.py", line 535, in _splitter
    c, q = p.as_poly(y).primitive()
AttributeError: 'NoneType' object has no attribute 'primitive'
>>>
```

Using sympy 1.10.1 on Ubuntu 20.04 with GCC 9.4.0 same exact integrals does not evaluate but no exception. 

```
>cat /etc/*-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=20.04
DISTRIB_CODENAME=focal
DISTRIB_DESCRIPTION="Ubuntu 20.04.4 LTS"
NAME="Ubuntu"
VERSION="20.04.4 LTS (Focal Fossa)"

>uname -r

5.10.102.1-microsoft-standard-WSL2

>python
Python 3.10.4 (main, Apr  8 2022, 17:35:13) [GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sympy
>>> sympy.__version__
'1.10.1'
>>> from sympy import *
>>> a,b,x=symbols('a b x')
>>> anti=integrate(1/(b*cos(x)+a*sin(x)),x)
>>> integrate(1/(b*cos(x)+a*sin(x)),x)
Integral(1/(a*sin(x) + b*cos(x)), x)
>>>

```

Why the difference?

Using WSL 2.0 under windows 10 in both cases.


Discussion:

Patch:
diff --git a/sympy/integrals/heurisch.py b/sympy/integrals/heurisch.py
index 85361d073d..198fef5a11 100644
--- a/sympy/integrals/heurisch.py
+++ b/sympy/integrals/heurisch.py
@@ -144,13 +144,18 @@ def heurisch_wrapper(f, x, rewrite=False, hints=None, mappings=None, retries=3,
                    unnecessary_permutations, _try_heurisch)
     if not isinstance(res, Basic):
         return res
+
     # We consider each denominator in the expression, and try to find
     # cases where one or more symbolic denominator might be zero. The
     # conditions for these cases are stored in the list slns.
+    #
+    # Since denoms returns a set we use ordered. This is important because the
+    # ordering of slns determines the order of the resulting Piecewise so we
+    # need a deterministic order here to make the output deterministic.
     slns = []
-    for d in denoms(res):
+    for d in ordered(denoms(res)):
         try:
-            slns += solve(d, dict=True, exclude=(x,))
+            slns += solve([d], dict=True, exclude=(x,))
         except NotImplementedError:
             pass
     if not slns:
@@ -160,7 +165,7 @@ def heurisch_wrapper(f, x, rewrite=False, hints=None, mappings=None, retries=3,
     slns0 = []
     for d in denoms(f):
         try:
-            slns0 += solve(d, dict=True, exclude=(x,))
+            slns0 += solve([d], dict=True, exclude=(x,))
         except NotImplementedError:
             pass
     slns = [s for s in slns if s not in slns0]
@@ -477,6 +482,15 @@ def heurisch(f, x, rewrite=False, hints=None, mappings=None, retries=3,
     for g in set(terms):  # using copy of terms
         terms |= components(dcache.get_diff(g), x)
 
+    # XXX: The commented line below makes heurisch more deterministic wrt
+    # PYTHONHASHSEED and the iteration order of sets. There are other places
+    # where sets are iterated over but this one is possibly the most important.
+    # Theoretically the order here should not matter but different orderings
+    # can expose potential bugs in the different code paths so potentially it
+    # is better to keep the non-determinism.
+    #
+    # terms = list(ordered(terms))
+
     # TODO: caching is significant factor for why permutations work at all. Change this.
     V = _symbols('x', len(terms))
 


Test patch:
diff --git a/sympy/integrals/tests/test_integrals.py b/sympy/integrals/tests/test_integrals.py
index 8fde77fff4..3449b8d91e 100644
--- a/sympy/integrals/tests/test_integrals.py
+++ b/sympy/integrals/tests/test_integrals.py
@@ -5,7 +5,7 @@
 from sympy.core.expr import Expr
 from sympy.core.function import (Derivative, Function, Lambda, diff)
 from sympy.core import EulerGamma
-from sympy.core.numbers import (E, Float, I, Rational, nan, oo, pi)
+from sympy.core.numbers import (E, Float, I, Rational, nan, oo, pi, zoo)
 from sympy.core.relational import (Eq, Ne)
 from sympy.core.singleton import S
 from sympy.core.symbol import (Symbol, symbols)
@@ -1929,6 +1929,27 @@ def test_issue_18527():
     assert integrate(expr, x, manual=True) == res_real == Integral(expr, x)
 
 
+def test_issue_23718():
+    f = 1/(b*cos(x) + a*sin(x))
+    Fpos = (-log(-a/b + tan(x/2) - sqrt(a**2 + b**2)/b)/sqrt(a**2 + b**2)
+            +log(-a/b + tan(x/2) + sqrt(a**2 + b**2)/b)/sqrt(a**2 + b**2))
+    F = Piecewise(
+        # XXX: The zoo case here is for a=b=0 so it should just be zoo or maybe
+        # it doesn't really need to be included at all given that the original
+        # integrand is really undefined in that case anyway.
+        (zoo*(-log(tan(x/2) - 1) + log(tan(x/2) + 1)),  Eq(a, 0) & Eq(b, 0)),
+        (log(tan(x/2))/a,                               Eq(b, 0)),
+        (-I/(-I*b*sin(x) + b*cos(x)),                   Eq(a, -I*b)),
+        (I/(I*b*sin(x) + b*cos(x)),                     Eq(a,  I*b)),
+        (Fpos,                                          True),
+    )
+    assert integrate(f, x) == F
+
+    ap, bp = symbols('a, b', positive=True)
+    rep = {a: ap, b: bp}
+    assert integrate(f.subs(rep), x) == Fpos.subs(rep)
+
+
 def test_issue_23566():
     i = integrate(1/sqrt(x**2-1), (x, -2, -1))
     assert i == -log(2 - sqrt(3))