Skip to content

Commit 2a3fb4f

Browse files
committed
Move more functions to eval. Simplify
1 parent 0c9c25e commit 2a3fb4f

File tree

2 files changed

+105
-84
lines changed

2 files changed

+105
-84
lines changed

mathics/builtin/specialfns/hypergeom.py

Lines changed: 43 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
"""
88

99
import mpmath
10-
import sympy
1110

1211
import mathics.eval.tracing as tracing
1312
from mathics.core.attributes import (
@@ -18,13 +17,13 @@
1817
)
1918
from mathics.core.builtin import MPMathFunction
2019
from mathics.core.convert.mpmath import from_mpmath
21-
from mathics.core.convert.sympy import from_sympy
2220
from mathics.core.evaluation import Evaluation
2321
from mathics.core.systemsymbols import SymbolComplexInfinity, SymbolMachinePrecision
2422
from mathics.eval.specialfns.hypergeom import (
2523
eval_Hypergeometric1F1,
2624
eval_Hypergeometric2F1,
2725
eval_HypergeometricPQF,
26+
eval_MeijerG,
2827
eval_N_HypergeometricPQF,
2928
)
3029

@@ -202,6 +201,47 @@ def eval(self, a, b, c, z, evaluation: Evaluation):
202201
return eval_Hypergeometric2F1(a, b, c, z)
203202

204203

204+
class HypergeometricU(MPMathFunction):
205+
"""
206+
<url>
207+
:Confluent hypergeometric function: https://en.wikipedia.org/wiki/Confluent_hypergeometric_function</url> (<url>
208+
:mpmath: https://mpmath.org/doc/current/functions/bessel.html#mpmath.hyperu</url>, <url>
209+
:WMA: https://reference.wolfram.com/language/ref/HypergeometricU.html</url>)
210+
<dl>
211+
<dt>'HypergeometricU'[$a$, $b$, $z$]
212+
<dd>returns $U(a, b, z)$.
213+
</dl>
214+
Result is symbollicaly simplified, where possible:
215+
>> HypergeometricU[3, 2, 1]
216+
= MeijerG[{{-2}, {}}, {{0, -1}, {}}, 1] / 2
217+
>> HypergeometricU[1,4,8]
218+
= HypergeometricU[1, 4, 8]
219+
unless a numerical evaluation is explicitly requested:
220+
>> HypergeometricU[3, 2, 1] // N
221+
= 0.105479
222+
>> HypergeometricU[3, 2, 1.]
223+
= 0.105479
224+
225+
Plot 'U'[3, 2, x] from 0 to 10 in steps of 0.5:
226+
>> Plot[HypergeometricU[3, 2, x], {x, 0.5, 10}]
227+
= -Graphics-
228+
229+
We handle this special case:
230+
>> HypergeometricU[0, b, z]
231+
= 1
232+
"""
233+
234+
attributes = A_LISTABLE | A_NUMERIC_FUNCTION | A_PROTECTED | A_READ_PROTECTED
235+
mpmath_name = "hyperu"
236+
nargs = {3}
237+
rules = {
238+
"HypergeometricU[0, c_, z_]": "1",
239+
"HypergeometricU[a_, b_, z_] /; (a > 0) && (a-b+1 > 0)": "MeijerG[{{1-a},{}},{{0,1-b},{}},z]/Gamma[a]/Gamma[a-b+1]",
240+
}
241+
summary_text = "compute the Tricomi confluent hypergeometric function"
242+
sympy_name = ""
243+
244+
205245
class MeijerG(MPMathFunction):
206246
"""
207247
<url>
@@ -232,15 +272,7 @@ class MeijerG(MPMathFunction):
232272

233273
def eval(self, a, b, z, evaluation: Evaluation):
234274
"MeijerG[a_, b_, z_]"
235-
try:
236-
a_sympy = [[e2.to_sympy() for e2 in e1] for e1 in a]
237-
b_sympy = [[e2.to_sympy() for e2 in e1] for e1 in b]
238-
result_sympy = tracing.run_sympy(
239-
sympy.meijerg, a_sympy, b_sympy, z.to_sympy()
240-
)
241-
return from_sympy(result_sympy)
242-
except Exception:
243-
pass
275+
return eval_MeijerG(a, b, z)
244276

245277
def eval_N(self, a, b, z, prec, evaluation: Evaluation):
246278
"N[MeijerG[a_, b_, z_], prec_]"
@@ -257,44 +289,3 @@ def eval_N(self, a, b, z, prec, evaluation: Evaluation):
257289
def eval_numeric(self, a, b, z, evaluation: Evaluation):
258290
"MeijerG[a:{___List?(AllTrue[#, NumericQ, Infinity]&)}, b:{___List?(AllTrue[#, NumericQ, Infinity]&)}, z_?MachineNumberQ]"
259291
return self.eval_N(a, b, z, SymbolMachinePrecision, evaluation)
260-
261-
262-
class HypergeometricU(MPMathFunction):
263-
"""
264-
<url>
265-
:Confluent hypergeometric function: https://en.wikipedia.org/wiki/Confluent_hypergeometric_function</url> (<url>
266-
:mpmath: https://mpmath.org/doc/current/functions/bessel.html#mpmath.hyperu</url>, <url>
267-
:WMA: https://reference.wolfram.com/language/ref/HypergeometricU.html</url>)
268-
<dl>
269-
<dt>'HypergeometricU'[$a$, $b$, $z$]
270-
<dd>returns $U(a, b, z)$.
271-
</dl>
272-
Result is symbollicaly simplified, where possible:
273-
>> HypergeometricU[3, 2, 1]
274-
= MeijerG[{{-2}, {}}, {{0, -1}, {}}, 1] / 2
275-
>> HypergeometricU[1,4,8]
276-
= HypergeometricU[1, 4, 8]
277-
unless a numerical evaluation is explicitly requested:
278-
>> HypergeometricU[3, 2, 1] // N
279-
= 0.105479
280-
>> HypergeometricU[3, 2, 1.]
281-
= 0.105479
282-
283-
Plot 'U'[3, 2, x] from 0 to 10 in steps of 0.5:
284-
>> Plot[HypergeometricU[3, 2, x], {x, 0.5, 10}]
285-
= -Graphics-
286-
287-
We handle this special case:
288-
>> HypergeometricU[0, b, z]
289-
= 1
290-
"""
291-
292-
attributes = A_LISTABLE | A_NUMERIC_FUNCTION | A_PROTECTED | A_READ_PROTECTED
293-
mpmath_name = "hyperu"
294-
nargs = {3}
295-
rules = {
296-
"HypergeometricU[0, c_, z_]": "1",
297-
"HypergeometricU[a_, b_, z_] /; (a > 0) && (a-b+1 > 0)": "MeijerG[{{1-a},{}},{{0,1-b},{}},z]/Gamma[a]/Gamma[a-b+1]",
298-
}
299-
summary_text = "compute the Tricomi confluent hypergeometric function"
300-
sympy_name = ""

mathics/eval/specialfns/hypergeom.py

Lines changed: 62 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
Evaluation functions for built-in Hypergeometric functions
33
"""
44

5-
from typing import Sequence, cast
5+
from typing import Sequence, Tuple, cast
66

77
import mpmath
88
import sympy
@@ -27,23 +27,7 @@ def eval_Hypergeometric1F1(a, b, z):
2727
"""
2828

2929
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-
30+
sympy_args, all_numeric = to_sympy_with_classification(args)
4731
sympy_result = tracing.run_sympy(
4832
sympy.hyper, [sympy_args[0]], [sympy_args[1]], sympy_args[2]
4933
)
@@ -79,20 +63,7 @@ def eval_Hypergeometric2F1(a, b, c, z):
7963
"""
8064

8165
args = (a, b, c, z)
82-
sympy_args = []
83-
all_numeric = True
84-
for arg in args:
85-
if isinstance(arg, Number):
86-
if isinstance(arg, Complex):
87-
sympy_arg = arg.to_python()
88-
else:
89-
sympy_arg = arg.value
90-
else:
91-
sympy_arg = arg.to_sympy()
92-
all_numeric = False
93-
94-
sympy_args.append(sympy_arg)
95-
66+
sympy_args, all_numeric = to_sympy_with_classification(args)
9667
if all_numeric:
9768
args = cast(Sequence[Number], args)
9869

@@ -138,7 +109,66 @@ def eval_N_HypergeometricPQF(a, b, z):
138109
return None
139110

140111

112+
def eval_MeijerG(a, b, z):
113+
"""
114+
Use sympy.meijerg to compute MeijerG(a, b, z)
115+
"""
116+
try:
117+
a_sympy = [[e2.to_sympy() for e2 in e1] for e1 in a]
118+
b_sympy = [[e2.to_sympy() for e2 in e1] for e1 in b]
119+
result_sympy = tracing.run_sympy(sympy.meijerg, a_sympy, b_sympy, z.to_sympy())
120+
# For now, we don't allow simplification and conversion
121+
# to other functions like Bessel, because this can introduce
122+
# SymPy's exp_polar() function for which we don't have a
123+
# Mathics3 equivalent for yet.
124+
# return from_sympy(sympy.hyperexpand(result_sympy))
125+
return from_sympy(result_sympy)
126+
except Exception:
127+
return None
128+
129+
130+
# FIXME: ADDING THIS causes test/doc/test_common.py to fail! It reports that Hypergeometric has been included more than once
131+
# Weird!
132+
133+
# def eval_N_MeijerG(a, b, z):
134+
# "N[MeijerG[a_, b_, z_], prec_]"
135+
# try:
136+
# result_mpmath = tracing.run_mpmath(
137+
# mpmath.meijerg, a.to_python(), b.to_python(), z.to_python()
138+
# )
139+
# return from_mpmath(result_mpmath)
140+
# except ZeroDivisionError:
141+
# return SymbolComplexInfinity
142+
# except Exception:
143+
# return None
144+
145+
141146
def run_sympy_hyper(a, b, z):
142147
sympy_result = tracing.run_sympy(sympy.hyper, a, b, z)
143148
result = sympy.hyperexpand(sympy_result)
144149
return from_sympy(result)
150+
151+
152+
def to_sympy_with_classification(args: tuple) -> Tuple[list, bool]:
153+
"""Converts `args` to its corresponding SymPy form. However, if
154+
all elements of args are numeric, then we detect and report that.
155+
We do this so that the caller can decide whether to use mpmath if
156+
SymPy fails. One might expect SymPy to do this automatically, but
157+
it doesn't catch all opportunites.
158+
"""
159+
sympy_args = []
160+
all_numeric = True
161+
for arg in args:
162+
if isinstance(arg, Number):
163+
# FIXME: in the future, .value
164+
# should work on Complex numbers.
165+
if isinstance(arg, Complex):
166+
sympy_arg = arg.to_python()
167+
else:
168+
sympy_arg = arg.value
169+
else:
170+
sympy_arg = arg.to_sympy()
171+
all_numeric = False
172+
173+
sympy_args.append(sympy_arg)
174+
return sympy_args, all_numeric

0 commit comments

Comments
 (0)