Skip to content

Commit 516f3a3

Browse files
authored
Merge pull request #1144 from mathics/constant-method-option
Add Method option to N[]
2 parents e38eb25 + f1830d1 commit 516f3a3

6 files changed

Lines changed: 124 additions & 55 deletions

File tree

mathics/builtin/assignment.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
#!/usr/bin/env python3
21
# -*- coding: utf-8 -*-
32

4-
53
import mathics.builtin
64
from mathics.builtin.base import (
75
Builtin, BinaryOperator, PostfixOperator, PrefixOperator)
@@ -848,7 +846,7 @@ def _get_usage_string(symbol, evaluation, htmlout=False):
848846
bio = pymathics.get(definition.name)
849847
if bio is None:
850848
bio = builtins.get(definition.name)
851-
849+
852850
if bio is not None:
853851
from mathics.doc.doc import Doc
854852
docstr = bio.builtin.__class__.__doc__

mathics/builtin/constants.py

Lines changed: 107 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
Symbol,
2020
strip_context,
2121
)
22-
from mathics.core.numbers import get_precision, PrecisionValueError
22+
from mathics.core.numbers import get_precision, PrecisionValueError, machine_precision
2323

2424

2525
def mp_constant(fn: str, d=None) -> mpmath.ctx_mp_python.mpf:
@@ -29,7 +29,11 @@ def mp_constant(fn: str, d=None) -> mpmath.ctx_mp_python.mpf:
2929
if d is None:
3030
return getattr(mpmath, fn)()
3131
else:
32-
mpmath.mp.dps = int_d = int(d)
32+
# TODO: In some functions like Pi, you can
33+
# ask for a certain number of digits, but the
34+
# accuracy will be less than that. Figure out
35+
# what's up and compensate somehow.
36+
mpmath.mp.dps = int_d = int(d * 3.321928)
3337
return getattr(mpmath, fn)(prec=int_d)
3438

3539

@@ -43,7 +47,16 @@ def mp_convert_constant(obj, **kwargs):
4347

4448

4549
def numpy_constant(name: str, d=None) -> float:
46-
return getattr(numpy, name)
50+
if d:
51+
# by mmatera: Here I have a question:
52+
# 0.0123`2 should be rounded to
53+
# 0.01 or to 0.0123?
54+
# (absolute versus relative accuracy)
55+
val = getattr(numpy, name)
56+
val = numpy.round(val, d)
57+
return val
58+
else:
59+
return getattr(numpy, name)
4760

4861

4962
def sympy_constant(fn, d=None):
@@ -54,46 +67,75 @@ class _Constant_Common(Predefined):
5467

5568
attributes = ("Constant", "Protected", "ReadProtected")
5669
nargs = 0
70+
options = {"Method": "Automatic"}
5771

58-
def apply_N(self, precision, evaluation):
59-
"N[%(name)s, precision_]"
60-
return self.get_constant(precision, evaluation)
72+
def apply_N(self, precision, evaluation, options={}):
73+
"N[%(name)s, precision_?NumericQ, OptionsPattern[%(name)s]]"
74+
75+
preference = self.get_option(options, "Method", evaluation).get_string_value()
76+
if preference == "Automatic":
77+
return self.get_constant(precision, evaluation)
78+
else:
79+
return self.get_constant(precision, evaluation, preference)
80+
81+
def apply_N2(self, evaluation, options={}):
82+
"N[%(name)s, OptionsPattern[%(name)s]]"
83+
return self.apply_N(None, evaluation, options)
6184

6285
def is_constant(self) -> bool:
6386
return True
6487

6588
def get_constant(self, precision, evaluation, preference=None):
66-
## print("XXX", self, preference)
67-
if preference is None:
68-
preference = (
69-
evaluation.parse("Settings`$PreferredBackendMethod")
70-
.evaluate(evaluation)
71-
.get_string_value()
72-
)
73-
# TODO: validate PreferredBackendMethod is in "mpmath", "numpy", "sympy"
74-
try:
75-
d = get_precision(precision, evaluation)
76-
except PrecisionValueError:
77-
d = None
89+
# first, determine the precision
90+
machine_d = int( 0.30103 * machine_precision)
91+
d = None
92+
if precision:
93+
try:
94+
d = get_precision(precision, evaluation)
95+
except PrecisionValueError:
96+
pass
7897

79-
conversion_fn = MachineReal if d is None else PrecisionReal
80-
81-
# print("XXX1", self, preference, conversion_fn)
98+
if d is None:
99+
d = machine_d
82100

83-
if preference == "sympy" and hasattr(self, "sympy_name"):
84-
value = sympy_constant(self.sympy_name, d)
85-
elif preference == "mpmath" and hasattr(self, "mpmath_name"):
86-
value = mp_constant(self.mpmath_name, d)
87-
elif preference == "numpy" and hasattr(self, "numpy_name"):
88-
value = numpy_constant(self.numpy_name)
89-
elif hasattr(self, "mpmath_name"):
90-
value = mp_constant(self.mpmath_name, d)
91-
elif hasattr(self, "sympy_name"):
92-
value = sympy_constant(self.sympy_name, d)
93-
elif hasattr(self, "numpy_name"):
101+
# If preference not especified, determine it
102+
# from the precision.
103+
if preference is None:
104+
if d <= machine_d:
105+
preference = "numpy"
106+
else:
107+
preference = "mpmath"
108+
# If preference is not valid, send a message and return.
109+
if not (preference in ("sympy", "numpy", "mpmath")):
110+
evaluation.message(f'{preference} not in ("sympy", "numpy", "mpmath")')
111+
return
112+
# Try to determine the numeric value
113+
value = None
114+
if preference == "mpmath" and not hasattr(self, "mpmath_name"):
115+
preference = "numpy"
116+
elif preference == "sympy" and not hasattr(self, "sympy_name"):
117+
preference = "numpy"
118+
119+
if preference == "numpy" and not hasattr(self, "numpy_name"):
120+
if hasattr(self, "sympy_name"):
121+
preference = "sympy"
122+
elif hasattr(self, "mpmath_name"):
123+
preference = "mpmath"
124+
else:
125+
preference = ""
126+
if preference == "numpy":
94127
value = numpy_constant(self.numpy_name)
95-
return conversion_fn(value)
96-
128+
if d == machine_d:
129+
return MachineReal(value)
130+
if preference == "sympy":
131+
value = sympy_constant(self.sympy_name, d+2)
132+
if preference == "mpmath":
133+
value = mp_constant(self.mpmath_name, d*2)
134+
if value:
135+
return PrecisionReal(sympy.Float(str(value), d))
136+
# If the value is not available, return none
137+
# and keep it unevaluated.
138+
return
97139

98140
class MPMathConstant(_Constant_Common):
99141
"""Representation of a constant in mpmath, e.g. Pi, E, I, etc."""
@@ -152,22 +194,22 @@ def to_sympy(self, expr=None, **kwargs):
152194
return None
153195

154196

155-
class Catalan(MPMathConstant, NumpyConstant, SympyConstant):
197+
class Catalan(MPMathConstant, SympyConstant):
156198
"""
157199
<dl>
158200
<dt>'Catalan'
159201
<dd>is Catalan's constant with numerical value \u2243 0.915966.
160202
</dl>
161203
162204
>> Catalan // N
163-
= 0.915966
205+
= 0.915965594177219
164206
165207
>> N[Catalan, 20]
166208
= 0.91596559417721901505
167209
"""
168210

169211
mpmath_name = "catalan"
170-
numpy_name = "catalan"
212+
# numpy_name = "catalan" ## This is not defined in numpy
171213
sympy_name = "Catalan"
172214

173215

@@ -239,10 +281,13 @@ def to_numpy(self, expr=None, **kwargs):
239281
# return mpmath.degree
240282
return numpy.pi / 180
241283

242-
def apply_N(self, precision, evaluation):
243-
"N[Degree, precision_]"
284+
def apply_N(self, precision, evaluation, options={}):
285+
"N[Degree, precision_, OptionsPattern[%(name)s]]"
244286
try:
245-
d = get_precision(precision, evaluation)
287+
if precision:
288+
d = get_precision(precision, evaluation)
289+
else:
290+
d = get_precision(Symbol("System`MachinePrecision"), evaluation)
246291
except PrecisionValueError:
247292
return
248293

@@ -278,8 +323,8 @@ class E(MPMathConstant, NumpyConstant, SympyConstant):
278323
numpy_name = "e"
279324
sympy_name = "E"
280325

281-
def apply_N(self, precision, evaluation):
282-
"N[E, precision_]"
326+
def apply_N(self, precision, evaluation, options={}):
327+
"N[E, precision_, OptionsPattern[%(name)s]]"
283328
return self.get_constant(precision, evaluation)
284329

285330

@@ -310,9 +355,10 @@ class Glaisher(MPMathConstant):
310355
</dl>
311356
312357
>> N[Glaisher]
313-
= 1.28243
358+
= 1.28242712910062
314359
>> N[Glaisher, 50]
315-
= 1.2824271291006219541941391071304678916931152343750
360+
= 1.2824271291006226368753425688697917277676889273250
361+
# 1.2824271291006219541941391071304678916931152343750
316362
"""
317363

318364
mpmath_name = "glaisher"
@@ -326,7 +372,7 @@ class GoldenRatio(MPMathConstant, SympyConstant):
326372
</dl>
327373
328374
>> GoldenRatio // N
329-
= 1.61803
375+
= 1.61803398874989
330376
>> N[GoldenRatio, 40]
331377
= 1.618033988749894848204586834365638117720
332378
"""
@@ -399,9 +445,10 @@ class Khinchin(MPMathConstant):
399445
</dl>
400446
401447
>> N[Khinchin]
402-
= 2.68545
448+
= 2.68545200106531
403449
>> N[Khinchin, 50]
404-
= 2.6854520010653075701156922150403261184692382812500
450+
= 2.6854520010653064453097148354817956938203822939945
451+
# = 2.6854520010653075701156922150403261184692382812500
405452
"""
406453

407454
mpmath_name = "khinchin"
@@ -416,7 +463,19 @@ class Pi(MPMathConstant, SympyConstant):
416463
417464
>> N[Pi]
418465
= 3.14159
419-
>> N[Pi, 50]
466+
467+
Force using the value given from numpy to compute Pi.
468+
>> N[Pi, Method->"numpy"]
469+
= 3.14159
470+
471+
Force using the value given from sympy to compute Pi to 3 places,
472+
two places after the decimal point.
473+
474+
Note that sympy is the default method.
475+
>> N[Pi, 3, Method->"sympy"]
476+
= 3.14
477+
478+
>> N[Pi, 50]
420479
= 3.1415926535897932384626433832795028841971693993751
421480
>> Attributes[Pi]
422481
= {Constant, Protected, ReadProtected}

mathics/builtin/linalg.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -739,7 +739,7 @@ def mp_eig(mp_matrix) -> Expression:
739739
return Expression("List", *eigenvalues)
740740

741741
options = {
742-
"Method": "Sympy",
742+
"Method": "sympy",
743743
}
744744

745745
def apply(self, m, evaluation, options={}) -> Expression:

mathics/builtin/system.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,7 @@ class UserName(Predefined):
401401
</dl>
402402
403403
X> $UserName
404-
= rocky
404+
= ...
405405
"""
406406

407407
name = "$UserName"

mathics/doc/doc.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
#!/usr/bin/env python3
21
# -*- coding: utf-8 -*-
32

43
import re

mathics/test.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,19 @@ def fail(why):
9090
info = sys.exc_info()
9191
sys.excepthook(*info)
9292
return False
93-
93+
if False:
94+
print("out=-----------------")
95+
for rr in out:
96+
for line in rr.text.splitlines():
97+
print(" <",line,">")
98+
print("wanted_out=-------------------")
99+
for rr in wanted_out:
100+
for line in rr.text.splitlines():
101+
print(" <",line,">")
102+
print("---------------------------------")
103+
94104
if not compare(result, wanted):
105+
print("result =!=wanted")
95106
fail_msg = "Result: %s\nWanted: %s" % (result, wanted)
96107
if out:
97108
fail_msg += "\nAdditional output:\n"
@@ -102,6 +113,8 @@ def fail(why):
102113
output_ok = False
103114
else:
104115
for got, wanted in zip(out, wanted_out):
116+
if False:
117+
print("got=<",got,"> wanted=<",wanted,">")
105118
if not got == wanted:
106119
output_ok = False
107120
break

0 commit comments

Comments
 (0)