Skip to content

Commit 4e51c61

Browse files
committed
Move more fns to mpmath.eval.specialfns...
Also some results were corrected by making more use of SymPy's hyperexpand function.
1 parent c46f005 commit 4e51c61

File tree

3 files changed

+133
-33
lines changed

3 files changed

+133
-33
lines changed

mathics/builtin/specialfns/hypergeom.py

Lines changed: 61 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from mathics.core.evaluation import Evaluation
2323
from mathics.core.systemsymbols import SymbolComplexInfinity, SymbolMachinePrecision
2424
from mathics.eval.specialfns.hypergeom import (
25+
eval_Hypergeometric1F1,
2526
eval_Hypergeometric2F1,
2627
eval_HypergeometricPQF,
2728
eval_N_HypergeometricPQF,
@@ -44,30 +45,42 @@ class HypergeometricPFQ(MPMathFunction):
4445
4546
Result is symbollicaly simplified by default:
4647
>> HypergeometricPFQ[{3}, {2}, 1]
47-
= HypergeometricPFQ[{3}, {2}, 1]
48+
= 3 E / 2
49+
4850
unless a numerical evaluation is explicitly requested:
4951
>> HypergeometricPFQ[{3}, {2}, 1] // N
5052
= 4.07742
53+
5154
>> HypergeometricPFQ[{3}, {2}, 1.]
5255
= 4.07742
5356
57+
>> Plot[HypergeometricPFQ[{1, 1}, {3, 3, 3}, x], {x, -30, 30}]
58+
= -Graphics-
59+
60+
>> HypergeometricPFQ[{1, 1, 2}, {3, 3}, z]
61+
= -4 PolyLog[2, z] / z ^ 2 + 4 Log[1 - z] / z ^ 2 - 4 Log[1 - z] / z + 8 / z
62+
5463
The following special cases are handled:
5564
>> HypergeometricPFQ[{}, {}, z]
56-
= 1
65+
= E ^ z
5766
>> HypergeometricPFQ[{0}, {b}, z]
5867
= 1
59-
>> Hypergeometric1F1[b, b, z]
60-
= E ^ z
68+
69+
>> HypergeometricPFQ[{1, 1, 3}, {2, 2}, x]
70+
= -Log[1 - x] / (2 x) - 1 / (-2 + 2 x)
71+
72+
'HypergeometricPFQ' evaluates to a polynomial if any of the parameters $a_k$ is a non-positive integer:
73+
>> HypergeometricPFQ[{-2, a}, {b}, x]
74+
= (-2 a x (1 + b) + a x ^ 2 (1 + a) + b (1 + b)) / (b (1 + b))
75+
76+
Value at origin:
77+
>> HypergeometricPFQ[{a1, b2, a3}, {b1, b2, b3}, 0]
78+
= 1
6179
"""
6280

6381
attributes = A_NUMERIC_FUNCTION | A_PROTECTED | A_READ_PROTECTED
6482
mpmath_name = "hyper"
6583
nargs = {3}
66-
rules = {
67-
"HypergeometricPFQ[{}, {}, z_]": "1",
68-
"HypergeometricPFQ[{0}, b_, z_]": "1",
69-
"HypergeometricPFQ[b_, b_, z_]": "Exp[z]",
70-
}
7184
summary_text = "compute the generalized hypergeometric function"
7285
sympy_name = "hyper"
7386

@@ -96,30 +109,55 @@ class Hypergeometric1F1(MPMathFunction):
96109
<dd>returns ${}_1 F_1(a; b; z)$.
97110
</dl>
98111
99-
Result is symbollicaly simplified by default:
100-
>> Hypergeometric1F1[3, 2, 1]
101-
= HypergeometricPFQ[{3}, {2}, 1]
102-
unless a numerical evaluation is explicitly requested:
103-
>> Hypergeometric1F1[3, 2, 1] // N
104-
= 4.07742
105-
>> Hypergeometric1F1[3, 2, 1.]
106-
= 4.07742
112+
Numeric evaluation:
113+
>> Hypergeometric1F1[1, 2, 3.0]
114+
= 6.36185
107115
108-
Plot 'M'[3, 2, x] from 0 to 2 in steps of 0.5:
109-
>> Plot[Hypergeometric1F1[3, 2, x], {x, 0.5, 2}]
116+
Plot over a subset of reals:
117+
>> Plot[Hypergeometric1F1[1, 2, x], {x, -5, 5}]
110118
= -Graphics-
111-
Here, plot explicitly requests a numerical evaluation.
119+
120+
>> Plot[{Hypergeometric1F1[1/2, Sqrt[2], x], Hypergeometric1F1[1/2, Sqrt[3], x], Hypergeometric1F1[1/2, Sqrt[5], x]}, {x, -4, 4}]
121+
= -Graphics-
122+
123+
>> Plot[{Hypergeometric1F1[Sqrt[3], Sqrt[2], z], -0.01}, {z, -10, -2}]
124+
= -Graphics-
125+
126+
>> Plot[{Hypergeometric1F1[Sqrt[2], b, 1], Hypergeometric1F1[Sqrt[5], b, 1], Hypergeometric1F1[Sqrt[7], b, 1]}, {b, -3, 3}]
127+
= -Graphics-
128+
129+
Compute the elementwise values of an array:
130+
>> Hypergeometric1F1[1, 1, {{1, 0}, {0, 1}}]
131+
= {{E, 1}, {1, E}}
132+
133+
>> Hypergeometric1F1[1/2, 1, x]
134+
= BesselI[0, x / 2] E ^ (x / 2)
135+
136+
Evaluate using complex arguments:
137+
>> Hypergeometric1F1[2 + I, 2, 0.5]
138+
= 1.61833 + 0.379258 I
139+
140+
'Hypergeometric1F1' evaluates to simpler functions for certain parameters:
141+
>> Hypergeometric1F1[1/2, 1, x]
142+
= BesselI[0, x / 2] E ^ (x / 2)
143+
144+
>> Hypergeometric1F1[2, 1, x]
145+
= (1 + x) E ^ x
146+
147+
>> Hypergeometric1F1[1, 1/2, x]
148+
= -Sqrt[x] (-E ^ (-x) / Sqrt[x] - Sqrt[Pi] Erf[Sqrt[x]]) E ^ x
112149
"""
113150

114151
attributes = A_LISTABLE | A_NUMERIC_FUNCTION | A_PROTECTED
115152
mpmath_name = "hymp1f1"
116153
nargs = {3}
117-
rules = {
118-
"Hypergeometric1F1[a_, b_, z_]": "HypergeometricPFQ[{a},{b},z]",
119-
}
120154
summary_text = "compute Kummer confluent hypergeometric function"
121155
sympy_name = ""
122156

157+
def eval(self, a, b, z, evaluation: Evaluation):
158+
"Hypergeometric1F1[a_, b_, z_]"
159+
return eval_Hypergeometric1F1(a, b, z)
160+
123161

124162
class Hypergeometric2F1(MPMathFunction):
125163
"""

mathics/eval/specialfns/hypergeom.py

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,69 @@
88
import sympy
99

1010
import mathics.eval.tracing as tracing
11-
from mathics.core.atoms import Number
11+
from mathics.core.atoms import Complex, Number
1212
from mathics.core.convert.mpmath import from_mpmath
1313
from mathics.core.convert.sympy import from_sympy
1414
from mathics.core.number import RECONSTRUCT_MACHINE_PRECISION_DIGITS, dps, min_prec
1515
from mathics.core.systemsymbols import SymbolComplexInfinity
1616
from mathics.eval.arithmetic import eval_mpmath_function
1717

1818

19+
def eval_Hypergeometric1F1(a, b, z):
20+
"""Hypergeometric2F1[a_, b_, z_]
21+
22+
We use SymPy's hyper and expand the symbolic result.
23+
But if that fails to expand and all arugments are numeric, use mpmath hyp1f1.
24+
25+
We prefer SymPy because it preserves constants like E whereas mpmath will
26+
convert E to a precisioned number.
27+
"""
28+
29+
args = (a, b, z)
30+
31+
sympy_args = []
32+
all_numeric = True
33+
for arg in args:
34+
if isinstance(arg, Number):
35+
# FIXME: in the future, .value
36+
# should work on Complex numbers.
37+
if isinstance(arg, Complex):
38+
sympy_arg = arg.to_python()
39+
else:
40+
sympy_arg = arg.value
41+
else:
42+
sympy_arg = arg.to_sympy()
43+
all_numeric = False
44+
45+
sympy_args.append(sympy_arg)
46+
47+
sympy_result = tracing.run_sympy(
48+
sympy.hyper, [sympy_args[0]], [sympy_args[1]], sympy_args[2]
49+
)
50+
expanded_result = sympy.hyperexpand(sympy_result)
51+
52+
# Oddly, expansion sometimes doesn't work for when complex arguments are given.
53+
# However mpmath can handle this.
54+
# I imagine at some point in the future this will be fixed.
55+
if isinstance(expanded_result, sympy.hyper) and all_numeric:
56+
args = cast(Sequence[Number], args)
57+
58+
if any(arg.is_machine_precision() for arg in args):
59+
prec = None
60+
else:
61+
prec = min_prec(*args)
62+
if prec is None:
63+
prec = RECONSTRUCT_MACHINE_PRECISION_DIGITS
64+
d = dps(prec)
65+
args = tuple([arg.round(d) for arg in args])
66+
67+
return eval_mpmath_function(
68+
mpmath.hyp1f1, *cast(Sequence[Number], args), prec=prec
69+
)
70+
else:
71+
return from_sympy(expanded_result)
72+
73+
1974
def eval_Hypergeometric2F1(a, b, c, z):
2075
"""Hypergeometric2F1[a_, b_, c_, z_]
2176
@@ -28,7 +83,10 @@ def eval_Hypergeometric2F1(a, b, c, z):
2883
all_numeric = True
2984
for arg in args:
3085
if isinstance(arg, Number):
31-
sympy_arg = arg.value
86+
if isinstance(arg, Complex):
87+
sympy_arg = arg.to_python()
88+
else:
89+
sympy_arg = arg.value
3290
else:
3391
sympy_arg = arg.to_sympy()
3492
all_numeric = False
@@ -51,19 +109,17 @@ def eval_Hypergeometric2F1(a, b, c, z):
51109
mpmath.hyp2f1, *cast(Sequence[Number], args), prec=prec
52110
)
53111
else:
54-
sympy_result = tracing.run_sympy(
55-
sympy.hyper, [sympy_args[0], sympy_args[1]], [sympy_args[2]], sympy_args[3]
112+
return run_sympy_hyper(
113+
[sympy_args[0], sympy_args[1]], [sympy_args[2]], sympy_args[3]
56114
)
57-
return from_sympy(sympy.hyperexpand(sympy_result))
58115

59116

60117
def eval_HypergeometricPQF(a, b, z):
61118
"HypergeometricPFQ[a_, b_, z_]"
62119
try:
63120
a_sympy = [e.to_sympy() for e in a]
64121
b_sympy = [e.to_sympy() for e in b]
65-
result_sympy = tracing.run_sympy(sympy.hyper, a_sympy, b_sympy, z.to_sympy())
66-
return from_sympy(result_sympy)
122+
return run_sympy_hyper(a_sympy, b_sympy, z.to_sympy())
67123
except Exception:
68124
return None
69125

@@ -80,3 +136,9 @@ def eval_N_HypergeometricPQF(a, b, z):
80136
return SymbolComplexInfinity
81137
except Exception:
82138
return None
139+
140+
141+
def run_sympy_hyper(a, b, z):
142+
sympy_result = tracing.run_sympy(sympy.hyper, a, b, z)
143+
result = sympy.hyperexpand(sympy_result)
144+
return from_sympy(result)

test/builtin/specialfns/test_hypergeom.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,14 @@
3434
(
3535
"Hypergeometric1F1[{3,5},{7,1},{4,2}]",
3636
None,
37-
"{HypergeometricPFQ[{3}, {7}, 4], HypergeometricPFQ[{5}, {1}, 2]}",
37+
"{-1545 / 128 + 45 E ^ 4 / 128, 27 E ^ 2}",
3838
None,
3939
),
4040
("N[Hypergeometric1F1[{3,5},{7,1},{4,2}]]", None, "{7.12435, 199.505}", None),
4141
("Hypergeometric1F1[b,b,z]", None, "E ^ z", None),
42-
("HypergeometricPFQ[{6},{1},2]", None, "HypergeometricPFQ[{6}, {1}, 2]", None),
42+
("HypergeometricPFQ[{6},{1},2]", None, "719 E ^ 2 / 15", None),
4343
("N[HypergeometricPFQ[{6},{1},2]]", None, "354.182", None),
44-
("HypergeometricPFQ[{},{},z]", None, "1", None),
44+
("HypergeometricPFQ[{},{},z]", None, "E ^ z", None),
4545
("HypergeometricPFQ[{0},{c1,c2},z]", None, "1", None),
4646
("HypergeometricPFQ[{c1,c2},{c1,c2},z]", None, "E ^ z", None),
4747
],

0 commit comments

Comments
 (0)