New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Integral with float exponent leads has incorrect sign #23494
Comments
The bug seems to be in In [32]: G = -meijerg(((1.5,), (1,)), ((0, 0), ()), x**2*exp_polar(I*pi))
In [33]: F = hyperexpand(G)
In [34]: G
Out[34]:
╭─╮2, 1 ⎛1.5 1 │ 2 ⅈ⋅π⎞
-│╶┐ ⎜ │ x ⋅ℯ ⎟
╰─╯2, 2 ⎝0, 0 │ ⎠
In [35]: F
Out[35]:
⎛ │ 1 ⎞
1.0 ┌─ ⎜-0.5, -0.5 │ ──⎟
-7.08981540362206⋅ⅈ⋅x ⋅ ├─ ⎜ │ 2⎟
2╵ 1 ⎝ 0.5 │ x ⎠
In [36]: G.subs(x, 0.5).n()
Out[36]: 3.19702813586035 - 11.1366559936634⋅ⅈ
In [37]: F.subs(x, 0.5).n()
Out[37]: -3.19702813586035 - 11.1366559936634⋅ⅈ When the input to the G function is rational a different expansion is found: In [41]: Gr = -meijerg(((Rational(3, 2),), (1,)), ((0, 0), ()), x**2*exp_polar(I*pi))
In [42]: Gr
Out[42]:
╭─╮2, 1 ⎛3/2 1 │ 2 ⅈ⋅π⎞
-│╶┐ ⎜ │ x ⋅ℯ ⎟
╰─╯2, 2 ⎝0, 0 │ ⎠
In [43]: print(hyperexpand(Gr))
-4*I*sqrt(pi)*x*(((1/4 - 1/(4*x**2))*(1 - 1/x**2) + (1/2 - 1/(2*x**2))*(1 + 1/(2*x**2)) + 1/4 - 1/(4*x**2))*Piecewise((I/sqrt(-1 + x**(-2)), 1/Abs(x**2) > 1), (1/sqrt(1 - 1/x**2), True)) + Piecewise((I*acosh(sqrt(x**(-2)))/sqrt(x**(-2)) + pi/(2*sqrt(x**(-2))), 1/Abs(x**2) > 1), (asin(sqrt(x**(-2)))/sqrt(x**(-2)), True))/x**2) This expansion is undefined at In [44]: N(Gr.subs(x, 1) - Gr.subs(x, 1/sqrt(2)))
Out[44]: -1.23551948433479 - 0.e-20⋅ⅈ
In [45]: Fr = hyperexpand(Gr)
In [46]: N(Fr.subs(x, 1) - Fr.subs(x, 1/sqrt(2)))
Out[46]: nan
In [47]: N(Fr.limit(x, 1) - Fr.limit(x, 1/sqrt(2)))
Out[47]: -1.23551948433479 + 0.e-21⋅ⅈ Presumably that's how the integral succeeds when the exponent is rational. I think the problem is maybe somewhere around here: sympy/sympy/simplify/hyperexpand.py Lines 2309 to 2316 in 68c37df
At least it is that call to _hyperexpand that seems to return different things depending on whether the input is float or rational.
|
The lookup for sympy/sympy/simplify/hyperexpand.py Lines 853 to 855 in 68c37df
In the debugger: (Pdb) p inv
(0, ((0.500000000000000, 2),), ((0.500000000000000, 1),))
(Pdb) p sizes
(2, 1)
(Pdb) p list(self.concrete_formulae[sizes])
[(0, ((0, 2),), ((0, 1),)), (0, ((0, 1), (1/2, 1)), ((1/2, 1),)), (0, ((1/2, 2),), ((1/2, 1),)), (0, ((0, 2),), ((1/2, 1),)), (0, ((1/2, 2),), ((0, 1),))]
(Pdb) p inv in self.concrete_formulae[sizes]
False
(Pdb) p inv in list(self.concrete_formulae[sizes])
True The dict list lookup succeeds and the float inv compares equal to the entry However the fallback case when a formula isn't found should still be able to return a result with the correct sign. The fallback path goes through |
This is a way to reproduce the bug without using floats: In [74]: G = meijerg(((Rational(1, 2),), (1,)), ((0, 0), ()), x*exp_polar(I*pi))
In [75]: F = hyperexpand(G)
In [76]: G
Out[76]:
╭─╮2, 1 ⎛1/2 1 │ ⅈ⋅π⎞
│╶┐ ⎜ │ x⋅ℯ ⎟
╰─╯2, 2 ⎝0, 0 │ ⎠
In [77]: F
Out[77]:
┌─ ⎛1/2, 1/2 │ 1⎞
-2⋅ⅈ⋅√π⋅ ├─ ⎜ │ ─⎟
2╵ 1 ⎝ 3/2 │ x⎠
────────────────────────────
√x
In [78]: G.subs(x, -1).n()
Out[78]: 3.12438801679839
In [79]: F.subs(x, -1).n()
Out[79]: -3.12438801679839 |
Actually I realise I used this diff when running that: diff --git a/sympy/simplify/hyperexpand.py b/sympy/simplify/hyperexpand.py
index aa0a534..5f93211 100644
--- a/sympy/simplify/hyperexpand.py
+++ b/sympy/simplify/hyperexpand.py
@@ -850,9 +850,9 @@ def lookup_origin(self, func):
"""
inv = func.build_invariants()
sizes = func.sizes
- if sizes in self.concrete_formulae and \
- inv in self.concrete_formulae[sizes]:
- return self.concrete_formulae[sizes][inv]
+ #if sizes in self.concrete_formulae and \
+ # inv in self.concrete_formulae[sizes]:
+ # return self.concrete_formulae[sizes][inv]
# We don't have a concrete formula. Try to instantiate.
if sizes not in self.symbolic_formulae: Without the diff In [1]: G = meijerg(((Rational(1, 2),), (1,)), ((0, 0), ()), x*exp_polar(I*pi))
In [2]: F = hyperexpand(G)
In [3]: G
Out[3]:
╭─╮2, 1 ⎛1/2 1 │ ⅈ⋅π⎞
│╶┐ ⎜ │ x⋅ℯ ⎟
╰─╯2, 2 ⎝0, 0 │ ⎠
In [4]: F
Out[4]:
⎛⎧ ⎛ ___⎞ ⎞
⎜⎪ ⎜ ╱ 1 ⎟ ⎟
⎜⎪ⅈ⋅acosh⎜ ╱ ─ ⎟ ⎟
⎜⎪ ⎝╲╱ x ⎠ π 1 ⎟
⎜⎪──────────────── + ───────── for ─── > 1⎟
⎜⎪ ___ ___ │x│ ⎟
⎜⎪ ╱ 1 ╱ 1 ⎟
⎜⎪ ╱ ─ 2⋅ ╱ ─ ⎟
⎜⎪ ╲╱ x ╲╱ x ⎟
-2⋅ⅈ⋅√π⋅⎜⎨ ⎟
⎜⎪ ⎛ ___⎞ ⎟
⎜⎪ ⎜ ╱ 1 ⎟ ⎟
⎜⎪ asin⎜ ╱ ─ ⎟ ⎟
⎜⎪ ⎝╲╱ x ⎠ ⎟
⎜⎪ ───────────── otherwise ⎟
⎜⎪ ___ ⎟
⎜⎪ ╱ 1 ⎟
⎜⎪ ╱ ─ ⎟
⎝⎩ ╲╱ x ⎠
─────────────────────────────────────────────────────
√x
In [5]: G.subs(x, -1).n()
Out[5]: 3.12438801679839
In [6]: F.subs(x, -1).n()
Out[6]: -3.12438801679839 |
These issues are caused by the multiple values of G-functions on branch cuts. Most G-functions are branched at 0 and infinity, and also at either 1 or -1 if the numbers of a-parameters and b-parameters are the same. I have not studied the code in detail but it seems that mpmath implements G-functions with branch cuts along the negative real axis and also along (1, oo) if 1 is a branch point. mpmath will return one of the boundary values but that may be the wrong one. SymPy has a (partial) solution to the problem: HyperRep functions whose arguments are polar numbers. Using Representations by HyperRep functions are currently implemented only for exact parameter values. There is no polar number support for plain sympy/sympy/simplify/hyperexpand.py Lines 2397 to 2408 in 918eb81
One way to implement that seems to be this:
|
Does that just prevent hyperexpand from converting a G function into anything involving hyper? |
No, it just prevents the hyper components from being analytically continued outside the unit disk in case the continuation is not unique, The hyper components may be included in the output by setting |
That seems reasonable. To be clear is the rationale for that something like: With MeijerG it is possible to control the desired branch using
The problem as described above seems to be purely to do with sympy's hyperexpand. Is there also a problem with mpmath's definition/implementation of MeijerG or hyper? |
G-functions are defined by contour integrals over mpmath does not implement G-functions by means of these integrals; instead, it computes their values by means of (generalized) hypergeometric functions ( If the number p of a-parameters of a G-function is different from the number q of its b-parameters, then (the principal branch of) the function is well defined with branch cut (-oo, 0). If p = q, there is no single contour defining (the principal branch of) a G-function, in general. The integral over the loop passing through +oo converges if |z| < 1, and can be represented by A. Use the series converging in the unit disk and extend them analytically to the whole plane. There will be a branch cut along (1, oo), if the argument of the B. Use the series converging in the exterior of the unit disk and extend then analytically to the disk. This will introduce a possible branch cut (0,1) (extending (-oo, 0)). This is the C. Use the series converging in the unit disk if |z| < 1 and the series converging in its exterior if |z| > 1. There will be branch cuts on the unit circle if 1 is a branch point. From the code it is seen that mpmath favors A in case p = q. B is not used and C is only used if EDIT: Link to mpmath code corrected. |
From SO:
https://stackoverflow.com/questions/72223270/sympy-evaluates-integral-sign-wrong
With a float exponent
0.5
the integral comes out with the wrong sign:The integral seems to be evaluated by meijerg so I guess that the bug is in there somewhere.
The text was updated successfully, but these errors were encountered: