Skip to content

Commit e32dc23

Browse files
authored
Merge pull request #1046 from mathics/improve-to-python
WIP Add PythonForm and use sympy numeric constants
2 parents 3373ec9 + 59269ba commit e32dc23

7 files changed

Lines changed: 120 additions & 11 deletions

File tree

mathics/builtin/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ def is_builtin(var):
6161

6262
# builtins = dict(builtins)
6363

64-
mathics_to_sympy = {}
64+
mathics_to_sympy = {} # here we have: name -> sympy object
65+
mathics_to_python = {} # here we have: name -> string
6566
sympy_to_mathics = {}
6667

6768
box_constructs = {}
@@ -72,6 +73,10 @@ def is_builtin(var):
7273
def add_builtins(new_builtins):
7374
for var_name, builtin in new_builtins:
7475
name = builtin.get_name()
76+
if hasattr(builtin, "python_equivalent"):
77+
# print("XXX", builtin.python_equivalent)
78+
mathics_to_python[name] = builtin.python_equivalent
79+
7580
if isinstance(builtin, SympyObject):
7681
mathics_to_sympy[name] = builtin
7782
for sympy_name in builtin.get_sympy_names():

mathics/builtin/arithmetic.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import sympy
1313
import mpmath
14+
import math
1415

1516
from mathics.builtin.base import (
1617
Builtin,
@@ -1034,6 +1035,7 @@ class Infinity(SympyConstant):
10341035
"""
10351036

10361037
sympy_name = "oo"
1038+
python_equivalent = math.inf
10371039

10381040
rules = {
10391041
"Infinity": "DirectedInfinity[1]",
@@ -1338,6 +1340,8 @@ class I(Predefined):
13381340
= 10
13391341
"""
13401342

1343+
python_equivalent = 1j
1344+
13411345
def evaluate(self, evaluation):
13421346
return Complex(Integer(0), Integer(1))
13431347

mathics/builtin/base.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ def has_option(options, name, evaluation):
5151
return get_option(options, name, evaluation, evaluate=False) is not None
5252

5353

54+
mathics_to_python = {}
55+
5456
class Builtin(object):
5557
name: typing.Optional[str] = None
5658
context = "System`"
@@ -75,6 +77,8 @@ def __new__(cls, *args, **kwargs):
7577

7678
def __init__(self, *args, **kwargs):
7779
super(Builtin, self).__init__()
80+
if hasattr(self, "python_equivalent"):
81+
mathics_to_python[self.get_name()] = self.python_equivalent
7882

7983
def contribute(self, definitions, is_pymodule=False):
8084
from mathics.core.parser import parse_builtin_rule

mathics/builtin/exptrig.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ def apply_N(self, precision, evaluation):
5353
else:
5454
return PrecisionReal(sympy.pi.n(d))
5555

56-
5756
class E(SympyConstant):
5857
"""
5958
<dl>

mathics/builtin/inout.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from mathics.builtin.lists import list_boxes
2121
from mathics.builtin.options import options_to_rules
2222
from mathics.core.expression import (
23-
Expression, String, Symbol, Integer, Real, BoxError,
23+
Expression, String, StringFromPython, Symbol, Integer, Real, BoxError,
2424
from_python, MachineReal, PrecisionReal)
2525
from mathics.core.numbers import (
2626
dps, convert_base, machine_precision, reconstruct_digits)
@@ -1938,6 +1938,42 @@ def apply_mathml(self, expr, evaluation) -> Expression:
19381938
return Expression('RowBox', Expression('List', String(mathml)))
19391939

19401940

1941+
class PythonForm(Builtin):
1942+
"""
1943+
<dl>
1944+
<dt>'PythonForm[$expr$]'
1945+
<dd>returns an approximate equivalent of $expr$ in Python, when that is possible. We assume
1946+
that Python has sympy imported. No explicit import will be include in the result.
1947+
</dl>
1948+
1949+
>> PythonForm[Infinity]
1950+
= math.inf
1951+
>> PythonForm[Pi]
1952+
= sympy.pi
1953+
>> E // PythonForm
1954+
= sympy.E
1955+
>> {1, 2, 3} // PythonForm
1956+
= [1, 2, 3]
1957+
"""
1958+
# >> PythonForm[HoldForm[Sqrt[a^3]]]
1959+
# = sympy.sqrt{a**3} # or something like this
1960+
1961+
1962+
def apply_python(self, expr, evaluation) -> Expression:
1963+
'MakeBoxes[expr_, PythonForm]'
1964+
1965+
try:
1966+
# from trepan.api import debug; debug()
1967+
python_equivalent = expr.to_python(python_form = True)
1968+
except:
1969+
return
1970+
return StringFromPython(python_equivalent)
1971+
1972+
def apply(self, expr, evaluation) -> Expression:
1973+
"PythonForm[expr_]"
1974+
return self.apply_python(expr, evaluation)
1975+
1976+
19411977
class TeXForm(Builtin):
19421978
r"""
19431979
<dl>

mathics/core/expression.py

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
# -*- coding: utf-8 -*-
44

55

6+
import ast
67
import sympy
78
import mpmath
89
import math
10+
import inspect
911
import re
1012

1113
import typing
@@ -135,10 +137,10 @@ def __ge__(self, other) -> bool:
135137
return self.get_sort_key() >= other.get_sort_key()
136138

137139
def __eq__(self, other) -> bool:
138-
return self.get_sort_key() == other.get_sort_key()
140+
return hasattr(other, "get_sort_key") and self.get_sort_key() == other.get_sort_key()
139141

140142
def __ne__(self, other) -> bool:
141-
return self.get_sort_key() != other.get_sort_key()
143+
return (not hasattr(other, "get_sort_key")) or self.get_sort_key() != other.get_sort_key()
142144

143145

144146
# ExpressionCache keeps track of the following attributes for one Expression instance:
@@ -937,20 +939,32 @@ def to_python(self, *args, **kwargs):
937939
numbers -> Python number
938940
If kwarg n_evaluation is given, apply N first to the expression.
939941
"""
942+
from mathics.builtin.base import mathics_to_python
940943

941944
n_evaluation = kwargs.get('n_evaluation')
942945
if n_evaluation is not None:
943946
value = Expression('N', self).evaluate(n_evaluation)
944947
return value.to_python()
945948
head_name = self._head.get_name()
946-
if head_name == 'System`List':
947-
return [leaf.to_python(*args, **kwargs) for leaf in self._leaves]
948949
if head_name == 'System`DirectedInfinity' and len(self._leaves) == 1:
949950
direction = self._leaves[0].get_int_value()
950951
if direction == 1:
951-
return float('inf')
952+
return math.inf
952953
if direction == -1:
953-
return -float('inf')
954+
return -math.inf
955+
elif head_name == 'System`List':
956+
return [leaf.to_python(*args, **kwargs) for leaf in self._leaves]
957+
if head_name in mathics_to_python:
958+
py_obj = mathics_to_python[head_name]
959+
# Start here
960+
# if inspect.isfunction(py_obj) or inspect.isbuiltin(py_obj):
961+
# args = [leaf.to_python(*args, **kwargs) for leaf in self._leaves]
962+
# return ast.Call(
963+
# func=py_obj.__name__,
964+
# args=args,
965+
# keywords=[],
966+
# )
967+
return py_obj
954968
return self
955969

956970
def get_sort_key(self, pattern_sort=False):
@@ -1785,8 +1799,10 @@ def to_python(self, *args, **kwargs):
17851799
value = Expression('N', self).evaluate(n_evaluation)
17861800
return value.to_python()
17871801

1788-
# return name as string (Strings are returned with quotes)
1789-
return self.name
1802+
if kwargs.get("python_form", False):
1803+
return self.to_sympy(**kwargs)
1804+
else:
1805+
return self.name
17901806

17911807
def default_format(self, evaluation, form) -> str:
17921808
return self.name
@@ -2614,6 +2630,19 @@ def __getnewargs__(self):
26142630
return (self.value,)
26152631

26162632

2633+
class StringFromPython(String):
2634+
def __new__(cls, value):
2635+
self = super(StringFromPython, cls).__new__(cls, value)
2636+
if isinstance(value, sympy.NumberSymbol):
2637+
self.value = "sympy." + str(value)
2638+
2639+
# Note that the test is done with math.inf first.
2640+
# This is to use float's ==, which may not strictly be necessary.
2641+
if math.inf == value:
2642+
self.value = "math.inf"
2643+
return self
2644+
2645+
26172646
def get_default_value(name, evaluation, k=None, n=None):
26182647
pos = []
26192648
if k is not None:

test/test_to_python.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# -*- coding: utf-8 -*-
2+
from .helper import session, check_evaluation
3+
4+
import math
5+
import sympy
6+
import sys
7+
from mathics.core.parser import parse, SingleLineFeeder
8+
from mathics.core.definitions import Definitions
9+
from mathics.core.evaluation import Evaluation
10+
import pytest
11+
12+
13+
def test_to_infinity():
14+
for str_expr, str_expected, message in (
15+
(
16+
"PythonForm[Infinity]",
17+
'"math.inf"',
18+
"Infinity",
19+
),
20+
(
21+
"PythonForm[{1, 2, 3, 4}]",
22+
'"[1, 2, 3, 4]"',
23+
"Simple List of integers",
24+
),
25+
(
26+
"Pi // PythonForm",
27+
'"sympy.pi"',
28+
"Pi",
29+
),
30+
31+
):
32+
check_evaluation(str_expr, str_expected, message)

0 commit comments

Comments
 (0)