Skip to content

Commit 5ea915b

Browse files
authored
Underscore instead of grave tilde in sympy names as context separators (#1536)
This continues #1535, by changing the way in which symbol names are translated to sympy.
1 parent 814f03f commit 5ea915b

File tree

5 files changed

+85
-59
lines changed

5 files changed

+85
-59
lines changed

mathics/builtin/numbers/calculus.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
SymbolPower,
5858
SymbolTimes,
5959
SymbolTrue,
60+
sympy_name,
6061
)
6162
from mathics.core.systemsymbols import (
6263
SymbolAnd,
@@ -528,7 +529,7 @@ def to_sympy(self, expr, **kwargs):
528529
return
529530

530531
func = exprs[1].elements[0]
531-
sym_func = sympy.Function(str(SYMPY_SYMBOL_PREFIX + func.__str__()))(*sym_args)
532+
sym_func = sympy.Function(sympy_name(func))(*sym_args)
532533

533534
counts = [element.get_int_value() for element in exprs[2].elements]
534535
if None in counts:

mathics/core/convert/sympy.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,16 @@
113113
}
114114

115115

116+
def sympy_decode_mathics_symbol_name(name: str):
117+
"""
118+
Remove the Prefix for Mathics symbols
119+
and restore the context separator character.
120+
"""
121+
if name.startswith(SYMPY_SYMBOL_PREFIX):
122+
return name[len(SYMPY_SYMBOL_PREFIX) :].replace("_", "`")
123+
return name
124+
125+
116126
def is_Cn_expr(name: str) -> bool:
117127
"""Check if name is of the form {prefix}Cnnn"""
118128
if name.startswith(SYMPY_SYMBOL_PREFIX) or name.startswith(SYMPY_SLOT_PREFIX):
@@ -236,7 +246,6 @@ def expression_to_sympy(expr: Expression, **kwargs):
236246
"""
237247
Convert `expr` to its sympy form.
238248
"""
239-
240249
if len(expr.elements) > 0:
241250
head_name = expr.get_head_name()
242251
if head_name.startswith("Global`"):
@@ -250,6 +259,7 @@ def expression_to_sympy(expr: Expression, **kwargs):
250259

251260
lookup_name = expr.get_lookup_name()
252261
builtin = mathics_to_sympy.get(lookup_name)
262+
253263
if builtin is not None:
254264
sympy_expr = builtin.to_sympy(expr, **kwargs)
255265
if sympy_expr is not None:
@@ -348,7 +358,6 @@ def old_from_sympy(expr) -> BaseElement:
348358
"""
349359
converts a SymPy object to a Mathics3 element.
350360
"""
351-
352361
if isinstance(expr, (tuple, list)):
353362
return to_mathics_list(*expr, elements_conversion_fn=from_sympy)
354363
if isinstance(expr, int):
@@ -373,16 +382,15 @@ def old_from_sympy(expr) -> BaseElement:
373382
name = str(expr)
374383
if isinstance(expr, sympy.Dummy):
375384
name = name[1:]
376-
if "`" not in name:
385+
if "_" not in name:
377386
name = f"sympy`dummy`Dummy${expr.dummy_index}" # type: ignore[attr-defined]
378387
else:
379-
name = name[len(SYMPY_SYMBOL_PREFIX) :]
388+
name = sympy_decode_mathics_symbol_name(name)
380389
# Probably, this should be the value attribute
381390
return Symbol(name)
382391
if is_Cn_expr(name):
383392
return Expression(SymbolC, Integer(int(name[1:])))
384-
if name.startswith(SYMPY_SYMBOL_PREFIX):
385-
name = name[len(SYMPY_SYMBOL_PREFIX) :]
393+
name = sympy_decode_mathics_symbol_name(name)
386394
if name.startswith(SYMPY_SLOT_PREFIX):
387395
index = int(name[len(SYMPY_SLOT_PREFIX) :])
388396
return Expression(SymbolSlot, Integer(index))
@@ -421,7 +429,8 @@ def old_from_sympy(expr) -> BaseElement:
421429
if isinstance(expr, sympy.core.numbers.NaN):
422430
return SymbolIndeterminate
423431
if isinstance(expr, sympy.core.function.FunctionClass):
424-
return Symbol(str(expr))
432+
name = str(expr).replace("_", "`")
433+
return Symbol(name)
425434
if expr is sympy.true:
426435
return SymbolTrue
427436
if expr is sympy.false:
@@ -547,8 +556,7 @@ def old_from_sympy(expr) -> BaseElement:
547556
Expression(Symbol("C"), Integer(int(name[1:]))),
548557
*[from_sympy(arg) for arg in expr.args],
549558
)
550-
if name.startswith(SYMPY_SYMBOL_PREFIX):
551-
name = name[len(SYMPY_SYMBOL_PREFIX) :]
559+
name = sympy_decode_mathics_symbol_name(name)
552560
args = [from_sympy(arg) for arg in expr.args]
553561
builtin = sympy_to_mathics.get(name)
554562
if builtin is not None:

mathics/core/symbols.py

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ def sympy_strip_context(name) -> str:
126126
produce invalid code. In a next round, we would like
127127
to use another character for split contexts in sympy variables.
128128
"""
129-
return strip_context(name)
129+
return name.split("_")[-1]
130130

131131

132132
# system_symbols_dict({'SomeSymbol': ...}) -> {Symbol('System`SomeSymbol'): ...}
@@ -342,6 +342,7 @@ class Symbol(Atom, NumericOperators, EvalMixin):
342342

343343
name: str
344344
hash: int
345+
sympy_dummy: Any
345346
_short_name: str
346347

347348
# Dictionary of Symbols defined so far.
@@ -354,7 +355,7 @@ class Symbol(Atom, NumericOperators, EvalMixin):
354355

355356
# __new__ instead of __init__ is used here because we want
356357
# to return the same object for a given "name" value.
357-
def __new__(cls, name: str):
358+
def __new__(cls, name: str, sympy_dummy=None):
358359
"""
359360
Allocate an object ensuring that for a given ``name`` and ``cls`` we get back the same object,
360361
id(object) is the same and its object.__hash__() is the same.
@@ -384,6 +385,18 @@ def __new__(cls, name: str):
384385
# For example, this can happen with String constants.
385386

386387
self.hash = hash((cls, name))
388+
389+
# TODO: revise how we convert sympy.Dummy
390+
# symbols.
391+
#
392+
# In some cases, SymPy returns a sympy.Dummy
393+
# object. It is converted to Mathics as a
394+
# Symbol. However, we probably should have
395+
# a different class for this kind of symbols.
396+
# Also, sympy_dummy should be stored as the
397+
# value attribute.
398+
self.sympy_dummy = sympy_dummy
399+
387400
self._short_name = strip_context(name)
388401

389402
return self
@@ -392,7 +405,7 @@ def __eq__(self, other) -> bool:
392405
return self is other
393406

394407
def __getnewargs__(self):
395-
return (self.name,)
408+
return (self.name, self.sympy_dummy)
396409

397410
def __hash__(self) -> int:
398411
"""
@@ -690,12 +703,6 @@ def __new__(cls, name, value):
690703
self.hash = hash((cls, name))
691704
return self
692705

693-
def __getnewargs__(self):
694-
return (
695-
self.name,
696-
self._value,
697-
)
698-
699706
@property
700707
def is_literal(self) -> bool:
701708
"""
@@ -754,7 +761,7 @@ def symbol_set(*symbols: Symbol) -> FrozenSet[Symbol]:
754761

755762
def sympy_name(mathics_symbol: Symbol):
756763
"""Convert a mathics symbol name into a sympy symbol name"""
757-
return SYMPY_SYMBOL_PREFIX + mathics_symbol.name
764+
return SYMPY_SYMBOL_PREFIX + mathics_symbol.name.replace("`", "_")
758765

759766

760767
# Symbols used in this module.

mathics/eval/drawing/plot_compile.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
import scipy
1919
import sympy
2020

21-
from mathics.core.convert.sympy import SympyExpression
22-
from mathics.core.symbols import strip_context
21+
from mathics.core.convert.sympy import SympyExpression, mathics_to_sympy
22+
from mathics.core.symbols import sympy_strip_context
2323
from mathics.core.util import print_expression_tree, print_sympy_tree
2424

2525

@@ -69,7 +69,8 @@ def plot_compile(evaluation, expr, names, debug=0):
6969

7070
# Strip symbols in sympy expression of context.
7171
subs = {
72-
sym: sympy.Symbol(strip_context(str(sym))) for sym in sympy_expr.free_symbols
72+
sym: sympy.Symbol(sympy_strip_context(str(sym)))
73+
for sym in sympy_expr.free_symbols
7374
}
7475
sympy_expr = sympy_expr.subs(subs)
7576

test/core/test_sympy_python_convert.py

Lines changed: 45 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
SYMPY_SYMBOL_PREFIX,
2626
Symbol,
2727
SymbolPlus,
28+
sympy_name,
2829
)
2930
from mathics.core.systemsymbols import (
3031
SymbolD,
@@ -36,6 +37,12 @@
3637
SymbolSlot,
3738
)
3839

40+
Symbol_f = Symbol("Global`f")
41+
Symbol_x = Symbol("Global`x")
42+
Symbol_y = Symbol("Global`y")
43+
Symbol_z = Symbol("Global`z")
44+
Symbol_Mathics_User_x = Symbol("Mathics`User`x")
45+
3946

4047
class SympyConvert(unittest.TestCase):
4148
def compare_to_sympy(self, mathics_expr, sympy_expr, **kwargs):
@@ -49,11 +56,17 @@ def compare(self, mathics_expr, sympy_expr, **kwargs):
4956
self.compare_to_mathics(mathics_expr, sympy_expr)
5057

5158
def testSymbol(self):
52-
self.compare(Symbol("Global`x"), sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`x"))
59+
self.compare(Symbol_x, sympy.Symbol(sympy_name(Symbol_x)))
5360
self.compare(
54-
Symbol("_Mathics_User_x"),
55-
sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}System`_Mathics_User_x"),
61+
Symbol_Mathics_User_x,
62+
sympy.Symbol(sympy_name(Symbol_Mathics_User_x)),
5663
)
64+
# Sympy symbols without prefix are mapped to symbols in
65+
# System` context:
66+
self.compare_to_mathics(Symbol("x"), sympy.Symbol("x"))
67+
# Notice that a sympy Symbol named "x" is converted
68+
# to the Mathics symbol "System`x", and then, when converted
69+
# back to sympy, goes to sympy.Symbol("_uSystem_x").
5770

5871
def testReal(self):
5972
self.compare(Real("1.0"), sympy.Float("1.0"))
@@ -87,25 +100,25 @@ def testString(self):
87100

88101
def testAdd(self):
89102
self.compare(
90-
Expression(SymbolPlus, Integer1, Symbol("Global`x")),
91-
sympy.Add(sympy.Integer(1), sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`x")),
103+
Expression(SymbolPlus, Integer1, Symbol_x),
104+
sympy.Add(sympy.Integer(1), sympy.Symbol(sympy_name(Symbol_x))),
92105
)
93106

94107
def testIntegrate(self):
95108
self.compare(
96-
Expression(SymbolIntegrate, Symbol("Global`x"), Symbol("Global`y")),
109+
Expression(SymbolIntegrate, Symbol_x, Symbol_y),
97110
sympy.Integral(
98-
sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`x"),
99-
sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`y"),
111+
sympy.Symbol(sympy_name(Symbol_x)),
112+
sympy.Symbol(sympy_name(Symbol_y)),
100113
),
101114
)
102115

103116
def testDerivative(self):
104117
self.compare(
105-
Expression(SymbolD, Symbol("Global`x"), Symbol("Global`y")),
118+
Expression(SymbolD, Symbol_x, Symbol_y),
106119
sympy.Derivative(
107-
sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`x"),
108-
sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`y"),
120+
sympy.Symbol(sympy_name(Symbol_x)),
121+
sympy.Symbol(sympy_name(Symbol_y)),
109122
),
110123
)
111124

@@ -114,47 +127,43 @@ def testDerivative2(self):
114127

115128
head = Expression(
116129
Expression(SymbolDerivative, Integer1, Integer0),
117-
Symbol("Global`f"),
130+
Symbol_f,
118131
)
119-
expr = Expression(head, Symbol("Global`x"), Symbol("Global`y"))
132+
expr = Expression(head, Symbol_x, Symbol_y)
120133

121-
sfxy = sympy.Function(str(f"{SYMPY_SYMBOL_PREFIX}Global`f"))(
122-
sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`x"),
123-
sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`y"),
124-
)
125-
sym_expr = sympy.Derivative(
126-
sfxy, sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`x")
134+
sfxy = sympy.Function(sympy_name(Symbol_f))(
135+
sympy.Symbol(sympy_name(Symbol_x)),
136+
sympy.Symbol(sympy_name(Symbol_y)),
127137
)
138+
sym_expr = sympy.Derivative(sfxy, sympy.Symbol(sympy_name(Symbol_x)))
128139

129140
self.compare_to_sympy(expr, sym_expr, **kwargs)
130141
# compare_to_mathics fails because Derivative becomes D (which then evaluates to Derivative)
131142

132143
def testConvertedFunctions(self):
133144
kwargs = {"converted_functions": set(["Global`f"])}
134145

135-
marg1 = Expression(Symbol("Global`f"), Symbol("Global`x"))
136-
sarg1 = sympy.Function(str(f"{SYMPY_SYMBOL_PREFIX}Global`f"))(
137-
sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`x")
138-
)
146+
marg1 = Expression(Symbol_f, Symbol_x)
147+
sarg1 = sympy.Function(sympy_name(Symbol_f))(sympy.Symbol(sympy_name(Symbol_x)))
139148
self.compare(marg1, sarg1, **kwargs)
140149

141-
marg2 = Expression(Symbol("Global`f"), Symbol("Global`x"), Symbol("Global`y"))
142-
sarg2 = sympy.Function(str(f"{SYMPY_SYMBOL_PREFIX}Global`f"))(
143-
sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`x"),
144-
sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`y"),
150+
marg2 = Expression(Symbol_f, Symbol_x, Symbol_y)
151+
sarg2 = sympy.Function(sympy_name(Symbol_f))(
152+
sympy.Symbol(sympy_name(Symbol_x)),
153+
sympy.Symbol(sympy_name(Symbol_y)),
145154
)
146155
self.compare(marg2, sarg2, **kwargs)
147156

148157
self.compare(
149-
Expression(SymbolD, marg2, Symbol("Global`x")),
150-
sympy.Derivative(sarg2, sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`x")),
158+
Expression(SymbolD, marg2, Symbol_x),
159+
sympy.Derivative(sarg2, sympy.Symbol(sympy_name(Symbol_x))),
151160
**kwargs,
152161
)
153162

154163
def testExpression(self):
155164
self.compare(
156-
Expression(SymbolSin, Symbol("Global`x")),
157-
sympy.sin(sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`x")),
165+
Expression(SymbolSin, Symbol_x),
166+
sympy.sin(sympy.Symbol(sympy_name(Symbol_x))),
158167
)
159168

160169
def testConstant(self):
@@ -163,14 +172,14 @@ def testConstant(self):
163172

164173
def testGamma(self):
165174
self.compare(
166-
Expression(SymbolGamma, Symbol("Global`z")),
167-
sympy.gamma(sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`z")),
175+
Expression(SymbolGamma, Symbol_z),
176+
sympy.gamma(sympy.Symbol(sympy_name(Symbol_z))),
168177
)
169178
self.compare(
170-
Expression(SymbolGamma, Symbol("Global`z"), Symbol("Global`x")),
179+
Expression(SymbolGamma, Symbol_z, Symbol_x),
171180
sympy.uppergamma(
172-
sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`z"),
173-
sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`x"),
181+
sympy.Symbol(sympy_name(Symbol_z)),
182+
sympy.Symbol(sympy_name(Symbol_x)),
174183
),
175184
)
176185

0 commit comments

Comments
 (0)