Skip to content

Commit 6669d53

Browse files
authored
Good bye sympy dummy attribute (#1535)
This continues #1533. Now, let's remove the `sympy_dummy` attribute for symbols. When it is necessary (or useful) like in `Sum` or `Product` expressions, we can tell `to_sympy` which symbols should be handled as sympy dummy symbols.
1 parent a7acec0 commit 6669d53

File tree

6 files changed

+104
-77
lines changed

6 files changed

+104
-77
lines changed

mathics/builtin/arithmetic.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -802,8 +802,13 @@ def to_sympy(self, expr, **kwargs):
802802
try:
803803
e_kwargs = kwargs.copy()
804804
e_kwargs["convert_all_global_functions"] = True
805+
e_kwargs["dummies"] = e_kwargs.get("dummies", set()).union((index,))
805806
e = expr.elements[0].to_sympy(**e_kwargs)
806-
i = index.elements[0].to_sympy(**kwargs)
807+
e_kwargs["convert_all_global_functions"] = kwargs.get(
808+
"convert_all_global_functions", False
809+
)
810+
811+
i = index.elements[0].to_sympy(**e_kwargs)
807812
start = index.elements[1].to_sympy(**kwargs)
808813
stop = index.elements[2].to_sympy(**kwargs)
809814

@@ -1045,23 +1050,27 @@ def to_sympy(self, expr, **kwargs) -> Optional[SympyExpression]:
10451050
index = expr.elements[1]
10461051
arg_kwargs = kwargs.copy()
10471052
arg_kwargs["convert_all_global_functions"] = True
1053+
arg_kwargs["dummies"] = kwargs.get("dummies", set()).union((index,))
10481054
f_sympy = expr.elements[0].to_sympy(**arg_kwargs)
10491055
if f_sympy is None:
10501056
return
10511057

10521058
evaluation = kwargs.get("evaluation", None)
10531059

10541060
# Handle summation parameters: variable, min, max
1055-
var_min_max = index.elements[:3]
1056-
bounds = [expr.to_sympy(**kwargs) for expr in var_min_max]
10571061

1062+
arg_kwargs["convert_all_global_functions"] = kwargs.get(
1063+
"convert_all_global_functions", False
1064+
)
1065+
var_min_max = index.elements[:3]
1066+
bounds = [expr.to_sympy(**arg_kwargs) for expr in var_min_max]
10581067
if evaluation:
10591068
# Min and max might be Mathics expressions. If so, evaluate them.
10601069
for i in (1, 2):
10611070
min_max_expr = var_min_max[i]
10621071
if not isinstance(expr, Symbol):
10631072
min_max_expr_eval = min_max_expr.evaluate(evaluation)
1064-
value = min_max_expr_eval.to_sympy(**kwargs)
1073+
value = min_max_expr_eval.to_sympy(**arg_kwargs)
10651074
bounds[i] = value
10661075

10671076
# FIXME: The below tests on SympyExpression, but really the
@@ -1075,7 +1084,7 @@ def to_sympy(self, expr, **kwargs) -> Optional[SympyExpression]:
10751084
# If we have integer bounds, we'll use Mathics's iterator Sum
10761085
# (which is Plus)
10771086

1078-
if all(
1087+
if evaluation and all(
10791088
(hasattr(i, "is_integer") and i.is_integer)
10801089
or (hasattr(i, "is_finite") and i.is_finite and i.is_constant())
10811090
for i in bounds[1:]

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: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@
77
from typing import TYPE_CHECKING, Dict, List, Optional, Sequence, Tuple, Union, cast
88

99
import sympy
10-
from sympy import Symbol as Sympy_Symbol, false as SympyFalse, true as SympyTrue
10+
from sympy import (
11+
Dummy as Sympy_Dummy,
12+
Symbol as Sympy_Symbol,
13+
false as SympyFalse,
14+
true as SympyTrue,
15+
)
1116
from sympy.core.singleton import S
1217

1318
from mathics.core.atoms import (
@@ -108,6 +113,16 @@
108113
}
109114

110115

116+
def sympy_decode_mathics_symbol_name(name: str) -> 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+
111126
def is_Cn_expr(name: str) -> bool:
112127
"""Check if name is of the form {prefix}Cnnn"""
113128
if name.startswith(SYMPY_SYMBOL_PREFIX) or name.startswith(SYMPY_SLOT_PREFIX):
@@ -152,7 +167,9 @@ def __new__(cls, *exprs, **kwargs):
152167
if kwargs.get("convert_functions_for_polynomialq", False):
153168
sympy_elements = []
154169
else:
155-
sympy_elements = [element.to_sympy() for element in expr.elements]
170+
sympy_elements = [
171+
element.to_sympy(**kwargs) for element in expr.elements
172+
]
156173
if sympy_head is None or None in sympy_elements:
157174
return None
158175
obj = super().__new__(cls, sympy_head, *sympy_elements)
@@ -229,7 +246,6 @@ def expression_to_sympy(expr: Expression, **kwargs):
229246
"""
230247
Convert `expr` to its sympy form.
231248
"""
232-
233249
if len(expr.elements) > 0:
234250
head_name = expr.get_head_name()
235251
if head_name.startswith("Global`"):
@@ -243,6 +259,7 @@ def expression_to_sympy(expr: Expression, **kwargs):
243259

244260
lookup_name = expr.get_lookup_name()
245261
builtin = mathics_to_sympy.get(lookup_name)
262+
246263
if builtin is not None:
247264
sympy_expr = builtin.to_sympy(expr, **kwargs)
248265
if sympy_expr is not None:
@@ -261,11 +278,10 @@ def symbol_to_sympy(symbol: Symbol, **kwargs) -> Sympy_Symbol:
261278
if result is not None:
262279
return result
263280

264-
if symbol.sympy_dummy is not None:
265-
return symbol.sympy_dummy
266-
267281
builtin = mathics_to_sympy.get(symbol.name)
268282
if builtin is None or not builtin.sympy_name or not builtin.is_constant(): # nopep8
283+
if symbol in kwargs.get("dummies", {}):
284+
return Sympy_Dummy(sympy_name(symbol))
269285
return Sympy_Symbol(sympy_name(symbol))
270286
return builtin.to_sympy(symbol, **kwargs)
271287

@@ -342,7 +358,6 @@ def old_from_sympy(expr) -> BaseElement:
342358
"""
343359
converts a SymPy object to a Mathics3 element.
344360
"""
345-
346361
if isinstance(expr, (tuple, list)):
347362
return to_mathics_list(*expr, elements_conversion_fn=from_sympy)
348363
if isinstance(expr, int):
@@ -366,13 +381,16 @@ def old_from_sympy(expr) -> BaseElement:
366381
if expr.is_Symbol:
367382
name = str(expr)
368383
if isinstance(expr, sympy.Dummy):
369-
name = f"sympy`dummy`Dummy${expr.dummy_index}" # type: ignore[attr-defined]
384+
name = name[1:]
385+
if "_" not in name:
386+
name = f"sympy`dummy`Dummy${expr.dummy_index}" # type: ignore[attr-defined]
387+
else:
388+
name = sympy_decode_mathics_symbol_name(name)
370389
# Probably, this should be the value attribute
371-
return Symbol(name, sympy_dummy=expr)
390+
return Symbol(name)
372391
if is_Cn_expr(name):
373392
return Expression(SymbolC, Integer(int(name[1:])))
374-
if name.startswith(SYMPY_SYMBOL_PREFIX):
375-
name = name[len(SYMPY_SYMBOL_PREFIX) :]
393+
name = sympy_decode_mathics_symbol_name(name)
376394
if name.startswith(SYMPY_SLOT_PREFIX):
377395
index = int(name[len(SYMPY_SLOT_PREFIX) :])
378396
return Expression(SymbolSlot, Integer(index))
@@ -411,7 +429,8 @@ def old_from_sympy(expr) -> BaseElement:
411429
if isinstance(expr, sympy.core.numbers.NaN):
412430
return SymbolIndeterminate
413431
if isinstance(expr, sympy.core.function.FunctionClass):
414-
return Symbol(str(expr))
432+
name = str(expr).replace("_", "`")
433+
return Symbol(name)
415434
if expr is sympy.true:
416435
return SymbolTrue
417436
if expr is sympy.false:
@@ -537,8 +556,7 @@ def old_from_sympy(expr) -> BaseElement:
537556
Expression(Symbol("C"), Integer(int(name[1:]))),
538557
*[from_sympy(arg) for arg in expr.args],
539558
)
540-
if name.startswith(SYMPY_SYMBOL_PREFIX):
541-
name = name[len(SYMPY_SYMBOL_PREFIX) :]
559+
name = sympy_decode_mathics_symbol_name(name)
542560
args = [from_sympy(arg) for arg in expr.args]
543561
builtin = sympy_to_mathics.get(name)
544562
if builtin is not None:

mathics/core/symbols.py

Lines changed: 7 additions & 18 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,7 +342,6 @@ class Symbol(Atom, NumericOperators, EvalMixin):
342342

343343
name: str
344344
hash: int
345-
sympy_dummy: Any
346345
_short_name: str
347346

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

356355
# __new__ instead of __init__ is used here because we want
357356
# to return the same object for a given "name" value.
358-
def __new__(cls, name: str, sympy_dummy=None):
357+
def __new__(cls, name: str):
359358
"""
360359
Allocate an object ensuring that for a given ``name`` and ``cls`` we get back the same object,
361360
id(object) is the same and its object.__hash__() is the same.
@@ -383,20 +382,7 @@ def __new__(cls, name: str, sympy_dummy=None):
383382
# Python objects, so we include the class in the
384383
# event that different objects have the same Python value.
385384
# For example, this can happen with String constants.
386-
387385
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-
400386
self._short_name = strip_context(name)
401387

402388
return self
@@ -405,7 +391,7 @@ def __eq__(self, other) -> bool:
405391
return self is other
406392

407393
def __getnewargs__(self):
408-
return (self.name, self.sympy_dummy)
394+
return (self.name,)
409395

410396
def __hash__(self) -> int:
411397
"""
@@ -703,6 +689,9 @@ def __new__(cls, name, value):
703689
self.hash = hash((cls, name))
704690
return self
705691

692+
def __getnewargs__(self):
693+
return (self.name, self._value)
694+
706695
@property
707696
def is_literal(self) -> bool:
708697
"""
@@ -761,7 +750,7 @@ def symbol_set(*symbols: Symbol) -> FrozenSet[Symbol]:
761750

762751
def sympy_name(mathics_symbol: Symbol):
763752
"""Convert a mathics symbol name into a sympy symbol name"""
764-
return SYMPY_SYMBOL_PREFIX + mathics_symbol.name
753+
return SYMPY_SYMBOL_PREFIX + mathics_symbol.name.replace("`", "_")
765754

766755

767756
# 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

0 commit comments

Comments
 (0)